До сих пор наши запросы обращались только к одной таблице за раз.
Запросы могут обращаться к нескольким таблицам одновременно или обращаться к одной и той же
таблице таким образом, что несколько строк таблицы обрабатываются
одновременно. Запросы, которые обращаются к нескольким таблицам
(или нескольким экземплярам одной и той же таблицы) одновременно, называются
соединениями (join queries). Они объединяют строки из одной таблицы
со строками из второй таблицы, используя выражение, указывающее, какие строки
должны быть сопоставлены. Например, чтобы вернуть все записи о погоде вместе
с местоположением соответствующего города, базе данных необходимо сравнить
столбец city
каждой строки таблицы weather со столбцом
name всех строк в таблице cities
и выбрать пары строк, где эти значения совпадают.[4]
Это будет выполнено следующим запросом:
SELECT * FROM weather JOIN cities ON city = name;
city | temp_lo | temp_hi | prcp | date | name | location
---------------+---------+---------+------+------------+---------------+-----------
San Francisco | 46 | 50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
San Francisco | 43 | 57 | 0 | 1994-11-29 | San Francisco | (-194,53)
(2 rows)
Обратите внимание на две вещи в результирующем наборе:
Нет строки результата для города Хейвард (Hayward). Это
потому, что нет соответствующей записи в таблице
cities для Хейварда, поэтому соединение
игнорирует несовпадающие строки в таблице weather. Мы скоро увидим,
как это можно исправить.
Есть два столбца, содержащих название города. Это
правильно, потому что списки столбцов из таблиц
weather и
cities объединяются. На практике это нежелательно,
однако, поэтому вы, вероятно, захотите
явно перечислить выходные столбцы, а не использовать
*:
SELECT city, temp_lo, temp_hi, prcp, date, location
FROM weather JOIN cities ON city = name;
Поскольку столбцы имели разные имена, анализатор автоматически определил, к какой таблице они принадлежат. Если бы в двух таблицах были дублирующиеся имена столбцов, вам нужно было бы квалифицировать имена столбцов, чтобы показать, какой из них вы имеете в виду:
SELECT weather.city, weather.temp_lo, weather.temp_hi,
weather.prcp, weather.date, cities.location
FROM weather JOIN cities ON weather.city = cities.name;
Широко считается хорошим стилем квалифицировать все имена столбцов в запросе соединения, чтобы запрос не провалился, если дублирующееся имя столбца позже будет добавлено в одну из таблиц.
Запросы соединения такого вида, как показано до сих пор, также могут быть записаны в такой форме:
SELECT *
FROM weather, cities
WHERE city = name;
Этот синтаксис предшествует синтаксису JOIN/ON,
который был введён в SQL-92. Таблицы просто перечислены в
предложении FROM, а выражение сравнения добавляется
в предложение WHERE. Результаты этого старого
неявного синтаксиса и нового явного
синтаксиса JOIN/ON идентичны. Но
для читателя запроса явный синтаксис делает его значение более лёгким для понимания:
условие соединения вводится своим собственным ключевым словом, тогда как
ранее условие смешивалось с предложением WHERE
вместе с другими условиями.
Теперь выясним, как мы можем вернуть записи Хейварда обратно.
Что мы хотим, чтобы запрос делал, — это сканировать таблицу
weather и для каждой строки находить соответствующие
строки таблицы cities. Если соответствующая строка не
найдена, мы хотим, чтобы некоторые «пустые значения» были подставлены
вместо столбцов таблицы cities. Такой вид
запроса называется внешним соединением (outer join).
(Соединения, которые мы видели до сих пор, являются внутренними соединениями (inner joins).)
Команда выглядит так:
SELECT *
FROM weather LEFT OUTER JOIN cities ON weather.city = cities.name;
city | temp_lo | temp_hi | prcp | date | name | location
---------------+---------+---------+------+------------+---------------+-----------
Hayward | 37 | 54 | | 1994-11-29 | |
San Francisco | 46 | 50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
San Francisco | 43 | 57 | 0 | 1994-11-29 | San Francisco | (-194,53)
(3 rows)
Этот запрос называется левым внешним соединением (left outer join), потому что таблица, упомянутая слева от оператора соединения, будет иметь каждую из своих строк в выводе хотя бы один раз, тогда как таблица справа будет иметь в выводе только те строки, которые соответствуют некоторой строке левой таблицы. При выводе строки левой таблицы, для которой нет соответствия в правой таблице, пустые (null) значения подставляются вместо столбцов правой таблицы.
Упражнение: Существуют также правые внешние соединения (right outer joins) и полные внешние соединения (full outer joins). Попробуйте выяснить, что они делают.
Мы также можем соединить таблицу с самой собой. Это называется
самосоединением (self join). В качестве примера предположим, что мы хотим
найти все записи о погоде, которые находятся в температурном диапазоне
других записей о погоде. Поэтому нам нужно сравнить
столбцы temp_lo и temp_hi
каждой строки weather со
столбцами temp_lo и
temp_hi всех других
строк weather. Мы можем сделать это с помощью
следующего запроса:
SELECT w1.city, w1.temp_lo AS low, w1.temp_hi AS high,
w2.city, w2.temp_lo AS low, w2.temp_hi AS high
FROM weather w1 JOIN weather w2
ON w1.temp_lo < w2.temp_lo AND w1.temp_hi > w2.temp_hi;
city | low | high | city | low | high
---------------+-----+------+---------------+-----+------
San Francisco | 43 | 57 | San Francisco | 46 | 50
Hayward | 37 | 54 | San Francisco | 46 | 50
(2 rows)
Здесь мы переименовали таблицу weather в w1 и
w2, чтобы различать левую и правую сторону
соединения. Вы также можете использовать такие псевдонимы в других
запросах, чтобы сэкономить на наборе, например:
SELECT *
FROM weather w JOIN cities c ON w.city = c.name;
Вы будете часто встречать этот стиль сокращения.
[4] Это только концептуальная модель. Соединение обычно выполняется более эффективным способом, чем фактическое сравнение каждой возможной пары строк, но это невидимо для пользователя.