【问题标题】:Mysql query per date and per group每个日期和每个组的 Mysql 查询
【发布时间】:2020-07-21 04:06:15
【问题描述】:

我有一个 SQL 查询,它按天计算特定 group 用户的用户吸收(注册)。它还进行累积(运行总计)

select Date, Cumulative, Up as Uptake
from (
    select Date, Up, @running_total:=@running_total + Up as Cumulative
    from (
        select distinct(date(`audit`.`created_at`)) as Date, COALESCE(f.uptake, 0) as Up
        from `audit`
        left join ( 
            select date(`users`.`created_at`) as day, count(`users`.`id`) as uptake
            from `users`
            where `users`.`group_uuid` = (select `groups`.`uuid` from `groups` where `groups`.`name` = "companyA")
            group by day 
        ) f on f.day = date(`audit`.`created_at`)
        where `audit`.`created_at` between '2019-07-03' and CURDATE()
    ) c
    JOIN (SELECT @running_total:=0) r
) final
order by Date desc

这个查询使用audit 表纯粹是为了让我得到一个可靠的日期列表,即使每天都没有创建用户,我知道audit 表有记录。

我可以通过

轻松获得所有组
select `groups`.`name` from `groups`

我想要的是......每天一个条目和一个组与吸收(如果可能的话,累积) 示例:

Date         |  Cumulative  |      Uptake     |   group
2020-04-07   |    2         |        1        |   comapnyA  
2020-04-07   |    5         |        3        |   comapnyB  
2020-04-06   |    1         |        0        |   comapnyA  
2020-04-06   |    2         |        1        |   comapnyB  
2020-04-05   |    1         |        1        |   comapnyA  
2020-04-05   |    1         |        1        |   comapnyB  
.... etc

【问题讨论】:

  • 专业提示:使用表名或(甚至更好)短表别名来限定所有列引用。此外,DISTINCT 是关键字,而不是函数。在 SELECT 列表中,它适用于 SELECT 列表中的所有表达式,允许使用不必要的括号但不会改变行为,所以不要添加虚假的括号。

标签: mysql sql date group-by count


【解决方案1】:

对于 8.0 之前的 MySQL 版本,我们可以使用用户定义的变量,如 OP 查询所示。

几点说明:

看起来我们从一个简单的GROUP BY created_date, group_uuid 查询开始,只针对users 表获取非零计数。但这缺少行,否则计数为零。

所以看起来我们需要一个日历源(给定范围内的不同日期列表,OP 查询使用审计表的查询作为日历源),我们需要将交叉连接(笛卡尔积)到一个不同的列表来自组的 uuid 值以及名称。对于本练习,我们将假设uuidgroups 表中是唯一的,并且每个uuid 值都与不同的name 值相关联。 (如果不是这样,我们需要进行一些调整。)

要按组单独获取运行总计,我们需要按组顺序处理行,然后在每个组内按日期升序处理。当我们处理行时,当我们开始一个新组时,即检测到组 uuid 值发生变化时,我们需要将运行总计重置为零。

注意:MySQL 参考手册给出了关于使用用户定义变量的具体警告,在同一条语句中读取和修改,操作顺序不保证。在 MySQL 5.7 之前,通过精心设计的查询,我们确实可以按照操作顺序观察到可预测的行为。

所以,我们可以这样做:

SELECT q.date           AS `Date`
     , q.running_total  AS `Cumulative`
     , q.uptake         AS `Uptake`
     , q.name           AS `Group`
  FROM ( SELECT @rtot := IF(@prev_uuid = grp.uuid,@rtot,0) + IFNULL(cnt.uptake,0) AS `running_total`
              , IFNULL(cnt.uptake,0)                                              AS `uptake` 
              , @prev_uuid := grp.uuid                                           AS `uuid`
              , grp.name                                                         AS `name`
              , cal.date                                                         AS `date`
           FROM ( -- initialize user-defined variables
                  SELECT @prev_uuid := NULL
                       , @rtot := 0
                ) i
          CROSS
           JOIN ( -- calendar source for distinct date values
                  SELECT DATE(a.created_at)  AS `date`
                    FROM `audit` a
                   WHERE a.created_at >= '2019-07-03'
                     AND a.created_at <= DATE(NOW())
                   GROUP BY DATE(a.created_at)
                   ORDER BY DATE(a.created_at)
                ) cal
          CROSS
           JOIN ( -- distinct list of group uuid we want to return
                  SELECT g.uuid       AS `uuid`
                       , MAX(g.name)  AS `name`
                    FROM `groups` g
                   WHERE g.name IN ('CompanyA','CompanyB')
                   GROUP BY g.uuid
                ) grp
           LEFT
           JOIN ( -- count by group and date
                  SELECT u.group_uuid        AS `group_uuid`
                       , DATE(u.created_at)  AS `date`
                       , COUNT(u.id)         AS `uptake`
                    FROM `users` u
                   WHERE u.created_at >= '2019-07-03'
                   GROUP
                      BY u.group_uuid
                       , DATE(u.created_at)
                ) cnt
             ON grp.uuid = cnt.group_uuid
            AND cal.date = cnt.date
          ORDER
             BY grp.uuid
              , cal.date
       ) q
 ORDER
    BY q.date DESC
     , q.name ASC

注意我们必须小心使用用户定义变量的 SELECT 列表中的操作顺序,例如,我们需要测试 @prev_uuid 的值(从上一行保存),然后再用当前行。

此外,这未经测试,可能存在语法错误,缺少括号。我强烈建议分别测试每个内联视图查询(calgrpcnt)以验证是否正在执行预期结果。 (grp 查询中的 WHERE 子句限制根据需要返回、调整或消除组。)

然后我们可以继续测试下一个外部查询 q,并确认结果,然后将 q 包装到最后的最外层查询中以重新排序行,并按所需顺序显示列。

** 跟进 **

要查看是否可以让 MySQL 兑现 ORDER BY uuid ,我们可以修改/替换 q

从此改变:

SELECT q.date           AS `Date`
     , q.running_total  AS `Cumulative`
     , q.uptake         AS `Uptake`
     , q.name           AS `Group`
  FROM ( SELECT @rtot := IF(@prev_uuid = grp.uuid,@rtot,0) + IFNULL(cnt.uptake,0) AS `running_total`
              , IFNULL(cnt.uptake,0)                                              AS `uptake` 
              , @prev_uuid := grp.uuid                                           AS `uuid`
              , grp.name                                                         AS `name`
              , cal.date                                                         AS `date`
           FROM 
                ...
          ORDER
             BY grp.uuid
              , cal.date
       ) q
 ORDER
    BY q.date DESC
     , q.name ASC

SELECT r.date           AS `Date`
     , r.running_total  AS `Cumulative`
     , r.uptake         AS `Uptake`
     , r.name           AS `Group`
  FROM ( SELECT @rtot := IF(@prev_uuid = q.uuid,@rtot,0) + q.uptake AS `running_total`
              , q.uptake                                            AS `uptake`
              , @prev_uuid := q.uuid                                AS `uuid`
              , q.name                                              AS `name`
              , q.date                                              AS `date`
           FROM ( SELECT grp.uuid             AS `uuid`
                       , grp.name             AS `name`
                       , cal.date             AS `date`
                       , IFNULL(cnt.uptake,0) AS `uptake`
                    FROM
                         ...
                   ORDER
                      BY grp.uuid
                       , cal.date
                ) q
          ORDER
             BY q.uuid
              , q.date
       ) r
ORDER
    BY r.date DESC
     , r.name ASC

【讨论】:

  • 谢谢。第 5 行应该是 IFNULL(cnt.uptake,0) 吗?
  • 在第 3 行也得到未知列 c.uptake
  • 是的,最外层查询的SELECT列表中的第三个表达式应该是q.running_total,引用从q返回的running_total col,是的,它应该是IFNULL(cnt.uptake引用uptake从cnt返回的col ...已编辑
  • 内联视图查询 calgrp 可以简化,如果裸表返回我们需要的内容,则替换为引用裸表...我还清理了 cnt 视图查询,删除了加入组...我们可以将其包含在内,然后从组表中返回名称,...模式仍然相同。 cal 与 group 交叉连接,然后外连接到 counts ,其中缺少 count 行,我们知道计数为零
  • 感谢@spencer7593 的更新,我已经尝试过它现在运行正常。虽然我在累积和更新列中都获得了吸收(每日注册计数)。
【解决方案2】:

假设 MySQL 8.0,我会这样写:

select
    a.created_date,
    count(u.created_at) uptake,
    sum(count(*)) over(partition by g.group_uuid order by a.created_at) cumulative
    g.name
from (select distinct date(created_at) created_date from audit) a
cross join groups g 
left join users u 
    on  u.created_at >= a.created_date 
    and u.created_at <  a.created_date + interval 1 day
    and u.group_uuid = g.group_uuid
where a.created_date between '2019-07-03' and current_date
group by a.created_date, g.group_uuid, g.name
order by a.created_date, g.name

查询使用cross join 生成日期(来自audit)和组(来自groups)的所有可能组合。然后我们带来users 表和left join。然后,我们可以汇总并计算每天/组中存在多少用户记录。窗口总和给出累积计数。

【讨论】:

  • 感谢@GMB,看起来很整洁 :) 我应该提到我在 mysql 5.7.17
猜你喜欢
  • 2015-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多