Нахождение разницы в запросе SQL Server и деление на секунды, прошедшие между записями - PullRequest
0 голосов
/ 20 сентября 2011

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

Timestamp, meterID, rawValue

Я пытаюсь построить график использования воды с помощью запроса и диаграмм Google.проблема в том, что мне нужно вычислить скорость из значений необработанного счетчика, которые обновляются каждые 15–30 минут.

Я хочу выполнить запрос, который возвращает значения для определенного счетчика воды.

MeterID, Timestamp, (rawValue-previousRawValue)/(timestamp difference in seconds)

любая помощь очень ценится.

Ответы [ 3 ]

2 голосов
/ 20 сентября 2011

Редактировать 1: Я изменил определение индекса, чтобы исключить оператор LookUp => меньше логических чтений.

Редактировать 2: Я добавил второе решение на основе причудливый метод обновления .Пожалуйста, прочитайте эту статью ( Решение проблем промежуточного и порядкового ранга ), написанную Джеффом Моденом.

Первое решение может быть протестировано с SQL Server 2005/2008:

--Create test table
CREATE TABLE dbo.MeterValues
(
    ID INT IDENTITY(1,1) PRIMARY KEY
    ,[Timestamp] DATETIME NOT NULL
    ,MeterID INT NOT NULL
    ,RawValue INT NOT NULL
);

CREATE UNIQUE INDEX IUN_MeterValues_MeterID_Timestamp
--SQL Server 2008
ON  dbo.MeterValues (MeterID, [Timestamp])
INCLUDE (RawValue)
--SQL Server 2005
--ON  dbo.MeterValues (MeterID, [Timestamp],RawValue)
--DROP INDEX dbo.MeterValues.IUN_MeterValues_MeterID_Timestamp

--Insert some values
INSERT  dbo.MeterValues ([Timestamp], MeterID, RawValue)
SELECT  '2011-01-01T00:00:00', 1, 100
UNION ALL
SELECT  '2011-01-01T00:00:15', 1, 105
UNION ALL
SELECT  '2011-01-01T00:00:30', 1, 102
UNION ALL
SELECT  '2011-01-01T00:00:45', 1, 108
UNION ALL
SELECT  '2011-01-01T00:01:00', 1, 109

UNION ALL
SELECT  '2011-01-01T00:00:00', 2, 1000
UNION ALL
SELECT  '2011-01-01T00:00:15', 2,  900
UNION ALL
SELECT  '2011-01-01T00:00:30', 2, 1105
UNION ALL
SELECT  '2011-01-01T00:00:45', 2, 1050
UNION ALL
SELECT  '2011-01-01T00:01:00', 2,  910;

--Check test data
SELECT  *
FROM    dbo.MeterValues mv
ORDER BY mv.MeterID, mv.ID DESC;

--Solution
WITH ValuesWithRowNumber
AS
(
    SELECT  mv.MeterID
            ,mv.RawValue
            ,mv.[Timestamp]
            ,ROW_NUMBER() OVER(PARTITION BY mv.MeterID ORDER BY mv.[Timestamp] ASC) RowNum
    FROM    dbo.MeterValues mv
)
SELECT  crt.MeterID
        ,crt.[Timestamp] AS CrtTimestamp
        ,prev.[Timestamp] AS PrevTimestamp
        ,crt.RawValue AS CrtRawValue
        ,prev.RawValue AS PrevRawValue
        ,(crt.RawValue - prev.RawValue)*1.00/DATEDIFF(SECOND, prev.[Timestamp], crt.[Timestamp]) Diff
        ,STR((crt.RawValue - prev.RawValue)*1.00/DATEDIFF(SECOND, prev.[Timestamp], crt.[Timestamp])*100, 10, 2)+'%' [Percent]
FROM    ValuesWithRowNumber crt --crt=current
LEFT JOIN ValuesWithRowNumber prev ON crt.MeterID = prev.MeterID --prev=previous
AND     crt.RowNum - 1 = prev.RowNum
ORDER BY crt.MeterID, crt.[Timestamp] DESC;

--By, by
DROP TABLE dbo.MeterValues;

Результаты:

MeterID     CrtTimestamp            PrevTimestamp           CrtRawValue PrevRawValue Diff                                    Percent
----------- ----------------------- ----------------------- ----------- ------------ --------------------------------------- -----------
1           2011-01-01 00:01:00.000 2011-01-01 00:00:45.000 109         108          0.0666666666666                               6.67%
1           2011-01-01 00:00:45.000 2011-01-01 00:00:30.000 108         102          0.4000000000000                              40.00%
1           2011-01-01 00:00:30.000 2011-01-01 00:00:15.000 102         105          -0.2000000000000                            -20.00%
1           2011-01-01 00:00:15.000 2011-01-01 00:00:00.000 105         100          0.3333333333333                              33.33%
1           2011-01-01 00:00:00.000 NULL                    100         NULL         NULL                                    NULL
2           2011-01-01 00:01:00.000 2011-01-01 00:00:45.000 910         1050         -9.3333333333333                           -933.33%
2           2011-01-01 00:00:45.000 2011-01-01 00:00:30.000 1050        1105         -3.6666666666666                           -366.67%
2           2011-01-01 00:00:30.000 2011-01-01 00:00:15.000 1105        900          13.6666666666666                           1366.67%
2           2011-01-01 00:00:15.000 2011-01-01 00:00:00.000 900         1000         -6.6666666666666                           -666.67%
2           2011-01-01 00:00:00.000 NULL                    1000        NULL         NULL                                    NULL

Второе решение может / должно работать с SQL 2000/2005/2008 (см. Раздел «ПРАВИЛА» из статьи Джеффа Модена):

--Create test table
CREATE TABLE dbo.MeterValues
(
    MeterID INT NOT NULL
    ,[Timestamp] DATETIME NOT NULL    
    ,RawValue INT NOT NULL
    ,Diff NUMERIC(10,3) NULL
    ,PRIMARY KEY CLUSTERED(MeterID,[Timestamp])
);

--Insert some values
INSERT  dbo.MeterValues ([Timestamp], MeterID, RawValue)
SELECT  '2011-01-01T00:00:00', 1, 100
UNION ALL
SELECT  '2011-01-01T00:00:15', 1, 105
UNION ALL
SELECT  '2011-01-01T00:00:30', 1, 102
UNION ALL
SELECT  '2011-01-01T00:00:45', 1, 108
UNION ALL
SELECT  '2011-01-01T00:01:00', 1, 109

UNION ALL
SELECT  '2011-01-01T00:00:00', 2, 1000
UNION ALL
SELECT  '2011-01-01T00:00:15', 2,  900
UNION ALL
SELECT  '2011-01-01T00:00:30', 2, 1105
UNION ALL
SELECT  '2011-01-01T00:00:45', 2, 1050
UNION ALL
SELECT  '2011-01-01T00:01:00', 2,  910;

--Check test data
SELECT  *
FROM    dbo.MeterValues mv
ORDER BY mv.MeterID, mv.[Timestamp];

DECLARE @OldRawValue INT
        ,@Diff NUMERIC(10,3)
        ,@OldMeterID INT
        ,@OldTimestamp DATETIME;

PRINT '*****Star*****'              
--Calculations
UPDATE  dbo.MeterValues WITH(TABLOCKX)
SET     @Diff = CASE WHEN @OldMeterID = MeterID THEN (RawValue - @OldRawValue)*1.00/DATEDIFF(SECOND,@OldTimeStamp,[TimeStamp]) END 
        ,Diff = @Diff
        ,@OldRawValue = RawValue
        ,@OldMeterID = MeterID
        ,@OldTimestamp = [Timestamp]        
OPTION(MAXDOP 1);

--Results
SELECT  *
FROM    dbo.MeterValues mv
ORDER BY mv.MeterID, mv.[Timestamp];
PRINT '*****Stop*****'

--By, by
DROP TABLE dbo.MeterValues;

Результаты:

MeterID     Timestamp               RawValue    Diff
----------- ----------------------- ----------- ---------------------------------------
1           2011-01-01 00:01:00.000 109         0.067
1           2011-01-01 00:00:45.000 108         0.400
1           2011-01-01 00:00:30.000 102         -0.200
1           2011-01-01 00:00:15.000 105         0.333
1           2011-01-01 00:00:00.000 100         NULL
2           2011-01-01 00:01:00.000 910         -9.333
2           2011-01-01 00:00:45.000 1050        -3.667
2           2011-01-01 00:00:30.000 1105        13.667
2           2011-01-01 00:00:15.000 900         -6.667
2           2011-01-01 00:00:00.000 1000        NULL
0 голосов
/ 20 сентября 2011
Try this

Select a.metered,a.timestamp,
   (a.rawValue-b.rawValue)/(a.timestamp-b.timestamp)

From meters A
Join (selec top 2 rawValue,Timestamp 
      From meters where metered = @meter
      order by timestamp DESC) b
On b.timestamp <> a.timestamp and a.meterId=B.meterId

Добавлен DESC к метке времени в среднем запросе. Это приведет к тому, что будут возвращены самые последние две метки времени, а затем JOIN «отфильтрует» ту, которая соответствует текущей строке из A

0 голосов
/ 20 сентября 2011

Я внес несколько незначительных изменений в свой запрос и запрос @ Bogdan, чтобы сделать их максимально похожими, а затем сравнил их. Модифицированный запрос Богдана находится внизу этого поста.

Сложенные вместе в одном запросе, согласно плану выполнения запросов SQL Server, моя составляет 53% от стоимости запроса, а Богдан - 47%.

Для набора данных, предлагаемых в посту Богдана:

  • Мой запрос: 6 сканирований и 27 логических чтений
  • Богдана: 6 сканирований и 72 логических чтения

Я добавлял значения каждые 15 секунд до 5 минут для meterID 1 и 2, чтобы получить в общей сложности 42 записи, затем перезапускал запросы с помощью SQL Server Profiler.

Там, где мой запрос выигрывает при чтении, Богдан по-прежнему выигрывает по процессору и продолжительности.

          CPU  SCANS  READS  DURATION
--------------------------------------
Mine       47     22    313     249ms
Bogdan's   16     22    972      15ms
--------------------------------------

Я делаю несколько предположений, например, ваш MeterID - INT. Измените это при необходимости.

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

Это должно работать на SQL Server 2005 и более поздних версиях.

Я делаю несколько вещей, которые могут отвлечь от фактического решения. Основная логика действительно в цикле WHILE.

CREATE PROCEDURE [dbo].[GetMeterResults]
    @MeterID INT
AS
BEGIN

    -- create a temp table to store results
    CREATE TABLE #tempResults
    (
        MeterID INT,
        [Timestamp] DATETIME,
        Result FLOAT
    )

    DECLARE  
        @Timestamp DATETIME, 
        @RawValue INT,
        @LastTimestamp DATETIME, 
        @LastRawValue INT,
        @FirstRun BIT = 1

    DECLARE cr CURSOR FAST_FORWARD FOR 
    SELECT 
        [Timestamp], 
        RawValue 
    FROM 
        YourTable
    WHERE
        MeterID = @MeterID
    ORDER BY
        [Timestamp]

    OPEN cr
    FETCH NEXT FROM cr INTO @Timestamp, @RawValue

    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        IF (@FirstRun = 1)
        BEGIN -- the first run
            SELECT @FirstRun = 0 -- flip the bit for all future runs
        END
        ELSE -- all subsequent runs after the first 
        BEGIN       
            INSERT INTO 
                #tempResults
            SELECT 
                @MeterID,
                @Timestamp, 
                (@RawValue - @LastRawValue) * 1.00 / DATEDIFF(s, @LastTimestamp, @Timestamp)
        END

            -- save the current values for comparison on the next run       
        SELECT  
            @LastTimestamp = @Timestamp, 
            @LastRawValue = @RawValue

        FETCH NEXT FROM cr INTO @Timestamp, @RawValue
    END

    CLOSE CR
    DEALLOCATE CR

    -- return the result set
    SELECT
        *
    FROM
        #tempResults

    -- clean up the temp table
    DROP TABLE #tempResults
END
GO

модифицированный запрос Богдана, который фильтрует по MeterID для сравнения яблок с яблоками с моим запросом выше:

DECLARE @MeterID INT = 1;

WITH ValuesWithRowNumber
AS
(
    SELECT  mv.MeterID
            ,mv.RawValue
            ,mv.[Timestamp]
            ,ROW_NUMBER() OVER(PARTITION BY mv.MeterID ORDER BY mv.[Timestamp] ASC) RowNum
    FROM    dbo.MeterValues mv
    WHERE mv.MeterID = @MeterID
)
SELECT  crt.MeterID
        ,crt.[Timestamp] AS CrtTimestamp
        ,prev.[Timestamp] AS PrevTimestamp
        ,crt.RawValue AS CrtRawValue
        ,prev.RawValue AS PrevRawValue
        ,(crt.RawValue - prev.RawValue)*1.00/DATEDIFF(SECOND, prev.[Timestamp], crt.[Timestamp]) Diff
FROM    ValuesWithRowNumber crt --crt=current
JOIN ValuesWithRowNumber prev ON crt.RowNum - 1 = prev.RowNum
ORDER BY crt.[Timestamp];
...