Введение в соединения таблиц
Мы продолжаем работу с SQL и переходим к соединениям таблиц. Сначала рассмотрим внутреннее соединение —
INNER JOIN, затем левое внешнее соединение —
LEFT JOIN. Далее изучим агрегационные функции и группировку, но сейчас сосредоточимся на INNER JOIN. Этот тип соединения возвращает только те строки, для которых в обеих таблицах найдена пара по указанному условию. Для примеров будем использовать базу данных AdventureWorks2017.
Соединение Product и ProductReview
Рассмотрим простой запрос с внутренним соединением. Выполним выборку из таблицы
Production.Product. Присвоим ей псевдоним P, чтобы сократить обращение к столбцам.
Из таблицы мы забираем:
•
Name — назовём это поле
ProductName;
•
ProductID — идентификатор товара;
• комментарий из связанной таблицы
Production.ProductReview.
Таблица
ProductReview содержит отзывы о товарах и имеет столбец
ProductID, который связывает её с таблицей
Product. Мы соединяем эти две таблицы оператором
INNER JOIN по равенству
ProductID и присваиваем второй таблице псевдоним PR.
Условие соединения:
sql
INNER JOIN Production.ProductReview AS PR
ON P.ProductID = PR.ProductID
Сортируем результат по названию товара (
ORDER BY P.Name). Запрос возвращает всего четыре строки, потому что только три продукта имеют отзывы, причём у одного из них — два отзыва. Остальные товары в выборку не попадают, так как INNER JOIN требует обязательного наличия пары.
Если заменить
INNER JOIN на
LEFT JOIN, запрос изменится. Левое внешнее соединение берёт
все строки из левой таблицы (
Product) и по возможности дополняет их данными из правой таблицы (
ProductReview). Если для товара нет ни одного отзыва, в столбцах правой таблицы будет значение
NULL. При выполнении LEFT JOIN мы получим уже 505 строк — по общему числу товаров. Только для четырёх из них будет заполнено поле комментария, у остальных оно останется пустым (NULL). Это наглядно демонстрирует разницу: INNER JOIN отсекает непарные записи, LEFT JOIN — сохраняет.
Добавление фильтрации по тексту
Усложним запрос, оставив внутреннее соединение. Вернём те же поля: название товара, его идентификатор и текст отзыва. Добавим условие в разделе
WHERE, чтобы получить только те отзывы, которые содержат слово «heavy». Используем оператор
LIKE с шаблоном
'%heavy%' — это значит, что перед и после искомого слова могут находиться любые символы.
Запрос:
sql
SELECT P.Name AS ProductName, P.ProductID, PR.Comments AS ProductReviewComments
FROM Production.Product AS P
INNER JOIN Production.ProductReview AS PR
ON P.ProductID = PR.ProductID
WHERE PR.Comments LIKE '%heavy%'
ORDER BY P.ProductID;
В результате мы получим отзывы, в тексте которых встречается «heavy». Фильтрация применена уже после соединения, поэтому сначала находятся все товары с отзывами, а затем из них отбираются подходящие под условие.
Соединение с таблицей ProductModel
Расширим запрос, добавив третью таблицу —
Production.ProductModel (псевдоним
PM). Теперь мы хотим получить информацию о модели товара. В таблице
Product есть столбец ProductModelID, который ссылается на ProductModelID в таблице ProductModel. Соединяем Product и ProductModel оператором
INNER JOIN.
Из таблицы
ProductModel выбираем:
•
ProductModelID как
ModelID;
•
Name как
ModelName.
Из таблицы
Product помимо прежних полей дополнительно забираем:
•
StandardCost — цену закупки, преобразованную к типу
DECIMAL с округлением до двух знаков после запятой;
•
ProductClass — класс продукта (столбец
Class).
Поскольку столбец
Name присутствует в обеих таблицах, уточняем его источник:
P.Name и
PM.Name. Соединение по-прежнему внутреннее, поэтому в результат попадут только те товары, у которых
обязательно указан идентификатор модели. Сортируем по цене закупки.
При выполнении такого запроса возвращается 295 строк. Это меньше общего числа товаров (504), потому что у многих товаров модель не указана, и
INNER JOIN их исключает. Если мы снова заменим INNER JOIN на LEFT JOIN для таблицы
ProductModel, то получим все 504 строки: для товаров без модели столбцы
ModelID и
ModelName будут содержать NULL.
Отбор по непустому классу и названию модели
Продолжим работать с тем же набором полей и внутренним соединением с таблицей
ProductModel. Добавим два условия фильтрации.
1. Поле Class (класс продукта) не должно быть пустым. Это достигается условием P.Class IS NOT NULL. Ранее в выборке из 295 строк присутствовали записи с незаполненным классом; теперь они исключаются, и количество строк сокращается до 229.
2. Название модели должно содержать слово «Fork» или слово «Front». Используем составное условие:
sql
AND (PM.Name LIKE '%Fork%' OR PM.Name LIKE '%Front%')
Оба условия объединяются оператором
AND, то есть должны выполняться одновременно: класс указан, и название модели содержит одну из указанных подстрок.
Итоговый запрос вернёт всего девять строк — это товары, удовлетворяющие обоим критериям.
Соединение трёх таблиц: категории и подкатегории
Часто требуется соединить более двух таблиц. Рассмотрим связь товара с категориями. В базе AdventureWorks иерархия построена так:
• таблица
Production.Product (псевдоним
P) содержит товар и ссылку на подкатегорию через
ProductSubcategoryID;
• таблица
Production.ProductSubcategory (псевдоним
PS) содержит название подкатегории и ссылку на категорию через ProductCategoryID;
• таблица
Production.ProductCategory (псевдоним
PC) содержит название категории.
Запрос с двумя внутренними соединениями:
sql
SELECT PC.Name AS CategoryName,
PS.Name AS SubcategoryName,
P.ProductID,
P.Name AS ProductName
FROM Production.Product AS P
INNER JOIN Production.ProductSubcategory AS PS
ON P.ProductSubcategoryID = PS.ProductSubcategoryID
INNER JOIN Production.ProductCategory AS PC
ON PS.ProductCategoryID = PC.ProductCategoryID
ORDER BY CategoryName, SubcategoryName, ProductName;
Сначала
Product соединяется с
ProductSubcategory по идентификатору подкатегории. Затем результат соединяется с
ProductCategory по идентификатору категории. Такой запрос вернёт 295 строк — только те товары, у которых заполнены и подкатегория, и категория.
Если изменить первый INNER JOIN на
LEFT JOIN (для
ProductSubcategory), сохранив второе соединение, результат увеличится до 504 строк. Товары без подкатегории будут включены, но столбцы подкатегории и категории для них окажутся равными NULL. Это иллюстрирует, что тип каждого соединения влияет на полноту итогового набора независимо, и можно гибко комбинировать INNER JOIN и LEFT JOIN в одном запросе.
Заключение
Внутреннее соединение (INNER JOIN) используется, когда нужны только сопоставленные данные. Левое внешнее соединение (LEFT JOIN) позволяет сохранить все записи ведущей таблицы и дополнить их при наличии связанных данных. Эти операции — основа для построения сложных запросов, с которыми мы продолжим работу.
Краткие итоги
Освоение соединений в SQL на практическом материале AdventureWorks выстраивает чёткое понимание того, как связанные таблицы взаимодействуют в реляционной модели. Ключевая развилка между INNER JOIN и LEFT JOIN становится не просто синтаксическим различием, а осознанным выбором бизнес-логики: показывать ли только обеспеченные данными объекты или демонстрировать полную картину с явными пробелами.
Разбор начинается с простейшего случая «товар–отзыв». Здесь сразу видно, что внутреннее соединение работает как фильтр по наличию пары, а левое внешнее — как механизм сохранения всех записей ведущей таблицы, независимо от того, нашлись ли для них отклики. Появление NULL в столбцах правой таблицы наглядно маркирует отсутствующие связи и учит интерпретировать такие ситуации в реальных отчётах.
Добавление текстового фильтра через LIKE показывает, что отбор по содержимому отзыва применяется уже к результату соединения, позволяя извлекать только осмысленные фрагменты данных. Переход к трём таблицам с участием ProductModel демонстрирует, как INNER JOIN последовательно отсекает всё, что не подкреплено ссылкой на модель. Условия IS NOT NULL и комбинация OR с LIKE внутри WHERE подводят к построению сложных критериев отбора на фоне уже соединённого набора.
Заключительный пример с трёхступенчатой иерархией категорий закрепляет навык связывания таблиц по цепочке внешних ключей. Практическое значение такого подхода трудно переоценить: любые аналитические выборки, будь то каталог товаров с рубрикацией или отчёт по отзывам, требуют точного определения типа соединения на каждом шаге. Итогом становится способность проектировать запрос, который вернёт ровно ту информацию, которая нужна пользователю — будь то строгий перечень с полными данными или реестр с явными лакунами, сигнализирующими о неполноте исходной информации. Эти навыки напрямую переносятся на задачи построения отчётности, интеграции данных и очистки массивов в любой прикладной области.
1. INNER JOIN возвращает только те строки, для которых существуют совпадения в обеих соединяемых таблицах.
2. LEFT JOIN сохраняет все строки левой таблицы и заполняет столбцы правой таблицы значением NULL при отсутствии соответствия.
3. Замена INNER JOIN на LEFT JOIN увеличивает количество возвращаемых строк, если в правой таблице есть непарные записи левой.
4. Псевдонимы таблиц упрощают синтаксис и делают запрос более читаемым, особенно при соединении нескольких таблиц.
5. Условие соединения задаётся в предложении ON и обычно связывает первичный ключ одной таблицы с внешним ключом другой.
6. Фильтрация через WHERE выполняется после соединения и может как сужать выборку, так и исключать записи с NULL.
7. Оператор LIKE с шаблоном %text% позволяет искать подстроку в текстовых полях, не привязываясь к её позиции.
8. Проверка IS NOT NULL отбирает только те строки, в которых указанный столбец заполнен.
9. В одном запросе можно последовательно соединять три и более таблицы, комбинируя разные типы соединений.
10. При соединении трёх таблиц тип каждого JOIN влияет на полноту итогового набора независимо от остальных.
11. Сортировка результатов с помощью ORDER BY применяется к итоговому набору данных после всех соединений и фильтров.
12. Понимание различий между INNER JOIN и LEFT JOIN критически важно для получения корректных и полных данных из реляционной базы.
1. Какой тип соединения следует использовать, чтобы получить все товары и дополнить их отзывами, если они существуют, и почему?
2. Почему при использовании INNER JOIN между Product и ProductReview итоговое число строк может быть меньше количества записей в таблице Product?
3. Что произойдёт с количеством строк, если в запросе с LEFT JOIN добавить условие WHERE right_table.column IS NOT NULL?
4. Как повлияет на результат добавление фильтра LIKE '%heavy%' до соединения таблиц (если бы это было возможно) и после?
5. Каким образом проверить, что у товара не указан класс, и вывести такие товары?
6. Можно ли в одном запросе одновременно использовать INNER JOIN и LEFT JOIN для разных таблиц? Приведите примерную ситуацию.
7. В запросе соединены три таблицы: Product, Subcategory и Category с помощью INNER JOIN. Какие товары будут исключены из результата?
8. Как правильно записать условие для отбора товаров, названия моделей которых содержат «Fork» или «Front», при этом поле класса обязательно должно быть заполнено?
9. Почему в запросе с LEFT JOIN значения некоторых столбцов оказались NULL, хотя в исходных таблицах NULL вроде бы не было?
10. Каким псевдонимом можно сократить обращение к таблице Production.ProductModel и как это улучшает читаемость запроса?
11. При соединении двух таблиц через INNER JOIN по столбцу, содержащему NULL, как поведёт себя соединение?
12. Как отсортировать результат многотабличного запроса сначала по названию категории, затем по названию подкатегории и наконец по названию товара?