MYSQL СУММА до последнего дня каждого месяца за последние 12 месяцев - PullRequest
0 голосов
/ 04 мая 2020

У меня есть две подобные таблицы

Таблица A

  date          amount     B_id 
'2020-1-01'     3000000      1
'2019-8-01'     15012        1
'2019-6-21'     90909        1
'2020-1-15'     84562        1
--------

Таблица B

id       type
1         7
2         5

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

SELECT num2.last_dates,
  (SELECT SUM(amount) FROM A
      INNER JOIN B ON A.B_id = B.id
      WHERE B.type = 7 AND A.date<=num2.last_dates
     ),
    (SELECT SUM(amount) FROM A
      INNER JOIN B ON A.B_id = B.id
      WHERE B.type = 5 AND A.date<=num2.last_dates)
FROM
  (SELECT last_dates
      FROM (
  SELECT LAST_DAY(CURDATE()  - INTERVAL CUSTOM_MONTH MONTH) last_dates
      FROM(
          SELECT 1 CUSTOM_MONTH UNION
            SELECT 0 UNION
            SELECT 2 UNION
            SELECT 3 UNION
            SELECT 4 UNION
            SELECT 5 UNION
            SELECT 6 UNION
            SELECT 7 UNION
            SELECT 8 UNION
            SELECT 9 UNION
            SELECT 10 UNION
          SELECT 11 UNION
            SELECT 12 )num
) num1
  )num2

ORDER BY num2.last_dates

Это дает мне такой результат, который мне нужен. Мне нужно, чтобы этот запрос выполнялся быстрее. Есть ли лучший способ сделать то, что я пытаюсь сделать?

2019-05-31  33488.69        109.127800
2019-06-30  263.690          1248932.227800
2019-07-31  274.690         131.827800
2019-08-31  627.690         13.687800
2019-09-30  1533.370000     08.347800
2019-10-31  1444.370000     01.327800
2019-11-30  5448.370000     247.227800
2019-12-31  61971.370000    016.990450
2020-01-31  19550.370000    2535.185450
2020-02-29  986.370000      405.123300
2020-03-31  1152.370000     26.793300
2020-04-30  9404.370000     11894.683300
2020-05-31  3404.370000     17894.683300



Ответы [ 3 ]

2 голосов
/ 04 мая 2020

Я бы использовал условное агрегирование и предварительно агрегировал ежемесячные итоги за один проход, вместо того, чтобы повторять двадцать шесть отдельных проходов через одни и те же данные.

Я бы начал с чего-то вроде этого:

SELECT CASE WHEN A.date < DATE(NOW()) + INTERVAL -14 MONTH
       THEN LAST_DAY(     DATE(NOW()) + INTERVAL -14 MONTH )
       ELSE LAST_DAY( A.date )
       END                                    AS _month_end
     , SUM(IF( B.type = 5 , B.amount , NULL)) AS tot_type_5
     , SUM(IF( B.type = 7 , B.amount , NULL)) AS tot_type_7
  FROM A
  JOIN B
    ON B.id = A.B_id
 WHERE B.type IN (5,7)
 GROUP
    BY _month_end

(сумма столбца не указана в исходном запросе, поэтому просто угадайте, из какой именно таблицы. Отрегулируйте при необходимости. Рекомендуется квалифицировать все ссылки на столбцы.

Это дает нам промежуточные итоги за каждый месяц за один проход через A и B.

Мы можем проверить и настроить этот запрос.

Затем мы можем включить это как встроенное представление во внешнем запросе, которое суммирует эти месячные итоги (я бы сделал внешнее объединение, на случай, если строки отсутствуют, то есть мы не упустим строки).

Примерно так :

SELECT d.dt + INTERVAL -i.n MONTH + INTERVAL -1 DAY  AS last_date
     , SUM(IFNULL(t.tot_type_5,0))                   AS rt_type_5
     , SUM(IFNULL(t.tot_type_7,0))                   AS rt_type_7
  FROM ( -- first day of next month
         SELECT DATE(NOW()) + INTERVAL -DAY(DATE(NOW()))+1 DAY + INTERVAL 1 MONTH AS dt
       ) d
 CROSS
  JOIN ( -- thirteen integers, integers 0 thru 12
         SELECT 0 AS n
         UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
         UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
         UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
       ) i

 LEFT
 JOIN ( -- totals by month
        SELECT CASE WHEN A.date < DATE(NOW()) + INTERVAL -14 MONTH
               THEN LAST_DAY(     DATE(NOW()) + INTERVAL -14 MONTH )
               ELSE LAST_DAY( A.date )
               END                                    AS _month_end
             , SUM(IF( B.type = 5 , B.amount , NULL)) AS tot_type_5
             , SUM(IF( B.type = 7 , B.amount , NULL)) AS tot_type_7
          FROM A
          JOIN B
            ON B.id = A.B_id
         WHERE B.type IN (5,7)
         GROUP
            BY _month_end
      ) t
   ON t._month_end < d.dt

GROUP BY d.dt + INTERVAL -i.n MONTH + INTERVAL -1 DAY
ORDER BY d.dt + INTERVAL -i.n MONTH + INTERVAL -1 DAY DESC

Дизайн предназначен для выполнения одного sw oop через набор A JOIN B. Мы ожидаем получить около 14 строк назад. И мы делаем полу- присоединяйтесь, дублируя самые старые месяцы несколько раз с, так ок. 14 x 13/2 = 91 строка, которые сгруппированы в 13 строк.

Большой камень в плане производительности будет реализован в этом запросе встроенного представления.

0 голосов
/ 05 мая 2020

Вы получаете несколько сложных ответов. Я думаю, что это проще. Начните с знания, что мы можем легко суммировать за каждый месяц:

SELECT SUM(amount) as monthtotal,
        type,
        MONTH(date) as month,
        YEAR(date) as year 
FROM A LEFT JOIN B on A.B_id=B.id 
GROUP BY type,month,year

Исходя из этих данных, мы можем использовать переменную для получения промежуточного итога. Лучше всего делать, инициализируя переменную, но не обязательно. Мы можем получить необходимые данные, например:

SET @running := 0;

SELECT (@running := @running + monthtotal) as running, type, LAST_DAY(CONCAT(year,'-',month,'-',1))
FROM 
(SELECT SUM(amount) as monthtotal,type,MONTH(date) as month,YEAR(date) as year FROM A LEFT JOIN B on A.B_id=B.id GROUP BY type,month,year) AS totals
ORDER BY year,month

. Вам действительно нужен соединитель, который поддерживает несколько операторов, или несколько вызовов для инициализации переменной. Несмотря на то, что вы можете проверить переменную null и установить значение по умолчанию 0, у вас все еще есть проблема, если вы выполняете запрос второй раз.

Последнее, если вы действительно хотите, чтобы типы суммировались отдельно:

SET @running5 := 0;
SET @running7 := 0;

SELECT 
    LAST_DAY(CONCAT(year,'-',month,'-',1)),
    (@running5 := @running5 + (CASE WHEN type=5 THEN monthtotal ELSE 0 END)) as running5, 
    (@running7 := @running7 + (CASE WHEN type=7 THEN monthtotal ELSE 0 END)) as running7
FROM 
(SELECT SUM(amount) as monthtotal,type,MONTH(date) as month,YEAR(date) as year FROM A LEFT JOIN B on A.B_id=B.id GROUP BY type,month,year) AS totals
ORDER BY year,month

Мы все еще не показываем месяцы, когда нет данных. Я не уверен, что это требование. Но для этого нужен только один проход таблицы A.

Кроме того, убедитесь, что идентификатор таблицы B. проиндексирован.

0 голосов
/ 04 мая 2020

Вот как я, вероятно, подхожу к этому в MySQL 8 с SUM OVER:

  1. Получите последние 12 месяцев.
  2. Используйте эти месяцы, чтобы добавить пустой месяц строк к исходным данным, поскольку MySQL не поддерживает полные внешние объединения.
  3. Получите промежуточные итоги за все месяцы.
  4. Показать только последние двенадцать месяцев.

Запрос:

with months (date) as
(
  select last_day(current_date - interval  1 month) union all
  select last_day(current_date - interval  2 month) union all
  select last_day(current_date - interval  3 month) union all
  select last_day(current_date - interval  4 month) union all
  select last_day(current_date - interval  5 month) union all
  select last_day(current_date - interval  6 month) union all
  select last_day(current_date - interval  7 month) union all
  select last_day(current_date - interval  8 month) union all
  select last_day(current_date - interval  9 month) union all
  select last_day(current_date - interval 10 month) union all
  select last_day(current_date - interval 11 month) union all
  select last_day(current_date - interval 12 month)
)
, data (date, amount, type) as
(
  select last_day(a.date), a.amount, b.type
  from a
  join b on b.id = a.b_id
  where b.type in (5, 7)
  union all
  select date, null, null from months
)
select
  date,
  sum(sum(case when type = 5 then amount end)) over (order by date) as t5,
  sum(sum(case when type = 7 then amount end)) over (order by date) as t7
from data
group by date
order by date
limit 12;

Демо: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ddeb3ab3e086bfc182f0503615fba74b

Я не знаю, быстрее ли это, чем ваш собственный запрос или нет. Просто попробуйте. (Вы бы получили мой запрос намного быстрее, добавив сгенерированный столбец для last_day(date) в свою таблицу и воспользовавшись этим. Если вам это нужно часто, это может быть вариант.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...