【问题标题】:How to Group records by DateRange如何按日期范围对记录进行分组
【发布时间】:2012-02-09 12:19:16
【问题描述】:

如何在sql中按日期范围对记录进行分组?

考虑一下这个表结构。

 Key       ID       VISITDATE
 1         1        2011-01-07
 2         1        2011-01-09
 3         2        2011-01-10
 4         1        2011-01-12
 5         3        2011-01-12
 6         1        2011-01-18
 7         2        2011-01-21
 9         1        2011-02-28
 10        2        2011-03-21
 11        1        2011-01-06
 12        1        2011-02-29

我需要获取出现次数。如果您在 10 天内对同一个 ID 进行了两次访问,则应仅计算一次访问。 因此,对于给定 id 1 = 3 (01/06,01/18,02/28) 的出现次数的示例

非常感谢递归 CTE 查询。

【问题讨论】:

  • 你还没有真正描述你的范围是什么。是一周吗?范围是开始还是周末结束有关系吗?另外,您为什么关心它是否是递归 CTE?它提供您正在寻找的答案不是更重要吗?此外,您能否从给定的样本数据中显示您想要的结果,而不仅仅是描述它? (顺便说一句,2011-02-29 不是有效日期!)
  • 澄清一下,如果您在(0, 5, 10, 15) 天有访问,您似乎将它们归为(0, 5, 10), (15),即使第三次和第四次访问相隔不到 10 天。那是对的吗?获得(0, 5, 10), (15) 而不是(0), (5, 10, 15) 重要吗?
  • 仍然不清楚为什么 1/6 和 1/15 被认为是分开的。那些是“10 天内”不是吗?
  • @dems 。我需要按组的第一次访问对它们进行分组。所以如果我有(0,5,10,15) 那么分组将是(0,5,10)(15)
  • @Aaron,我想我在 1/6 和 1/15 上做错了。

标签: sql sql-server sql-server-2005 sql-server-2008-r2


【解决方案1】:

我知道您要求递归 CTE,但没有 SQL Server 2012 中的新窗口增强功能(FIRST_VALUE() 等)。我认为,当您必须跟踪仅前一行,但同时还有较早的行。这是我相信可以实现您想要的光标版本:

DECLARE @f TABLE([Key] INT, ID INT, VISITDATE DATE);

INSERT @f VALUES
  (1 ,1,'2011-01-07'), (2 ,1,'2011-01-09'), (3 ,2,'2011-01-10'), (4 ,1,'2011-01-12'),
  (5 ,3,'2011-01-12'), (6 ,1,'2011-01-18'), (7 ,2,'2011-01-21'), (9 ,1,'2011-02-28'), 
  (10,2,'2011-03-21'), (11,1,'2011-01-06'), (12,1,'2011-03-01');

DECLARE @ID INT, @dt DATE;

DECLARE @result TABLE(ID INT, FirstDate DATE, VisitCount INT);

DECLARE c CURSOR LOCAL STATIC READ_ONLY FORWARD_ONLY
    FOR SELECT ID, VISITDATE FROM @f ORDER BY ID, VISITDATE;

OPEN c;

FETCH NEXT FROM c INTO @ID, @dt;

WHILE @@FETCH_STATUS = 0
BEGIN
    IF NOT EXISTS 
    (
        SELECT 1 FROM @result WHERE ID = @ID
            AND DATEDIFF(DAY, FirstDate, @dt) <= 10 -- maybe < 10?
    )
    INSERT @result SELECT @ID, @dt, 1;

    FETCH NEXT FROM c INTO @ID, @dt;
END

SELECT ID, FirstDate FROM @result;

SELECT ID, VisitCount = COUNT(*) FROM @result GROUP BY ID;

CLOSE c;
DEALLOCATE c;

结果:

ID          FirstDate
----------- ----------
1           2011-01-06
1           2011-01-18
1           2011-02-28
2           2011-01-10
2           2011-01-21
2           2011-03-21
3           2011-01-12


ID          VisitCount
----------- -----------
1           3
2           3
3           1

是的,我知道您总是被警告不要与陌生人交谈并远离光标,但在某些情况下,它们是最直接的解决方案(并且有时比基于集合的解决方案的多次扫描执行得更好可能会发生)。

【讨论】:

    【解决方案2】:

    您没有说明如何定义日期范围

    如果从今天开始,你可以这样做:

    SELECT     id, COUNT(DISTINCT DATEDIFF(dd, visitdate, GETDATE()) / 10) AS Expr1
    FROM         test
    GROUP BY id
    

    如果您想从数据的最新日期开始范围,您可以这样做

    DECLARE @maxdate as datetime
    SET @maxdate = (select MAX(visitdate) from test)
    
    SELECT     id, COUNT(DISTINCT DATEDIFF(dd, visitdate, @maxdate) / 10) 
    FROM         test
    GROUP BY id
    

    这样的查询可以解决@Dems 指出的情况

    SELECT id, count(DISTINCT visitdate)
    FROM (
        SELECT   id, visitdate,
            (SELECT MAX(visitdate) 
            FROM test AS t 
            WHERE t.id = test.id AND t.visitdate<test.visitdate) AS prev_date
        FROM     test ) as temp
    WHERE DATEDIFF(dd, prev_date, visitdate) > 10 OR prev_date IS NULL
    GROUP BY id
    

    上述查询不计算具有相同 id 且存在另一条日期小于十天的记录的记录

    如果您想从每个 id 的最小日期开始拆分日历,您可以执行以下操作

    SELECT     t.id, COUNT(DISTINCT DATEDIFF(dd, mindate, visitdate) / 10) 
    FROM         test as t JOIN 
      (SELECT id, MIN(visitdate) AS mindate FROM test GROUP BY id) as mindates
        ON t.id = mindates.id
    GROUP BY t.id
    

    【讨论】:

    • 范围是“从其他记录开始的 10 天”,这不是将日历量化为 10 天长的块的问题。
    • @Dems:你说得有道理,但我认为原帖并没有说明这一点
    • @Dems 我明白你的意思,但我发现“10 天内”有点含糊不清(实际上 10 可能只是一个例子,而不是他的确切界限)。 1 月 1 日和 1 月 11 日是“10 天内”还是“11 天内”?希望OP可以更具体地指定。
    • 边界条件是包含还是排除不清楚,但代码差异很可能只是&lt;= vs &lt;。 10 天窗口是一个相对时期,对我来说似乎相当强大。从 1 日到 10 日有一个窗口,从 11 日到 20 日(含)有另一个窗口意味着 10 日和 11 日的访问被认为是不相关的。这看起来很疯狂这是一个官方的技术术语;)
    • 是的,我想我们在 SQL Server 2012 RC0 中发现了一个错误。当我有更多详细信息时,我会发布。
    【解决方案3】:

    创建一个标量函数,为给定范围内的所有日期返回相同的日期,然后按该日期分组。

    【讨论】:

    • 该函数不仅仅取决于单个日期。它不能像 Month() 或 Year() 那样工作。这也取决于之前的日期。
    • 无论哪种方式,您都应该能够编写一个函数,为您分组提供必要的输出值。总的来说,您肯定想按dateFunction(VISITDATE), id 分组吗?会有想法的。
    • @twilson - 如果是这样,我可以建议你写一个函数及其使用的例子吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-25
    • 1970-01-01
    • 1970-01-01
    • 2014-04-01
    • 2019-02-19
    • 2019-07-10
    相关资源
    最近更新 更多