Объединение в три таблицы с объединениями, отличными от INNER JOIN - PullRequest
5 голосов
/ 11 августа 2009

Я изучаю SQL и пытаюсь выучить СОЕДИНЕНИЯ на этой неделе.

Я достиг уровня, на котором я могу сделать три объединения таблиц, подобно многим примерам, которые я видел. Я все еще пытаюсь выяснить крошечные детали того, как все работает. Все примеры трех объединений таблиц, которые я видел, используют только INNER JOINS. А как насчет левых и правых соединений? Вы когда-нибудь использовали их в трех таблицах? Что бы это значило?

SELECT ~some columns~ FROM ~table name~
LEFT JOIN ~table 2~ ON ~criteria~
INNER JOIN ~table 3~ ON ~criteria~

или

SELECT ~some columns~ FROM ~table name~
INNER JOIN ~table 2~ ON ~criteria~
LEFT JOIN ~table 3~ ON ~criteria~

или

SELECT ~some columns~ FROM ~table name~
LEFT JOIN ~table 2~ ON ~criteria~
LEFT JOIN ~table 3~ ON ~criteria~

или

???

Просто пытаюсь как можно больше исследовать пространство

Ответы [ 8 ]

22 голосов
/ 11 августа 2009

Да, я использую все три из этих JOIN, хотя я склонен использовать только LEFT (OUTER) JOIN вместо того, чтобы смешивать LEFT и RIGHT JOIN. Я также использую FULL OUTER JOIN с и CROSS JOIN с.

Таким образом, INNER JOIN ограничивает набор результатов только теми записями, которые удовлетворяют условию JOIN. Рассмотрим следующие таблицы

РЕДАКТИРОВАТЬ: Я переименовал имена таблиц и префикс их с @, так что переменные таблицы могут использоваться для любого, кто читает этот ответ и хочет экспериментировать.

Если вы также хотите поэкспериментировать с этим в браузере, Я настроил все это на SQL Fiddle тоже;

@Table1

id | name
---------
1  | One
2  | Two
3  | Three
4  | Four

@Table2

id | name
---------
1  | Partridge
2  | Turtle Doves
3  | French Hens
5  | Gold Rings

код SQL

DECLARE @Table1 TABLE (id INT PRIMARY KEY CLUSTERED, [name] VARCHAR(25))

INSERT INTO @Table1 VALUES(1, 'One');
INSERT INTO @Table1 VALUES(2, 'Two');
INSERT INTO @Table1 VALUES(3, 'Three');
INSERT INTO @Table1 VALUES(4, 'Four');

DECLARE @Table2 TABLE (id INT PRIMARY KEY CLUSTERED, [name] VARCHAR(25))

INSERT INTO @Table2 VALUES(1, 'Partridge');
INSERT INTO @Table2 VALUES(2, 'Turtle Doves');
INSERT INTO @Table2 VALUES(3, 'French Hens');
INSERT INTO @Table2 VALUES(5, 'Gold Rings');

Оператор SQL INNER JOIN, объединенный в поле id

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
INNER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

Результаты в

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens

A LEFT JOIN вернет набор результатов со всеми записями из таблицы в левой части объединения (если вы выписали инструкцию в виде одного строки, таблицы, которая появляется первой) и полей из таблицы в правой части объединения, которые соответствуют выражению объединения и включены в предложение SELECT. Отсутствующие детали будут заполнены значением NULL

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
LEFT JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

Результаты в

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens
4  | Four | NULL

A RIGHT JOIN - это та же логика, что и LEFT JOIN, но она возвращает все записи с правой стороны объединения и поля с левой стороны, которые соответствуют выражению соединения и включены в предложение SELECT .

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
RIGHT JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

Результаты в

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens
NULL| NULL| Gold Rings

Конечно, есть также FULL OUTER JOIN, который включает записи из обеих объединенных таблиц и заполняет любые пропущенные детали NULL.

SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
FULL OUTER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id

Результаты в

id | name | name
----------------
1  | One  | Partridge
2  | Two  | Turtle Doves
3  | Three| French Hens
4  | Four | NULL
NULL| NULL| Gold Rings

И CROSS JOIN (также известный как CARTESIAN PRODUCT), который является просто результатом перекрестного применения полей в операторе SELECT из одной таблицы с полями в операторе SELECT из другой таблицы. Обратите внимание, что в CROSS JOIN

нет выражения объединения
SELECT 
    t1.id,
    t1.name,
    t2.name
FROM
    @Table1 t1
CROSS JOIN
    @Table2 t2

Результаты в

id | name  | name
------------------
1  | One   | Partridge
2  | Two   | Partridge
3  | Three | Partridge
4  | Four  | Partridge
1  | One   | Turtle Doves
2  | Two   | Turtle Doves
3  | Three | Turtle Doves
4  | Four  | Turtle Doves
1  | One   | French Hens
2  | Two   | French Hens
3  | Three | French Hens
4  | Four  | French Hens
1  | One   | Gold Rings
2  | Two   | Gold Rings
3  | Three | Gold Rings
4  | Four  | Gold Rings

EDIT:

Представьте, что теперь есть Таблица3

@Table3

id | name
---------
2  | Prime 1
3  | Prime 2
5  | Prime 3

Код SQL

DECLARE @Table3 TABLE (id INT PRIMARY KEY CLUSTERED, [name] VARCHAR(25))

INSERT INTO @Table3 VALUES(2, 'Prime 1');
INSERT INTO @Table3 VALUES(3, 'Prime 2');
INSERT INTO @Table3 VALUES(5, 'Prime 3');

Теперь все три таблицы объединены с INNER JOINS

SELECT 
    t1.id,
    t1.name,
    t2.name,
    t3.name
FROM
    @Table1 t1
INNER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id
INNER JOIN
    @Table3 t3
    ON 
        t1.id = t3.id

Результаты в

id | name | name         | name
-------------------------------
2  | Two  | Turtle Doves | Prime 1
3  | Three| French Hens  | Prime 2

Это может помочь понять этот результат, полагая, что записи с идентификаторами 2 и 3 являются единственными общими для всех трех таблиц и также являются полями, к которым мы присоединяемся к каждой таблице.

Теперь все три с LEFT JOINS

SELECT 
    t1.id,
    t1.name,
    t2.name,
    t3.name
FROM
    @Table1 t1
LEFT JOIN
    @Table2 t2
    ON 
        t1.id = t2.id
LEFT JOIN
    @Table3 t3
    ON 
        t1.id = t3.id

Результаты в

id | name | name         | name
-------------------------------
1  | One  | Partridge    | NULL
2  | Two  | Turtle Doves | Prime 1
3  | Three| French Hens  | Prime 2
4  | Four | NULL         | NULL

Ответ Джоэля - хорошее объяснение для объяснения этого набора результатов (Таблица1 - таблица базы / происхождения).

Теперь с INNER JOIN и LEFT JOIN

SELECT 
    t1.id,
    t1.name,
    t2.name,
    t3.name
FROM
    @Table1 t1
INNER JOIN
    @Table2 t2
    ON 
        t1.id = t2.id
LEFT JOIN
    @Table3 t3
    ON 
        t1.id = t3.id

Результаты в

id | name | name         | name
-------------------------------
1  | One  | Partridge    | NULL
2  | Two  | Turtle Doves | Prime 1
3  | Three| French Hens  | Prime 2

Хотя нам неизвестен порядок, в котором оптимизатор запросов будет выполнять операции, мы рассмотрим этот запрос сверху вниз, чтобы понять набор результатов. INNER JOIN для идентификаторов между таблицами Table1 и Table2 будет ограничивать набор результатов только теми записями, которые удовлетворяют условию соединения, то есть тремя строками, которые мы видели в самом первом примере. Этот временный набор результатов будет затем LEFT JOIN преобразован в Таблицу3 для идентификаторов между Таблицей1 и Таблицами; В Таблице 3 есть записи с идентификаторами 2 и 3, но без идентификатора 1, поэтому в поле t3.name будут указаны детали для 2 и 3, но не 1.

6 голосов
/ 11 августа 2009

Объединения - это просто способы объединения таблиц. Присоединение к трем таблицам ничем не отличается от объединения 2 ... или 200. Вы можете смешивать и сопоставлять INNER, [LEFT / RIGHT / FULL] OUTER и даже CROSS присоединяться столько раз, сколько хотите. Единственное отличие состоит в том, какие результаты сохраняются: INNER объединяет только те строки, в которых обе стороны соответствуют выражению. Соединения OUTER выбирают таблицу «origin» в зависимости от спецификации LEFT / RIGHT / FULL, всегда сохраняют все строки из таблицы origin и предоставляют значения NULL для строк с другой стороны, которые не соответствуют выражению. Соединения CROSS возвращают все возможные комбинации обеих сторон.

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

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

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

Что касается использования различных типов соединения, я использовал все перечисленные здесь типы: INNER, LEFT OUTER, RIGHT OUTER, FULL OUTER и CROSS. Но большинство из них вам нужно использовать только изредка. INNER JOIN и LEFT JOIN покроют, вероятно, 95% или более того, что вы хотите сделать.

Теперь поговорим о производительности. Часто порядок, в котором вы перечисляете таблицы, диктуется вам: вы начинаете с TableA, и вам нужно сначала перечислить TableB, чтобы иметь доступ к столбцам, необходимым для объединения в TableC. Но иногда TableB и TableC зависят только от TableA, и вы можете перечислить их в любом порядке. Когда это происходит, оптимизатор запросов обычно выбирает лучший для вас заказ, но иногда он не знает как. Даже если это так, это помогает создать хорошую систему для отображения таблиц, чтобы вы всегда могли посмотреть на запрос и знать, что он «правильный».

Имея это в виду, вы должны думать о запросе в терминах working set, который в данный момент находится в памяти при его создании. Когда вы начинаете с TableA, база данных просматривает все столбцы с TableA в списке выбора или где-либо еще (например, предложения WHERE или ORDER BY или потенциальные индексы) в запросе, учитывает соответствующие условия из предложения WHERE. и загружает наименьшую часть этой таблицы в память, с которой она может обойтись. Он делает это для каждой таблицы по очереди, всегда загружая как можно меньше. И это ключ: вы хотите, чтобы этот рабочий набор был как можно меньшего размера как можно дольше.

Итак, возвращаясь к нашему соединению из трех таблиц, мы хотим перечислить таблицы в том порядке, в котором рабочий набор будет уменьшаться дольше. Это означает перечисление таблицы меньшего размера над таблицей большего размера. Еще одно хорошее практическое правило заключается в том, что соединения INNER имеют тенденцию к сокращению наборов результатов, тогда как соединения OUTER имеют тенденцию к росту наборов результатов, поэтому вы хотите сначала перечислить свои INNER объединения. Тем не менее, это не требование для выполнения запроса, и при этом оно не всегда верно; иногда может произойти и обратное.

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

2 голосов
/ 11 августа 2009

Выбор из трех таблиц ничем не отличается от выбора из двух (или целых сотен, хотя это будет довольно уродливый запрос для чтения).

Для КАЖДОГО объединения вы пишете, если INNER означает, что вам нужны только строки, которые успешно объединяют эти две таблицы вместе. Если другие таблицы были объединены ранее в запросе, эти результаты теперь совершенно не имеют значения, за исключением тех случаев, когда ваши собственные условия объединения вызывают их.

Например:

SELECT person.*
FROM person
LEFT JOIN vehicle ON (person.person_id = vehicle.owner_id)
LEFT JOIN house ON (person.person_id = house.owner_id)

Здесь я хочу получить список всех людей и (если доступно) всех транспортных средств и домов, которыми они владеют.

В качестве альтернативы:

SELECT person.*
FROM person
INNER JOIN vehicle ON (person.person_id = vehicle.owner_id)
LEFT JOIN house ON (person.person_id = house.owner_id)

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

Каждое соединение здесь совершенно отдельное.

Конечно, изменяя то, что вы указали в предложении ON, вы можете создавать объединения взаимосвязанных таблиц любым удобным вам способом.

0 голосов
/ 13 августа 2009

Ради полноты и стандартных евангеликов я приведу краткий синтаксис вложенного соединения ansi-92:

select t1.*
    ,t2.*
    ,t3.*
from table1 t1
    left outer join (
        table2 t2 left outer join table3 t3 on (t2.b = t3.b)
    ) on (t1.a = t2.a)

Ваш движок SQL может быть оптимизирован для них.

0 голосов
/ 11 августа 2009

В некоторых движках sql есть проблема, когда вы присоединяетесь, используя левое соединение. Если вы присоединяетесь к A-> B-> C, а строка в B не существует, тогда столбец соединения из B равен NULL. Некоторые из них, которые я использовал, требуют, чтобы соединение из B-> C было левым, если соединение из A-> B является левым.

Это нормально

select a.*, b.*, c.*
 from a
 left join b on b.id = a.id
 left join c on c.id = b.id

это не

 select a.*, b.*, c.*
 from a
 left join b on b.id = a.id
 inner join c on c.id = b.id
0 голосов
/ 11 августа 2009

Для примера рассмотрим таблицу «сотрудники» с полями ID, NAME и MANAGER_ID.

Вот простой запрос:

SELECT E.ID, E.NAME, M.NAME AS MANAGER
FROM EMPLOYEES E
JOIN EMPLOYEE M ON E.MANAGER_ID = M.ID

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

Та же логика была бы применима для написания запроса с 2 + n объединениями. Если вы, возможно, захотите иметь строки, у которых нет совпадений в предложении соединения, и хотите, чтобы эти строки вернулись (хотя и с нулями), то вы золотые.

0 голосов
/ 11 августа 2009

Это действительно зависит от того, что вы делаете. Я написал много 3+ табличных запросов, которые будут иметь внешнее соединение. Это зависит только от данных, которые вы запрашиваете, и от того, за чем вы пытаетесь следовать.

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

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