Основы языка SQL

Запросы к нескольким таблицам

В материале рассматривается конструирование SQL-запросов с соединением нескольких таблиц. Изложение выстроено от простого объединения трёх сущностей (покупатель, его адрес, компания) к анализу различий между внутренним (INNER) и левым внешним (LEFT OUTER) соединениями. Демонстрируется, как тип JOIN влияет на полноту возвращаемых данных: внутреннее соединение исключает записи без пары, тогда как внешнее сохраняет все строки ведущей таблицы, подставляя NULL. На практических примерах показана проверка согласованности базы данных и выявление записей с отсутствующими связями. Логика раскрывается через призму внешних ключей и фильтрации, позволяя осмысленно выбирать способ склейки под конкретную аналитическую задачу.

Основные мысли

В результате изучения лекции слушатель будет способен:
1. Объяснять назначение оператора JOIN для объединения строк из связанных таблиц.
2. Различать поведение INNER JOIN (внутреннего соединения) и LEFT OUTER JOIN (левого внешнего соединения) по наличию NULL-значений в результирующем наборе.
3. Формулировать SQL-запрос, последовательно соединяющий три и более таблицы с указанием предикатов равенства ключей в секции ON.
4. Интерпретировать появление незаполненных полей в результате LEFT JOIN как признак отсутствия связанной записи в правой таблице.
5. Применять LEFT JOIN для диагностики целостности данных: выявлять сущности, у которых не заполнены зависимые атрибуты (например, покупатели без адресов).
6. Определять, когда использование LEFT JOIN не изменяет число строк по сравнению с INNER JOIN, и связывать это с ограничениями внешних ключей.
7. Выбирать подходящий тип соединения в зависимости от бизнес-требований (полнота охвата сущностей против строгого соответствия).
Показывать лекцию целиком
Краткое изложение

Соединение таблиц в SQL: JOIN

Введение в соединения

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

Пример 1: Получение компании, типа и строки адреса покупателя

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

Для этого потребуются три таблицы:
• Customer (Покупатель) — содержит поле CompanyName (название компании) и первичный ключ CustomerID.
• CustomerAddress (АдресПокупателя) — связующая таблица, хранит CustomerID, AddressID и AddressType (тип адреса).
• Address (Адрес) — хранит AddressID и AddressLine1 (строку адреса).

Логика соединения:
1. Из таблицы Customer берём CompanyName и скрыто забираем CustomerID.
2. По равенству CustomerID соединяемся с CustomerAddress, получая все адреса, привязанные к покупателю. Забираем AddressType и AddressID.
3. Полученный AddressID используем для соединения с таблицей Address по равенству ключей, чтобы извлечь текст адреса AddressLine1.

Итоговый запрос выглядит так:

sql
SELECT
Customer.CompanyName,
CustomerAddress.AddressType,
Address.AddressLine1
FROM Customer
JOIN CustomerAddress ON Customer.CustomerID = CustomerAddress.CustomerID
JOIN Address ON CustomerAddress.AddressID = Address.AddressID;

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

Типы JOIN: INNER и LEFT OUTER

Когда в запросе написано просто JOIN, подразумевается INNER JOIN (внутреннее соединение). Оно возвращает только те строки, для которых нашлось соответствие в обеих соединяемых таблицах.

LEFT OUTER JOIN (левое внешнее соединение), записываемое как LEFT JOIN, работает иначе: он возвращает все строки из левой (первой) таблицы. Если для строки левой таблицы нет соответствующей пары в правой, то поля правой таблицы заполняются значением NULL.

Рассмотрим скрипт, в котором вместо JOIN для связи с Address применяется LEFT JOIN:

sql
SELECT ...
FROM Customer
JOIN CustomerAddress ON Customer.CustomerID = CustomerAddress.CustomerID
LEFT JOIN Address ON CustomerAddress.AddressID = Address.AddressID;

Если в базе данных есть покупатели без записей в Address, то при использовании INNER JOIN они были бы исключены. LEFT JOIN же гарантирует их сохранение — в столбцах AddressType и AddressLine1 для них будет проставлен NULL. Это позволяет анализировать полноту данных, выявляя сущности с отсутствующими связями.

Практическая проверка целостности

Применим описанную механику для проверки реальной базы данных. Убрав фильтр по конкретной компании, выполним запрос с INNER JOIN — он возвращает 417 строк. Заменив соединение с Address на LEFT JOIN, мы получаем уже 857 строк. Разница (440 записей) — это покупатели, у которых в качестве компании указан, например, "Bike Store", но адрес доставки ни разу не был задан. Их поля адреса в результирующей выборке содержат NULL.

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

Пример 2: Детализация заказа клиента с ценой товара

Построим запрос для получения истории покупок конкретного покупателя с идентификатором 3050. Нужно вывести количество товара, его название и номенклатурную цену (ListPrice).

Задействуются таблицы:
• SalesOrderHeader (ЗаголовокЗаказа) — «шапка» чека, содержит SalesOrderID и CustomerID.
• SalesOrderDetail (ДеталиЗаказа) — позиции чека, содержат SalesOrderID, ProductID, количество (OrderQty) и цену продажи с учётом скидки.
• Product (Товар) — номенклатура, хранит ProductID, название (Name) и исходную цену ListPrice.

Структура запроса:

sql
SELECT
SalesOrderDetail.OrderQty,
Product.Name,
Product.ListPrice
FROM SalesOrderHeader
JOIN SalesOrderDetail ON SalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID
JOIN Product ON SalesOrderDetail.ProductID = Product.ProductID
WHERE SalesOrderHeader.CustomerID = 3050;

Фильтрация по идентификатору покупателя выполняется после объединения всех таблиц. Результат покажет все товары, купленные данным клиентом, с их исходной ценой по справочнику.

Почему LEFT JOIN может не менять результат?

При проверке запроса для клиента 3050 замена соединения с Product на LEFT JOIN не увеличила число строк. Это объясняется согласованностью данных: каждая проданная позиция обязательно имеет идентификатор товара, существующий в таблице Product. Аналогично, LEFT JOIN между SalesOrderHeader и SalesOrderDetail избыточен, поскольку чек не может существовать без детализации позиций.

Таким образом, если структура базы данных гарантирует целостность связей (через внешние ключи и ограничения NOT NULL), результаты INNER JOIN и LEFT JOIN могут полностью совпадать. Однако явное использование LEFT JOIN остаётся полезным как инструмент аудита данных и страховки от гипотетических нарушений целостности.

Краткие итоги

Понимание механики соединений в SQL не сводится к механическому запоминанию синтаксиса — это фундамент для построения точных аналитических выборок. Практическая ценность рассмотренного материала заключается в осознании того, как тип склейки влияет на семантику результирующего набора и, следовательно, на управленческие выводы. Внутреннее соединение отсекает неполные цепочки данных, предоставляя «чистую» картину только по полностью описанным объектам. Внешнее левое соединение, напротив, сохраняет каждую запись ведущей таблицы и маркирует пробелы значением NULL. Эта разница становится критичной при решении таких задач, как вычисление доли клиентов с заполненным адресом, аудит справочников на предмет незавершённых карточек товаров или поиск заказов с «потерянными» позициями.

Применение этих знаний позволяет выйти за рамки пассивного извлечения данных и перейти к активной диагностике информационной системы. Способность целенаправленно сопоставлять результаты INNER и LEFT JOIN превращает разработчика в аналитика, проверяющего гипотезы о качестве данных. Например, стабильно равное число строк при замене типа соединения сигнализирует о жёстких ограничениях внешних ключей; резкий прирост строк выявляет сущности, существующие без обязательных атрибутов. В практической деятельности такой приём незаменим при миграции данных, сверке после ETL-процессов и подготовке отчётов, где пропущенные связи могут исказить итоговые суммы или средние показатели.

Дальнейшее развитие этой логики ведёт к конструированию комплексных запросов с несколькими соединениями, каждое из которых выбирается осмысленно: где-то допустимо строгое отсечение, а где-то необходимо сохранение основного массива с последующей обработкой NULL-значений через функции COALESCE или условную агрегацию. Таким образом, владение типами JOIN формирует базу для построения надёжных, интерпретируемых и устойчивых к аномалиям данных SQL-отчётов.
Соединение таблиц (JOIN) — механизм SQL, позволяющий объединить строки из двух или более таблиц на основе заданного условия. По умолчанию, JOIN означает INNER JOIN (внутреннее соединение), которое возвращает только те комбинации строк, для которых выполняется условие в секции ON. Если для строки из первой таблицы не находится пары во второй, она исключается из результата.

Альтернатива — LEFT OUTER JOIN (левое внешнее соединение), записываемое как LEFT JOIN. Оно возвращает все строки из левой (первой) таблицы, а для несовпавших строк правой таблицы подставляет NULL. Это ключевое отличие позволяет выявлять записи с отсутствующими связями.

Пример объединения трёх таблиц

Задача: получить для каждого покупателя (Customer) название компании (CompanyName), тип адреса (AddressType) и строку адреса (AddressLine1). Данные распределены по таблицам Customer, CustomerAddress и Address.

Связи:
Customer.CustomerID = CustomerAddress.CustomerID
CustomerAddress.AddressID = Address.AddressID
Запрос с внутренним соединением:

sql
SELECT
C.CompanyName,
CA.AddressType,
A.AddressLine1
FROM Customer C
JOIN CustomerAddress CA ON C.CustomerID = CA.CustomerID
JOIN Address A ON CA.AddressID = A.AddressID;

Такой запрос исключит покупателей без адресов. Чтобы сохранить всех покупателей, включая тех, у кого адрес не указан, нужно использовать LEFT JOIN для связи с таблицей Address (а возможно, и с CustomerAddress, в зависимости от задачи).

Практический аудит с помощью LEFT JOIN

Замена JOIN на LEFT JOIN при соединении с Address увеличила количество результирующих строк с 417 до 857. Разница (440 записей) представляет собой покупателей, для которых адрес не заполнен — в столбцах AddressType и AddressLine1 у них стоит NULL. Таким образом, LEFT JOIN выступает инструментом проверки полноты данных.

Если же при замене типа соединения количество строк остаётся прежним, это говорит о жёсткой целостности: внешний ключ или бизнес-логика гарантируют наличие пары для каждой записи.

Детализация заказа с номенклатурной ценой

Для получения истории покупок клиента с CustomerID = 3050 (количество, название товара и его исходная цена ListPrice) соединяются:
SalesOrderHeader (заголовки заказов)
SalesOrderDetail (позиции заказов)
Product (справочник товаров)

Условия: равенство SalesOrderID между заголовком и деталями, а также ProductID между деталями и товарами. Фильтр по покупателю накладывается через WHERE. Структура:

sql
SELECT
OD.OrderQty,
P.Name,
P.ListPrice
FROM SalesOrderHeader OH
JOIN SalesOrderDetail OD ON OH.SalesOrderID = OD.SalesOrderID
JOIN Product P ON OD.ProductID = P.ProductID
WHERE OH.CustomerID = 3050;

Проверка с LEFT JOIN показала, что количество строк не растёт — значит, все проданные товары присутствуют в справочнике, и каждый чек имеет позиции. В подобных согласованных базах INNER JOIN и LEFT JOIN дают идентичный результат.

Ключевые выводы

INNER JOIN – строгое пересечение; подходит, когда важны только полные цепочки данных.
LEFT JOIN – сохранение всех записей ведущей таблицы; незаменим для отчётов с полным охватом сущностей и для поиска «пробелов».
• Сравнение числа строк при разных JOIN-стратегиях — простой и действенный метод аудита целостности данных.
• При наличии внешних ключей без пропусков поведение соединений совпадает, что указывает на качественную структуру базы данных.

Выводы

1. JOIN объединяет строки таблиц на основе условия в секции ON, формируя временный набор данных для последующей обработки.
2. INNER JOIN исключает из результата все записи, для которых не найдено соответствия в соединяемой таблице.
3. LEFT OUTER JOIN сохраняет все строки левой таблицы и подставляет NULL в поля правой таблицы при отсутствии совпадений.
4. Внешнее соединение является основным инструментом для выявления сущностей с отсутствующими зависимыми атрибутами (например, покупатели без адресов).
5. Если структура БД гарантирует наличие пары для каждой строки (ограничения внешнего ключа), то результаты INNER JOIN и LEFT JOIN могут совпадать.
6. Сравнение количества строк при использовании разных типов JOIN позволяет выполнить аудит согласованности и целостности данных.
7. При соединении трёх и более таблиц цепочка склеек строится последовательно: результат первого JOIN становится левой таблицей для следующего.
8. Значение NULL в результатах внешнего соединения не является ошибкой, а отражает объективное отсутствие информации по данной сущности.
9. Фильтрацию по атрибутам (WHERE) можно накладывать как до, так и после соединений, что влияет на логику и производительность запроса.
10. Понимание разницы между типами соединений позволяет осознанно выбирать способ склейки, исходя из бизнес-требований к полноте данных.

Вопросы для самопроверки

1. Какой тип соединения используется по умолчанию, если в запросе указано просто ключевое слово JOIN?
2. Чем принципиально различаются множества строк, возвращаемые операторами INNER JOIN и LEFT OUTER JOIN?
3. В каком случае LEFT JOIN может вернуть столько же записей, сколько и INNER JOIN для тех же таблиц?
4. Предположим, запрос с LEFT JOIN вернул в столбцах правой таблицы значения NULL. О чём это свидетельствует?
5. Почему при диагностике целостности данных полезно выполнять один и тот же запрос с разными типами соединений?
6. В какой последовательности обрабатываются соединения при склейке трёх таблиц?
7. Чем обусловлен прирост числа строк при замене INNER JOIN на LEFT JOIN в сценарии с таблицами CustomerAddress и Address?
8. Можно ли утверждать, что наличие внешнего ключа между таблицами делает результаты INNER и LEFT JOIN идентичными? Поясните причину.
9. Для чего в запросе с соединениями может потребоваться указывать имя таблицы перед названием поля (префикс)?
10. Какие практические выводы можно сделать о бизнес-процессах компании, если у значительного числа покупателей не указан ни один адрес доставки?
11. Влияет ли порядок перечисления таблиц в секции FROM на семантику LEFT JOIN?
12. Как с помощью LEFT JOIN проверить, все ли проданные товары имеют запись в номенклатурном справочнике Product?
Вернуться к учебному плану