#mysql #replace #coalesce
#mysql #заменить #объединить
Вопрос:
У меня есть 2 таблицы:
table: transaction:
====================
id billed_date amount
1 2016-09-30 5
2 2016-10-04 15
3 2016-10-06 10
table: report_date
====================
transaction_id report_date
1 2016-10-01
Я хочу:
- Создайте отчет, в котором суммируется сумма всех транзакций за октябрь 2016 года
- Основывается на дате отчета, а не на дате выставления счета
- Если дата отчета не задана, она основывается на billed_date
- В приведенном выше примере я хочу, чтобы результат был равен 30 (а не 25).
Затем я пишу:
Первый:
SELECT
sum(t.amount),
CASE WHEN d.report_date IS NOT NULL THEN d.report_date ELSE t.billed_date END AS new_date
FROM
transaction t LEFT JOIN report_date d ON t.id = d.transaction_id
WHERE new_date BETWEEN '2016-10-01' AND '2016-10-30'
Второй:
SELECT sum(amount) FROM
(SELECT t.amount,
CASE WHEN d.report_date IS NOT NULL THEN d.report_date ELSE t.billed_date END AS date
FROM transaction t LEFT JOIN report_date d ON t.id = d.transaction_id
) t
WHERE t.date BETWEEN '2016-10-01' AND '2016-10-30'
Результат:
Первый:
- Неизвестный столбец ‘new_date’ в предложении ‘where’
- Если я заменю ‘new_date’ на ‘date’: результат = 25 (исключить id = 1)
Второй:
- результат = 30 => Правильно, но в моем случае, когда в таблице транзакций около 30 тыс. записей, процесс идет слишком медленно.
Кто-нибудь может мне помочь?
Комментарии:
1. Конечно. Запустите ОБЪЯСНЕНИЕ для вашего второго запроса и посмотрите, где может потребоваться один или несколько дополнительных индексов.
2. В вашем описании таблицы у вас есть
billed_date
. Но в ваших запросах вы используетеt.date
. Что правильно?3. Извините, «billed_date» — это правильно. Я исправил свои запросы. Спасибо
Ответ №1:
Прежде всего — часть
CASE WHEN d.report_date IS NOT NULL THEN d.report_date ELSE t.billed_date END
может быть записано короче как
COALESCE(d.report_date, t.billed_date)
или как
IFNULL(d.report_date, t.billed_date)
В вашем первом запросе вы используете псевдоним столбца в предложении WHERE, что недопустимо. Вы можете исправить это, переместив выражение за псевдонимом в предложение WHERE:
SELECT sum(t.amount)
FROM transaction t LEFT JOIN report_date d ON t.id = d.transaction_id
WHERE COALESCE(d.report_date, t.billed_date) BETWEEN '2016-10-01' AND '2016-10-30'
Это почти то же самое, что и ваше собственное решение.
Ваш второй запрос выполняется медленно, потому что MySQL должен сохранять результат подзапроса (30 тыс. строк) во временной таблице. Пытаясь оптимизировать его, вы получите то же решение, что и выше.
Однако, если у вас есть индексы transaction.billed_date
, и report_date.report_date
этот запрос все еще не может их использовать. Чтобы использовать индексы, вы можете разделить запрос на две части:
Записи с отчетом (будут использовать report_date.report_date
индекс):
SELECT sum(amount)
FROM transaction t JOIN report_date d ON id = transaction_id
WHERE d.report_date BETWEEN '2016-10-01' AND '2016-10-30'
Записи без отчета (будут использовать transaction.billed_date
индекс):
SELECT sum(amount)
FROM transaction t LEFT JOIN report_date d ON id = transaction_id
WHERE d.report_date IS NULL AND t.billed_dateBETWEEN '2016-10-01' AND '2016-10-30'
Оба запроса могут использовать индекс. Вам просто нужно суммировать результаты, что также можно сделать, объединив два запроса:
SELECT (
SELECT sum(amount)
FROM transaction t JOIN report_date d ON id = transaction_id
WHERE d.report_date BETWEEN '2016-10-01' AND '2016-10-30'
) (
SELECT sum(amount)
FROM transaction t LEFT JOIN report_date d ON id = transaction_id
WHERE d.report_date IS NULL AND t.billed_dateBETWEEN '2016-10-01' AND '2016-10-30'
) AS sum_amount
Ответ №2:
Я, наконец, нашел решение с помощью моего брата:
SELECT sum(amount)
FROM transaction t LEFT JOIN report_date d ON id = transaction_id
WHERE (report_date BETWEEN '2016-10-01' AND '2016-10-30') OR (report_date IS NULL AND billed_date BETWEEN '2016-10-01' AND '2016-10-30')
Спасибо за заботу обо мне!
Ответ №3:
Заполняется table: report_date
отсутствующими значениями из table: transaction:
регистра?
SELECT id FROM report_date WHERE report_date BETWEEN '2016-10-01' AND '2016-10-30';
INSERT INTO report_date SELECT id, billed_date FROM transaction WHERE billed_date BETWEEN '2016-10-01' AND '2016-10-30' AND id NOT IN (ids_from previous_query);
SELECT sum(t.amount) FROM transaction LEFT JOIN report_date d ON (t.id = d.transaction_id) WHERE d.report_date BETWEEN '2016-10-01' AND '2016-10-30';
Ответ №4:
Ваш второй запрос правильный, нет необходимости переписывать запрос. Но я должен сказать вам одну вещь, которая вам очень поможет при работе с тысячами / миллионами записей. Мы также сосредоточились на некоторых других вещах. Потому что, когда ваша таблица содержит большое количество данных (в тысячах и миллионах) записей, для выполнения запроса требуется время. Это также может привести к блокировке, возможно, проблема с блокировкой запроса или удалением базы данных. Чтобы избежать этой проблемы, вы просто создаете индекс одного столбца. Создайте индекс для этого столбца, который действует / используется в предложениях where. Как и в вашем случае, вы можете создать индекс для столбца billed_date из таблицы транзакций. Потому что ваш результат основан на таблице транзакций. Для получения более подробной информации о том, как создать индекс в mysql / phpmyadmin, вы можете взять ссылку из этого http://www.yourwebskills.com/dbphpmyadmintable.php ссылка.
В какой-то момент я столкнулся с такой же проблемой, после чего я создал ИНДЕКС по столбцу. Сейчас я имею дело с миллионами записей, использующих mysql.
Комментарии:
1. В моей базе данных billed_date не может быть установлен в качестве столбца индекса, потому что некоторые записи имеют одинаковое значение даты выставления счета. Во всяком случае, я понял вашу идею и заранее благодарю вас.
2. @Phong Hoang насколько мне известно, это не большая проблема, billed_date содержит то же значение даты. Поскольку мы устанавливаем индекс для столбца, он не определяет значение этого столбца. Благодаря Благодарности за то, что вы цените меня.