Левое внешнее объединение при проблеме производительности двух столбцов - PullRequest
11 голосов
/ 15 января 2009

Я использую запрос SQL, похожий на следующую форму:

SELECT col1, col2
FROM table1
LEFT OUTER JOIN table2
ON table1.person_uid = table2.person_uid
AND table1.period = table2.period

И это либо слишком медленно, либо что-то зашло в тупик, потому что для возврата требуется не менее 4 минут. Если бы я изменил это на это:

SELECT col1, col2
FROM table1
LEFT OUTER JOIN table2
ON table1.person_uid = table2.person_uid
WHERE table1.period = table2.period

тогда все работает нормально (хотя и не возвращает правильное количество столбцов). Есть ли способ ускорить это?

ОБНОВЛЕНИЕ : То же самое происходит, если я переключаю две последние строки последнего запроса:

SELECT col1, col2
FROM table1
LEFT OUTER JOIN table2
ON table1.period = table2.period
WHERE table1.person_uid = table2.person_uid

ОБНОВЛЕНИЕ 2: Это фактически представления, к которым я присоединяюсь. К сожалению, они находятся в базе данных, которую я не могу контролировать, поэтому я не могу (легко) внести какие-либо изменения в индексирование. Я склонен согласиться с тем, что это проблема индексации. Я подожду немного, прежде чем принять ответ, если есть какой-то волшебный способ настроить этот запрос, о котором я не знаю. В противном случае я приму один из текущих ответов и попытаюсь найти другой способ сделать то, что я хочу сделать. Спасибо всем за помощь.

Ответы [ 8 ]

16 голосов
/ 15 января 2009

Имейте в виду, что утверждения 2 и 3 отличаются от первого.

Как? Что ж, вы делаете внешнее левое соединение, и ваше предложение WHERE не учитывает это (как это делает предложение ON). Как минимум, попробуйте:

SELECT col1, col2
FROM table1, table2
WHERE table1.person_uid = table2.person_uid (+)
AND table1.period = table2.period (+)

и посмотрите, не возникает ли такая же проблема с производительностью.

Какие индексы у вас есть в этих таблицах? Это отношение определяется ограничением внешнего ключа?

Вам, вероятно, понадобится составной индекс для person_uid и period (в обеих таблицах).

5 голосов
/ 15 января 2009

Я думаю, вам нужно понять, почему два последних запроса не совпадают с первым. Если вы выполняете левое соединение, а затем добавляете предложение where, ссылающееся на поле в таблице справа от объединения (то, которое может не всегда иметь запись, совпадающую с первой таблицей), тогда вы фактически изменили соединение на внутреннее соединение. Есть одно исключение из этого, и это если вы ссылаетесь на что-то вроде

SELECT col1, col2
FROM table1
LEFT OUTER JOIN table2
ON table1.person_uid = table2.person_uid
WHERE table2.person_uid is null

В этом случае вы запрашиваете запись, у которой нет записи во второй таблице. Но, кроме этого особого случая, вы меняете левое соединение на внутреннее, если вы повторно ссылаетесь на поле в table2 в предложении where.

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

4 голосов
/ 15 января 2009

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

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

http://download.oracle.com/docs/cd/B28359_01/server.111/b28274/ex_plan.htm#PFGRF009

3 голосов
/ 15 января 2009

Есть ли у вас индексы покрытия на person_uid и period для обеих таблиц?

Если нет, добавьте их и попробуйте снова.

Посмотрите на план выполнения и посмотрите, что на самом деле делает запрос.

Также: каковы типы данных полей? Они одинаковы в обеих таблицах? Неявное приведение может действительно замедлить ход событий.

2 голосов
/ 15 января 2009

В левом соединении вы будете сканировать table1 для каждой уникальной комбинации (person_uid, period), а затем искать в table2 все соответствующие записи. Если таблица2 не имеет подходящего индекса, это может включать сканирование всей этой таблицы.

Мое лучшее предположение, без представления плана выполнения, состоит в том, что первый запрос (единственный, который кажется правильным) должен отсканировать table2 и table1.

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

SELECT
   col1, col2
FROM
   table2
FULL OUTER JOIN
   table1
      ON table1.person_uid = table2.person_uid
      AND table1.period = table2.period
WHERE
   table1.person_uid IS NOT NULL

Надежда здесь состоит в том, что вы сканируете table2 для каждой уникальной комбинации (person_uid, period), но используете индексы для table1. (В отличие от сканирования таблицы1 и использования индексов таблицы2, что я ожидал от вашего запроса.)

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

Демс.

2 голосов
/ 15 января 2009

Есть ли в этих таблицах индексы для столбцов, к которым вы присоединяетесь? Установите бесплатный продукт Oracle SQLDeveloper и используйте его для «объяснения» этого запроса и посмотрите, выполняет ли он последовательное сканирование обеих таблиц.

0 голосов
/ 08 января 2017

Синтаксис соединения ANSI обеспечивает очень четкое различие между условиями JOIN и предикатами FILTER; это очень важно при написании внешних объединений. Используя таблицы emp / dept, посмотрите на результаты следующих двух внешних объединений

Q1

SELECT dname, d.deptno, e.ename, e.mgr, d.loc
FROM dept d
LEFT OUTER JOIN emp e
on  d.deptno = e.deptno
and loc in ('NEW YORK','BOSTON' )
;

DNAME              DEPTNO ENAME             MGR LOC
-------------- ---------- ---------- ---------- -------------
ACCOUNTING             10 CLARK            7839 NEW YORK
ACCOUNTING             10 KING                  NEW YORK
ACCOUNTING             10 MILLER           7782 NEW YORK
RESEARCH               20                       DALLAS
SALES                  30                       CHICAGO
OPERATIONS             40                       BOSTON

====

Q2
SELECT dname, d.deptno, e.ename, e.mgr, d.loc
FROM dept d
LEFT OUTER JOIN emp e
on  d.deptno = e.deptno
where loc in ('NEW YORK','BOSTON' )
;

DNAME              DEPTNO ENAME             MGR LOC
-------------- ---------- ---------- ---------- -------------
ACCOUNTING             10 CLARK            7839 NEW YORK
ACCOUNTING             10 KING                  NEW YORK
ACCOUNTING             10 MILLER           7782 NEW YORK
OPERATIONS             40                       BOSTON

Первый пример, показанный Q1, является примером "соединения по константе". По сути, условие фильтра применяется перед выполнением внешнего соединения. Таким образом, вы исключаете строки, которые впоследствии добавляются обратно как часть внешнего соединения. Это не обязательно неправильно, но это тот запрос, который вы действительно просили? Часто требуются результаты, показанные в Q2, где фильтр применяется после (внешнего) соединения.

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

Для разработчиков, знакомых с синтаксисом внешнего соединения Oracle, запрос, вероятно, был бы записан как

SELECT dname, d.deptno, e.ename, e.mgr, d.loc
FROM dept d
        ,emp e
where  d.deptno = e.deptno(+)
and loc in ('NEW YORK','BOSTON' )

Этот запрос семантически эквивалентен Q2 выше.

Итак, в целом, очень важно, чтобы вы понимали разницу между предложением JOIN и предложением WHERE при написании внешних объединений ANSI.

0 голосов
/ 19 октября 2016

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

...