Этот мой запрос с его производительностью (slow_query_log):
SELECT j.'offer_id', o.'offer_name', j.'success_rate'
FROM
(
SELECT
t.'offer_id',
(
SUM(CASE WHEN 'offer_id' = t.'offer_id' AND 'sales_status' = 'SUCCESS' THEN 1 ELSE 0 END) / COUNT(*)
) AS 'success_rate'
FROM 'tblSales' AS t
WHERE DATE(t.'sales_time') = CURDATE()
GROUP BY t.'offer_id'
ORDER BY 'success_rate' DESC
) AS j
LEFT JOIN 'tblOffers' AS o
ON j.'offer_id' = o.'offer_id'
LIMIT 5;
# Time: 180113 18:51:19
# User@Host: root[root] @ localhost [127.0.0.1] Id: 71
# Query_time: 10.472599 Lock_time: 0.001000 Rows_sent: 0 Rows_examined: 1156134
Здесь tblOffers
имеют все ПРЕДЛОЖЕНИЯ, перечисленные. И tblSales
содержит все продажи. То, что я пытаюсь выяснить, - это самые продаваемые предложения, основанные на коэффициенте успеха (т.е. Продажах, которые являются УСПЕХОМ).
Запрос работает отлично и предоставляет требуемый результат. Но, похоже, он немного медленнее.
offer_id
и sales_status
уже проиндексированы в tblSales
. Итак, есть ли у вас предложения по улучшению внутреннего запроса (где он вычисляет коэффициент успеха), чтобы производительность могла быть улучшена? Я играю с математикой более двух часов. Но не мог лучше.
tblSales
, tblSales
имеет много данных. Он содержит те продажи, которые являются УСПЕШНЫМИ, НЕИСПРАВНОСТЬЮ, ОТКАЗОМ и т.д.
Спасибо
РЕДАКТИРОВАТЬ
По мере того как вы просили включить в него также дизайн таблицы (включены только соответствующие поля):
tblSales
'sales_id' bigint UNSIGNED NOT NULL AUTO_INCREMENT,
'offer_id' bigint UNSIGNED NOT NULL DEFAULT '0',
'sales_time' DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
'sales_status' ENUM('WAITING', 'SUCCESS', 'FAILED', 'CANCELLED') NOT NULL DEFAULT 'WAITING',
PRIMARY KEY ('sales_id'),
KEY ('offer_id'),
KEY ('sales_status')
В этой таблице есть и другие поля, в которых содержится другая информация. Сумма, user_id и т.д., Которые не имеют отношения к моему вопросу.
Здесь tblOffers имеют все ПРЕДЛОЖЕНИЯ, перечисленные. И tblSales содержит все продажи. То, что я пытаюсь выяснить, - это самые продаваемые предложения, основанные на коэффициенте успеха (т.е. Продажах, которые являются УСПЕХОМ).
Подходите к этому простым JOIN
и GROUP BY
:
SELECT s.offer_id, o.offer_name,
AVG(s.sales_status = 'SUCCESS') as success_rate
FROM tblSales s JOIN
tblOffers o
ON o.offer_id = s.offer_id
WHERE s.sales_time >= CURDATE() AND
s.sales_time < CURDATE() + INTERVAL 1 DAY
GROUP BY s.offer_id, o.offer_name
ORDER BY success_rate DESC;
Заметки:
tblSales(sales_time)
- или еще лучше tblSales(salesTime, offer_id, sales_status)
.success_rate
была упрощена, хотя это имеет минимальное влияние на производительность.offer_name
в группу GROUP BY
. Если вы изучаете SQL, у вас всегда должны быть все неагрегированные ключи в предложении GROUP BY
.LEFT JOIN
необходим, только если у вас есть предложения в tblSales
которые не находятся в tblOffers
. Я предполагаю, что у вас есть правильные отношения с внешним ключом, и это не так.LIMIT 5
также необходима.
sales_time
и теперь, кажется, лучше. Но только сомнение, у primary_key=sales_id
уже есть primary_key=sales_id
, затем проиндексированные поля offer_id
, sales_status
, а теперь sales_times
. Но этот tblSales
будет добавлять новые записи очень часто. Я имею в виду в течение минуты, может быть, от 100 до 1000 записей. Так не затруднит ли все эти проиндексированные поля вставку и обновление записей в этой таблице? На данный момент у меня есть около 1156134
строк в этой таблице, и она растет.
Многочисленные "проблемы", ни одна из которых не связана с "математикой".
JOINs
Сложно. LEFT JOIN
говорит: "Мне все равно, существует ли строка в таблице" справа "(я подозреваю, что вам не нужно LEFT
?), Но в ней также говорится:" В правой таблице может быть несколько строк. На основе имен столбцов, я предполагаю, что есть только один offer_name
для каждого offer_id
. Если это правильно, то вот моя первая рекомендация. (Это убедит Оптимизатора в том, что с JOIN
нет проблемы.) Изменение из
SELECT ..., o.offer_name, ...
LEFT JOIN 'tblOffers' AS o ON j.'offer_id' = o.'offer_id'
...
в
SELECT ...,
( SELECT offer_name FROM tbloffers WHERE offer_id j.offer_id
) AS offer_name, ...
Он также избавляется от ошибки, в которой вы предполагаете, что внутренний ORDER BY
будет сохранен для LIMIT
. Это имело место, но в новых версиях MariaDB/MySQL это не так. ORDER BY
в "производной таблице" (ваш подзапрос) теперь игнорируется.
2 вниз, еще несколько.
"Не скрывайте индексированный столбец в функции". Я имею в виду DATE(t.sales_time) = CURDATE()
. Предполагая, что у вас нет значений sales_time
для "будущего", тогда этот тест можно изменить на t.sales_time >= CURDATE()
. Если вам действительно нужно ограничиться только сегодня, сделайте следующее:
AND sales_time >= CURDATE()
AND sales_time < CURDATE() + INTERVAL 1 DAY
ORDER BY
и LIMIT
обычно должны быть объединены. В вашем случае вы также можете добавить LIMIT
в "производную таблицу", тем самым приводя к 5 строкам для внешнего запроса. Но... Есть еще вопрос, как правильно их отсортировать. Итак, измените
SELECT ...
FROM ( SELECT ...
ORDER BY ... )
LIMIT ...
в
SELECT ...
FROM ( SELECT ...
ORDER BY ...
LIMIT 5 ) -- trim sooner
ORDER BY ... -- deal with the loss of ordering from derived table
Продвигаясь все вместе, у меня есть
SELECT j.'offer_id',
( SELECT offer_name
FROM tbloffers
WHERE offer_id = j.offer_id
) AS offer_name,
j.'success_rate'
FROM
( SELECT t.'offer_id',
AVG(t.sales_status = 'SUCCESS') AS 'success_rate'
FROM 'tblSales' AS t
WHERE t.sales_time >= CURDATE()
GROUP BY t.'offer_id'
ORDER BY 'success_rate' DESC
LIMIT 5
) AS j
ORDER BY 'success_rate' DESC;
(Я взял на себя смелость сократить SUM(...)
двумя способами.)
Теперь для индексов...
tblSales
нужно как минимум (sales_time)
, но отпустите "покрытие" (sales_time
):
INDEX(sales_time, sales_status, order_id)
Если tbloffers
имеет PRIMARY KEY(offer_id)
, то дальнейший индекс не стоит добавлять. Иначе добавьте этот индекс покрытия (в этом порядке):
INDEX(offer_id, offer_name)
(Извиняюсь перед другими Ответчиками, я украл некоторые из ваших идей.)
t.sales_status
вместо s.sales_status
. Возможно, вы перепутали письмо, которое я использовал для псевдонима. И спасибо за подсказку по limit
Не зная вашей схемы, самый низкий висящий плод, который я вижу, - это эта часть....
WHERE DATE(t.'sales_time') = CURDATE()
Попробуйте изменить это на что-то похожее
Where t.sales_time >= @12-midnight-of-current-date and t.sales_time <= @23:59:59-of-current-date
Основываясь на небольшой информации, которую вы предоставили (я имею в виду схему таблицы), вы можете попробовать следующее.
SELECT 'o'.'offer_id', 'o'.'offer_name', SUM(CASE WHEN 't'.'sales_status' = 'SUCCESS' THEN 1 ELSE 0 END) AS 'success_rate'
FROM 'tblOffers' 'o'
INNER JOIN 'tblSales' 't'
ON 'o'.'offer_id' = 't'.'offer_id'
WHERE DATE('t'.'sales_time') = CURDATE()
GROUP BY 'o'.'offer_id'
ORDER BY 'success_rate' DESC
LIMIT 0,5;
Вы можете найти образец этого запроса в этом примере SQL Fiddle
EXPLAIN
чтобы мы могли предоставить вам лучшее решение. Также будет полезна ссылка на sqlfiddle.