在没有的查询的返回中插入日期

Posted

技术标签:

【中文标题】在没有的查询的返回中插入日期【英文标题】:Insert Dates in the return from a query where there is none 【发布时间】:2008-12-17 02:25:52 【问题描述】:

我们正在构建一个查询来计算每天每小时的事件数。大多数日子有几个小时没有任何活动,因此在运行查询的地方会显示每小时的活动计数,但存在差距,查询会排除这些差距。我们仍然希望显示没有活动的时间并显示零,以便随后可以绘制零值。我们使用的查询看起来像这样……

select datepart(Year, dev_time) as Year,
    datepart(Month, dev_time) as Month,
    datepart(Day, dev_time) as Day,
    datepart(Hour, dev_time) as Hour,
    count(tdm_msg) as Total_ACTIVITES
from TCKT_ACT
where tdm_msg = ‘4162′ and dev_time >= DATEADD(day, - 1, GETDATE())
group by datepart(Year, dev_time) ,
    datepart(Month, dev_time) ,
    datepart(Day, dev_time),
    datepart(Hour, dev_time)
order by datepart(Year, dev_time) asc,
    datepart(Month, dev_time) asc,
    datepart(Day, dev_time) asc,
    datepart(Hour, dev_time) asc

【问题讨论】:

投票赞成,因为这个问题已经影响了地球上 6,000,000,000 人中的至少 2 人:-)。 【参考方案1】:

您将需要一个包含天数和小时数的表,然后您必须在该表和您的查询之间进行外部连接。这就是我将如何做到的。请注意,此解决方案仅适用于 SQL Server 2005 和 2008。如果您没有这些平台,则必须在数据库中实际创建一个时间表,您可以从中加入:

DECLARE @MinDate DATETIME;
SET @MinDate =  CONVERT(varchar, GETDATE(), 101);

WITH times AS (
    SELECT @MinDate as dt, 1 as depth
    UNION ALL
    SELECT DATEADD(hh, depth, @MinDate), 1 + depth as depth
    FROM times
    WHERE DATEADD(hh, depth, @MinDate) <= GETDATE())
SELECT DATEPART(YEAR, t.dt) as [Year],
    DATEPART(MONTH, t.dt) as [Month],
    DATEPART(DAY, t.dt) as [Day],
    DATEPART(HOUR, t.dt) as [Hour],
    COUNT(tdm_msg) as Total_ACTIVITES
FROM times t
LEFT JOIN (SELECT * FROM TCKT_ACT WHERE tdm_msg = '4162' and dev_time >= @MinDate) a
    ON  DATEPART(HOUR, t.dt)  = DATEPART(HOUR, a.dev_time)
    AND MONTH(t.dt) = MONTH(a.dev_time)
    AND DAY(t.dt)   = DAY(a.dev_time)
    AND YEAR(t.dt)  = YEAR(a.dev_time)
GROUP BY DATEPART(YEAR, t.dt) ,
    DATEPART(MONTH, t.dt) ,
    DATEPART(DAY, t.dt),
    DATEPART(HOUR, t.dt)
ORDER BY DATEPART(YEAR, t.dt) asc,
    DATEPART(MONTH, t.dt) asc,
    DATEPART(DAY, t.dt) asc,
    DATEPART(HOUR, t.dt) asc
OPTION (MAXRECURSION 0); /* Just in case you want a longer timespan later on... */

请注意,顶部的 WITH 语句称为递归公用表表达式,它是生成元素数量相对较少的顺序表的好方法,就像您在此处一样。

【讨论】:

【参考方案2】:

首先我创建了一个基于递归公用表查询的表函数 戴夫马克尔(感谢你给我看这个戴夫!)。这非常棒,因为我只需要创建一次函数,就可以使用它来分析任何区间。

if exists (select * from dbo.sysobjects where name = 'fn_daterange') drop function fn_daterange;
go

create function fn_daterange
   (
   @MinDate as datetime,
   @MaxDate as datetime,
   @intval  as datetime
   )
returns table
--**************************************************************************
-- Procedure: fn_daterange()
--    Author: Ron Savage
--      Date: 12/16/2008
--
-- Description:
-- This function takes a starting and ending date and an interval, then
-- returns a table of all the dates in that range at the specified interval.
--
-- Change History:
-- Date        Init. Description
-- 12/16/2008  RS    Created.
-- **************************************************************************
as
return
   WITH times (startdate, enddate, intervl) AS
      (
      SELECT @MinDate as startdate, @MinDate + @intval - .0000001 as enddate, @intval as intervl
         UNION ALL
      SELECT startdate + intervl as startdate, enddate + intervl as enddate, intervl as intervl
      FROM times
      WHERE startdate + intervl <= @MaxDate
      )
   select startdate, enddate from times;

go

因此,如果您单独从该函数中进行选择,您会得到一个时间间隔表,如下所示:

fn_daterange('12/14/2008 10:00:00', '12/14/2008 20:00:00', '01:00:00' )

返回:

startdate               enddate                 intervl                 
----------------------- ----------------------- ----------------------- 
2008-12-14 10:00:00.000 2008-12-14 10:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 11:00:00.000 2008-12-14 11:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 12:00:00.000 2008-12-14 12:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 13:00:00.000 2008-12-14 13:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 14:00:00.000 2008-12-14 14:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 15:00:00.000 2008-12-14 15:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 16:00:00.000 2008-12-14 16:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 17:00:00.000 2008-12-14 17:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 18:00:00.000 2008-12-14 18:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 19:00:00.000 2008-12-14 19:59:59.997 1900-01-01 01:00:00.000 
2008-12-14 20:00:00.000 2008-12-14 20:59:59.997 1900-01-01 01:00:00.000 

然后我做了一个事件数据的示例表:

    eventdate               eventnote            
    ----------------------- -------------------- 
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:01:00.000 oo! an event!        
    2008-12-14 10:23:00.000 oo! an event!        
    2008-12-14 10:23:00.000 oo! an event!        
    2008-12-14 10:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 11:23:00.000 oo! an event!        
    2008-12-14 14:23:00.000 oo! an event!        
    2008-12-14 14:23:00.000 oo! an event!        
    2008-12-14 14:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:23:00.000 oo! an event!        
    2008-12-14 19:00:00.000 oo! an event!        
    2008-12-14 19:00:00.000 oo! an event!        
    2008-12-14 19:00:00.000 oo! an event!        

    22 Row(s) affected

然后我用 LEFT OUTER JOIN 将它们连接在一起,如下所示:

select
   dr.startdate,
   dr.enddate,
   count(me.eventdate) as eventcount
from
   fn_daterange('12/14/2008 10:00:00', '12/14/2008 20:00:00', '01:00:00' ) dr

   LEFT OUTER JOIN myevents me
      on ( me.eventdate between dr.startdate and dr.enddate)
group by
   dr.startdate,
   dr.enddate


startdate               enddate                 eventcount 
----------------------- ----------------------- ---------- 
2008-12-14 10:00:00.000 2008-12-14 10:59:59.993 7          
2008-12-14 11:00:00.000 2008-12-14 11:59:59.993 5          
2008-12-14 12:00:00.000 2008-12-14 12:59:59.993 0          
2008-12-14 13:00:00.000 2008-12-14 13:59:59.993 0          
2008-12-14 14:00:00.000 2008-12-14 14:59:59.993 3          
2008-12-14 15:00:00.000 2008-12-14 15:59:59.993 0          
2008-12-14 16:00:00.000 2008-12-14 16:59:59.993 0          
2008-12-14 17:00:00.000 2008-12-14 17:59:59.993 0          
2008-12-14 18:00:00.000 2008-12-14 18:59:59.993 0          
2008-12-14 19:00:00.000 2008-12-14 19:59:59.993 7          
2008-12-14 20:00:00.000 2008-12-14 20:59:59.993 0          

11 Row(s) affected

太棒了——我可以在工作中使用它进行各种分析! :-)

感谢 Fred 提出问题,感谢 Dave 提供有关常见表查询的信息!

罗恩

【讨论】:

【参考方案3】:

我们在使用一些性能监控软件时遇到了类似的问题,但是在 DB2/z 大型机商店中,我们坚决反对必须通过 SQL 体操来获得那种结果。众所周知,在检索到的每一行上执行“函数”的 SQL 查询是不可扩展的,如果我们尝试使用它们,DBA 会嘲笑我们。

相反,我们发现重构数据库架构以在每行中包含事件计数更容易(显然我们的 DBA 不介意使用更多磁盘空间,只是更多的 CPU 需求)。在您的情况下,这将添加一个名为 tdm_quant 的列,您将为插入的每一行(即每个事件)设置为 1。

然后查询的第五个字段从count(tdm_msg) 更改为sum(tdm_quant),这将达到相同的结果。

除此之外,您还可以在设置了tdm_quant 字段的位置插入一条特殊记录(每小时一次,或每天开始时的 24 条记录,或者填充 1 月 1 日的整个年份)为零。为零时,这些记录对sum(tdm_quant) 没有影响,但您将获得所需的行为,一天中每个 小时返回一行,其中没有发生任何事件的Total_ACTIVITIES 为零在那一小时内。

您的查询的其余部分不需要更改。

【讨论】:

它被认为是简洁的 - 对于扩展表的一次性成本,每年一次的工作在包含 500,000+ 500 个八位字节行的表中添加 8860 (365*24) 行(所以额外的磁盘对我们来说也不是很重要),我们得到更快的查询和报告,而且每天要完成几十次。 最棒的一点是,我的奖金因创新思维而增加,而且我还获得了 DBA 的奖励,以表彰他们让他们的生活更轻松。好吧,无论如何,对我来说是最好的:-)【参考方案4】:

听起来您可以使用“左外连接”来使用另一个包含数字 1 到 24 的表...

【讨论】:

我认为至少需要日期和时间的组合 - 尽管时间是“按时”。在一般的 DBMS 中,在正确的范围内生成一组足够的值并非易事。【参考方案5】:

这里的基本答案涉及左外连接 (LOJ) 和显式 COUNT(column),因为它不计算空值,但 COUNT(*) 计算所有行。困难的部分是生成一个表来进行 LOJ。 WITH 子句和递归解决方案适用于许多 DBMS(显然是 MS SQL Server,几乎可以肯定是 DB2 —— 也可能是其他 DBMS)。

许多 DBMS 支持临时表和存储过程;该组合可用于使用日期/时间字段的一组适当值填充表,然后针对该表执行 LOJ(或更准确地说,FROM temp_table LEFT OUTER JOIN main_table ...)。没有那么整洁,但适用于大多数地方。

【讨论】:

以上是关于在没有的查询的返回中插入日期的主要内容,如果未能解决你的问题,请参考以下文章

正确格式化的 MySQL 日期插入语句返回全 0

检查 postgresql 的返回查询中是不是存在值

mysql日期函数及批量循环返回主键ID

sql日期

Mysql日期函数

Oracle:类似于 sysdate 但只返回时间和日期