Переписать IN подзапрос как JOIN
У меня никогда не было хорошей производительности с IN в MySQL, и я снова столкнулся с проблемой производительности.
Я пытаюсь создать представление. Соответствующая часть этого:
SELECT
c.customer_id,
....
IF (c.customer_id IN (
SELECT cn.customer_id FROM customer_notes cn
), 1, 0) AS has_notes
FROM customers c;
По сути, я просто хочу знать, есть ли у клиента примечание или нет. Неважно, сколько заметок. Как я могу переписать это, используя JOIN, чтобы ускорить его?
Таблица клиентов в настоящее время имеет 1,5 миллиона строк, поэтому производительность является проблемой.
3 ответа
Вам не нужен выбранный идентификатор клиента? Разве вы не запускаете подзапрос один раз для каждого клиента и получаете поток истинных или ложных значений, не зная, какое из них применимо к какому клиенту?
Если это то, что вам нужно, вам не нужно ссылаться на таблицу клиентов (если только вы не держите свою базу данных в состоянии семантической дезинтеграции, и могут быть записи в customer_notes, для которых нет соответствующего клиента - но тогда у вас есть большие проблемы чем выполнение этого запроса); Вы можете просто использовать:
SELECT DISTINCT Customer_ID
FROM Customer_Notes
ORDER BY Customer_ID;
получить список значений идентификатора клиента с хотя бы одной записью в таблице Customer_Notes.
Если вам нужен список значений идентификатора клиента и связанное с ним значение true/false, необходимо выполнить объединение:
SELECT C.Customer_ID,
CASE WHEN N.Have_Notes IS NULL THEN 0 ELSE 1 END AS Has_Notes
FROM Customers AS C
LEFT JOIN (SELECT Customer_ID, COUNT(*) AS Have_Notes
FROM Customer_Notes
GROUP BY Customer_ID) AS N
ON C.Customer_ID = N.Customer_ID
ORDER BY C.Customer_ID;
Если это приводит к низкой производительности, убедитесь, что у вас есть индекс Customer_Notes.Customer_ID. Если это не проблема, изучите план запроса.
Не могу сделать... в представлении
Мелкие ограничения на то, что разрешено в представлении, всегда неприятны в любой СУБД (MySQL не одинок в своих ограничениях). Однако мы можем сделать это с помощью одного регулярного соединения. Я только что вспомнил. COUNT(column)
учитывает только ненулевые значения, возвращая 0, если все значения равны нулю, поэтому - если вы не возражаете получить счет, а не просто 0 или 1 - вы можете использовать:
SELECT C.Customer_ID,
COUNT(N.Customer_ID) AS Num_Notes
FROM Customers AS C
LEFT JOIN Customer_Notes AS N
ON C.Customer_ID = N.Customer_ID
GROUP BY C.Customer_ID
ORDER BY C.Customer_ID;
И если вам абсолютно необходимо иметь 0 или 1:
SELECT C.Customer_ID,
CASE WHEN COUNT(N.Customer_ID) = 0 THEN 0 ELSE 1 END AS Has_Notes
FROM Customers AS C
LEFT JOIN Customer_Notes AS N
ON C.Customer_ID = N.Customer_ID
GROUP BY C.Customer_ID
ORDER BY C.Customer_ID;
Обратите внимание, что использование N.Customer_ID имеет решающее значение - хотя любой столбец в таблице подойдет (но вы не разглашаете имена других столбцов, AFAICR), и я обычно использовал бы что-то другое, кроме соединяющего столбца для ясность.
Я думаю EXISTS
подходит вашей ситуации лучше, чем JOIN
или же IN
,
SELECT
IF (EXISTS (
SELECT *
FROM customer_notes cn
WHERE c.customer_id = cn.customer_id),
1, 0) AS filter_notes
FROM customers
Попробуй это
SELECT
CASE WHEN cn.customer_id IS NOT NULL THEN 1
ELSE 0
END AS filter_notes
FROM customers c LEFT JOIN customer_notes cn
ON c.customer_id= cn.customer_id