Более эффективный SQL, чем использование «A UNION (B in A)»? - PullRequest
5 голосов
/ 23 января 2010

Изменить 1 (уточнение): Спасибо за ответы до сих пор! Ответ отрадный.
Я хочу немного уточнить вопрос, потому что, основываясь на ответах, я думаю, что неправильно описал один из аспектов проблемы (и я уверен, что это моя вина, так как мне было трудно определить ее даже для себя).
Вот в чем проблема: результирующий набор должен содержать ТОЛЬКО записи с tstamp МЕЖДУ '2010-01-03' И '2010-01-09' И И one запись, где tstamp IS NULL для каждого order_num в первый набор ( всегда будет один с нулевым tstamp для каждого order_num).
Ответы, представленные до сих пор, включают все записи для определенного order_num, если есть любой с tstamp МЕЖДУ '2010-01-03' И '2010-01-09'. Например, если была другая запись с order_num = 2 и tstamp = 2010-01-12 00:00:00, в результат следует включить , а не .

Оригинальный вопрос:
Рассмотрим таблицу заказов, содержащую id (уникальные), order_num, tstamp (временная метка) и item_id (один элемент, включенный в заказ). tstamp равен нулю, если только порядок не был изменен, в этом случае есть другая запись с идентичным order_num, а tstamp содержит метку времени, когда произошло изменение.

Пример ...

id  order_num  tstamp               item_id
__  _________  ___________________  _______
 0          1                           100
 1          2                           101
 2          2  2010-01-05 12:34:56      102
 3          3                           113
 4          4                           124
 5          5                           135
 6          5  2010-01-07 01:23:45      136
 7          5  2010-01-07 02:46:00      137
 8          6                           100
 9          6  2010-01-13 08:33:55      105

Каков наиболее эффективный оператор SQL для извлечения всех заказов (на основе order_num), которые были изменены один или несколько раз в течение определенного диапазона дат? Другими словами, для каждого заказа нам нужны все записи с одинаковым order_num (включая запись с нулевым tstamp), для каждого order_num, ГДЕ хотя бы один из order_num имеет tstamp NOT NULL и tstamp МЕЖДУ '2010-01-03' И «2010-01-09». У меня возникли трудности с тем, «Где по крайней мере у одного из order_num есть tstamp NOT NULL».

Набор результатов должен выглядеть следующим образом:

id  order_num  tstamp               item_id
__  _________  ___________________  _______
 1          2                           101
 2          2  2010-01-05 12:34:56      102
 5          5                           135
 6          5  2010-01-07 01:23:45      136
 7          5  2010-01-07 02:46:00      137

SQL, который я придумал, - это, по сути, «СОЮЗ (B в A)», но он выполняется медленно, и я надеюсь, что есть более эффективное решение:

SELECT history_orders.order_id, history_orders.tstamp, history_orders.item_id
FROM
   (SELECT orders.order_id, orders.tstamp, orders.item_id
    FROM orders
    WHERE orders.tstamp BETWEEN '2010-01-03' AND '2010-01-09')
    AS history_orders
UNION
SELECT current_orders.order_id, current_orders.tstamp, current_orders.item_id
FROM
   (SELECT orders.order_id, orders.tstamp, orders.item_id
    FROM orders
    WHERE orders.tstamp IS NULL)
    AS current_orders
WHERE current_orders.order_id IN
   (SELECT orders.order_id
    FROM orders
    WHERE orders.tstamp BETWEEN '2010-01-03' AND '2010-01-09');

Ответы [ 6 ]

3 голосов
/ 23 января 2010

возможно подзапрос:

select * from order o where o.order_num in (select distinct
  order_num from order where tstamp between '2010-01-03' and '2010-01-09')
1 голос
/ 21 мая 2010

Я знаю, что уже слишком поздно для репликации, но я только что увидел эту публикацию и подумал, может быть, мне стоит попробовать это один раз, как насчет этого запроса, он действительно очень мал по сравнению со всеми вышеупомянутыми решениями и решает задачу.

select * from orders_gc where order_num in 
    (select order_num
     from orders_gc 
     group by order_num 
     having count(id) > 1 and 
     MAX(tstamp) between '03-jan-2010' and '09-jan-2010')
1 голос
/ 23 января 2010

Если я не понял неправильно, что-то вроде этого должно сработать:

SELECT o1.id, o1.order_num, o.tstamp, o.item_id
FROM  orders o1
WHERE EXISTS(
    SELECT * FROM orders o2 
    WHERE o1.order_num = o2.order_num 
        AND o2.tstamp BETWEEN '2010-01-03' AND '2010-01-09')

Преимущество использования EXISTS заключается в том, что он останавливается, как только оштрафует первый матч.

0 голосов
/ 25 января 2010

Еще раз спасибо за все предложения. Я нашел три решения, которые работают, в том числе мой оригинал. В конце я добавил некоторые результаты производительности, которые не так хороши, как я надеялся. Если кто-то может улучшить это, я был бы в восторге!

1) Лучшее решение, найденное на данный момент, выглядит так:

SELECT history_orders.order_id, history_orders.tstamp, history_orders.item_id
FROM
   (SELECT orders.order_id, orders.tstamp, orders.item_id
    FROM orders
    WHERE orders.tstamp BETWEEN '2010-01-03' AND '2010-01-09'
    OR orders.tstamp IS NULL)
    AS history_orders
WHERE history_orders.order_id IN
   (SELECT orders.order_id
    FROM orders
    WHERE orders.tstamp BETWEEN '2010-01-03' AND '2010-01-09');

2) Я также попытался использовать EXISTS вместо IN, что требует дополнительного предложения WHERE в последнем SELECT:

SELECT history_orders.order_id, history_orders.tstamp, history_orders.item_id
FROM
   (SELECT orders.order_id, orders.tstamp, orders.item_id
    FROM orders
    WHERE orders.tstamp BETWEEN '2010-01-03' AND '2010-01-09'
    OR orders.tstamp IS NULL)
    AS history_orders
WHERE EXISTS
   (SELECT orders.order_id
    FROM orders
    WHERE history_orders.order_id = orders.order_id
    AND orders.tstamp BETWEEN '2010-01-03' AND '2010-01-09');

3) И, наконец, мое оригинальное решение, использующее UNION.

Комментарии:
Чтобы прокомментировать размер таблицы, моя реальная проблема «реального мира» включает 4 таблицы (связанные с внутренними объединениями), содержащие 98, 2189, 43897, 785656 записей соответственно.

Производительность - я запускал каждое решение три раза, и вот мои реальные результаты:
1: 52, 51, 51 секунда
2: 54, 54, 53 с
3: 56, 56, 56 с

0 голосов
/ 23 января 2010

Вы можете самостоятельно присоединиться к столу. Упрощенно это будет выглядеть так:

select order_id
from orders all_orders
inner join orders not_null_orders
    on all_orders.order_id = not_null_orders.order_id
where
    not_null_orders.tstamp is not null
    and all_orders.tstamp between '2010-01-03' AND '2010-01-09'
0 голосов
/ 23 января 2010

Надеюсь, я правильно понял ваш вопрос. Это должно вернуть все заказы, которые были в заказе, который был изменен в пределах предоставленной отметки времени.

SELECT o.order_id, o.tstamp, o.item_id
FROM orders o
JOIN ( SELECT DISTINCT o2.order_num
       FROM orders o2
       WHERE o2.tstamp BETWEEN '2010-01-03' AND '2010-01-09' ) o3
ON ( o3.order_num = o.order_num )
...