Глава 3. Запросы с использованием нескольких таблиц
3.1. О средствах одновременной работы с множеством таблиц
3.2. Запросы, использующие соединения
3.3. Вложенные подзапросы
3.4. Объединение (UNION)
3.5. Реализация операций реляционной алгебры предложением SELECT
3.6. Резюме
В литературе показано, что соединения - это подмножества декартова произведения. Так как декартово произведение n таблиц - это таблица, содержащая все возможные строки r, такие, что r является сцеплением какой-либо строки из первой таблицы, строки из второй таблицы, ... и строки из n-й таблицы (а мы уже научились выделять с помощью SELECT любое подмножество реляционной таблицы), то осталось лишь выяснить, можно ли с помощью SELECT получить декартово произведение. Для получения декартова произведения нескольких таблиц надо указать во фразе FROM перечень перемножаемых таблиц, а во фразе SELECT – все их столбцы.
Так, для получения декартова произведения Вид_блюд и Трапезы надо выдать запрос
SELECT Вид_блюд.*, Трапезы.*
FROM Вид_блюд, Трапезы;
|
Получим таблицу, содержащую 5 х 3 = 15 строк:
В |
Вид |
Т |
Трапеза |
З |
Закуска |
1 |
Завтрак |
З |
Закуска |
2 |
Обед |
З |
Закуска |
3 |
Ужин |
С |
Суп |
1 |
Завтрак |
С |
Суп |
2 |
Обед |
С |
Суп |
3 |
Ужин |
Г |
Горячее |
1 |
Завтрак |
Г |
Горячее |
2 |
Обед |
Г |
Горячее |
3 |
Ужин |
Д |
Десерт |
1 |
Завтрак |
Д |
Десерт |
2 |
Обед |
Д |
Десерт |
3 |
Ужин |
Н |
Напиток |
1 |
Завтрак |
Н |
Напиток |
2 |
Обед |
Н |
Напиток |
3 |
Ужин |
В другом примере, где перемножаются таблицы Меню, Трапезы, Вид_блюд, Блюда:
SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.*
FROM Меню, Трапезы, Вид_блюд, Блюда;
|
образуется таблица (рис 3.1), содержащая 21 х 3 х 5 х 33 = 10395 строк.
Из первых 39 строк этой таблицы лишь две актуальных (отмечены "*"): в них совпадают номера блюд таблиц Меню и Блюда. В остальных – полная чепуха: к закускам отнесены супы и напитки, на завтрак предлагается незапланированный суп и т.д.
Если из декартова произведения убрать ненужные строки и столбцы, то можно получить актуальные таблицы, соответствующие любому из соединений.
Меню |
Трапезы |
Вид_блюд |
Блюда |
Т |
В |
БЛ |
Т |
Трапеза |
В |
Вид |
БЛ |
Блюдо |
В |
Основа |
Выход |
Труд |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
1 |
Салат летний |
З |
Овощи |
200. |
3 |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
2 |
Салат мясной |
З |
Мясо |
200. |
4 |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
3 |
Салат витаминный |
З |
Овощи |
200. |
4 * |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
12 |
Суп молочный |
С |
Молоко |
500. |
3 |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
13 |
Бастурма |
Г |
Мясо |
300. |
5 |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
32 |
Кофе черный |
Н |
Кофе |
100. |
1 |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
33 |
Кофе на молоке |
Н |
Кофе |
200. |
2 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
1 |
Салат летний |
З |
Овощи |
200. |
3 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
2 |
Салат мясной |
З |
Мясо |
200. |
4 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
3 |
Салат витаминный |
З |
Овощи |
200. |
4 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
4 |
Салат рыбный |
З |
Рыба |
200. |
4 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
5 |
Паштет из рыбы |
З |
Рыба |
120. |
5 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
6 |
Мясо с гарниром |
З |
Мясо |
250. |
3 * |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
Рис. 3.1. Иллюстрация декартова произведения
Очевидно, что отбор актуальных строк обеспечивается вводом в запрос WHERE фразы, в которой устанавливается соответствие между:
- кодами трапез (Т) в таблицах Меню и Трапезы (Меню.Т = Трапезы.Т),
- кодами видов блюд (В) в таблицах Меню и Вид_блюд (Меню.В = Вид_блюд.В),
- номерами блюд (БЛ) в таблицах Меню и Блюда (Меню.БЛ = Блюда.БЛ).
Такой скорректированный запрос
SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.*
FROM Меню, Трапезы, Вид_блюд, Блюда
WHERE Меню.Т = Трапезы.Т
AND Меню.В = Вид_блюд.В
AND Меню.БЛ = Блюда.БЛ;
|
позволит получить эквисоединение таблиц Меню, Трапезы, Вид_блюд и Блюда:
Т |
В |
БЛ |
Т |
Трапеза |
В |
Вид |
БЛ |
Блюдо |
В |
Основа |
Выход |
Труд |
1 |
З |
3 |
1 |
Завтрак |
З |
Закуска |
3 |
Салат витаминный |
З |
Овощи |
200. |
4 |
1 |
З |
6 |
1 |
Завтрак |
З |
Закуска |
6 |
Мясо с гарниром |
З |
Мясо |
250. |
3 |
1 |
Г |
19 |
1 |
Завтрак |
Г |
Горячее |
19 |
Омлет с луком |
Г |
Яйца |
200. |
5 |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
. |
3 |
Г |
16 |
3 |
Ужин |
Г |
Горячее |
16 |
Драчена |
Г |
Яйца |
180. |
4 |
3 |
Н |
30 |
3 |
Ужин |
Н |
Напиток |
30 |
Компот |
Н |
Фрукты |
200. |
2 |
3 |
Н |
31 |
3 |
Ужин |
Н |
Напиток |
31 |
Молочный напиток |
Н |
Молоко |
200. |
2 |
Легко заметить, что в эквисоединение таблиц вошли дубликаты столбцов, по которым проводилось соединение (Т, В и БЛ). Для исключения этих дубликатов можно создать естественное соединение тех же таблиц:
SELECT Т, В, БЛ, Трапеза, Вид, Блюдо, Основа, Выход, Труд
FROM Меню, Трапезы, Вид_блюд, Блюда
WHERE Меню.Т = Трапезы.Т
AND Меню.В = Вид_блюд.В
AND Меню.БЛ = Блюда.БЛ;
|
Реализация естественного соединения таблиц имеет вид
Т |
В |
БЛ |
Трапеза |
Вид |
Блюдо |
Основа |
Выход |
Труд |
1 |
З |
3 |
Завтрак |
Закуска |
Салат витаминный |
Овощи |
200. |
4 |
1 |
З |
6 |
Завтрак |
Закуска |
Мясо с гарниром |
Мясо |
250. |
3 |
1 |
Г |
19 |
Завтрак |
Горячее |
Омлет с луком |
Яйца |
200. |
5 |
. |
. |
. |
. |
. |
. |
. |
. |
. |
3 |
Г |
16 |
Ужин |
Горячее |
Драчена Яйца |
180. |
4 |
3 |
Н |
30 |
Ужин |
Напиток |
Компот |
Фрукты |
200. |
2 |
3 |
Н |
31 |
Ужин |
Напиток |
Молочный напиток |
Молоко |
200. |
2 |
Для исключения всех столбцов, по которым проводится соединение таблиц, надо создать композицию
SELECT Трапеза, Вид, Блюдо, Основа, Выход, Труд
FROM Меню, Трапезы, Вид_блюд, Блюда
WHERE Меню.Т = Трапезы.Т
AND Меню.В = Вид_блюд.В
AND Меню.БЛ = Блюда.БЛ;
|
имеющую вид
Трапеза |
Блюдо |
Вид |
Основа |
Выход |
Труд |
Завтрак |
Салат витаминный |
Закуска |
Овощи |
200. |
4 |
Завтрак |
Мясо с гарниром |
Закуска |
Мясо |
250. |
3 |
Завтрак |
Омлет с луком |
Горячее |
Яйца |
200. |
5 |
. |
. |
. |
. |
. |
. |
Ужин |
Драчена |
Горячее |
Яйца |
180. |
4 |
Ужин |
Компот |
Напиток |
Фрукты |
200. |
2 |
Ужин |
Молочный напиток |
Напиток |
Молоко |
200. |
2 |
В базе данных ПАНСИОН трудно подобрать несложный пример, иллюстрирующий тета-соединение таблиц. Поэтому сконструируем такой надуманный запрос:
SELECT Вид_блюд.*, Трапезы.*
FROM Вид_блюд, Трапезы
WHERE Вид > Трапеза;
|
позволяющий выбрать из полученного в п.3.2.1 декартова произведения таблиц Вид_блюд и Трапезы лишь те строки, в которых значение трапезы "меньше" (по алфавиту) значения вида блюда
В |
Вид |
Т |
Трапеза |
З |
Закуска |
1 |
Завтрак |
С |
Суп |
1 |
Завтрак |
С |
Суп |
2 |
Обед |
Н |
Напиток |
1 |
Завтрак |
При формировании соединения создается рабочая таблица, к которой применимы все операции, рассмотренные в главе 2: отбор нужных строк соединения (WHERE фраза), упорядочение получаемого результата (ORDER BY фраза) и агрегатирование данных (SQL-функции и GROUP BY фраза).
Например, для получения перечня блюд, предлагаемых в меню на завтрак, можно сформировать запрос на основе композиции (п. 3.2.4):
SELECT Вид, Блюдо, Основа, Выход, 'Номер -', БЛ
FROM Меню, Трапезы, Вид_блюд, Блюда
WHERE Меню.Т = Трапезы.Т
AND Меню.В = Вид_блюд.В
AND Меню.БЛ = Блюда.БЛ
AND Трапеза = ’Завтрак’;
|
Получим
Вид |
Блюдо |
Основа |
Выход |
'Номер -' |
БЛ |
Закуска |
Салат витаминный |
Овощи |
200. |
Номер - |
3 |
Закуска |
Мясо с гарниром |
Мясо |
250. |
Номер - |
6 |
Горячее |
Омлет с луком |
Яйца |
200. |
Номер - |
19 |
Горячее |
Пудинг рисовый |
Крупа |
160. |
Номер - |
21 |
Напиток |
Молочный напиток |
Молоко |
200. |
Номер - |
31 |
Напиток |
Кофе черный |
Кофе |
100. |
Номер - |
32 |
В п.3.6 можно познакомиться с достаточно полным примером соединения таблиц с различными дополнительными фразами.
В ряде приложений возникает необходимость одновременной обработки данных какой-либо таблицы и одной или нескольких ее копий, создаваемых на время выполнения запроса.
Например, при создании списков студентов (таблица Студенты) возможен повторный ввод данных о каком-либо студенте с присвоением ему второго номера зачетной книжки. Для выявления таких ошибок можно соединить таблицу Студенты с ее временной копией, установив в WHERE фразе равенство значений всех одноименных столбцов этих таблиц кроме столбцов с номером зачетной книжки (для последних надо установить условие неравенства значений).
Временную копию таблицы можно сформировать, указав имя псевдонима за именем таблицы во фразе FROM. Так, с помощью фразы
FROM Блюда X, Блюда Y, Блюда Z
|
будут сформированы три копии таблицы Блюда с именами X, Y и Z.
В качестве примера соединения таблицы с ней самой сформируем запрос на вывод таких пар блюд таблицы Блюда, в которых совпадает основа, а название первого блюда пары меньше (по алфавиту), чем номер второго блюда пары. Для этого можно создать запрос с одной копией таблицы Блюда (Копия):
SELECT Блюдо, Копия.Блюдо, Основа
FROM Блюда, Блюда Копия
WHERE Основа = Копия.Основа
AND Блюдо < Копия.Блюдо;
|
или двумя ее копиями (Первая и Вторая):
SELECT Первая.Блюдо, Вторая.Блюдо, Основа
FROM Блюда Первая, Блюда Вторая
WHERE Первая.Основа = Вторая.Основа
AND Первая.Блюдо < Вторая.Блюдо;
|
Получим результат вида
Первая.Блюдо |
Вторая.Блюдо |
Основа |
Морковь с рисом |
Помидоры с луком |
Овощи |
Морковь с рисом |
Салат летний |
Овощи |
Морковь с рисом |
Салат витаминный |
Овощи |
Помидоры с луком |
Салат витаминный |
Овощи |
Помидоры с луком |
Салат летний |
Овощи |
Салат витаминный |
Салат летний |
Овощи |
Бастурма |
Бефстроганов |
Мясо |
Бастурма |
Мясо с гарниром |
Мясо |
Бефстроганов |
Мясо с гарниром |
Мясо |
|