Mysql запрос на дату и на группу - PullRequest
0 голосов
/ 08 апреля 2020

У меня есть запрос 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

Ответы [ 2 ]

1 голос
/ 08 апреля 2020

Для версий MySQL до 8.0 мы могли бы использовать пользовательские переменные, как показано в запросе OP.

Несколько замечаний:

Похоже, мы начали с простым GROUP BY created_date, group_uuid запросом к таблице users, чтобы получить ненулевые значения. Но там есть пропущенные строки, где в противном случае было бы нулевое число.

Так что, похоже, нам нужен источник календаря (отдельный список дат в данном диапазоне, запрос OP использует запрос таблицы аудита как источник календаря), и нам нужно это перекрестное соединение (декартово произведение) к отдельному списку значений uuid из группы вместе с именем. Для этого упражнения мы будем предполагать, что uuid является уникальным в таблице groups, и что каждое значение uuid связано с отдельным значением name. (Если это не так, нам нужно внести некоторые коррективы.)

Чтобы получить промежуточную сумму отдельно по группам, нам нужно обработать строки в групповом порядке, а затем в каждой группе по возрастанию. приказ. Когда мы обрабатываем строки, нам нужно сбросить промежуточный итог до нуля, когда мы начинаем новую группу, т.е. когда обнаруживаем изменение в значении uuid группы.

ПРИМЕЧАНИЕ. Справочное руководство MySQL дает специфические значения c предупреждение об использовании пользовательских переменных, как чтение, так и изменение в одном выражении, порядок операций не гарантируется. Вплоть до 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 (сохраненное из предыдущей строки) перед тем, как перезаписать его значением текущей строки.

Кроме того, это не проверяется, возможны синтаксические ошибки, пропущены парены. Я настоятельно рекомендую тестировать каждый из запросов встроенного представления отдельно (cal, grp, cnt), чтобы убедиться, что ожидаемые результаты выполняются. (Предложение WHERE в запросе grp запрещает возвращать, корректировать или удалять группы по мере необходимости.)

Затем мы можем перейти к тестированию следующего внешнего запроса q и подтвердить результаты из что перед переносом q в последнем самом внешнем запросе для переупорядочения строк и отображения столбцов в нужном порядке.

** FOLLOWUP **

, чтобы увидеть, можем ли мы получить 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
1 голос
/ 08 апреля 2020

Предполагая 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) и групп (из * 1006). *). Затем мы приносим users стол с left join. Затем мы можем собрать и подсчитать, сколько пользовательских записей существует для каждого дня / группы. Сумма окна дает совокупный счет.

...