获得微小的平均值

时间:2017-01-26 17:18:36

标签: sql-server tsql sql-server-2012

我正在使用此查询来获取值的每小时平均值,它似乎工作正常。

select Time_Stamp_Hour = dateadd(hh,datepart(hh,Time_Stamp), cast(CAST(Time_Stamp as date) as datetime))
      ,AvgValue = AVG(Value)
from ValueLog
group by dateadd(hh,datepart(hh,Time_Stamp)
        ,cast(CAST(Time_Stamp as date) as datetime))

我需要一整天的平均分数,所以有一天返回的结果应该有1440条记录。

我已经尝试将hh切换到mi,但它只给了我整天平均60个单独的分钟,而不是每天按小时分解。如何将此语句转换为当天每小时的分钟平均值。我正在使用sql server 2012。

sample result when using mi instead of hh

当使用mi代替hh时,小时列不会返回正确的小时(仅显示00)以及上述语句,而我每天只能获得60个结果。

这是一个例子,当我以hh作为我的条件运行上述语句时,结果显示正确。

hh

2 个答案:

答案 0 :(得分:1)

要截断时间戳的秒数,请使用:dateadd(minute, datediff(minute, 0, Time_Stamp), 0)

select Time_Stamp_Hour_Minute = dateadd(minute, datediff(minute, 0, Time_Stamp), 0)
  , AvgValue = avg(Value)
from ValueLog
group by dateadd(minute, datediff(minute, 0, Time_Stamp), 0)

您还可以将问题中的表达式替换为按小时截断dateadd(hour, datediff(hour, 0, Time_Stamp), 0)

使用convert(varchar(5),time_stamp,8)会将时间返回为hh:mi,这听起来就像您尝试做的那样。样式8返回格式hh:mi:ss,并将其转换为varchar(5)将其限制为前5个字符(hh:mi)。

select Time_Stamp_Hour_Minute   = convert(varchar(5),Time_Stamp,8)
      , AvgValue = avg(Value)
from ValueLog
group by convert(varchar(5),Time_Stamp,8)

参考:cast() and convert() styles

答案 1 :(得分:0)

如果您正在寻找“TWA”时间加权平均值,请检查此怪物。我为我的SCADA包的接口构建了这个。使用此查询,您不限于标准1分钟,1天,1小时间隔。您可以更改@i以获得您想要的间隔。查询的主要工作是处理表中存在的缺失间隔。 I.E如果你在凌晨1点有价值,但在早上8点之前没有其他价值......等等......

注意:删除最终的select语句,并使COMBINE成为最终的select语句,以查看发生了什么。您可以将以下内容直接复制并粘贴到SQL Server中,无需更改即可运行。 在你的情况下,将@i改为等于1,祝你好运。

--Create the test table! Add any values you want!
DECLARE @TEST001 TABLE ( Time_Stamp datetime, TagA INT, TagB INT, TagC INT, TagD INT, TagE INT, TagF INT ) 
-- Insert test Values so we can test what is going on with the monster query below.       
 INSERT INTO @TEST001
 VALUES
    ('2017-01-21 00:01:09.042', NULL, NULL, NULL, 7000, NULL, NULL),
    ('2017-01-21 00:02:03.042', NULL, NULL, 87,   NULL, NULL, NULL),
    ('2017-01-21 00:04:10.155', NULL, 1239, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:04:10.959', NULL, NULL, 86,   NULL, NULL, NULL),
    ('2017-01-21 00:06:49.401', NULL, 1240, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:06:59.301', 100,  1239, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:07:00.000', 109,  NULL, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:07:10.124', 108,  NULL, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:12:11.789', 109,  NULL, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:16:12.190', 108,  NULL, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:16:13.987', 107,  NULL, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:17:31.410', NULL, 1260, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:17:32.511', NULL, 1261, 87,   NULL, NULL, NULL),
    ('2017-01-21 00:17:32.966', NULL, 1262, NULL, NULL, NULL, NULL),
    ('2017-01-21 00:18:53.140', NULL, 1262, NULL, NULL, 6000, NULL)

-- Begin of our query. NOTE to Tech's, The above is not needed WHEN using real table data.
-- To query real data change #TEST001 to your real table data. and TagA to your desired column.
declare @s DATETIME2 --Start date and time
declare @e DATETIME2 --End date and time
declare @i INT   -- Holds our desired interval. if you set to 60 you will get hour intervals, set to 1440 to get daily intervals.
                 -- set to 120 for every two hours
                 -- set to 2880 for every 2 days...
set @s = '2017-01-21 00:00:00.000'
set @e = '2017-01-21 23:59:59.000'
set @i = 60

--TIME GENERATOR, Generates all times between our start and end date.
;WITH ALL_INTERVALS
 AS (
    SELECT TOP (DATEDIFF(MINUTE,@s,@e))
    TIMES = DATEADD(MINUTE,CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id])-1),@s),
    NULL AS VALUE
        FROM sys.all_objects AS s1
        CROSS JOIN
        sys.all_objects AS s2
),
--Union All possable times from our generator with all real data from our table.
ALL_TIMES
AS (
SELECT
        H.Time_Stamp as TIMES,
        H.TagA AS VALUE
    FROM @TEST001 H
    WHERE Time_Stamp BETWEEN @s and @e
    UNION ALL
    SELECT 
        AI.TIMES AS TIMES, 
        AI.VALUE AS VALUE
        FROM ALL_INTERVALS AI           
),
-- We need to fill across, up and down. 
-- TIMES=AT.TIMES, just inCASE our generated time is equal to our real data time.
-- TIMES>AT.TIMES Fill nulls in the upward dirrection.
-- TIMES>AT.TIMES Fill nulls in the downward dirrection.
FILL_UP_DOWN_ACCROSS 
AS ( 
    SELECT 
    TIMES AS TIMES, 
    VALUE AS VALUE1,
    ISNULL(AT.VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES = AT.TIMES AND VALUE IS NOT NULL ORDER BY TIMES ASC)) AS VALUE2,
    ISNULL(AT.VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES > AT.TIMES AND VALUE IS NOT NULL ORDER BY TIMES ASC)) AS VALUE3,
    ISNULL(AT.VALUE, (SELECT TOP 1 VALUE FROM ALL_TIMES WHERE TIMES < AT.TIMES AND VALUE IS NOT NULL ORDER BY TIMES DESC)) AS VALUE4
    FROM ALL_TIMES AT
),
-- COMBINE, Select all non null values. The value1,2,3 can be removed! As they are not needed in the final select statement. 
-- I just have them here for quickly viewing what is going on if i make this COMBINE statement as my final select statement.
COMBINE 
AS (
SELECT TIMES,
        VALUE1,
        VALUE2,
        VALUE3,
        VALUE4,
        COALESCE( CASE WHEN FUDA.VALUE2 IS NULL AND FUDA.VALUE4 IS NULL THEN (FUDA.VALUE3) ELSE FUDA.VALUE2 END,
                  CASE WHEN FUDA.VALUE2 IS NULL AND FUDA.VALUE3 IS NULL THEN (FUDA.VALUE4) ELSE FUDA.VALUE2 END,
                  CASE WHEN FUDA.VALUE2 IS NULL AND FUDA.VALUE4 IS NOT NULL THEN (FUDA.VALUE4) ELSE FUDA.VALUE2 END
                  ) AS VALUE5
        FROM FILL_UP_DOWN_ACCROSS FUDA
)
--Final Select Statement, do the TWA! 
--DO TIME WEIGHED AVERAGE.
SELECT DATEADD(MINUTE,((DATEDIFF(MINUTE,@s,TIMES)/@i)*@i),@s) AS TIMES,
SUM( CONVERT(FLOAT,CAST(TIMES AS DATETIME)) * VALUE5) / SUM(CONVERT(FLOAT,CAST(TIMES AS DATETIME))) AS TagAvg
FROM COMBINE
GROUP BY DATEADD(MINUTE,((DATEDIFF(MINUTE,@s,TIMES)/@i)*@i),@s)
ORDER BY DATEADD(MINUTE,((DATEDIFF(MINUTE,@s,TIMES)/@i)*@i),@s)