Очень медленные подзапросы при использовании "NOT IN" - PullRequest
7 голосов
/ 09 августа 2011

Я работаю над созданием отчетов для данных, содержащихся в большой существующей базе данных Access (~ 500 МБ после сжатия и восстановления), и у меня возникли проблемы с медленным подзапросом.

База данных имеет большую таблицу, в которой содержится запись о каждой покупке клиента. Вот простой запрос, который находит клиентов, которые купили синий виджет. Он завершается за несколько секунд и возвращает около десяти тысяч записей.

SELECT DISTINCT CustomerId 
FROM ProductSales
WHERE Product = 'BLUE' 

Вот запрос, который пытается найти клиентов, которые купили синий виджет, но не красный виджет. Это займет около часа.

SELECT DISTINCT CustomerId FROM ProductSales
WHERE Product = 'BLUE' 
AND CustomerId NOT IN (
    SELECT CustomerId 
    FROM ProductSales 
    WHERE Product = 'RED'
)

Есть ли способ реорганизовать второй запрос, чтобы он занимал несколько минут вместо часа?

Ответы [ 2 ]

11 голосов
/ 09 августа 2011

Access 'ядро базы данных не может использовать индекс для Not In, поэтому он должен быть медленным. С индексом на CustomerId этот запрос должен быть намного быстрее, потому что механизм базы данных может использовать индекс.

SELECT DISTINCT blue.CustomerId
FROM
    ProductSales AS blue
    LEFT JOIN
        (
            SELECT CustomerId 
            FROM ProductSales 
            WHERE Product = 'RED'
        ) AS red
    ON blue.CustomerId = red.CustomerId
WHERE
        blue.Product = 'BLUE'
    AND red.CustomerId Is Null; 

Возможно, вы также можете попробовать подход Not Exists, но использование индекса там не гарантируется. Также см. Комментарий Дэвида Фентона ниже, в котором более подробно обсуждается влияние на производительность.

0 голосов
/ 09 августа 2011

Конечно, добавьте индекс, если у вас его нет. Если это проблема, то, вероятно, просто есть много клиентов с заказами на что-то иное, чем RED, но не так много с BLUE; этот (непроверенный) запрос пытается это исправить.

SELECT DISTINCT CustomerId FROM ProductSales
LEFT JOIN (
  SELECT DISTINCT CustomerId cid FROM ProductSales
  LEFT JOIN (
    SELECT DISTINCT CustomerId
    FROM ProductSales
    WHERE Product = 'BLUE'
  ) foo ON CustomerId = cid
  WHERE Product = 'RED'
) bar USING (CustomerId)
WHERE cid IS NULL
...