mysql group by month with custom starting day

时间:2013-09-04 17:43:11

标签: mysql sql count group-by

mysql中有没有办法按月分组,但是有自定义的开始日期。

假设我想按月计算登录数,但条件是月份在用户注册时开始。

例如,1月30日注册用户 A ,1月15日用户 B

我应该将登录分组如下:

* User A: January 30th - February 28th, March 1st - March 30th, March 31 - April 30 and so on and so forth 
* User B: January 15th - February 14th, February 15th - March 14th and so on and so forth

我想我需要使用类似DATE_ADD('2013-01-30', INTERVAL 1 MONTH);的内容,但我似乎无法找到进行分组的方法。

更新

@GarethD:你是对的,这是一个错字

一般情况下,月份应该从下个月的同一天或下个月的最后一天开始,以防第一个月不可能,所以如果您在第31天注册,则月份将在第30天开始没有31天和2月的最后一天28或29的月份

示例:

鉴于此

id 1 registered on 2012-12-16
id 2 registered on 2013-01-29

和下表

+----+------------+
| id |    date    |
+----+------------+
|  1 | 2013-01-15 |
|  1 | 2013-01-16 |
|  1 | 2013-01-17 |
|  1 | 2013-01-17 |
|  2 | 2013-03-20 |
|  2 | 2013-03-21 |
|  2 | 2013-03-28 |
|  2 | 2013-03-29 |
|  2 | 2013-03-30 |
+----+------------+

结果应该是

+----+----------------------------+-------+
| id |           range            | count |
+----+----------------------------+-------+
| 1  | 2012-12-16, 2013-01-15     |     1 |
| 1  | 2013-01-16, 2013-02-15     |     3 |
| 2  | 2013-02-2[8|9], 2013-03-28 |     3 |
| 2  | 2013-03-29, 2013-04-28     |     2 |
+----+----------------------------+-------+

我希望现在的意图更加明确。

1 个答案:

答案 0 :(得分:4)

对于以下我假设你已经有一个数字表,如果你没有数字表,那么我建议你先制作一个,但是如果你不想那么你就可以{{3 }}

您可以通过将您的用户ID和注册日期与您的号码表交叉加入来获取所有边界的列表:

SELECT  u.ID, 
        DATE_ADD(RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
        DATE_ADD(RegisteredDate, INTERVAL n.Number + 1 MONTH) PeriodEnd
FROM    User u
        CROSS JOIN Numbers n;

这给出了一个表格:

ID  PERIODSTART     PERIODEND
1   2012-12-16      2012-12-16
2   2013-01-29      2013-01-29
1   2013-01-16      2013-01-16
2   2013-02-28      2013-02-28

create a number list on the fly

然后,您需要将此连接到主表,并执行计数:

SELECT  u.ID,
        u.PeriodStart,
        DATE_ADD(PeriodEnd, INTERVAL -1 DAY) PeriodEnd,
        COUNT(*) AS `COUNT`
FROM    (   SELECT  u.ID, 
                    DATE_ADD(RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
                    DATE_ADD(RegisteredDate, INTERVAL n.Number + 1 MONTH) PeriodEnd
            FROM    User u
                    CROSS JOIN Numbers n
        ) u
        INNER JOIN T
            ON T.ID = u.ID
            AND T.Date >= u.PeriodStart
            AND T.Date < PeriodEnd
GROUP BY u.ID, u.PeriodStart, u.PeriodEnd;

给出最终结果:

ID  PERIODSTART     PERIODEND   COUNT
1   2012-12-16      2013-01-15  1
1   2013-01-16      2013-02-15  3
2   2013-02-28      2013-03-28  3
2   2013-03-29      2013-04-28  2

<强> Example on SQL Fiddle

显然,您可以连接句点的开始日期和结束日期以制作“范围”字符串,但这可能最适合在您的应用程序层中处理。

修改

这可以通过没有可能表现更好的子查询来实现:

SELECT  u.ID,
        DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
        DATE_ADD(DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH), INTERVAL -1 DAY) PeriodEnd,
        COUNT(*) AS `COUNT`
 FROM   User u
        CROSS JOIN Numbers n 
        INNER JOIN T
            ON T.ID = u.ID
            AND T.Date >= DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH)
            AND T.Date < DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH)
GROUP BY u.ID, u.RegisteredDate, n.Number;

<强> Full Example on SQL-Fiddle

编辑2

这将为您提供所有用户的所有期间,直至当前期间(即今天属于该日期范围内)

SELECT  u.ID,
        DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH) PeriodStart,
        DATE_ADD(DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH), INTERVAL -1 DAY) PeriodEnd,
        COUNT(T.ID) AS `COUNT`
 FROM   User u
        CROSS JOIN Numbers n 
        LEFT JOIN T
            ON T.ID = u.ID
            AND T.Date >= DATE_ADD(u.RegisteredDate, INTERVAL n.Number MONTH)
            AND T.Date < DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH)
WHERE   DATE_ADD(u.RegisteredDate, INTERVAL n.Number + 1 MONTH) <= CURRENT_TIMESTAMP
GROUP BY u.ID, u.RegisteredDate, n.Number;

<强> Example with no subquery on SQL-Fiddle