用于按日期计算事件的SQL

时间:2009-04-06 14:45:47

标签: sql logging date

我觉得我之前已经看过这个问题了,但SO搜索和谷歌都没有帮助我...也许我只是不知道如何说出这个问题。我需要计算给定时间跨度内每天的事件数(在本例中为登录),以便我可以制作网站使用情况的图表。我到目前为止的查询是:

select 
   count(userid) as numlogins, 
   count(distinct userid) as numusers, 
   convert(varchar, entryts, 101) as date 
from 
   usagelog 
group by 
   convert(varchar, entryts, 101)

这完成了我需要的大部分工作(每个日期我得到一行作为包含登录总数和该日期唯一用户数的输出)。问题是如果没有人在给定日期登录,那么该日期的数据集中就不会有一行。我希望它添加行,表示这些日期的零登录。我可以想到两种方法来解决这个问题,而且我也不会非常优雅。

  1. 在结果集中添加一列,列出期间开始与当前行日期之间的天数。当我构建图表输出时,我会跟踪这个值,如果下一行不等于当前行加一,则在每个缺失的日子里将零插入图表。
  2. 创建一个“日期”表,其中包含感兴趣的时段和外部联接中的所有日期。可悲的是,我正在研究的系统已经有一个用于此目的的表格,其中包含了将来每个日期的行...我不喜欢这样,我宁愿避免使用它,特别是因为那个table适用于系统的另一个模块,因此会依赖于我当前正在开发的内容。
  3. 谷歌更好的搜索条件有哪些更好的解决方案或提示?感谢。

6 个答案:

答案 0 :(得分:3)

坦率地说,我在构建最终输出时以编程方式执行此操作。你本质上是想从数据库中读取一些不存在的东西(没有数据的天数)。 SQL并不是真正意义上的那种东西。

如果你真的想这样做,那么“约会”表似乎是你最好的选择。为了使它更好一些,您可以使用即数据库的日期函数和派生表来动态生成它。

答案 1 :(得分:2)

我最近必须做同样的事情。这就是我在T-SQL中的做法( YMMV的速度,但我发现它在足够数百万行的事件数据中表现得足够高):

DECLARE @DaysTable TABLE ( [Year] INT, [Day] INT )

DECLARE @StartDate DATETIME
SET @StartDate = whatever

WHILE (@StartDate <= GETDATE())
BEGIN

  INSERT INTO @DaysTable ( [Year], [Day] )
  SELECT DATEPART(YEAR, @StartDate), DATEPART(DAYOFYEAR, @StartDate)

  SELECT @StartDate = DATEADD(DAY, 1, @StartDate)
END

-- This gives me a table of all days since whenever
-- you could select @StartDate as the minimum date of your usage log)

SELECT days.Year, days.Day, events.NumEvents
FROM @DaysTable AS days
LEFT JOIN (
  SELECT
    COUNT(*) AS NumEvents
    DATEPART(YEAR, LogDate) AS [Year],
    DATEPART(DAYOFYEAR, LogDate) AS [Day]
  FROM LogData
  GROUP BY
    DATEPART(YEAR, LogDate),
    DATEPART(DAYOFYEAR, LogDate)
) AS events ON days.Year = events.Year AND days.Day = events.Day

答案 2 :(得分:1)

创建一个内存表(一个表变量),在其中插入日期范围,然后将登录表外部连接到它。按开始日期分组,然后您就可以执行汇总和计算。

答案 3 :(得分:1)

我通常使用的策略是与查询相反的UNION,通常是检索不存在的行的数据的查询。

如果我想获得一门课程的平均分,但是有些课程没有被任何学生带走,我需要UNION和那些没有被任何人带走的人在每个班级上显示一行:

SELECT AVG(mark), course FROM `marks` 
    UNION
SELECT NULL, course FROM courses WHERE course NOT IN
    (SELECT course FROM marks)

您的查询会更复杂,但应遵循相同的原则。您可能确实需要第二个查询的日期表

答案 4 :(得分:1)

选项1 您可以创建临时表并使用范围插入日期,并使用usagelog执行左外连接 选项2 您可以在评估结果集时以编程方式插入缺少的日期以生成最终输出

答案 5 :(得分:0)

WITH q(n) AS
          (
          SELECT  0
          UNION   ALL
          SELECT  n + 1
          FROM    q
          WHERE   n < 99
          ),
    qq(n) AS 
          (
          SELECT  0
          UNION   ALL
          SELECT  n + 1
          FROM    q
          WHERE   n < 99
          ),
    dates AS
          (
          SELECT  q.n * 100 + qq.n AS ndate
          FROM    q, qq
          )
SELECT    COUNT(userid) as numlogins,
          COUNT(DISTINCT userid) as numusers,
          CAST('2000-01-01' + ndate AS DATETIME) as date
FROM      dates
LEFT JOIN
          usagelog
ON        entryts >= CAST('2000-01-01' AS DATETIME) + ndate
          AND entryts < CAST('2000-01-01' AS DATETIME) + ndate + 1
GROUP BY
          ndate

这将最多选择动态构建的10,000日期,这应该足够30年。

SQL Server每个100的{​​{1}}递归限制,这就是内部查询每次最多返回CTE行的原因。

如果您需要的时间超过100,只需添加第三个10,000 CTE并在qqq(n)中与其交叉加入。