Некоторые предпосылки:
У нас есть только привилегия SELECT
для базы данных MySQL, и вам нужно рассчитать, сколько денег каждый пользователь потратил за последние 1, 2,..., 45 дней. Запрос представляет собой огромную коллекцию LEFT JOIN
и мне было поручено ее оптимизировать. Обратите внимание, что для упрощения кода я удалил большую часть Round
и LEFT JOIN
с комментариями.
Код:
SELECT m.username AS 'Username',
# Actually we have 45 instead of 2 Round()
Round(y2.spend, 2) AS '2',
Round(y1.spend, 2) AS '1'
FROM fund_m AS fm
LEFT JOIN member AS m
ON fm.user_id = m.id
LEFT JOIN employee AS e
ON m.account_manager = e.id
# Again we have 45 instead of 2 LEFT JOIN following
LEFT JOIN (SELECT fm.user_id AS id,
Sum(fm.amount_changed) *- 1 AS 'spend'
FROM fund_m AS fm
WHERE fm.arrival_date = Subdate(CURRENT_DATE, 1)
AND fm.type = 'impressions'
GROUP BY fm.user_id) AS y1
ON fm.user_id = y1.id
LEFT JOIN (SELECT fm.user_id AS id,
Sum(fm.amount_changed) *- 1 AS 'spend'
FROM fund_m AS fm
WHERE fm.arrival_date = Subdate(CURRENT_DATE, 2)
AND fm.type = 'impressions'
GROUP BY fm.user_id) AS y2
ON fm.user_id = y2.id
WHERE fm.arrival_date BETWEEN Subdate(CURRENT_DATE, 45) AND Subdate(
CURRENT_DATE, 1)
AND fm.type = 'impressions'
GROUP BY m.username;
Структура таблицы fm
следующая:
Каждый пользователь имеет user_id
и arrival_date
колонки, поэтому код проверяет, является ли дата прибытия 1, 2,..., 45 дней до CURRENT_DATE
, а затем Sum
amount_charged
на его основе. В сценарии также будет показано суммирование в виде столбцов. Таким образом, для каждого user_id
45 столбцов результата суммирования.
Это мой первый день с SQL, после некоторых исследований я пришел к выводу:
1) Он содержит 45 Round()
и LEFT JOIN
и кричит о LOOP или что-то в этом роде. Однако AFAIK я могу использовать LOOP
только в хранимой процедуре, которую я не могу сделать. Следующая мысль состоит в том, чтобы увидеть, могу ли я создать временную таблицу для сохранения последовательности от 1 до 45. Однако я некоторое время искал SO и не мог точно определить, что действительно помогает.
2) Можно ли генерировать 45 столбцов без использования LEFT JOIN
? Потому что я знаю, что LEFT JOIN
занимает много ресурсов, а 45 (на самом деле 47) LEFT JOIN
не кажется хорошей идеей (45 из них - LEFT JOIN
). Но я понятия не имею, как оптимизировать это.
Обновлено Мысль, извините, что я не в рабочем пространстве, поэтому не могу проверить. Просто прочитайте о SELECT
CASE
WHEN
ELSE
END
. Поэтому, возможно, я могу использовать CASE
для фильтрации даты (например, CASE
WHEN
разница между now и arrival_date
составляет 1, 2,..., 45 дней). Но все же мне нужно создать 45 столбцов...
Более упрощенная версия. Предварительная сводка на основе квалифицированных записей типа даты и типа показаний для каждого пользователя. От этого,
select
m.username AS 'Username',
SUM( if( PQ.Days = 1, PQ.Spend, 0 )) as '1',
SUM( if( PQ.Days = 2, PQ.Spend, 0 )) as '2',
SUM( if( PQ.Days = 3, PQ.Spend, 0 )) as '3',
SUM( if( PQ.Days = 44, PQ.Spend, 0 )) as '44',
SUM( if( PQ.Days = 45, PQ.Spend, 0 )) as '45',
from
( select
fm.user_id,
datediff( current_date, fm.arrival_date ) as Days,
Round( Sum(fm.amount_changed) * -1, 2 ) AS 'spend'
from
fund_m AS fm
where
fm.arrival_date >= Date_Sub( Current_Date, interval 45 day )
AND fm.type = 'impressions'
group by
fm.user_id,
datediff( current_date, fm.arrival_date )) PQ
LEFT JOIN member AS m
ON PQ.user_id = m.id
LEFT JOIN employee AS e
ON m.account_manager = e.id
group by
m.username
Если вы посмотрите на внутренний PQ (предварительный запрос), я применяю все 45 дней (предложение where), а группировка пользователем и днями, у меня есть расчетные дни IF() (0-45 дней). В конце этого запроса у вас будет AT MOST, 1 запись на человека в день, поэтому объединение внешнего запроса может получить остальную часть данных из соединения.
Большая разница здесь заключается в том, что он не бросает 45 сопоставлений даты для каждой строки, а только для определения # дней с текущего. Теперь у вас есть конечный набор, намного меньший от предварительной агрегации, чтобы сделать простую проверку в дни, чтобы получить этот день.
Группы, как у вас, есть у пользователя, но затем вы присоединяетесь к таблице сотрудников по значению менеджера аккаунта. Вы намеревались получить итоговые суммы на основе имени менеджера MANAGER? Если нет, нет необходимости даже присоединяться к таблице сотрудников, если у вас нет других полей, которые вы не захватываете по этому образцу.
Это должно обеспечить результаты, которые вы ищете:
SELECT
m.username,
Round(SUM(IF(SubDate(CURRENT_DATE,1) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '1',
Round(SUM(IF(SubDate(CURRENT_DATE,2) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '2',
Round(SUM(IF(SubDate(CURRENT_DATE,3) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '3',
Round(SUM(IF(SubDate(CURRENT_DATE,4) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '4',
Round(SUM(IF(SubDate(CURRENT_DATE,5) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '5',
Round(SUM(IF(SubDate(CURRENT_DATE,6) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '6',
Round(SUM(IF(SubDate(CURRENT_DATE,7) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '7',
Round(SUM(IF(SubDate(CURRENT_DATE,8) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '8',
Round(SUM(IF(SubDate(CURRENT_DATE,9) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '9',
Round(SUM(IF(SubDate(CURRENT_DATE,10) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '10',
Round(SUM(IF(SubDate(CURRENT_DATE,11) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '11',
Round(SUM(IF(SubDate(CURRENT_DATE,12) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '12',
Round(SUM(IF(SubDate(CURRENT_DATE,13) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '13',
Round(SUM(IF(SubDate(CURRENT_DATE,14) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '14',
Round(SUM(IF(SubDate(CURRENT_DATE,15) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '15',
Round(SUM(IF(SubDate(CURRENT_DATE,16) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '16',
Round(SUM(IF(SubDate(CURRENT_DATE,17) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '17',
Round(SUM(IF(SubDate(CURRENT_DATE,18) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '18',
Round(SUM(IF(SubDate(CURRENT_DATE,19) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '19',
Round(SUM(IF(SubDate(CURRENT_DATE,20) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '20',
Round(SUM(IF(SubDate(CURRENT_DATE,21) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '21',
Round(SUM(IF(SubDate(CURRENT_DATE,22) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '22',
Round(SUM(IF(SubDate(CURRENT_DATE,23) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '23',
Round(SUM(IF(SubDate(CURRENT_DATE,24) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '24',
Round(SUM(IF(SubDate(CURRENT_DATE,25) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '25',
Round(SUM(IF(SubDate(CURRENT_DATE,26) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '26',
Round(SUM(IF(SubDate(CURRENT_DATE,27) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '27',
Round(SUM(IF(SubDate(CURRENT_DATE,28) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '28',
Round(SUM(IF(SubDate(CURRENT_DATE,29) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '29',
Round(SUM(IF(SubDate(CURRENT_DATE,30) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '30',
Round(SUM(IF(SubDate(CURRENT_DATE,31) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '31',
Round(SUM(IF(SubDate(CURRENT_DATE,32) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '32',
Round(SUM(IF(SubDate(CURRENT_DATE,33) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '33',
Round(SUM(IF(SubDate(CURRENT_DATE,34) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '34',
Round(SUM(IF(SubDate(CURRENT_DATE,35) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '35',
Round(SUM(IF(SubDate(CURRENT_DATE,36) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '36',
Round(SUM(IF(SubDate(CURRENT_DATE,37) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '37',
Round(SUM(IF(SubDate(CURRENT_DATE,38) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '38',
Round(SUM(IF(SubDate(CURRENT_DATE,39) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '39',
Round(SUM(IF(SubDate(CURRENT_DATE,40) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '40',
Round(SUM(IF(SubDate(CURRENT_DATE,41) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '41',
Round(SUM(IF(SubDate(CURRENT_DATE,42) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '42',
Round(SUM(IF(SubDate(CURRENT_DATE,43) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '43',
Round(SUM(IF(SubDate(CURRENT_DATE,44) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '44',
Round(SUM(IF(SubDate(CURRENT_DATE,45) = fm.arrival_date,fm.amount_changed * -1,0)),2) as '45'
FROM fund_m AS fm
LEFT JOIN member AS m
ON fm.user_id = m.id
LEFT JOIN employee AS e
ON m.account_manager = e.id
WHERE fm.arrival_date BETWEEN SubDate(CURRENT_DATE,45) AND CURRENT_DATE
AND fm.type = 'impressions'
GROUP BY m.username
ORDER BY m.username;
То, что вы можете сделать, это первая группа и развернуть их на 45 столбцов, используя MAX на таблице fund_m, сгруппированной по user_id, а затем присоедините их к члену. Это позволит избежать объединения нескольких соединений в одну и ту же таблицу.
Пример:
SELECT m.username, ROUND('spend1',2) '1', ROUND('spend2',2) '2', .... ROUND('spend45',2) '45'
FROM member AS m
LEFT OUTER JOIN
(
SELECT fm.user_id AS id,
SUM(CASE WHEN fm.arrival_date = Subdate(CURRENT_DATE, 1)
THEN fm.amount_changed *- 1 END) AS 'spend1',
SUM(CASE WHEN fm.arrival_date = Subdate(CURRENT_DATE, 2)
THEN fm.amount_changed *- 1 END) AS 'spend2',
....,
SUM(CASE WHEN fm.arrival_date = Subdate(CURRENT_DATE, 44)
THEN fm.amount_changed *- 1 END) AS 'spend44',
SUM(CASE WHEN fm.arrival_date = Subdate(CURRENT_DATE, 45)
THEN fm.amount_changed *- 1 END) AS 'spend45'
FROM fund_m AS fm
WHERE fm.arrival_date BETWEEN Subdate(CURRENT_DATE, 45) AND Subdate(CURRENT_DATE, 1)
AND fm.type = 'impressions'
GROUP BY fm.user_id
) X ON fm.user_id = X.id
LEFT JOIN employee AS e ON m.account_manager = e.id
SUM(IF(SubDate(CURRENT_DATE,1) = arrival_date,spend,0))
. Измените «1» на количество дней в прошлом (т.е. 1-45)