Переписать IN подзапрос как JOIN - PullRequest
0 голосов
/ 17 декабря 2010

У меня никогда не было хорошей производительности с 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 ]

1 голос
/ 17 декабря 2010

Вам не нужен выбранный идентификатор клиента?Разве вы не запускаете подзапрос один раз для каждого клиента и получаете поток истинных или ложных значений, не зная, какое из них применимо к какому клиенту?

Если это то, что вам нужно, вам не нужно ссылаться на таблицу клиентов (если вы не сохраните свою базу данных в состоянии семантической дезинтеграции, и в 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), и для ясности я обычно использовал бы что-то иное, чем объединяющий столбец.

1 голос
/ 17 декабря 2010

Я думаю, 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 
0 голосов
/ 17 декабря 2010

Попробуйте это

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
...