2012-06-14 2 views
1

У нас есть запрос, который занимает очень много времени, чтобы заполнить большой набор данных. Я думаю, что я отследил его до функции table-value на SQL-сервере.Оптимизация/улучшение функции табличных значений SQL

Запрос предназначен для возврата разницы в использовании печати между двумя датами. Поэтому, если на принтере было использовано 100 на дату x и 200 на дату, то строка должна быть возвращена, что отражает изменение использования 100.

Эти данные берутся периодически (но не каждый день) и сохраняются в таблице под названием MeterReadings. Код для функции table-value приведен ниже. Затем он вызывается из другого SQL-запроса, который присоединяется к возвращенной таблице в таблице устройств с внутренним соединением для получения дополнительной информации об устройстве.

Любые рекомендации относительно того, как оптимизировать нижеследующее, будут оценены.

ALTER FUNCTION [dbo].[DeviceUsage] 
-- Add the parameters for the stored procedure here 
(@StartDate DateTime , @EndDate DateTime) 
RETURNS table 
AS 
RETURN 
(

SELECT  MAX(dbo.MeterReadings.ScanDateTime) AS MX, 
     MAX(dbo.MeterReadings.DeviceTotal - reading.DeviceTotal) AS TotalDiff, 
     MAX(dbo.MeterReadings.TotalCopy - reading.TotalCopy) AS CopyDiff, 
     MAX(dbo.MeterReadings.TotalPrint - reading.TotalPrint) AS PrintDiff, 
     MAX(dbo.MeterReadings.TotalScan - reading.TotalScan) AS ScanDiff, 
     MAX(dbo.MeterReadings.TotalFax - reading.TotalFax) AS FaxDiff, 
     MAX(dbo.MeterReadings.TotalMono - reading.TotalMono) AS MonoDiff, 
     MAX(dbo.MeterReadings.TotalColour - reading.TotalColour) AS ColourDiff, 
     MIN(reading.ScanDateTime) AS MN, dbo.MeterReadings.DeviceID 

FROM  dbo.MeterReadings INNER JOIN (SELECT * FROM dbo.MeterReadings WHERE  
     (dbo.MeterReadings.ScanDateTime > @StartDate) AND 
     (dbo.MeterReadings.ScanDateTime < @EndDate)) 
     AS reading ON dbo.MeterReadings.DeviceID = reading.DeviceID 

WHERE  (dbo.MeterReadings.ScanDateTime > @StartDate) AND (dbo.MeterReadings.ScanDateTime < @EndDate) 

GROUP BY dbo.MeterReadings.DeviceID); 
+0

Эта функция выполняет только то, что вы просите, если значения только увеличиваются. Может ли какое-либо из значений когда-либо уменьшаться с одной даты на другую? – MatBailie

+0

Кроме того, если есть чтение на 1-м, то 3-е, но ваш @startDate является вторым, какая дата должна быть в первом чтении? Я понимаю, что он должен быть первым, но здесь используется третий. – MatBailie

+0

Спасибо за ответ. Поле, которое установлено в значение false, если чтение идет вниз, поэтому я могу довольно легко отфильтровать его. В контексте приложения я бы никогда не ожидал, что использование снизится. Также в ответ на ваш второй пункт ваше предположение верно. Запрос должен вернуться с 1-го. –

ответ

0

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

SELECT 
    DeviceID, 
    MIN(ScanDateTime)      AS MN, 
    MAX(ScanDateTime)      AS MX, 
    MAX(DeviceTotal) - MIN(DeviceTotal) AS TotalDiff, 
    MAX(TotalCopy ) - MIN(TotalCopy ) AS CopyDiff, 
    MAX(TotalPrint ) - MIN(TotalPrint) AS PrintDiff, 
    MAX(TotalScan ) - MIN(TotalScan ) AS ScanDiff, 
    MAX(TotalFax ) - MIN(TotalFax ) AS FaxDiff, 
    MAX(TotalMono ) - MIN(TotalMono ) AS MonoDiff, 
    MAX(TotalColour) - MIN(TotalColour) AS ColourDiff 
FROM 
    dbo.MeterReadings 
WHERE 
     ScanDateTime > @StartDate 
    AND ScanDateTime < @EndDate 
GROUP BY 
    DeviceID 

Это предполагает, что если у вас есть чтение по датам 1, 3, 5, 7, 9 и вы хотите сообщить о 2 -> 8, то вы хотите reading 7 - reading 3. Я бы подумал, что вы хотите reading 7 - reading 1?

Вышеприведенный запрос должен быть точным для относительно небольших диапазонов. Если у вас огромный диапазон времени, то MAX() - MIN() будет работать на большом количестве строк. Это может быть, возможно, еще более улучшено с помощью следующего (с коррелированными подзапросами для поиска только двух строк, которые вы хотите).

В качестве побочного преимущества это также работает, даже если значения могут уменьшаться, а также вверх.

(Я предполагаю существование таблицы устройств для более простого запроса и повышения производительности.)

SELECT 
    Device.DeviceID, 
    start.ScanDateTime      AS MN, 
    finish.ScanDateTime      AS MX, 
    finish.DeviceTotal - start.DeviceTotal AS TotalDiff, 
    finish.TotalCopy - start.TotalCopy AS CopyDiff, 
    finish.TotalPrint - start.TotalPrint AS PrintDiff, 
    finish.TotalScan - start.TotalScan AS ScanDiff, 
    finish.TotalFax - start.TotalFax  AS FaxDiff, 
    finish.TotalMono - start.TotalMono AS MonoDiff, 
    finish.TotalColour - start.TotalColour AS ColourDiff 
FROM 
    dbo.Device     AS device 
INNER JOIN 
    dbo.MeterReadings   AS start 
    ON start.DeviceID = device.DeviceID 
    AND start.ScanDateTime = (SELECT MIN(ScanDateTime) 
           FROM dbo.MeterReadings 
           WHERE DeviceID = device.DeviceID 
           AND ScanDateTime > @startDate 
           AND ScanDateTime < @endDate) 
INNER JOIN 
    dbo.MeterReadings   AS finish 
    ON finish.DeviceID  = device.DeviceID 
    AND finish.ScanDateTime = (SELECT MAX(ScanDateTime) 
           FROM dbo.MeterReadings 
           WHERE DeviceID = device.DeviceID 
           AND ScanDateTime > @startDate 
           AND ScanDateTime < @endDate) 

Это также может быть изменен, чтобы забрать начало как первую дату или до @ startDate, если требуется.

EDIT: Модификация, чтобы выбрать начало чтения как находящееся на первое свидание до или после @startDate.

SELECT 
    Device.DeviceID, 
    start.ScanDateTime            AS MN, 
    finish.ScanDateTime            AS MX, 
    COALESCE(finish.DeviceTotal, 0) - COALESCE(start.DeviceTotal, 0) AS TotalDiff, 
    COALESCE(finish.TotalCopy , 0) - COALESCE(start.TotalCopy , 0) AS CopyDiff, 
    COALESCE(finish.TotalPrint , 0) - COALESCE(start.TotalPrint , 0) AS PrintDiff, 
    COALESCE(finish.TotalScan , 0) - COALESCE(start.TotalScan , 0) AS ScanDiff, 
    COALESCE(finish.TotalFax , 0) - COALESCE(start.TotalFax , 0) AS FaxDiff, 
    COALESCE(finish.TotalMono , 0) - COALESCE(start.TotalMono , 0) AS MonoDiff, 
    COALESCE(finish.TotalColour, 0) - COALESCE(start.TotalColour, 0) AS ColourDiff 
FROM 
    dbo.Device     AS device 
LEFT JOIN 
    dbo.MeterReadings   AS start 
    ON start.DeviceID = device.DeviceID 
    AND start.ScanDateTime = (SELECT MAX(ScanDateTime) 
           FROM dbo.MeterReadings 
           WHERE DeviceID = device.DeviceID 
           AND ScanDateTime < @startDate) 
LEFT JOIN 
    dbo.MeterReadings   AS finish 
    ON finish.DeviceID  = device.DeviceID 
    AND finish.ScanDateTime = (SELECT MAX(ScanDateTime) 
           FROM dbo.MeterReadings 
           WHERE DeviceID = device.DeviceID 
           AND ScanDateTime < @endDate) 
+0

Большое спасибо. Этот ответ был прекрасен. Вы были правы - это сильно сократило время запроса. –

+0

Ах да, спасибо, я вижу, где предыдущий запрос шел неправильно. Все работает так, как должно быть сейчас, большое спасибо. –

0

Ваш запрос, кажется, вычисляет кросс-продукт всех показаний в диапазоне времени для каждого конкретного устройства. Это работает семантически, потому что агрегаты MIN и MAX не заботятся о дубликатах. Но это очень медленно. Если вы сравниваете 100 дат с самим собой, вам нужно обработать 10 000 строк.

Я предлагаю вам рассчитать значения MIN и MAX для каждого показателя/столбца за весь временной интервал, а затем вычесть их. Таким образом, вам не нужно присоединяться, и вам нужен один проход данных. Например:

select Diff = MAX(col) - MIN(col) 
from readings 
group by DeviceID 
Смежные вопросы