2013-07-30 2 views
0

Рассмотрим следующий запрос ...TSQL Последняя запись Эффективность Cursor, подзапрос или КТР

SELECT 
    * 
    ,CAST(
      (CurrentSampleDateTime - PreviousSampleDateTime) AS FLOAT 
     ) * 24.0 * 60.0 AS DeltaMinutes 
FROM  
( 
    SELECT 
     C.SampleDateTime AS CurrentSampleDateTime 
     ,C.Location 
     ,C.CurrentValue 
     ,(
      SELECT TOP 1 
       Previous.SampleDateTime 
      FROM Samples AS Previous 
      WHERE 
        Previous.Location = C.Location 
       AND Previous.SampleDateTime < C.SampleDateTime 
      ORDER BY Previous.SampleDateTime DESC  
     ) AS PreviousSampleDateTime 
    FROM Samples AS C 
) AS TempResults 

Предполагая, что при прочих равных условиях, таких как индексирование и т.д. это наиболее эффективный способ достижения вышеуказанных результатов? Это использует SubQuery для извлечения последней записи?

Будет ли я лучше создавать курсор, который упорядочивает по Location, SampleDateTime и настраивает переменные для CurrentSampleDateTime и PreviousSampleDateTime ..., устанавливая значение Previous to the Current в нижней части цикла while?

Я не очень хорош с CTE, это что-то, что можно было бы сделать более эффективно с помощью CTE? Если да, то как это будет выглядеть?

Скорее всего, мне нужно будет вернуть PreviousValue вместе с предыдущим SampleDateTime, чтобы получить среднее значение из двух. Это влияет на результаты.

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

---- UPDATE Следует отметить, что у меня есть кластерный индекс в Location, SampleDateTime, CurrentValue, так что, возможно, именно это влияет на результаты больше всего на свете.

с 5,591,571 записывает мой запрос (тот выше) в среднем занимает 3 минуты и 20 секунд

КТР, что Йоахим Исакссон ниже в среднем занимает 5 минут и 15 секунд.

Возможно, это занимает больше времени, потому что оно не использует кластерный индекс, но использует рябину для соединений?

Я начал тестировать метод курсора, но это уже через 10 минут ... так что не идти дальше.

Я дам ему день или около того, но думаю, что я приму ответ CTE, предоставленный Йоахимом Исакссоном, только потому, что нашел новый метод получения последней строки.

Может ли кто-нибудь согласиться с тем, что это индекс в Location, SampleDateTime, CurrentValue, который ускоряет метод подзапроса?

У меня нет SQL Server 2012, поэтому я не могу протестировать метод LEAD/LAG. Я бы поспорил, что это будет быстрее, чем все, что я пробовал, предполагая, что Microsoft реализовала это эффективно. Вероятно, просто нужно поменять указатель на ссылку памяти в конце каждой строки.

+0

я по крайней мере использовать 'MIN' вместо' ТОП 1 х ... ORDER BY x' (если конструкция не предназначена для сортировки нулевой первый) –

+0

@JoachimIsaksson Но он нуждается в ' MAX' – Lamak

+0

Какую версию SQL Server вы используете? –

ответ

1

Если вы используете SQL Server 2012, вы можете использовать функцию окна LAG, которая извлекает значение указанного столбца из предыдущей строки. Он возвращает null, если предыдущей строки нет.

SELECT 
a.*, 
CAST((a.SampleDateTime - LAG(a.SampleDateTime) OVER(PARTITION BY a.location ORDER BY a.SampleDateTime ASC)) AS FLOAT) 
      * 24.0 * 60.0 AS DeltaMinutes 
FROM samples a 
ORDER BY 
a.location, 
a.SampleDateTime 

Вам нужно будет запустить несколько тестов, чтобы узнать, быстрее ли это. Если вы не используете SQL Server 2012, то, по крайней мере, это может дать другим понять, как это можно сделать с 2012 годом. Мне нравится ответ @Joachim Isaksson с использованием CTE с Row_Number()/Partition By для 2008 и 2005 годов .

SQL Fiddle

Рассматривали ли вы создать временную таблицу, чтобы использовать вместо КТР или подзапрос? Вы можете создавать индексы в таблице temp, которые больше подходят для соединения в RowNumber.

CREATE TABLE #tmp (
    RowNumber INT, 
    Location INT, 
    SampleDateTime DATETIME, 
    CurrentValue INT) 
; 

INSERT INTO #tmp 
SELECT 
    ROW_NUMBER() OVER (PARTITION BY Location 
          ORDER BY SampleDateTime DESC) rn, 
    Location, 
    SampleDateTime, 
    CurrentValue 
FROM Samples 
; 

CREATE INDEX idx_location_row ON #tmp(Location,RowNumber) INCLUDE (SampleDateTime,CurrentValue); 

SELECT 
a.Location, 
a.SampleDateTime, 
a.CurrentValue, 
CAST((a.SampleDateTime - b.SampleDateTime) AS FLOAT) * 24.0 * 60.0 AS DeltaMinutes 
FROM #tmp a 
LEFT JOIN #tmp b ON 
a.Location = b.Location 
AND b.RowNumber = a.RowNumber +1 
ORDER BY 
a.Location, 
a.SampleDateTime 

SQL Fiddle #2

+0

4:10 минут для моего набора результатов ... быстрее, чем у CTE, но все же медленнее, чем у моего оригинала. Благодарим за дополнительную информацию об индексах на временных таблицах. Я предполагаю, что если бы удалил мой текущий индекс, метод temp table был бы быстрее, чем мой, и жаль, что я не могу протестировать метод LEAD/LAG на моем сервере 2008 года. –

+0

Пробовал ли вы с «ORDER BY» или без? Если вам не нужен ORDER BY, вы можете взять это из окончательного выбора, так как вы имеете дело с большим набором данных, и упорядочение питает столько CPU. Это может ускорить процесс. Вы также можете играть с созданием индекса CLUSTERED в таблице temp по местоположению, rownumber, sampleDateTime, currentValue вместо обычного индекса с include. –

+0

На самом деле у вас осталось 3:51, изменив заказ на «ORDER BY Location, SampleDateTime DESC» ... в основном положил место перед ним.Я предполагаю, что это увеличивает производительность, потому что это то, на чем определен исходный индекс. –

1

Как всегда, тестирование с вашими настоящими данными - это король.

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

WITH cte AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY Location 
           ORDER BY SampleDateTime DESC) rn 
    FROM Samples 
) 
SELECT a.*,CAST((a.SampleDateTime - b.SampleDateTime) AS FLOAT) 
       * 24.0 * 60.0 AS DeltaMinutes 
FROM cte a 
LEFT JOIN cte b ON a.Location = b.Location AND b.rn = a.rn +1 

An SQLfiddle to test with.

+0

В вашем примере с 5 вставками в таблицу ... в результате не должно быть 5 записей. 3. DeltaMinutes будет null для самой первой записи каждого местоположения, так как у нее не будет предыдущего времени выборки. Скажем, у меня есть одно место, которое получает выборочное 7 раз в неделю, в результирующем наборе должно быть 7 записей для этого местоположения. Я не уменьшаю/сокращаю набор записей, а просто добавляю расчетную информацию на основе текущей записи и предыдущей записи. –

+0

@ DanP http://sqlfiddle.com/#!3/97abb/3? –

+0

Выполняйте мой запрос в примере SQLFiddle, чтобы увидеть результаты, которые я ищу. Также не обязательно вопрос о том, как преобразовать SQL в CTE, хотя было бы неплохо ... но что более важно, какой метод, подзапрос, курсор, cte будет давать мне оптимальную производительность. Мой текущий запрос выполняется очень медленно. –

Смежные вопросы