2012-01-13 2 views
2

У меня есть таблица, которая отслеживает обслуживание машины, которое происходит в произвольные моменты времени. Вот упрощенная структура таблицы:Выберите предыдущую дату для каждой строки в таблице

Maintenance Table 
---------------------------------------- 
ID   - integer 
DateCompleted - date 
MachineName - varchar 

и вот некоторые примеры данных таблицы:

ID DateCompleted MachineName 
---------------------------------------- 
1  1/6/2011  'Machine 1' 
2  1/13/2011 'Machine 2' 
3  1/14/2011 'Machine 1' 
4  2/2/2011  'Machine 3' 
5  2/26/2011 'Machine 1' 
6  3/9/2011  'Machine 2' 
7  4/20/2011 'Machine 3' 

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

ID MachineName CurDate  PrevDate 
---------------------------------------- 
1 'Machine 1' 1/6/2011 NULL 
2 'Machine 2' 1/13/2011 NULL 
3 'Machine 1' 1/14/2011 1/6/2011 
4 'Machine 3' 2/2/2011 NULL 
5 'Machine 1' 2/26/2011 1/14/2011 
6 'Machine 2' 3/9/2011 1/13/2011 
7 'Machine 3' 4/20/2011 2/2/2011 

Какой был бы лучший способ написать такой запрос? Моя единственная идея до сих пор было бы что-то вроде этого:

SELECT ID, MachineName, DateCompleted AS CurDate, 
    (
    SELECT TOP 1 DateCompleted FROM Maintenance m2 
    WHERE m1.MachineName = m2.MachineName 
     AND m1.DateCompleted > m2.DateCompleted 
    ORDER BY DateCompleted DESC 
) AS PrevDate 

FROM Maintenance m1 

ORDER BY ID 

Любые мысли, предложения или поправки были бы весьма желательны.

+0

Какие СУБД вы используете? – Lamak

+0

Microsoft, но я приветствую решения для любого. – Sparafusile

+2

«Microsoft» не является РСУБД. «SQL Server» - это dbms. Я отметил его для вас. –

ответ

1

Как вы сказали ", но я приветствую решение для любого".

Это решение с ANSI SQL:

SELECT ID, 
     DateCompleted, 
     MachineName, 
     lag(DateCompleted) over (partition by MachineName order by DateCompleted) as PrevDate 
FROM Maintenance 
ORDER BY id; 

Работает в PostgreSQL, Oracle, DB2 и Teradata.

SQL Server еще не поддерживает функцию lag(), но предстоящая версия «Denali» (2012) будет иметь ее.

+0

Это аккуратная функция. Жаль, что это не поддерживается всеми. – Sparafusile

+0

@Sparafusile: функции windowing являются важной функцией (например, рекурсивными запросами), и в настоящее время я считаю, что СУБД не поддерживает их, чтобы они не были «современными» (Firebird 3.0 также будет иметь их) –

+0

Правильно, я неправильно прочитал ваше заявление , Я думал, вы сказали «Windows», как в Microsoft. После большего чтения я осознал свою ошибку. Это тот тип решения, которого я искал, большое спасибо. – Sparafusile

1

Как об этом:

SELECT 
    m.ID, m.MachineName, m.DateCompleted AS CurDate, MAX(m_past.DateCompleted) AS PrevDate 

FROM Maintenance m 

    LEFT JOIN Maintenance m_past 
    ON m.MachineName = m_past.MachineName 

WHERE m_past.DateCompleted < m.DateCompleted 

GROUP BY m.ID 
+0

Ваш 'GROUP BY' не позволит вам выбрать' m.MachineName, m.DateCompleted AS CurDate'. Я бы предложил удалить 'm.ID' из select и группировать' m.MachineName, m.DateCompleted'. – jzila

+0

Является ли это лучше, по производительности, чем мое оригинальное решение? Если да, то почему? – Sparafusile

+0

@jzilla: Возможно, вы правы, но я этого не вижу. Кроме того, если это правда, я не могу просто использовать 'GROUP BY m.ID, m.MachineName, m.DateCompleted'? @Sparafusile: Я думаю, что ваш метод будет выполнять внутренние запросы (по одному на строку), а мой не будет. Хотя 'JOIN' может заставить его получить больше данных в общей сложности, вам нужно будет протестировать. Не забудьте указать индекс на 'MachineName'! – mbillard

1

Попробуйте это:

SELECT A.Id, A.MachineName, A.DateCompleted [CurDate], B.DateCompleted PrevDate 
FROM Maintenance A 
OUTER APPLY (SELECT TOP 1 * 
      FROM Maintenance 
      WHERE MachineName = A.MachineName AND DateCompleted < A.DateCompleted 
      ORDER BY DateCompleted DESC) B 
+0

Downvoter хочет прокомментировать? – Lamak

1

ли TOP п работы зависит от ваших DBMS. MAX() будет работать на разных платформах. Index DateCompleted и MachineName; они оба используются в предложении WHERE.

select m1.id, m1.machinename, m1.datecompleted as curdate, 
    (select max(datecompleted) 
    from maintenance 
    where machinename = m1.machinename 
     and datecompleted < m1.datecompleted) as prevdate 
from maintenance m1 
order by machinename, curdate 

Если DBMS поддерживает оконные функции, вы можете использовать

select m1.id, m1.machinename, m1.datecompleted as curdate, 
     max(datecompleted) over (partition by machinename 
           order by m1.datecompleted 
           rows between unbounded preceding 
             and 1 preceding) as prevdate 
from maintenance m1 

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

В процессе тестирования вы хотите узнать, как generate and read an execution plan.

+0

Спасибо. Это по сути то же самое, что и мое решение. Я надеялся на что-то глубокое, чего еще не было. – Sparafusile

+0

Хорошо, что вы знаете о функциях окон. Генерация данных всегда дороже, чем хранение. –

0

Ваш запрос кажется мне разумным, и его легко понять. Игнорируя возможную стоимость окончательной сортировки, я считаю, что сложность по существу равна O (n log n), предполагая, что существуют соответствующие индексы. Для каждой записи в таблице механизм запроса должен найти предыдущую запись даты, которая должна быть O (log n) с правильными индексами.

Один из способов: Возможно, увеличить производительность за счет сложности кода - это написать хранимую процедуру для получения результата. Я думаю, что неупорядоченный результат может быть получен в O (n). Процедура могла проходить через два курсора по таблице, упорядоченной по MachineName, а затем по DateCompleted. Он мог бы построить результирующий набор в O (n), поскольку он прошел через оба курсора. Однако тогда результат должен быть отсортирован по идентификатору, который будет O (n log n). Поэтому я думаю, что теоретическая сложность будет такой же, как и запрос, но процедура может иметь меньше накладных расходов и работать немного быстрее. Но я определенно не рекомендовал бы это решение, потому что было бы уродливо и намного сложнее поддерживать.

+0

Это на самом деле довольно легко и может быть выполнено с помощью одного сканирования таблицы для любой СУБД, поддерживающей функции окон. См. Мой ответ. –

1

РЕШЕНИЕ:

declare @tmp table (Id int, DateCompleted datetime, MachineName varchar(100)) 
insert into @tmp 
select 1,'1/6/2011','Machine 1' 
union select 2,'1/13/2011', 'Machine 2' 
union select 3,'1/14/2011', 'Machine 1' 
union select 4,'2/2/2011',  'Machine 3' 
union select 5,'2/26/2011', 'Machine 1' 
union select 6,'3/9/2011',  'Machine 2' 
union select 7,'4/20/2011', 'Machine 3' 



select t.Id, t.DateCompleted, t.MachineName, max(t2.DateCompleted) PrevDate 
from @tmp t 
left join @tmp t2 
    on t.MachineName = t2.MachineName 
    and t.DateCompleted > t2.DateCompleted 
group by t.Id, t.DateCompleted, t.MachineName 
1

Начиная с SQL Server 2012, вы можете использовать оконные агрегаты для записи требуемого запроса. Просто используйте следующий код:

select 
    ID, 
    MachineName, 
    DateCompleted AS CurDate, 
    min(DateCompleted) 
     over (partition by MachineName order by DateCompleted 
      rows between 1 preceding and 1 preceding) as PrevDate 
from Maintenance 
order by Id