Оптимизация ROW_NUMBER () в SQL Server - PullRequest
6 голосов
/ 02 июня 2010

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

Я могу сделать это, используя ROW_NUMBER следующим образом:

WITH TempTable AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY Machine_ID ORDER BY Date_Time) AS Ordering
    FROM dbo.DataTable
)

SELECT [Current].*, Previous.Date_Time AS PreviousDateTime
FROM TempTable AS [Current]
INNER JOIN TempTable AS Previous 
    ON [Current].Machine_ID = Previous.Machine_ID
    AND Previous.Ordering = [Current].Ordering + 1

Проблема в том, что она идет очень медленно (несколько минут на столе с примерно 10 тыс. Записей) - я попытался создать отдельные индикаторы для Machine_ID и Date_Time и один объединенный индекс, но ничего не помогает.

Есть ли способ переписать этот запрос, чтобы он работал быстрее?

Ответы [ 6 ]

7 голосов
/ 02 июня 2010

Для данного раздела и порядка ROW_NUMBER () требуется индекс на (Machine_ID, Date_Time) для выполнения за один проход:

CREATE INDEX idxMachineIDDateTime ON DataTable (Machine_ID, Date_Time);

Отдельные индексы для Machine_ID и Date_Time мало что помогут, если таковые имеются.

6 голосов
/ 03 июня 2010

Как это соотносится с этой версией?

SELECT x.*
    ,(SELECT MAX(Date_Time)
      FROM dbo.DataTable
      WHERE Machine_ID = x.Machine_ID
          AND Date_Time < x.Date_Time
    ) AS PreviousDateTime
FROM dbo.DataTable AS x

Или эта версия?:

SELECT x.*
    ,triang_join.PreviousDateTime
FROM dbo.DataTable AS x
INNER JOIN (
    SELECT l.Machine_ID, l.Date_Time, MAX(r.Date_Time) AS PreviousDateTime
    FROM dbo.DataTable AS l
    LEFT JOIN dbo.DataTable AS r
    ON l.Machine_ID = r.Machine_ID
        AND l.Date_Time > r.Date_Time
    GROUP BY l.Machine_ID, l.Date_Time
) AS triang_join
ON triang_join.Machine_ID = x.Machine_ID
    AND triang_join.Date_Time = x.Date_Time

Оба будут работать лучше всего с индексом Machine_ID, Date_Time, и для правильных результатов я предполагаю, что это уникально.

Вы не упомянули, что скрыто в *, и это иногда может много значить, так как индекс Machine_ID, Date_Time обычно не покрывает, и если у вас много столбцов или у них много данных,. ..

4 голосов
/ 02 июня 2010

Если количество строк в dbo.DataTable велико, вероятно, у вас возникла проблема из-за самосоединения CTE с самим собой. В блоге есть подробное объяснение проблемы здесь

Иногда в таких случаях я прибегал к созданию временной таблицы для вставки результата запроса CTE в и последующего выполнения объединений с этой временной таблицей (хотя обычно это было для случаев, когда большое количество объединений против временной таблица обязательна - в случае одиночного объединения разница в производительности будет менее заметна)

2 голосов
/ 02 июня 2010

У меня были некоторые странные проблемы с производительностью при использовании CTE в SQL Server 2005. Во многих случаях замена CTE на реальную временную таблицу решила проблему.

Я бы попробовал это перед дальнейшим использованием CTE.

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

Обновление

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

Если это так, и использование сохраненного процесса не является вариантом, вы можете рассмотреть возможность превращения текущего CTE в индексированное / материализованное представление. Прежде чем идти по этому пути, вы захотите ознакомиться с предметом, так как это хорошая идея, зависит от множества факторов, не в последнюю очередь от того, как часто обновляются данные.

0 голосов
/ 02 июня 2010

Если вам часто нужны эти данные, а не вычислять их каждый раз, когда вы извлекаете данные, почему бы не добавить столбец и не вычислять / не заполнять его при добавлении строки?

(составной индекс Remus сделает запрос быстрым; выполнение его только один раз должно сделать его еще быстрее.)

0 голосов
/ 02 июня 2010

Что, если вы используете триггер для сохранения последней временной отметки вычитания каждый раз, чтобы получить разницу?

...