Как я должен оптимизировать этот запрос MySQL? - PullRequest
0 голосов
/ 14 декабря 2011

У меня есть этот запрос, но он занимает слишком много времени, примерно 30 секунд через NaviCat. Как это можно оптимизировать, если это возможно?

SELECT DISTINCT c.clientid, c.name, c.email, c.region 
FROM clients c RIGHT JOIN orders o ON c.clientid = o.clientid 
WHERE o.order_status = 'pending' 
AND c.clientid NOT IN (
    SELECT DISTINCT c.clientid 
    FROM clients c, orders o
    WHERE c.clientid = o.clientid AND o.order_status = 'paid'
    ) 
ORDER BY c.id DESC

Чтобы лучше понять, что мне нужно: у меня есть 2 таблицы:

clients (id, clientid, name, email, region) 
orders (id, orderid, clientid, order_amount, order_status, ….)

Пример записи:

Client | Order | Status
-----------------------
C1     | O1    | (paid)
C1     | O2    | (pending)
C2     | O3    | (paid)
C3     | O4    | (pending)
C4     | O5    | (paid)
C5     | O6    | (pending)

Мне нужно вернуть только C3 и C5

Большое спасибо за ваши ответы.

Ответы [ 5 ]

1 голос
/ 14 декабря 2011

Здесь есть несколько замечательных идей, но попытка оптимизировать запрос, не зная, что происходит в ядре базы данных, не является самым прямым путем к лучшему ответу. Иногда для оптимизации просто необходим дополнительный индекс, а не изменение SQL.

Первое, что вы должны сделать, это взглянуть на план объяснения ( документация для 5.1 ) и затем решить, можете ли вы изменить запрос или добавить индексы или что-то еще. Возможно, один из приведенных ответов верен, но без плана выполнения вы просто угадываете.

Пара мыслей по вашему запросу.

Я не понимаю, зачем тебе ПРАВИЛЬНОЕ СОЕДИНЕНИЕ. Поскольку вы после клиентов, ВНУТРЕННЕГО СОЕДИНЕНИЯ должно быть достаточно.

Любой запрос, использующий DISTINCT или GROUP BY, потребует окончательной сортировки. Если количество строк, которые нужно отсортировать (клиенты х заказов) велико, это снизит производительность. Если это так, подход @ ypercube может быть хорошим, в противном случае трюк @ ajreal выглядит многообещающим. Удачи.

Редактировать: Вот интересный блог по этому типу запроса и нескольким подходам.

1 голос
/ 14 декабря 2011

Есть много способов, вот один из приемов: -

SELECT c.clientid, c.name, c.email, c.region,
  SUM(IF(o.order_status = 'paid', 1, 0)) as paid
FROM clients c
INNER JOIN orders o 
ON c.clientid = o.clientid 
WHERE o.order_status IN( 'pending', 'paid')
GROUP BY c.clientid
HAVING paid = 0;
1 голос
/ 14 декабря 2011

Не уверен, как это будет работать, но попробуйте что-то вроде:

SELECT DISTINCT c.clientid, c.name, c.email, c.region 
FROM clients c
RIGHT JOIN orders o ON c.clientid = o.clientid AND o.order_status = 'pending'
LEFT JOIN orders o2 ON o.clientid = o2.clientid AND o.order_status = 'paid'
WHERE o2.clientid IS NULL

По сути, попробуйте сопоставить отложенные и оплаченные ордера и принимать только отложенные ордера там, где это не удается.

Что касается профессионалов, у вас нет миллиона подзапросов. Дело в том, что число сгенерированных строк до того, как WHERE их отбраковывает, потенциально намного больше. Так что я не знаю, поможет ли это или навредит.

РЕДАКТИРОВАТЬ: Кроме того, да, как @ruakh в комментариях, я задавался вопросом, почему RIGHT JOIN там ... может заказ не иметь клиентов, или я что-то упустил?

0 голосов
/ 14 декабря 2011

Использование EXISTS:

SELECT c.clientid, c.name, c.email, c.region 
FROM clients c 
WHERE EXISTS
      ( SELECT *
        FROM orders o 
        WHERE o.clientid = c.clientid 
          AND o.order_status = 'pending'
      ) 
  AND NOT EXISTS
      ( SELECT *
        FROM orders o 
        WHERE o.clientid = c.clientid 
          AND o.order_status = 'paid'
      ) 
ORDER BY c.id DESC

Использование JOIN:

SELECT c.clientid, c.name, c.email, c.region 
FROM clients c 
  JOIN orders o
    ON  o.clientid = c.clientid 
    AND o.order_status = 'pending'
  LEFT JOIN orders o2
    ON  o2.clientid = c.clientid 
    AND o2.order_status = 'paid'
WHERE o2.clientid IS NULL
GROUP BY c.clientid
ORDER BY c.id DESC

Я не понимаю, почему у вас есть два столбца, которые, кажется, служатодно и то же назначение (первичный ключ) в обеих таблицах (id и clientid в таблице client и то же в таблице order).

0 голосов
/ 14 декабря 2011

Примерно так будет лучше:

SELECT DISTINCT c.clientid, c.name, c.email, c.region 
    FROM clients c 
INNER JOIN orders o ON c.clientid = o.clientid 
LEFT OUTER JOIN (
    SELECT cc.clientid FROM clients cc 
        INNER JOIN orders oo WHERE cc.clientid = oo.clientid AND      
        oo.order_status = 'paid'
    GROUP BY cc.clientid) cp ON cp.clientid = c.clientid
WHERE o.order_status = 'pending' 
AND cc.clientid IS NULL
ORDER BY c.id DESC

Если ваши таблицы большие, вы не хотите использовать IN или OR в своих запросах, они не позволят MySQL использовать индексы, плюс,в вашем подзапросе вы не использовали внутреннее соединение, это было неправильно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...