left join что делает
Оператор LEFT JOIN MySQL
В этой статье мы расскажем об операторе LEFT JOIN MySQL и том, как применять его для запроса данных из двух или более таблиц базы данных.
Введение в LEFT JOIN MySQL
Другими словами, условие LEFT JOIN MySQL позволяет выбирать строки обеих таблиц, которые совпадают, плюс все строки из левой таблицы ( t1 ) даже без совпадения со строками правой таблицы ( t2 ).
Примеры использования LEFT JOIN MySQL
Использование оператора LEFT JOIN MySQL для объединения двух таблиц
Возьмем две таблицы клиентов и заказов из демонстрационной базы данных :
В базе данных, описанной на диаграмме:
Чтобы найти заказы, принадлежащие каждому клиенту, можно использовать LEFT JOIN MySQL пример:
Поскольку мы использовали одно и то же имя столбца ( orderNumber ) для объединения двух таблиц, можно сделать запрос короче, используя приведенный ниже синтаксис:
Использование оператора LEFT JOIN MySQL для поиска несовпадающих строк
Например, чтобы найти всех клиентов, которые не оформили ни одного заказа, используется следующий запрос:
Операции LEFT JOIN, RIGHT JOIN (Microsoft Access SQL)
Область применения: Access 2013, Office 2013
Объединяют записи исходных таблиц при использовании в любом предложении FROM.
Синтаксис
FROM таблица1 [ LEFT | RIGHT ] JOIN таблица2 ON таблица1.поле1 оператор_сравнения таблица2.поле2
Операции LEFT JOIN и RIGHT JOIN состоят из следующих элементов:
Имена таблиц, содержащих объединяемые записи.
Имена объединяемых полей. Поля должны относиться к одному типу данных и содержать данные одного вида. Однако имена этих полей могут быть разными.
Примечания
Операция LEFT JOIN создает левое внешнее соединение. С помощью левого внешнего соединения выбираются все записи первой (левой) таблицы, даже если они не соответствуют записям во второй (правой) таблице.
Операция RIGHT JOIN создает правое внешнее соединение. С помощью правого внешнего соединения выбираются все записи второй (правой) таблицы, даже если они не соответствуют записям в первой (левой) таблице.
Например, в случае с таблицами «Отделы» (левая) и «Сотрудники» (правая) можно воспользоваться операцией LEFT JOIN для выбора всех отделов (включая те, в которых нет сотрудников). Чтобы выбрать всех сотрудников (в том числе и не закрепленных за каким-либо отделом), используйте RIGHT JOIN.
В следующем примере показано, как можно объединить таблицы Categories и Products по полю CategoryID. Результат запроса представляет собой список категорий, включая те, которые не содержат товаров.
В этом примере CategoryID является объединенным полем, но оно не включается в результаты запроса, поскольку не указано в инструкции SELECT. Чтобы включить объединенное поле в результаты запроса, укажите его имя в инструкции SELECT. В данном случае это Categories.CategoryID.
Пример
Предполагается существование гипотетических полей Department Name (Название отдела) и Department ID (Код отдела) в таблице Employees (Сотрудники). Обратите внимание, что эти поля на самом деле не существуют в таблице Employees (Сотрудники) базы данных Northwind.
Выбираются все отделы, в том числе без сотрудников.
Выполняется вызов процедуры EnumFields, которую можно найти в примере для инструкции SELECT.
Операции LEFT JOIN, RIGHT JOIN
Объединяют записи исходных таблиц при использовании в любом предложении FROM.
Синтаксис
FROM таблица1 [ LEFT | RIGHT ] JOIN таблица2
ON таблица1.поле1 оператор_сравнения таблица2.поле2
Операции LEFT JOIN и RIGHT JOIN состоят из следующих элементов:
Имена таблиц, содержащих объединяемые записи.
Имена полей, которые соединены. Поля должны быть одного типа тип данных и содержать данные одинакового типа, но имена этих полей могут быть одинаковыми.
Замечания
Операция LEFT JOIN создает левое внешнее соединение. С помощью левого внешнего соединения выбираются все записи первой (левой) таблицы, даже если они не соответствуют записям во второй (правой) таблице.
Операция RIGHT JOIN создает правое внешнее соединение. С помощью правого внешнего соединения выбираются все записи второй (правой) таблицы, даже если они не соответствуют записям в первой (левой) таблице.
Например, в случае с таблицами «Отделы» (левая) и «Сотрудники» (правая) можно воспользоваться операцией LEFT JOIN для выбора всех отделов (включая те, в которых нет сотрудников). Чтобы выбрать всех сотрудников (в том числе и не закрепленных за каким-либо отделом), используйте RIGHT JOIN.
В следующем примере показано, как можно объединить таблицы Categories и Products по полю CategoryID. Результат запроса представляет собой список категорий, включая те, которые не содержат товаров.
SELECT CategoryName, ProductName FROM Categories LEFT JOIN Products ON Categories.CategoryID = Products.CategoryID;
Чтобы создать запрос, результатом которого являются только те записи, для которых совпадают данные в связующих полях, воспользуйтесь операцией INNER JOIN.
Операции LEFT JOIN и RIGHT JOIN могут быть вложены в операцию INNER JOIN, но операция INNER JOIN не может быть вложена в операцию LEFT JOIN или RIGHT JOIN. Подробные сведения о вложении объединений можно найти в статье, посвященной операции INNER JOIN.
Вы можете связать несколько предложений ON. Сведения о связывании предложений см. в статье, посвященной операции INNER JOIN.
При попытке связи полей, содержащих данные типа Memo или объекты OLE, возникнет ошибка.
SQL JOIN: руководство по объединению таблиц. Часть 2
Перевод второй части статьи «SQL Joins Tutorial: Cross Join, Full Outer Join, Inner Join, Left Join, and Right Join».
Продолжаем разбираться с видами JOIN в SQL.
В первой части статьи мы рассмотрели:
LEFT JOIN / RIGHT JOIN (левое и правое объединение)
Следующие два вида объединений используют модификатор (LEFT или RIGHT), который определяет, какие данные таблицы включаются в результирующий набор.
Примечание: LEFT JOIN и RIGHT JOIN также называются LEFT OUTER JOIN и RIGHT OUTER JOIN (соответственно левое и правое внешнее объединение).
Эти виды объединений используются в запросах, когда мы хотим вернуть все данные из одной таблицы и добавить к ним связанные данные из другой таблицы (если таковые есть).
Если связанных данных во второй таблице нет, мы получаем в выводе все данные только из «первичной» таблицы.
То есть, это запрос информации о конкретной вещи с дополнительными сведениями, если они есть.
Понять это будет проще на примере. Давайте найдем все фильмы с их режиссерами, но не будем зацикливаться на последних: если режиссер есть, это дополнительная информация, а нет — так нет.
Этот запрос практически идентичен тому, что мы писали ранее. Единственная разница в том, что мы указали тип объединения LEFT JOIN.
В нашем примере таблица movies является «левой».
Если написать запрос в одну строчку, «левизну» будет проще заметить:
LEFT JOIN возвращает все записи из «левой» таблицы. Из «правой» возвращаются только те, что соответствуют условию объединения. Строки «правой» таблицы, не соответствующие условию объединения, возвращаются как NULL.
На этом примере мы видим, что запрос LEFT JOIN полезен для случаев, когда нам нужно получить «все из этого и, если есть, что-нибудь из того».
RIGHT JOIN
Правое объединение работает точно так же, как и левое, за исключением того, что таблицы как бы меняются местами.
При правом объединении возвращаются все строки «правой» таблицы. А строки «левой» добавляются, только если отвечают заданному условию.
Давайте используем предыдущий запрос, в котором заменим LEFT JOIN на RIGHT JOIN:
В результирующем наборе мы видим строки со всеми режиссерами, к которым добавлены строки с их фильмами (если есть).
Все, что мы сделали, это изменили то, какая таблица считается «первичной». Именно из нее мы получаем все данные независимо от того, есть ли для них дополнительные данные в другой таблице.
LEFT JOIN / RIGHT JOIN в реальной жизни
В приложениях, поступающих в производство, я всегда использую только LEFT JOIN и никогда RIGHT JOIN.
Я так делаю, потому что с LEFT JOIN запрос легче читать и проще понять.
Составляя запросы, я предпочитаю начинать с «базового» набора результатов, в нашем примере это все фильмы, а затем добавлять что-то в этот набор (или убирать из него).
Для такого подхода лучше при менять LEFT JOIN. Я хочу получать все строки из «левой» таблицы, а из правой — только соответствующие условиям.
На практике я, кажется, ни разу не видел использование RIGHT JOIN в приложении, идущем в производство. В правом объединении нет ничего плохого, просто лично я считаю, что такой запрос хуже читается.
Переписывание RIGHT JOIN
Если мы хотим «перевернуть» наш сценарий и вернуть строки со всеми режиссерами, добавив к ним фильмы (если есть), мы с легкостью можем превратить RIGHT JOIN в LEFT JOIN.
Все что нужно сделать — изменить порядок таблиц в запросе и поменять RIGHT на LEFT:
Примечание: я предпочитаю в условии сначала указывать таблицу, данные из которой являются добавочными («правую» таблицу, в примере — movies) — ( ON movies.director_id = … ), но это мои личные предпочтения.
Фильтрация с использованием LEFT JOIN
LEFT JOIN (или RIGHT JOIN) имеют два сценария использования.
Первый мы уже рассмотрели: вывод всех строк из одной таблицы и вывод некоторых (соответствующих условию) из второй.
Второй сценарий использования — вывод тех строк из первой таблицы, для которых во второй таблице нет соответствий.
Если говорить о нашем примере, то этот сценарий звучит так: «найти всех режиссеров, которые не связаны с имеющимися фильмами».
Чтобы это сделать, мы начинаем с LEFT JOIN, а наша таблица directors будет первичной («левой»):
Для режиссеров, не имеющих фильмов, столбцы из таблицы movies будут заполнены значениями NULL:
В нашем примере режиссеры с ID 3, 4 и 5 не имеют фильмов.
Чтобы отфильтровать результаты и получить только эти строки, мы можем добавить оборот WHERE. Благодаря ему будут возвращены только те строки, где данные о фильмах — NULL:
А это как раз три наших режиссера без фильмов!
Для фильтрации часто используется именно столбец id (WHERE movies.id IS NULL), но на самом деле в нашем случае все столбцы из таблицы movies имеют значение NULL, так что в обороте WHERE мог быть указан любой из них.
(Поскольку мы знаем, что все столбцы из таблицы movies будут NULL, то чтобы вернуть всю информацию о режиссерах, в запросе мы могли бы написать SELECT directors.*, а не SELECT *).
Использование LEFT JOIN для поиска соответствий
В нашем предыдущем примере мы искали режиссеров, у которых нет фильмов.
Используя ту же структуру, мы могли бы найти режиссеров, у которых фильмы есть. Для этого нужно изменить условие WHERE и искать строки, где данные о фильмах не NULL:
Это может казаться удобным, но по факту мы всего лишь заново реализовали INNER JOIN!
Множественные объединения
Мы рассмотрели, как можно объединить строки двух таблиц, а как насчет множественных объединений в одной строке?
Собственно, тут все достаточно просто, но чтобы это проиллюстрировать, нам понадобится третья таблица: tickets (билеты).
Эта таблица будет представлять проданные билеты по каждому фильму:
Таблица tickets содержит только id и ссылку на фильм: movie_id.
Мы также добавили сведения о том, что на фильм с ID 1 было продано два билета, а на фильм с ID 3 — один билет.
Теперь давайте объединим directors с movies, а movies — с tickets!
Поскольку это внутренние объединения, порядок написания JOIN-ов не имеет значения. Мы могли бы начать с tickets, добавить movies, а потом добавить directors.
Опять же, все зависит от того, что именно вы хотите запросить. На основе этого вы и формируете свой запрос так, чтобы он был максимально понятным.
В нашем результирующем наборе мы увидим, что мы еще больше сузили поиск и получили меньше строк:
Это логично, потому что мы добавили еще одно внутреннее объединение. В результате в запросе появился дополнительное условие «AND».
В нашем запросе говорится: «Верни всех режиссеров, связанных с фильмами, по которым были продажи билетов».
Если бы мы хотели найти режиссеров, у которых есть фильмы, но по этим фильмам не обязательно уже были проданы билеты, мы могли бы заменить наш последний INNER JOIN на LEFT JOIN:
Мы видим, что теперь в результаты попал и Movie 2:
По этому фильму ни одного билета не продано, поэтому в предыдущей выборке, когда применялся INNER JOIN, его не было.
Теперь вопрос: как нам найти режиссеров, связанных с фильмами, по которыми продаж не было?
Порядок выполнения объединений
Наконец, нам совершенно безразлично, в каком порядке выполняются операции объединения.
Одно из ключевых отличий SQL от современных языков программирования состоит в том, что SQL это декларативный язык.
Это означает, что мы определяем, какой итог мы хотим получить, но не определяем детали выполнения. Этими деталями занимается планировщик запросов базы данных. Мы указываем нужные нам объединения и их условия, а планировщик запросов занимается всем остальным.
Но на самом деле база данных не объединяет все три таблицы одновременно. Она скорее объединяет две первые таблицы в промежуточный результат, а затем объединяет этот промежуточный результат и третьей таблицей.
(Примечание: это несколько упрощенное объяснение).
Поэтому, работая с несколькими объединениями в запросах, мы можем представлять их в виде серии объединений двух таблиц (просто одна из этих таблиц может оказаться очень большой).
Операции объединения с дополнительными условиями
Последняя тема, которую мы рассмотрим, это объединение с дополнительными условиями.
Так же, как раньше мы добавляли оборот WHERE, мы можем добавлять сколько угодно условий.
Например, если мы хотим найти фильмы с режиссерами, которых зовут не John Smith, мы можем добавить дополнительное условие при помощи AND:
Мы можем использовать любые операторы, которые могли бы поставить в оборот WHERE.
Собственно, мы могли бы получить такой же результат, если бы поместили условия внутрь оборота WHERE:
Тут есть некоторая разница в происходящем под капотом, но для целей нашей статьи результат можем считать одинаковым.
Правда жизни о написании запросов с объединениями
В реальной жизни я сталкивался с операциями объединения в трех разных видах:
INNER JOIN
Первый случай использования — записи с существующими отношениями между таблицами. Здесь применяется INNER JOIN.
Это ситуации вроде поиска «фильмов, для которых указаны режиссеры» или «пользователей, у которых есть посты».
LEFT JOIN
Второй случай использования — получение записей из одной таблицы и, если отношения с записями в другой таблице существуют, — записей из второй таблицы. Здесь применяется LEFT JOIN.
Это ситуации вроде «вывести фильмы и имена режиссеров, если для этих фильмов режиссеры указаны» или «вывести пользователей, а если у них есть посты, то и посты тоже вывести».
LEFT JOIN для исключения
Третий самый распространенный случай использования — это еще один случай для использования LEFT JOIN: поиск в первой таблице записей, для которых нет отношений во второй таблице.
Это ситуации вроде «фильмы без режиссеров» или «пользователи без постов».
Два очень полезных вида JOIN-ов
Мне кажется, я вообще ни разу не использовал FULL OUTER JOIN или RIGHT JOIN в приложениях, идущих в продакшен. Либо подходящие случаи встречаются редко, либо такой запрос можно написать более понятно (это касается, в частности, RIGHT JOIN).
Иногда мне случается использовать CROSS JOIN для таких вещей как распределение записей по диапазону дат (мы рассматривали пример в первой части статьи), но этот сценарий возникает не слишком часто.
В общем, хорошие новости! На самом деле есть только два вида JOIN-ов, в которых нужно разобраться и которые покрывают 99,9% use cases: INNER JOIN и LEFT JOIN!
Учебник по языку SQL (DDL, DML) на примере диалекта MS SQL Server. Часть четвертая
Предыдущие части
В данной части мы рассмотрим
Добавим немного новых данных
Для демонстрационных целей добавим несколько отделов и должностей:
JOIN-соединения – операции горизонтального соединения данных
Здесь нам очень пригодится знание структуры БД, т.е. какие в ней есть таблицы, какие данные хранятся в этих таблицах и по каким полям таблицы связаны между собой. Первым делом всегда досконально изучайте структуру БД, т.к. нормальный запрос можно написать только тогда, когда ты знаешь, что откуда берется. У нас структура состоит из 3-х таблиц Employees, Departments и Positions. Приведу здесь диаграмму из первой части:
Если суть РДБ – разделяй и властвуй, то суть операций объединений снова склеить разбитые по таблицам данные, т.е. привести их обратно в человеческий вид.
Если говорить просто, то операции горизонтального соединения таблицы с другими таблицами используются для того, чтобы получить из них недостающие данные. Вспомните пример с еженедельным отчетом для директора, когда при запросе из таблицы Employees, нам для получения окончательного результата недоставало поля «Название отдела», которое находится в таблице Departments.
Понимание каждого вида соединения очень важно, т.к. от применения того или иного вида, результат запроса может отличаться. Сравните результаты одного и того же запроса с применением разного типа соединения, попробуйте пока просто увидеть разницу и идите дальше (мы сюда еще вернемся):
ID | Name | DepartmentID | ID | Name |
---|---|---|---|---|
1000 | Иванов И.И. | 1 | 1 | Администрация |
1001 | Петров П.П. | 3 | 3 | ИТ |
1002 | Сидоров С.С. | 2 | 2 | Бухгалтерия |
1003 | Андреев А.А. | 3 | 3 | ИТ |
1004 | Николаев Н.Н. | 3 | 3 | ИТ |
ID | Name | DepartmentID | ID | Name |
---|---|---|---|---|
1000 | Иванов И.И. | 1 | 1 | Администрация |
1001 | Петров П.П. | 3 | 3 | ИТ |
1002 | Сидоров С.С. | 2 | 2 | Бухгалтерия |
1003 | Андреев А.А. | 3 | 3 | ИТ |
1004 | Николаев Н.Н. | 3 | 3 | ИТ |
1005 | Александров А.А. | NULL | NULL | NULL |
ID | Name | DepartmentID | ID | Name |
---|---|---|---|---|
1000 | Иванов И.И. | 1 | 1 | Администрация |
1002 | Сидоров С.С. | 2 | 2 | Бухгалтерия |
1001 | Петров П.П. | 3 | 3 | ИТ |
1003 | Андреев А.А. | 3 | 3 | ИТ |
1004 | Николаев Н.Н. | 3 | 3 | ИТ |
NULL | NULL | NULL | 4 | Маркетинг и реклама |
NULL | NULL | NULL | 5 | Логистика |
ID | Name | DepartmentID | ID | Name |
---|---|---|---|---|
1000 | Иванов И.И. | 1 | 1 | Администрация |
1001 | Петров П.П. | 3 | 3 | ИТ |
1002 | Сидоров С.С. | 2 | 2 | Бухгалтерия |
1003 | Андреев А.А. | 3 | 3 | ИТ |
1004 | Николаев Н.Н. | 3 | 3 | ИТ |
1005 | Александров А.А. | NULL | NULL | NULL |
NULL | NULL | NULL | 4 | Маркетинг и реклама |
NULL | NULL | NULL | 5 | Логистика |
ID | Name | DepartmentID | ID | Name |
---|---|---|---|---|
1000 | Иванов И.И. | 1 | 1 | Администрация |
1001 | Петров П.П. | 3 | 1 | Администрация |
1002 | Сидоров С.С. | 2 | 1 | Администрация |
1003 | Андреев А.А. | 3 | 1 | Администрация |
1004 | Николаев Н.Н. | 3 | 1 | Администрация |
1005 | Александров А.А. | NULL | 1 | Администрация |
1000 | Иванов И.И. | 1 | 2 | Бухгалтерия |
1001 | Петров П.П. | 3 | 2 | Бухгалтерия |
1002 | Сидоров С.С. | 2 | 2 | Бухгалтерия |
1003 | Андреев А.А. | 3 | 2 | Бухгалтерия |
1004 | Николаев Н.Н. | 3 | 2 | Бухгалтерия |
1005 | Александров А.А. | NULL | 2 | Бухгалтерия |
1000 | Иванов И.И. | 1 | 3 | ИТ |
1001 | Петров П.П. | 3 | 3 | ИТ |
1002 | Сидоров С.С. | 2 | 3 | ИТ |
1003 | Андреев А.А. | 3 | 3 | ИТ |
1004 | Николаев Н.Н. | 3 | 3 | ИТ |
1005 | Александров А.А. | NULL | 3 | ИТ |
1000 | Иванов И.И. | 1 | 4 | Маркетинг и реклама |
1001 | Петров П.П. | 3 | 4 | Маркетинг и реклама |
1002 | Сидоров С.С. | 2 | 4 | Маркетинг и реклама |
1003 | Андреев А.А. | 3 | 4 | Маркетинг и реклама |
1004 | Николаев Н.Н. | 3 | 4 | Маркетинг и реклама |
1005 | Александров А.А. | NULL | 4 | Маркетинг и реклама |
1000 | Иванов И.И. | 1 | 5 | Логистика |
1001 | Петров П.П. | 3 | 5 | Логистика |
1002 | Сидоров С.С. | 2 | 5 | Логистика |
1003 | Андреев А.А. | 3 | 5 | Логистика |
1004 | Николаев Н.Н. | 3 | 5 | Логистика |
1005 | Александров А.А. | NULL | 5 | Логистика |
Настало время вспомнить про псевдонимы таблиц
Пришло время вспомнить про псевдонимы таблиц, о которых я рассказывал в начале второй части.
В многотабличных запросах, псевдоним помогает нам явно указать из какой именно таблицы берется поле. Посмотрим на пример:
В нем поля с именами ID и Name есть в обоих таблицах и в Employees, и в Departments. И чтобы их различать, мы предваряем имя поля псевдонимом и точкой, т.е. «emp.ID», «emp.Name», «dep.ID», «dep.Name».
Вспоминаем почему удобнее пользоваться именно короткими псевдонимами – потому что, без псевдонимов наш запрос бы выглядел следующим образом:
По мне, стало очень длинно и хуже читаемо, т.к. имена полей визуально потерялись среди повторяющихся имен таблиц.
В многотабличных запросах, хоть и можно указать имя без псевдонима, в случае если имя не дублируется во второй таблице, но я бы рекомендовал всегда использовать псевдонимы в случае соединения, т.к. никто не гарантирует, что поле с таким же именем со временем не добавят во вторую таблицу, а тогда ваш запрос просто сломается, ругаясь на то что он не может понять к какой таблице относится данное поле.
Только используя псевдонимы, мы сможем осуществить соединения таблицы самой с собой. Предположим встала задача, получить для каждого сотрудника, данные сотрудника, который был принят прямо до него (табельный номер отличается на единицу меньше). Допустим, что у нас табельные номера выдаются последовательно и без дырок, тогда мы можем это сделать примерно следующим образом:
Т.е. здесь одной таблице Employees, мы дали псевдоним «e1», а второй «e2».
Разбираем каждый вид горизонтального соединения
Для этой цели рассмотрим 2 небольшие абстрактные таблицы, которые так и назовем LeftTable и RightTable:
Посмотрим, что в этих таблицах:
LCode | LDescr |
---|---|
1 | L-1 |
2 | L-2 |
3 | L-3 |
5 | L-5 |
RCode | RDescr |
---|---|
2 | B-2 |
3 | B-3 |
4 | B-4 |
LCode | LDescr | RCode | RDescr |
---|---|---|---|
2 | L-2 | 2 | B-2 |
3 | L-3 | 3 | B-3 |
Здесь были возвращены объединения строк для которых выполнилось условие (l.LCode=r.RCode)
LEFT JOIN
LCode | LDescr | RCode | RDescr |
---|---|---|---|
1 | L-1 | NULL | NULL |
2 | L-2 | 2 | B-2 |
3 | L-3 | 3 | B-3 |
5 | L-5 | NULL | NULL |
Здесь были возвращены все строки LeftTable, которые были дополнены данными строк из RightTable, для которых выполнилось условие (l.LCode=r.RCode)
RIGHT JOIN
LCode | LDescr | RCode | RDescr |
---|---|---|---|
2 | L-2 | 2 | B-2 |
3 | L-3 | 3 | B-3 |
NULL | NULL | 4 | B-4 |
Здесь были возвращены все строки RightTable, которые были дополнены данными строк из LeftTable, для которых выполнилось условие (l.LCode=r.RCode)
По сути если мы переставим LeftTable и RightTable местами, то аналогичный результат мы получим при помощи левого соединения:
LCode | LDescr | RCode | RDescr |
---|---|---|---|
2 | L-2 | 2 | B-2 |
3 | L-3 | 3 | B-3 |
NULL | NULL | 4 | B-4 |
Я за собой заметил, что я чаще применяю именно LEFT JOIN, т.е. я сначала думаю, данные какой таблицы мне важны, а потом думаю, какая таблица/таблицы будет играть роль дополняющей таблицы.
FULL JOIN – это по сути одновременный LEFT JOIN + RIGHT JOIN
LCode | LDescr | RCode | RDescr |
---|---|---|---|
1 | L-1 | NULL | NULL |
2 | L-2 | 2 | B-2 |
3 | L-3 | 3 | B-3 |
5 | L-5 | NULL | NULL |
NULL | NULL | 4 | B-4 |
Вернулись все строки из LeftTable и RightTable. Строки для которых выполнилось условие (l.LCode=r.RCode) были объединены в одну строку. Отсутствующие в строке данные с левой или правой стороны заполняются NULL-значениями.
CROSS JOIN
LCode | LDescr | RCode | RDescr |
---|---|---|---|
1 | L-1 | 2 | B-2 |
2 | L-2 | 2 | B-2 |
3 | L-3 | 2 | B-2 |
5 | L-5 | 2 | B-2 |
1 | L-1 | 3 | B-3 |
2 | L-2 | 3 | B-3 |
3 | L-3 | 3 | B-3 |
5 | L-5 | 3 | B-3 |
1 | L-1 | 4 | B-4 |
2 | L-2 | 4 | B-4 |
3 | L-3 | 4 | B-4 |
5 | L-5 | 4 | B-4 |
Каждая строка LeftTable соединяется с данными всех строк RightTable.
Возвращаемся к таблицам Employees и Departments
Надеюсь вы поняли принцип работы горизонтальных соединений. Если это так, то возвратитесь на начало раздела «JOIN-соединения – операции горизонтального соединения данных» и попробуйте самостоятельно понять примеры с объединением таблиц Employees и Departments, а потом снова возвращайтесь сюда, обсудим это вместе.
Давайте попробуем вместе подвести резюме для каждого запроса:
Запрос | Резюме |
---|---|
По сути данный запрос вернет только сотрудников, у которых указано значение DepartmentID. Т.е. мы можем использовать данное соединение, в случае, когда нам нужны данные по сотрудникам числящихся за каким-нибудь отделом (без учета внештаткиков). | |
Вернет всех сотрудников. Для тех сотрудников у которых не указан DepartmentID, поля «dep.ID» и «dep.Name» будут содержать NULL. Вспоминайте, что NULL значения в случае необходимости можно обработать, например, при помощи ISNULL(dep.Name,’вне штата’). Этот вид соединения можно использовать, когда нам важно получить данные по всем сотрудникам, например, чтобы получить список для начисления ЗП. | |
Здесь мы получили дырки слева, т.е. отдел есть, но сотрудников в этом отделе нет. Такое соединение можно использовать, например, когда нужно выяснить, какие отделы и кем у нас заняты, а какие еще не сформированы. Эту информацию можно использовать для поиска и приема новых работников из которых будет формироваться отдел. | |
Этот запрос важен, когда нам нужно получить все данные по сотрудникам и все данные по имеющимся отделам. Соответственно получаем дырки (NULL-значения) либо по сотрудникам, либо по отделам (внештатники). Данный запрос, например, может использоваться в целях проверки, все ли сотрудники сидят в правильных отделах, т.к. может у некоторых сотрудников, которые числятся как внештатники, просто забыли указать отдел. | |
В таком виде даже сложно придумать где это можно применить, поэтому пример с CROSS JOIN я покажу ниже. |
Обратите внимание, что в случае повторения значений DepartmentID в таблице Employees, произошло соединение каждой такой строки со строкой из таблицы Departments с таким же ID, то есть данные Departments объединились со всеми записями для которых выполнилось условие (emp.DepartmentID=dep.ID):
В нашем случае все получилось правильно, т.е. мы дополнили таблицу Employees, данными таблицы Departments. Я специально заострил на этом внимание, т.к. бывают случаи, когда такое поведение нам не нужно. Для демонстрации поставим задачу – для каждого отдела вывести последнего принятого сотрудника, если сотрудников нет, то просто вывести название отдела. Возможно напрашивается такое решение – просто взять предыдущий запрос и поменять условие соединение на RIGHT JOIN, плюс переставить поля местами:
ID | Name | ID | Name |
---|---|---|---|
1 | Администрация | 1000 | Иванов И.И. |
2 | Бухгалтерия | 1002 | Сидоров С.С. |
3 | ИТ | 1001 | Петров П.П. |
3 | ИТ | 1003 | Андреев А.А. |
3 | ИТ | 1004 | Николаев Н.Н. |
4 | Маркетинг и реклама | NULL | NULL |
5 | Логистика | NULL | NULL |
Но мы для ИТ-отдела получили три строчки, когда нам нужна была только строчка с последним принятым сотрудником, т.е. Николаевым Н.Н.
Задачу такого рода, можно решить, например, при помощи использования подзапроса:
ID | Name | ID | Name |
---|---|---|---|
1 | Администрация | 1000 | Иванов И.И. |
2 | Бухгалтерия | 1002 | Сидоров С.С. |
3 | ИТ | 1004 | Николаев Н.Н. |
4 | Маркетинг и реклама | NULL | NULL |
5 | Логистика | NULL | NULL |
При помощи предварительного объединения Employees с данными подзапроса, мы смогли оставить только нужных нам для соединения с Departments сотрудников.
Здесь мы плавно переходим к использованию подзапросов. Я думаю использование их в таком виде должно быть для вас понятно на интуитивном уровне. То есть подзапрос подставляется на место таблицы и играет ее роль, ничего сложного. К теме подзапросов мы еще вернемся отдельно.
Посмотрите отдельно, что возвращает подзапрос:
MaxEmployeeID |
---|
1005 |
1000 |
1002 |
1004 |
Т.е. он вернул только идентификаторы последних принятых сотрудников, в разрезе отделов.
Соединения выполняются последовательно сверху-вниз, наращиваясь как снежный ком, который катится с горы. Сначала происходит соединение «Employees emp JOIN (Подзапрос) lastEmp», формируя новый выходной набор:
Потом идет объединение набора, полученного «Employees emp JOIN (Подзапрос) lastEmp» (назовем его условно «ПоследнийРезультат») с Departments, т.е. «ПоследнийРезультат RIGHT JOIN Departments dep»:
Самостоятельная работа для закрепления материала
Если вы новичок, то вам обязательно нужно прорабатывать каждую JOIN-конструкцию, до тех пор, пока вы на 100% не будете понимать, как работает каждый вид соединения и правильно представлять результат какого вида будет получен в итоге.
Для закрепления материала про JOIN-соединения сделаем следующее:
Посмотрим, что в таблицах:
LCode | LDescr |
---|---|
1 | L-1 |
2 | L-2a |
2 | L-2b |
3 | L-3 |
5 | L-5 |
RCode | RDescr |
---|---|
2 | B-2a |
2 | B-2b |
3 | B-3 |
4 | B-4 |
А теперь попытайтесь сами разобрать, каким образом получилась каждая строчка запроса с каждым видом соединения (Excel вам в помощь):
LCode | LDescr | RCode | RDescr |
---|---|---|---|
2 | L-2a | 2 | B-2a |
2 | L-2a | 2 | B-2b |
2 | L-2b | 2 | B-2a |
2 | L-2b | 2 | B-2b |
3 | L-3 | 3 | B-3 |
LCode | LDescr | RCode | RDescr |
---|---|---|---|
1 | L-1 | NULL | NULL |
2 | L-2a | 2 | B-2a |
2 | L-2a | 2 | B-2b |
2 | L-2b | 2 | B-2a |
2 | L-2b | 2 | B-2b |
3 | L-3 | 3 | B-3 |
5 | L-5 | NULL | NULL |
LCode | LDescr | RCode | RDescr |
---|---|---|---|
2 | L-2a | 2 | B-2a |
2 | L-2b | 2 | B-2a |
2 | L-2a | 2 | B-2b |
2 | L-2b | 2 | B-2b |
3 | L-3 | 3 | B-3 |
NULL | NULL | 4 | B-4 |
LCode | LDescr | RCode | RDescr |
---|---|---|---|
1 | L-1 | NULL | NULL |
2 | L-2a | 2 | B-2a |
2 | L-2a | 2 | B-2b |
2 | L-2b | 2 | B-2a |
2 | L-2b | 2 | B-2b |
3 | L-3 | 3 | B-3 |
5 | L-5 | NULL | NULL |
NULL | NULL | 4 | B-4 |
LCode | LDescr | RCode | RDescr |
---|---|---|---|
1 | L-1 | 2 | B-2a |
2 | L-2a | 2 | B-2a |
2 | L-2b | 2 | B-2a |
3 | L-3 | 2 | B-2a |
5 | L-5 | 2 | B-2a |
1 | L-1 | 2 | B-2b |
2 | L-2a | 2 | B-2b |
2 | L-2b | 2 | B-2b |
3 | L-3 | 2 | B-2b |
5 | L-5 | 2 | B-2b |
1 | L-1 | 3 | B-3 |
2 | L-2a | 3 | B-3 |
2 | L-2b | 3 | B-3 |
3 | L-3 | 3 | B-3 |
5 | L-5 | 3 | B-3 |
1 | L-1 | 4 | B-4 |
2 | L-2a | 4 | B-4 |
2 | L-2b | 4 | B-4 |
3 | L-3 | 4 | B-4 |
5 | L-5 | 4 | B-4 |
Еще раз про JOIN-соединения
Еще один пример с использованием нескольких последовательных операций соединении. Здесь повтор получился не специально, так получилось – не выбрасывать же материал. 😉 Но ничего «повторение – мать учения».
Если используется несколько операций соединения, то в таком случае они применяются последовательно сверху-вниз. Грубо говоря, после каждого соединения создается новый набор и следующее соединение уже происходит с этим расширенным набором. Рассмотрим простой пример:
Первым делом выбрались все записи таблицы Employees:
Дальше произошло соединение с таблицей Departments:
Дальше уже идет соединение этого набора с таблицей Positions:
Т.е. это выглядит примерно так:
И в последнюю очередь идет возврат тех данных, которые мы просим вывести:
Соответственно, ко всему этому полученному набору можно применить фильтр WHERE и сортировку ORDER BY:
ID | EmployeeName | PositionName | DepartmentName |
---|---|---|---|
1004 | Николаев Н.Н. | Программист | ИТ |
1001 | Петров П.П. | Программист | ИТ |
То есть последний полученный набор – представляет собой такую же таблицу, над которой можно выполнять базовый запрос:
То есть если раньше в роли источника выступала только одна таблица, то теперь на это место мы просто подставляем наше выражение:
В результате чего получаем тот же самый базовый запрос:
А теперь, применим группировку:
Видите, мы все так же крутимся вокруг да около базовых конструкций, теперь надеюсь понятно, почему очень важно в первую очередь хорошо понять их.
И как мы увидели, в запросе на месте любой таблицы может стоять подзапрос. В свою очередь подзапросы могут быть вложены в подзапросы. И все эти подзапросы тоже представляют из себя базовые конструкции. То есть базовая конструкция, это кирпичики, из которых строится любой запрос.
Обещанный пример с CROSS JOIN
Давайте используем соединение CROSS JOIN, чтобы подсчитать сколько сотрудников, в каком отделе и на каких должностях числится. Для каждого отдела перечислим все существующие должности:
В данном случае сначала выполнилось соединение при помощи CROSS JOIN, а затем к полученному набору сделалось соединение с данными из подзапроса при помощи LEFT JOIN. Вместо таблицы в LEFT JOIN мы использовали подзапрос.
Подзапрос заключается в скобки и ему присваивается псевдоним, в данном случае это «e». То есть в данном случае объединение происходит не с таблицей, а с результатом следующего запроса:
DepartmentID | PositionID | EmplCount |
---|---|---|
NULL | NULL | 1 |
2 | 1 | 1 |
1 | 2 | 1 |
3 | 3 | 2 |
3 | 4 | 1 |
Вместе с псевдонимом «e» мы можем использовать имена DepartmentID, PositionID и EmplCount. По сути дальше подзапрос ведет себя так же, как если на его месте стояла таблица. Соответственно, как и у таблицы,
все имена колонок, которые возвращает подзапрос, должны быть заданы явно и не должны повторяться.
Связь при помощи WHERE-условия
Для примера перепишем следующий запрос с JOIN-соединением:
Через WHERE-условие он примет следующую форму:
Здесь плохо то, что происходит смешивание условий соединения таблиц (emp.DepartmentID=dep.ID) с условием фильтрации (emp.DepartmentID=3).
Теперь посмотрим, как сделать CROSS JOIN:
Через WHERE-условие он примет следующую форму:
Т.е. в этом случае мы просто не указали условие соединения таблиц Employees и Departments. Чем плох этот запрос? Представьте, что кто-то другой смотрит на ваш запрос и думает «кажется тот, кто писал запрос забыл здесь дописать условие (emp.DepartmentID=dep.ID)» и с радостью, что обнаружил косяк, дописывает это условие. В результате чего задуманное вами может сломаться, т.к. вы подразумевали CROSS JOIN. Так что, если вы делаете декартово соединение, то лучше явно укажите, что это именно оно, используя конструкцию CROSS JOIN.
Для оптимизатора запроса может быть и без разницы как вы реализуете соединение (при помощи WHERE или JOIN), он их может выполнить абсолютно одинаково. Но из соображения понимаемости кода, я бы рекомендовал в современных СУБД стараться никогда не делать соединение таблиц при помощи WHERE-условия. Использовать WHERE-условия для соединения, в том случае, если в СУБД реализованы конструкции JOIN, я бы назвал сейчас моветоном. WHERE-условия служат для фильтрации набора, и не нужно перемешивать условия служащие для соединения, с условиями отвечающими за фильтрацию. Но если вы пришли к выводу, что без реализации соединения через WHERE не обойтись, то конечно приоритет за решеной задачей и «к черту все устои».
UNION-объединения – операции вертикального объединения результатов запросов
Я специально использую словосочетания горизонтальное соединение и вертикальное объединение, т.к. заметил, что новички часто недопонимают и путают суть этих операций.
Давайте первым делом вспомним как мы делали первую версию отчета для директора:
Так вот, если бы мы не знали, что существует операция группировки, но знали бы, что существует операция объединения результатов запроса при помощи UNION ALL, то мы могли бы склеить все эти запросы следующим образом:
Т.е. UNION ALL позволяет склеить результаты, полученные разными запросами в один общий результат.
Соответственно количество колонок в каждом запросе должно быть одинаковым, а также должны быть совместимыми и типы этих колонок, т.е. строка под строкой, число под числом, дата под датой и т.п.
Немного теории
В MS SQL реализованы следующие виды вертикального объединения:
Операция | Описание |
---|---|
UNION ALL | В результат включаются все строки из обоих наборов. (A+B) |
UNION | В результат включаются только уникальные строки двух наборов. DISTINCT(A+B) |
EXCEPT | В результат попадают уникальные строки верхнего набора, которые отсутствуют в нижнем наборе. Разница 2-х множеств. DISTINCT(A-B) |
INTERSECT | В результат включаются только уникальные строки, присутствующие в обоих наборах. Пересечение 2-х множеств. DISTINCT(A&B) |
Все это проще понять на наглядном примере.
Создадим 2 таблицы и наполним их данными:
Посмотрим на содержимое:
T1 | T2 |
---|---|
1 | Text 1 |
1 | Text 1 |
2 | Text 2 |
3 | Text 3 |
4 | Text 4 |
5 | Text 5 |
B1 | B2 |
---|---|
2 | Text 2 |
3 | Text 3 |
6 | Text 6 |
6 | Text 6 |
UNION ALL
UNION
По сути UNION можно представить, как UNION ALL, к которому применена операция DISTINCT: