Оптимизация запроса SELECT, который медленно выполняется в Oracle, который быстро выполняется в SQL Server

Я пытаюсь запустить следующий оператор SQL в Oracle, и на его выполнение уходит много времени:

SELECT orderID FROM tasks WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL)

Если я запускаю только ту часть, которая находится в предложении IN, это выполняется очень быстро в Oracle, т.е.

SELECT DISTINCT orderID FROM tasks WHERE
engineer1 IS NOT NULL AND engineer2 IS NOT NULL

Почему весь оператор Oracle занимает так много времени? В SQL Server вся инструкция выполняется быстро.

В качестве альтернативы, есть ли более простой / другой / лучший оператор SQL, который я должен использовать?

Еще несколько подробностей о проблеме:

  • Каждый заказ состоит из множества задач
  • Каждый заказ будет распределен (для одной или нескольких его задач будут установлены Engine1 и Engineer2) или заказ может быть нераспределенным (все его задачи имеют нулевые значения для полей инженера)
  • Я пытаюсь найти все нераспределенные идентификаторы заказов.

На всякий случай, в таблице ~ 120 тыс. Строк и 3 задачи на заказ, так что ~ 40 тыс. Разных заказов.

Ответы на ответы:

  • Я бы предпочел оператор SQL, который работает как в SQL Server, так и в Oracle.
  • У задач есть только индекс по идентификатору заказа и идентификатору задачи.
  • Я попробовал НЕ СУЩЕСТВУЕТ версию инструкции, но она работала более 3 минут, прежде чем я ее отменил. Возможно, нужна версия оператора JOIN?
  • Также есть таблица заказов со столбцом orderID. Но я пытался упростить вопрос, не включив его в исходный оператор SQL.

Я предполагаю, что в исходном операторе SQL подзапрос запускается каждый раз для каждой строки в первой части оператора SQL - даже если он статичен и его нужно запускать только один раз?

Выполнение

ANALYZE TABLE tasks COMPUTE STATISTICS;

заставил мой исходный оператор SQL выполняться намного быстрее.

Хотя мне все еще любопытно, почему я должен это делать, и если / когда мне нужно будет запустить его снова?

The statistics give Oracle's cost-based optimzer information that it needs to determine the efficiency of different execution plans: for example, the number of rowsin a table, the average width of rows, highest and lowest values per column, number of distinct values per column, clustering factor of indexes etc.

In a small database you can just setup a job to gather statistics every night and leave it alone. In fact, this is the default under 10g. For larger implementations you usually have to weigh the stability of the execution plans against the way that the data changes, which is a tricky balance.

Oracle also has a feature called "dynamic sampling" that is used to sample tables to determine relevant statistics at execution time. It's much more often used with data warehouses where the overhead of the sampling it outweighed by the potential performance increase for a long-running query.

Я никогда не пойму, почему программисты так часто помещают DISTINCT в свои предложения IN. 7 в (1, 1, 1, 1, 2, 2, 2, 7)? 5? Ответ не изменится, если мой список будет (1, 2, 7). Когда я запускаю это в Oracle, он просто игнорирует отдельные ... CBO понимает, что в этом нет никакой ценности.

Mark Brady 25.10.2008 01:38
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
12
1
74 286
18
Перейти к ответу Данный вопрос помечен как решенный

Ответы 18

Предложение «IN» известно в Oracle как довольно медленное. Фактически, внутренний оптимизатор запросов в Oracle не может нормально обрабатывать операторы с IN. попробуйте использовать EXISTS:

SELECT orderID FROM tasks WHERE orderID NOT EXISTS 
    (SELECT DISTINCT orderID FROM tasks WHERE
         engineer1 IS NOT NULL AND engineer2 IS NOT NULL)`print("code sample");`

Внимание: проверьте, дает ли запрос те же результаты данных.

Эдит говорит: упс, запрос плохо сформирован, но общая идея верна. Oracle должен выполнить полное сканирование таблицы для второго (внутреннего) запроса, построить результаты, а затем сравнить их с первым (внешним) запросом, поэтому он замедляется. Пытаться

SELECT orderID AS oid FROM tasks WHERE NOT EXISTS 
    (SELECT DISTINCT orderID AS oid2 FROM tasks WHERE
         engineer1 IS NOT NULL AND engineer2 IS NOT NULL and oid=oid2)

или что-то похожее ;-)

Я пришел к тому же запросу (см. Мой ответ ниже), за исключением: * в подзапросе нет причин для выбора DISTINCT orderID. * опустите «идентификатор заказа» между ГДЕ и НЕ СУЩЕСТВУЕТ (синтаксическая ошибка). * бросьте 'print ("образец кода"), очевидно ;-)

Mac 23.09.2008 16:08

Когда я пытаюсь выполнить второй запрос, возникает ошибка? ORA-00904: «OID2»: неверный идентификатор

RickL 23.09.2008 16:16

Используйте "AS oid2", который я вставил, его раньше не было в запросе.

Georgi 23.09.2008 16:19

@Mac: Ой, 'print ("образец кода")', хммм ... мое единственное оправдание - я работаю над новым ноутбуком и не видел его :) - спасибо за подсказки!

Georgi 23.09.2008 16:20

Хм, нет, все равно выдает ту же ошибку ... пробовал в SQL Plus и Toad Freeware.

RickL 23.09.2008 16:25

У меня на данный момент нет Oracle на моем ноутбуке, но попробуйте следующее: полностью пропустите "AS oid2" и завершите запрос "oid = orderID)" - если вы не можете исправить это, удалите его; -)

Georgi 23.09.2008 16:39

"Предложение" IN "известно в Oracle как довольно медленное. Фактически, внутренний оптимизатор запросов в Oracle не может достаточно хорошо обрабатывать операторы с" IN ". Попробуйте использовать" EXISTS ":" Nonense. Где этому оправдание?

David Aldridge 23.09.2008 17:36

AJ дал правильный синтаксис для НЕ СУЩЕСТВУЮЩЕЙ версии оператора SQL, но он все еще слишком медленный (более 3 минут).

RickL 23.09.2008 17:59

Георгий, где ваше доказательство того, что IN "медленный" и CBO не может с этим справиться? Если вы собираетесь отвечать на вопросы здесь, постарайтесь сделать это с позиции понимания, а не с позиций слухов и предположений.

Mark Brady 25.10.2008 01:49

На самом деле IN не так уж и плох, иногда он работает быстрее, чем EXISTS (вероятно, из-за кеширования, я этого никогда не отслеживал). Однако НЕ В (или НЕ СУЩЕСТВУЕТ) может быть заведомо медленным.

orbfish 13.12.2010 19:39

Разве ваш запрос не совпадает с

SELECT orderID FROM tasks
WHERE engineer1 IS NOT NULL OR engineer2 IS NOT NULL

?

Нет, это не так. Я совершил ту же ошибку :-) В каждом заказе есть несколько задач, и если к одной из этих задач назначен инженер, заказ считается «выделенным».

RB. 23.09.2008 16:01

Как насчет :

SELECT DISTINCT orderID FROM tasks t1 WHERE NOT EXISTS (SELECT * FROM tasks t2 WHERE t2.orderID=t1.orderID AND (engineer1 IS NOT NULL OR engineer2 IS NOT NULL));

Я не гуру оптимизации, но, возможно, вы также упустили из виду некоторые индексы в своей базе данных Oracle.

Я пробовал это, но он все еще работал более минуты, когда я отменил его.

RickL 23.09.2008 16:10

Другой вариант - использовать МИНУС (ИСКЛЮЧАЯ MSSQL)

SELECT orderID FROM tasks
MINUS
SELECT DISTINCT orderID FROM tasks WHERE engineer1 IS NOT NULL 
AND engineer2 IS NOT NULL

Я тоже думал об этом, но мне кажется, что этот запрос тоже не самый быстрый.

Georgi 23.09.2008 16:10

Да, будьте осторожны, оператор минус использует много памяти

Mike McAllister 23.09.2008 16:30

Обычно неявное различие было бы ненужными накладными расходами, но в этом случае, вероятно, было бы целесообразно. Однако два сканирования таблицы могут оказаться неэффективными.

David Aldridge 23.09.2008 22:07

Подзапросы "плохи" с Oracle. Обычно лучше использовать объединения.

Вот статья о том, как переписать ваши подзапросы с помощью join: http://www.dba-oracle.com/sql/t_rewrite_subqueries_performance.htm

Не надо ничего "плохого". Разница между преимуществами различных методов почти всегда сводится к распределению значений определенного набора данных и наличию или отсутствию индексов и ограничений.

David Aldridge 23.09.2008 19:09

Вот альтернативный подход, который, я думаю, дает то, что вы хотите:

SELECT orderID
 FROM tasks
 GROUP BY orderID
 HAVING COUNT(engineer1) = 0 OR COUNT(engineer2) = 0

Я не уверен, хотите ли вы «И» или «ИЛИ» в предложении HAVING. Похоже, что в соответствии с бизнес-логикой эти два поля должны быть либо заполнены, либо оба должны иметь значение NULL; если это гарантировано, вы можете свести условие к простой проверке Engine1.

Я думаю, что ваш исходный запрос даст несколько строк для каждого идентификатора заказа, тогда как мой даст только одну. Я предполагаю, что это нормально, поскольку вы получаете только идентификатор заказа.

Некоторые вопросы:

  • Сколько строк в задачах?
  • Какие показатели по нему определены?
  • Таблица недавно анализировалась?

Другой способ написать тот же запрос:

select orderid from tasks
minus
select orderid from tasks
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL

Однако я бы предпочел, чтобы запрос содержал таблицу «заказов»:

select orderid from ORDERS
minus
select orderid from tasks
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL

или же

select orderid from ORDERS
where orderid not in
( select orderid from tasks
  where engineer1 IS NOT NULL AND engineer2 IS NOT NULL
)

или же

select orderid from ORDERS
where not exists
( select null from tasks
  where tasks.orderid = orders.orderid
  and   engineer1 IS NOT NULL OR engineer2 IS NOT NULL
)

Ваше второе предложение - это НЕ одно и то же, поскольку для каждого идентификатора заказа может быть несколько задач, некоторые из которых назначены, а некоторые нет. Ему нужны только идентификаторы заказа, для которых НИ ОДНА из задач не назначена.

Dave Costa 23.09.2008 16:22

В вашем последнем предложении должен быть коррелированный подзапрос (например, добавить «AND tasks.orderID = orders.orderID»). Вы не можете просто изменить NOT IN на NOT EXISTS, не изменив подзапрос.

Dave Costa 23.09.2008 16:24

Я согласен с TZQTZIO, я не понимаю ваш запрос.

Если мы предполагаем, что запрос действительно имеет смысл, вы можете попробовать использовать EXISTS, как некоторые предлагают, и избегать IN. IN не всегда плох, и есть вероятные случаи, когда можно показать, что он действительно работает лучше, чем EXISTS.

Заголовок вопроса не очень полезен. Я мог бы настроить этот запрос в одной базе данных Oracle и заставить его работать медленно, а в другой - быстро. Есть много факторов, которые определяют, как база данных разрешает запрос, статистику объекта, статистику схемы SYS и параметры, а также производительность сервера. Sqlserver против Oracle - не проблема.

Для тех, кто интересуется настройкой и производительностью запросов и хочет узнать больше, некоторые из поисковых запросов Google: «oak table oracle» и «oracle jonathan lewis».

Я бы попробовал вместо этого использовать объединения

SELECT 
    t.orderID 
FROM 
    tasks  t
    LEFT JOIN tasks t1
        ON t.orderID =  t1.orderID
        AND t1.engineer1 IS NOT NULL 
        AND t1.engineer2 IS NOT NULL
WHERE
    t1.orderID IS NULL 

также ваш исходный запрос, вероятно, было бы легче понять, если бы он был указан как:

SELECT orderID FROM orders WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL)

(при условии, что у вас есть таблица заказов со всеми перечисленными заказами)

который затем можно переписать с помощью объединений как:

SELECT 
    o.orderID 
FROM 
    orders o
    LEFT JOIN tasks t
        ON o.orderID =  t.orderID
        AND t.engineer1 IS NOT NULL 
        AND t.engineer2 IS NOT NULL
WHERE
    t.orderID IS NULL 

Это не работает, потому что он должен возвращать только заказы, в которых поля разработчика задач все не равны нулю, но это будет возвращать заказы, в которых некоторые задачи не являются инженерными, а некоторые задачи инженерными не равны нулю. Вы имели в виду, чтобы предложение WHERE было другим?

RickL 23.09.2008 17:56

Он думает, что возвращает тот же результат, что и указанный вами запрос: ВЫБРАТЬ ИД заказа ИЗ задач, ГДЕ ИД заказа НЕ ВХОДИТ (ВЫБРАТЬ ОТЛИЧНЫЙ ИД заказа ИЗ задач, ГДЕ инженер1 НЕ НУЛЬ И инженер2 НЕ НУЛЬ), который, как мне кажется, возвращает: Все заказы, которые не имеют любые задачи с обоими инженерами

kristof 23.09.2008 18:22

Но, возможно, мне что-то не хватает, если одного назначенного инженера достаточно, чтобы не указывать порядок, тогда измените левое соединение, чтобы оно читалось как: LEFT JOIN tasks t1 ON t.orderID = t1.orderID AND (t1.engineer1 IS NOT NULL OR t1. Engineer2 НЕ ПУСТО) Но это будет отличаться от вашего исходного запроса.

kristof 23.09.2008 18:27

Если у вас нет индекса по столбцам Engineer1 и Engineer2, вы всегда собираетесь генерировать сканирование таблицы в SQL Server и эквивалентное, что может быть в Oracle.

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

SELECT DISTINCT orderID 
FROM tasks 
WHERE (engineer1 IS NULL OR engineer2 IS NULL)

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

tzot 23.09.2008 23:36

@ ΤΖΩΤΖΙΟΥ Спасибо за отрицательный голос, но вы прочитали вопрос? «Задачи имеют только индекс по идентификатору заказа и идентификатору задачи»

Jim Birchall 24.09.2008 13:13

Если вы решите создать таблицу ORDERS, я бы добавил к ней флаг ALLOCATED и создал индекс растрового изображения. Этот подход также заставляет вас изменять бизнес-логику, чтобы флаг постоянно обновлялся, но запросы будут выполняться молниеносно. Это зависит от того, насколько критичны запросы для приложения.

Что касается ответов, то в данном случае чем проще, тем лучше. Забудьте о подзапросах, объединениях, отдельных и групповых байтах, они вообще не нужны!

Я думаю, что у некоторых людей есть в значительной степени правильный SQL, но отсутствует соединение между внутренними и внешними запросами. Попробуй это:

SELECT t1.orderID 
FROM   tasks t1
WHERE  NOT EXISTS
       (SELECT 1 
        FROM   tasks t2 
        WHERE  t2.orderID   = t1.orderID
        AND    t2.engineer1 IS NOT NULL 
        AND    t2.engineer2 IS NOT NULL)

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

RickL 23.09.2008 17:49
Ответ принят как подходящий

Часто проблема этого типа исчезает, если вы анализируете задействованные таблицы (чтобы Oracle лучше понимала распределение данных).

ANALYZE TABLE tasks COMPUTE STATISTICS;

Невероятно, после того, как я запустил это, мне потребовалось чуть менее 1 секунды, чтобы выполнить мой исходный оператор SQL.

RickL 23.09.2008 18:12

Это устаревший синтаксис для сбора статистики. DBMS_STATS - более надежный способ. download.oracle.com/docs/cd/B19306_01/server.102/b14211/…

David Aldridge 23.09.2008 19:13

Я согласен, это устарело. Пожалуйста, старайтесь избегать его использования. Используйте DBMS_STATS и убедитесь, что вы также получаете свои индексы, вы можете установить cascade => true при сборе статистики для таблицы.

Ethan Post 23.09.2008 20:51

Обратите внимание, я попробовал Analyze сегодня, но ничего не сделал. НЕ ИСПОЛЬЗУЙТЕ ЕГО. Вместо этого используйте DBMS_STATS. Запрос увеличился с 5 минут до 0,3 секунды с DBMS_STATS

Chad 06.08.2013 12:34

Какая доля строк в таблице удовлетворяет условию «инженер1 НЕ НУЛЬ И инженер2 НЕ НУЛЬ»?

Это говорит вам (примерно), стоит ли пытаться использовать индекс для получения связанных orderid.

Другой способ написать запрос в Oracle, который бы очень хорошо обрабатывал неиндексированные случаи, был бы:

select distinct orderid
from
(
select orderid,
       max(case when engineer1 is null and engineer2 is null then 0 else 1)
          over (partition by orderid)
          as max_null_finder
from   tasks
)
where max_null_finder = 0

Оптимизатор Oracle хорошо справляется с обработкой операторов MINUS. Если вы переписываете свой запрос с помощью MINUS, он, скорее всего, будет выполняться довольно быстро:

SELECT orderID FROM tasks
MINUS
SELECT DISTINCT orderID FROM tasks WHERE
 engineer1 IS NOT NULL AND engineer2 IS NOT NULL

Новый взгляд.

Iff:

  • Функция COUNT () не считает значения NULL

и

  • Вам нужен orderID всех задач, в которых для никто задач для Engine1 или Engine2 установлено значение

потом это должно делать то, что вы хотите:

SELECT orderID
FROM tasks
GROUP BY orderID
HAVING COUNT(engineer1) = 0 AND COUNT(engineer2) = 0

Пожалуйста, проверьте это.

«Хотя мне все еще любопытно, почему я должен это делать, и если / когда мне нужно будет запустить это снова?»

Статистика дает оптимизатору Oracle информацию о затратах, необходимую ему для определения эффективности различных планов выполнения: например, количество строк в таблице, средняя ширина строк, максимальное и минимальное значения в столбце, количество различных значений в столбце. , коэффициент кластеризации индексов и т. д.

В небольшой базе данных вы можете просто настроить задание для сбора статистики каждую ночь и оставить его в покое. Фактически, это значение по умолчанию ниже 10g. Для более крупных реализаций вам обычно необходимо взвесить стабильность планов выполнения и способ изменения данных, что является сложной задачей.

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

Дэйв в разгаре динамической выборки

Mark Brady 25.10.2008 01:41

Мне нравится динамическая выборка. Не знаю, чаще об этом не говорят.

David Aldridge 28.10.2008 21:37

@Dave, только если ваши запросы большие и нечастые. В противном случае вы потратите больше времени на выборку того, что делаете.

WW. 27.09.2009 09:57

Ну, конечно, все это требует измерения. Влияние DS зависит от множества проблем, и вы можете получить информацию из запроса DS, который длится крошечную долю секунды, что может занять от 10 секунд до долей секунды. Например, динамическая выборка части запроса, в которой несколько предикатов выполняются для небольшой таблицы измерения (государственные праздники в понедельник в определенном году? Женщины старше 5'11 дюймов?), Не требует много времени, но может быть очень полезной. Короче говоря, это просто еще один инструмент, чтобы понять и оценить то, что это такое.

David Aldridge 27.09.2009 15:18

Я согласен с ΤΖΩΤΖΙΟΥ и wearejimbo, что ваш запрос должен быть ...

SELECT DISTINCT orderID FROM Tasks 
WHERE Engineer1 IS NULL OR Engineer2 IS NULL;

Я не знаю о SQL Server, но этот запрос не сможет использовать какие-либо индексы, потому что пустые строки не входят в индексы. Решением этой проблемы было бы переписать запрос таким образом, чтобы можно было создать индекс на основе функции, который включает только строки с нулевым значением. Это можно сделать с помощью NVL2, но, скорее всего, не будет переноситься на SQL Server.

Я думаю, что лучший ответ - это не тот, который соответствует вашим критериям, а это написание отдельного заявления для каждой платформы, которая лучше всего подходит для этой платформы.

SQL Server допускает только одну строку NULL в индексе, за исключением того, что вы создаете отфильтрованный индекс. В этом случае он не включает NULL.

usr 01.02.2010 05:19

Другие вопросы по теме