@ray1992

Как оптимизировать запрос с JOIN, в котором выбирается сумма?

Здравствуйте. Есть таблица транзакций. Типы:
Daily Earning - дневной заработок
Hold - временная заморозка суммы
UnHold - разморозка
Chargeback - штраф
Bonus - безвозмездный подарок
PayOut - выплата

Для типов кроме Daily Earning, поле id_daily_earning - это ссылка на какой либо день.
Нужно посчитать баланс, то есть от суммы заработка отнять сумму Hold, прибавить сумму UnHold и т.д.
Проблема в том, что при количестве записей в 100к запрос выполняется 15-20 сек

Вот пример таблицы:
3eQW1pc.png

Вот таблица: pastebin.com/hFaBMNEM
Вот запрос: pastebin.com/XVZHdytE
Вот EXPLAIN:
tDQ04.jpg

Можно вставить создание таблицы и запрос сюда: sqlfiddle.com/#!2/65a283

Из примера таблицы: надо взять записи с Daily Earning, к каждой из них получить суммы Hold, UnHold и пр. и присоединить. А потом уже сгруппировать по выбранному пользователем полю.

П.С. Группировки могут быть по разным полям. Там есть account_id, date и пр. В зависимости от группировки нужно видеть картину по заработкам (по дням, аккаунтам и пр.)
  • Вопрос задан
  • 2334 просмотра
Решения вопроса 1
@ray1992 Автор вопроса
В общем самый приемлемый вариант оказался таким:

SELECT 
  t2.id,
  t2.offer_id,  
  SUM(t2.sum_hold) AS sum_hold,
  SUM(t2.sum_unhold) AS sum_unhold,
  SUM(t2.sum_chargeback) AS sum_chargeback,
  SUM(t2.sum_bonus) AS sum_bonus,
  SUM(t2.earning) AS sum_earning
  FROM   
  (
    SELECT
    `t`.`id`,
    `t`.`offer_id`,
    `t`.`amount`,
    SUM(IF(OtherTransactions.type = 'Hold', OtherTransactions.amount, 0)) AS sum_hold,
    SUM(IF(OtherTransactions.type = 'UnHold', OtherTransactions.amount, 0)) AS sum_unhold,
    SUM(IF(OtherTransactions.type = 'Chargeback', OtherTransactions.amount, 0)) AS sum_chargeback,
    SUM(IF(OtherTransactions.type = 'Bonus', OtherTransactions.amount, 0)) AS sum_bonus,
    t.amount AS earning
    FROM `payments_transactions` `t`
    LEFT OUTER JOIN `payments_transactions` `OtherTransactions`
      ON (`OtherTransactions`.`id_daily_earning` = `t`.`id`)
      AND (OtherTransactions.type = 'Hold'
      OR OtherTransactions.type = 'UnHold'
      OR OtherTransactions.type = 'Chargeback'
      OR OtherTransactions.type = 'Bonus')
    WHERE (t.type = "Daily Earning")
    GROUP BY t.id
)
t2
GROUP BY t2.offer_id
ORDER BY t2.id DESC
LIMIT 30


Сначала джойним холды и пр, считаем их сумму в группировке по t.id. Как заработок берем не сумму amount, а просто amount. Затем оборачиваем это все в еще один запрос, в котором уже группируем как хотим, снова считаем суммы уже в этой группировке и делаем LIMIT.
На 350к записей отрабатывает за ~0.7 сек. Вполне терпимо.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
Serhioromano
@Serhioromano
Web Developer
Если такая перрушка с этими дневными заработками, то это вообще должны быть вынесено в другую таблицу по идее.

Это bad practice созадвать таблици кторые ссылаются сами на себя.

Я попробовал несколько вариантов, но все они не работают нормально. Так что посоветовать ни чего не могу в плане оптимизации запроса, только оптимизацию баззы.
Ответ написан
@carryx
MySQL Developer
а если не допускать значения Null, и по умолчанию ставить 0
то проверки IFNULL не нужны, т.к. там Null не будет
вместо строковых значений ('Daily Earning', 'PayOut', 'Hold', 'UnHold', 'Chargeback', 'Bonus') поставить цифры!
зачем проверять сами строки, работайте по возможностью с числами - это немного должно ускорить работу и вес базы
да вариантов куча на оптимизации
от реконструкции самой базы, до NOSQL
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы