Переписать 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
Другие вопросы по тегам