Как оптимизировать несколько LEFT JOIN

0

Некоторые предпосылки:

У нас есть только привилегия 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 столбцов...

  • 1
    Вы также можете использовать функцию IF, так как у вас просто есть два варианта. Дело хорошо, когда у вас есть несколько вариантов. SUM(IF(SubDate(CURRENT_DATE,1) = arrival_date,spend,0)) . Измените «1» на количество дней в прошлом (т.е. 1-45)
  • 1
    Кроме того, в когда для запроса, ограничьте количество строк до последних 45 дней.
Показать ещё 2 комментария
Теги:

3 ответа

1
Лучший ответ

Более упрощенная версия. Предварительная сводка на основе квалифицированных записей типа даты и типа показаний для каждого пользователя. От этого,

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? Если нет, нет необходимости даже присоединяться к таблице сотрудников, если у вас нет других полей, которые вы не захватываете по этому образцу.

  • 0
    Спасибо! Как новичок, идея «Pre-Query» очень интересна и имеет смысл. Я протестирую сценарий, чтобы увидеть, как он работает. Могу ли я спросить вас еще одну вещь? Вы упомянули «конечный набор намного меньше», я подумал, что это, вероятно, один из столпов оптимизации запроса, как я должен это учитывать при изучении SQL? На данный момент я знаю только несколько команд и предпочел бы делать что-то медленно, чем ничего не делать, но я уверен, что оптимизация станет очень важной в ближайшем будущем.
  • 0
    О последнем абзаце: Нет, мне не нужно группировать итоги по account_manager, это по пользователям. Второй «LEFT JOIN», как я понимаю, - это выборка пользователей, которые были приобретены только менеджерами аккаунтов. Я проверю базу данных, чтобы узнать, нужно ли это.
Показать ещё 1 комментарий
1

Это должно обеспечить результаты, которые вы ищете:

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;
  • 1
    этот запрос является неполным и не соответствует структуре OP, имя пользователя происходит из другой таблицы. Вы можете обновить это, чтобы присоединиться к таблице членов и искать только строки типа показа.
  • 0
    @rs. Я обновил запрос. Спасибо за внимание.
1

То, что вы можете сделать, это первая группа и развернуть их на 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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню