0

こんにちは、次の列を持つテーブルを含む SQL Server データベースにメーター値のテーブルがあります。

Timestamp, meterID, rawValue

クエリと Google チャートを使用して水の使用率をグラフ化しようとしていますが、問題は、15 ~ 30 分ごとに更新される未加工のメーター値から使用率を計算する必要があることです。

特定の水道メーターの値を返すクエリを実行したいと考えています。

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

どんな助けでも大歓迎です。

4

3 に答える 3

2

編集1:ルックアップ演算子を削除するようにインデックス定義を変更しました=>論理読み取りが少なくなりました。

編集2:風変わりな更新方法に基づく2番目のソリューションを追加しました。Jeff Modenによって書かれたこの記事(現在の合計と序数のランクの問題の解決)をお読みください。

最初のソリューションは、SQLServer2005/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

2番目のソリューションはSQL2000/2005/2008で機能する/機能するはずです(Jeff Modenの記事の「ルール」セクションをお読みください)。

--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
于 2011-09-20T08:05:37.697 に答える
0

私のクエリと@Bogdanのクエリの両方にいくつかの小さな変更を加えて、それらを可能な限り類似させてから比較しました。Bogdan が修正したクエリは、この投稿の最後にあります。

SQL Server Query Execution Plan によると、同じクエリに積み重ねると、私のクエリ コストはクエリ コストの 53% で、Bogdan のクエリは 47% です。

Bogdan の投稿で提供されているデータ セットの場合:

  • 私のクエリ: 6 回のスキャンと 27 回の論理読み取り
  • Bogdan: 6 回のスキャンと 72 回の論理読み取り

meterID 1 と 2 の両方に最大 5 分まで 15 秒ごとに値を追加して、合計 42 レコードにし、SQL Server Profiler でクエリを再実行しました。

私のクエリは読み取りで勝っていますが、Bogdan は CPU と期間で勝っています。

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

MeterID が INT であるなど、いくつかの仮定を立てています。必要に応じて変更してください。

また、特定のメーター ID に対してクエリを実行する必要があるため、パラメーターとしてストアド プロシージャに渡されると想定しています。

これは、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 でフィルタリングする Bogdan の変更されたクエリ:

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];
于 2011-09-20T04:47:27.883 に答える