【问题标题】:Finding the difference within a SQL Server query and dividing by seconds elapsed between records查找 SQL Server 查询中的差异并除以记录之间经过的秒数
【发布时间】:2011-09-20 03:39:29
【问题描述】:

您好,我在 SQL Server 数据库中有一个仪表值表,其中包含一个包含以下列的表:

Timestamp, meterID, rawValue

我正在尝试使用查询和谷歌图表绘制用水率图表,问题是我需要根据每 15 到 30 分钟更新一次的原始水表值计算用水率。

我想运行一个返回特定水表值的查询。

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

非常感谢任何帮助。

【问题讨论】:

  • 您使用 SQL Server 2000 还是 2005/8 ?

标签: sql-server coalesce rate


【解决方案1】:

编辑 1:我修改了索引定义以消除查找运算符 => 更少的逻辑读取。

编辑 2: 我添加了基于 quirky update 方法的第二个解决方案。请阅读 Jeff Moden 撰写的这篇文章 (Solving the Running Total and Ordinal Rank Problems)。

可以使用 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(请阅读 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

【讨论】:

    【解决方案2】:
    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 中“过滤掉”与当前行匹配的那个

    【讨论】:

    • 您将每个新值与相同的第一个值进行比较,然后计算每个值自开始以来的变化率。我相信@tbrew 想要将每个值与前一个值进行比较,并仅计算自前一个值以来的变化率。另外,我认为您将第 1 行与第 2 行连接,然后将第 2 行与第 1 行连接。后者会给您带来与前者相反的结果。不要误会,我喜欢你的解决方案,我特别喜欢它是一个集合操作而不是游标,我只是不确定它是否满足要求。
    • 回复:您的更新,现在您正在计算从值的时间点到最近时间点的所有值的变化率。同样,这不是将每个值与前一个值进行比较。
    【解决方案3】:

    我对我的查询和@Bogdan 的查询进行了一些小改动,以使它们尽可能相似,然后进行比较。 Bogdan 修改后的查询在这篇文章的底部。

    在同一个查询中堆叠在一起,根据 SQL Server 查询执行计划,我的是 53% 的查询成本,而博格丹的是 47%。

    对于博格丹帖子中提供的数据集:

    • 我的查询:6 次扫描和 27 次逻辑读取
    • Bogdan:6 次扫描和 72 次逻辑读取

    我每隔 15 秒为meterID 1 和 2 添加值,最多 5 分钟,总共 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
    

    Bogdan 修改后的查询,按 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];
    

    【讨论】:

    • FORWARD_ONLY FAST_FORWARD READ_ONLY ? FAST_FORWARD = FORWARD_ONLY + READ_ONLY
    • 您是否使用 IUN_MeterValues_MeterID_Timestamp 索引进行测试?
    • 是的 - 我使用了您发布的 CREATE TABLE 和 INDEX 语句。我只是从我的帖子中省略了 DDL。出于好奇,我会根据您的索引更改重新测试。
    • 我已经修改了IUN_MeterValues_MeterID_Timestamp 索引,所以Bookmark Lookup 运算符将被淘汰。这意味着更少的逻辑读取。此外,我还添加了另一种基于“古怪更新”方法的解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-09-17
    • 2011-06-17
    • 2020-05-15
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多