【问题标题】:SQL Query to return 24 hour, hourly count even when no values exist?SQL 查询返回 24 小时,每小时计数,即使不存在值?
【发布时间】:2010-03-03 21:28:18
【问题描述】:

我编写了一个查询,它根据给定的日期范围对每小时的行数进行分组。

SELECT CONVERT(VARCHAR(8),TransactionTime,101) + ' ' + CONVERT(VARCHAR(2),TransactionTime,108) as TDate, 
    COUNT(TransactionID) AS TotalHourlyTransactions
    FROM MyTransactions WITH (NOLOCK)
    WHERE TransactionTime BETWEEN CAST(@StartDate AS SMALLDATETIME) AND CAST(@EndDate AS SMALLDATETIME)
    AND TerminalId = @TerminalID
    GROUP BY CONVERT(VARCHAR(8),TransactionTime,101) + ' ' + CONVERT(VARCHAR(2),TransactionTime,108)
    ORDER BY TDate ASC

显示如下内容:

02/11/20 07 4
02/11/20 10 1
02/11/20 12 4
02/11/20 13 1
02/11/20 14 2
02/11/20 16 3

给出交易数量和一天中的给定时间。

如何显示一天中的所有时间 - 从 0 到 23,并为没有值的时间显示 0?

谢谢。

更新

使用下面的 tvf 对我有用一天,但我不确定如何使它适用于某个日期范围。

使用 24 小时临时表:

 -- temp table to store hours of the day    
 DECLARE @tmp_Hours TABLE ( WhichHour SMALLINT )

 DECLARE @counter SMALLINT
 SET @counter = -1
 WHILE @counter < 23 
    BEGIN
        SET @counter = @counter + 1
      --print 
        INSERT  INTO @tmp_Hours
                ( WhichHour )
        VALUES  ( @counter )
    END 

    SELECT MIN(CONVERT(VARCHAR(10),[dbo].[TerminalTransactions].[TransactionTime],101)) AS TDate, [@tmp_Hours].[WhichHour], CONVERT(VARCHAR(2),[dbo].[TerminalTransactions].[TransactionTime],108) AS TheHour,
        COUNT([dbo].[TerminalTransactions].[TransactionId]) AS TotalTransactions, 
        ISNULL(SUM([dbo].[TerminalTransactions].[TransactionAmount]), 0) AS TransactionSum
    FROM [dbo].[TerminalTransactions] RIGHT JOIN @tmp_Hours ON [@tmp_Hours].[WhichHour] = CONVERT(VARCHAR(2),[dbo].[TerminalTransactions].[TransactionTime],108) 
    GROUP BY [@tmp_Hours].[WhichHour], CONVERT(VARCHAR(2),[dbo].[TerminalTransactions].[TransactionTime],108),  COALESCE([dbo].[TerminalTransactions].[TransactionAmount], 0)

给我一​​个结果:

TDate      WhichHour TheHour TotalTransactions TransactionSum
---------- --------- ------- ----------------- ---------------------
02/16/2010 0         00      4                 40.00
NULL       1         NULL    0                 0.00
02/14/2010 2         02      1                 10.00
NULL       3         NULL    0                 0.00
02/14/2010 4         04      28                280.00
02/14/2010 5         05      11                110.00
NULL       6         NULL    0                 0.00
02/11/2010 7         07      4                 40.00
NULL       8         NULL    0                 0.00
02/24/2010 9         09      2                 20.00

那么我怎样才能让它正确分组呢?

另一个问题是,有几天没有交易,这几天也需要出现。

谢谢。

【问题讨论】:

  • 您要报告多少天/哪天的小时数?
  • 对于@start/@enddate 范围

标签: sql-server tsql count group-by


【解决方案1】:

您首先构建 23 hours 表,对 transactions 表进行外部连接。出于同样的目的,我使用表值函数:

create function tvfGetDay24Hours(@date datetime)
returns table
as return (
select dateadd(hour, number, cast(floor(cast(@date as float)) as datetime)) as StartHour
  , dateadd(hour, number+1, cast(floor(cast(@date as float)) as datetime)) as EndHour
from master.dbo.spt_values
where number < 24 and type = 'p');

然后我可以在需要获取“每小时”基础数据的查询中使用 TVF,即使数据中缺少间隔:

select h.StartHour, t.TotalHourlyTransactions
from tvfGetDay24Hours(@StartDate) as h
outer apply (
  SELECT 
    COUNT(TransactionID) AS TotalHourlyTransactions
    FROM MyTransactions 
    WHERE TransactionTime BETWEEN h.StartHour and h.EndHour
    AND TerminalId = @TerminalID) as t
order by h.StartHour

更新

在任意日期之间返回 24 小时的 TVF 示例:

create function tvfGetAnyDayHours(@dateFrom datetime, @dateTo datetime)
returns table
as return (
select dateadd(hour, number, cast(floor(cast(@dateFrom as float)) as datetime)) as StartHour
  , dateadd(hour, number+1, cast(floor(cast(@dateFrom as float)) as datetime)) as EndHour
from master.dbo.spt_values
where type = 'p'
and number < datediff(hour,@dateFrom, @dateTo) + 24);

请注意,由于 master.dbo.spt_values 仅包含 2048 个数字,因此该函数在相隔超过 2048 小时的日期之间不起作用。

【讨论】:

  • +1,我在想一个 CTE,但是这个表函数会更好地集成到查询中!
  • 感谢大家的建议。就在我昨晚离开之前,我正在考虑值 0-23 的临时表,正如上面 Mark 所建议的那样。更进一步,您将如何在数日和数年内做到这一点?
  • Remus,最后一个查询将返回一天的值。但是,您将如何在日期范围内使用 tvf?
  • 您可以拥有一个接受两个日期并在这两个日期之间每小时返回一次的 tvf,因此您可以将其用于任意范围。请记住,虽然非常大的日期范围(年)可能会影响性能,因为 OUTER APPLY 运算符将在该范围内每小时运行一次内部查询
  • Remus,我明白了,所以大约 85 天是极限。我想做的是能够在日期范围集中重用这个函数。由于该集合可以通过该范围内的每一天,那么脚本将是什么?
【解决方案2】:

您刚刚发现了 NUMBERS 表的值。您需要创建一个包含数字 0 到 23 的单列的表。然后使用 OUTER 连接再次连接此表,以确保始终返回 24 行。

【讨论】:

  • zackly 我要输入什么:)
【解决方案3】:

所以回到使用 Remus 的原始函数,我在递归调用中重新使用它并将结果存储在临时表中:

DECLARE @count INT
DECLARE @NumDays INT
DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
DECLARE @CurrentDay DATE

    DECLARE @tmp_Transactions TABLE 
    (
        StartHour DATETIME,
        TotalHourlyTransactions INT
    )   

SET @StartDate = '2000/02/10'
SET @EndDate = '2010/02/13'
SET @count = 0
SET @NumDays = DateDiff(Day, @StartDate, @EndDate)
WHILE @count < @NumDays 
    BEGIN
        SET @CurrentDay = DateAdd(Day, @count, @StartDate)
        INSERT INTO @tmp_Transactions (StartHour, TotalHourlyTransactions)
            SELECT  h.StartHour ,
                    t.TotalHourlyTransactions
            FROM    tvfGetDay24Hours(@CurrentDay) AS h
                    OUTER APPLY ( SELECT    COUNT(TransactionID) AS TotalHourlyTransactions
                                  FROM      [dbo].[TerminalTransactions]
                                  WHERE     TransactionTime BETWEEN h.StartHour AND h.EndHour
                                            AND TerminalId = 4
                                ) AS t
            ORDER BY h.StartHour
        SET @count = @Count + 1
    END 

SELECT *
FROM @tmp_Transactions

【讨论】:

    【解决方案4】:

    按日期部分('小时',时间)分组。要显示那些没有值的时间,您必须离开加入一个针对分组的时间表 (coalesce(transaction.amount, 0))

    【讨论】:

      【解决方案5】:

      我之前遇到过这个问题的一个版本。最有效的建议是使用一天中的时间设置一个表(临时或非临时),然后对该表进行外部连接并按 datepart('h', timeOfRecord) 分组。

      我不记得为什么,但可能是由于需要另一个表而缺乏灵活性,我最终使用了一种方法,我按我想要的任何日期部分分组并按日期时间排序,然后循环遍历并用 0 填充任何跳过的空格。这种方法对我来说效果很好,因为我不依赖数据库来为我完成所有工作,而且为它编写自动化测试也容易得多。

      【讨论】:

        【解决方案6】:

        第 1 步,创建 #table 或 CTE 以生成小时天表。外环天数和内环小时 0-23。这应该是 3 列 Date、Days、Hours。

        第 2 步,编写您的主查询,使其也包含天数和小时数列,并为其添加别名,以便您可以加入它。 CTE 必须在这个主要查询之上,并且枢轴应该在 CTE 内部才能自然地工作。

        第 3 步,从第 1 步的表中选择并左连接此主查询表

        ON A.[DATE] = B.[DATE] 
        AND A.[HOUR] = B.[HOUR]
        

        如果您的日期列像这样,您也可以创建订单

        ORDER BY substring(CONVERT(VARCHAR(15), A.[DATE], 105),4,2)

        指导方针

        这将为您提供小时和天的所有数据,包括小时的零,没有匹配项使用isnull([col1],0) as [col1]

        您现在可以根据天数和小时数绘制事实图表。

        【讨论】:

          猜你喜欢
          • 2018-08-15
          • 1970-01-01
          • 2015-12-18
          • 1970-01-01
          • 2020-12-09
          • 2019-12-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多