Много к одному присоединяется - PullRequest
1 голос
/ 05 июля 2011

Допустим, у меня есть следующие таблицы:

create table table_a
(
  id_a,
  name_a,
  primary_key (id_a)
);

create table table_b
(
  id_b,
  id_a is not null, -- (Edit)
  name_b,
  primary_key (id_b),
  foreign_key (id_a) references table_a (id_a)
);

Мы можем создать представление объединения этих таблиц несколькими способами:

create view join_1 as
(
  select 
    b.id_b, 
    b.id_a, 
    b.name_b, 
    a.name_a 
  from table_a a, table_b b
  where a.id_a = b.id_a
);

create view join_2 as
(
  select 
    b.id_b, 
    b.id_a, 
    b.name_b, 
    a.name_a 
  from table_b b left outer join table_a a
  on a.id_a = b.id_a
);

create view join_3 as
(
  select 
    b.id_b, 
    b.id_a, 
    b.name_b, 
    (select a.name_a from table_a a where b.id_b = a.id_a) as name_a 
  from table_b b;
);

Здесь мы знаем:

(1) Должна быть хотя бы одна запись из table_a с id_a (из-за внешнего ключа в таблице B) И
(2) Не более одной записи из table_a с id_a (из-за первичного ключа в таблице A)

тогда мы знаем, что в table_a есть ровно одна запись, которая связывается с объединением.

Теперь рассмотрим следующий SQL:

select id_b, name_b from join_X;

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

Итак, как лучше написать вышеприведенный вид объединения?

Должен ли я просто использовать стандарт join_1 и надеяться, что оптимизатор выяснит, что нет необходимости доступа к table_a на основе первичного и внешнего ключей?

Или лучшезаписать его как join_2 или даже join_3, что делает более явным то, что для соединения есть ровно одна строка из каждой строки из table_b?

Правка + дополнительный вопрос

Могу ли я когда-нибудь предпочесть дополнительный выбор (как в join_3), а не обычное соединение (как в join_1)?

Ответы [ 4 ]

0 голосов
/ 05 июля 2011

Это будет зависеть от платформы.

SQL Server анализирует логические последствия ограничений (внешние ключи, первичные ключи и т. Д.) И расширяет встроенные функции VIEW. Это означает, что «нерелевантная» часть кода VIEW устарела оптимизатором. SQL Server даст одинаковый план выполнения для всех трех случаев. (Обратите внимание: есть предел сложности, с которым может справиться оптимизатор, но он, безусловно, может справиться с этим.)

Однако не все платформы созданы равными.
- Некоторые могут не анализировать ограничения одним и тем же способом, предполагая, что вы закодировали соединение по причине
- Некоторые могут предварительно скомпилировать план исполнения / объяснения VIEW

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

EDIT

В ответ на ваш дополнительный вопрос, являются ли предпочтительными коррелированные подзапросы? Простого ответа не существует, поскольку он зависит от данных и логики, которую вы пытаетесь реализовать.

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

Если поле table_b.id_a ссылается на множество записей в таблице_а, вам может потребоваться имя только из самой последней. И вы могли бы реализовать это, используя (SELECT TOP 1 name_a FROM table_a WHERE id_a = table_b.id_a ORDER BY id_a DESC).

Короче, это зависит.
- по логике запроса
- о структуре данных
- На окончательном макете кода

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


Примечание:

В зависимости от коррелированного подзапроса, он не всегда выполняется «один раз для каждой записи». Например, SQL Server расширяет требуемую логику, которая должна выполняться в соответствии с остальной частью запроса. Важно отметить, что код SQL обрабатывается / компилируется / что угодно перед выполнением. SQL - это просто метод формулирования логики на основе множеств, который затем преобразуется в традиционные циклы и т. Д. С использованием наиболее оптимальных алгоритмов, доступных оптимизатору.

Другие СУБД могут работать по-другому из-за возможностей или ограничений оптимизатора. Некоторые СУБД работают хорошо при использовании IN (SELECT blah FROM blah) или при использовании EXISTS (SELECT * FROM blah), но довольно ужасно. То же самое относится и к коррелированным подзапросам. Sub работают с ними исключительно хорошо, некоторые не так хорошо работают, но большинство справляются очень хорошо по моему опыту.

0 голосов
/ 05 июля 2011

1 + 2 фактически идентичны в SQL Server.

Я никогда не использовал [3], но это выглядит довольно странно. Я сильно подозреваю, что оптимизатор сделает его эквивалентным 2.

Это хорошее упражнение для запуска всех 3 операторов и сравнения созданных планов выполнения.

Таким образом, при одинаковом исполнении мой голос получает наибольшее значение - [2] - это стандарт, в котором он поддерживается в противном случае [1].

В вашем случае, если вам не нужны какие-либо столбцы из A, зачем вообще включать Table_A в оператор?

Если это просто фильтр - т.е. включать только строки из таблицы B, где есть строка в таблице A, даже если я не хочу никаких столбцов из таблицы A, тогда все 3 синтаксиса в порядке, хотя вы можете обнаружить, что использование ЕСЛИ EXISTS более производительный в некоторых БД:

 SELECT * from Table_B b WHERE EXISTS (SELECT 1 FROM Table_A a WHERE b.id_b = a.id_a)

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

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

В основном - коррелированный подзапрос должен выполняться один раз для каждой строки во внешнем выражении - это верно для вышеизложенного - для каждой строки в таблице B вы должны запускать подзапрос к таблице A.

Если подзапрос может быть запущен только один раз

 SELECT * from Table_B b WHERE b.id_a IN (SELECT a.id_a FROM Table_A a WHERE a.id_a > 10)

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

Опять же, лучше всего выполнить оба оператора и сравнить планы выполнения.

Наконец и самое простое - учитывая ФК, вы можете просто написать:

 SELECT * From Table_B b WHERE b.id_a IS NOT NULL
0 голосов
/ 05 июля 2011

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

Или, если вам нужно представление для перевода некоторых столбцов в таблицах (например, объединение NULL в нули), создайте представление только для таблицы B. Это также будет применяться, если какой-то желающий администратор БД реализовал политику, согласно которой все выборы должны выполняться с помощью представлений, а не таблиц: -)

В обоих случаях вам не нужно беспокоиться о многостольном доступе.

0 голосов
/ 05 июля 2011

Интуитивно я думаю, что join_1 будет работать немного медленнее, потому что ваше предположение, что оптимизатор может преобразовать объединение, неверно, поскольку вы не объявили столбец table_b.id_a равным NOT NULL.Фактически это означает, что (1) неверно.table_b.id_a может быть NULL.Даже если вы знаете, что этого не может быть, оптимизатор не знает этого.

Что касается join_2 и join_3, то в зависимости от вашей базы данных оптимизация может быть возможной.Лучший способ выяснить это - запустить (синтаксис Oracle)

EXPLAIN select id_b, name_b from join_X;

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

...