Лабораторна робота №3
ТЕМА РОБОТИ: Зв'язок таблиць.
МЕТА РОБОТИ: Навчитися використати декілько таблиць і зв'язки між ними.
Теоретичні відомості Запити з використанням декількох таблиць
Торкаючись питань проектування баз даних, ми з'ясували, що бази даних - це безліч взаємозалежних сутностей або відносин (таблиць) у термінології реляційних СУБД. При проектуванні прагнуть створювати таблиці, у кожній з яких утримувалася б інформація про один тип сутностей. Це полегшує модифікацію бази даних і підтримку її цілісності. Але такий підхід важко засвоюється починаючими проектантами, які намагаються прив'язати проект до майбутніх додатків і так організувати таблиці, щоб у кожній з них зберігалося все необхідне для реалізації можливих запитів. Типове питання: як же одержати відомості про те, де купити продукти для готування того або іншого блюда й визначити його калорійність і вартість, якщо потрібні дані "розсипані" по сімох різних таблицях? Чи не краще мати одну велику таблицю, що містить всі відомості бази даних ПАНСІОН ?
Навіть при відсутності засобів одночасного доступу до багатьом таблицям небажаний проект, у якому інформація про багатьох типах сутностей перемішана в одній таблиці. SQL же має чудовий механізм для одночасної або послідовної обробки даних з декількох взаємозалежних таблиць. У ньому реалізовані можливості "з'єднувати" або "поєднувати" кілька таблиць і так називані "вкладені підзапити". Наприклад, щоб одержати перелік постачальників продуктів, необхідних для готування Сирників, можливий запит
SELECT Продукт, Ціна, Назва, Статус
FROM Продукти, Склад, Блюда, Поставки, Постачальники
WHERE Продукти.ПР = Склад.ПР
AND Склад.БЛ = Блюда.БЛ
AND Поставки.ПР = Склад.ПР
AND Поставки.ПС = Постачальники.ПС
AND Блюдо = 'Сирники'
AND Ціна IS NOT NULL;
Продукт |
Ціна |
Назва |
Статус |
Яйця |
1.8 |
ПОРТОС |
кооператив |
Яйця |
2. |
КОРЮШКА |
кооператив |
Сметана |
3.6 |
ПОРТОС |
кооператив |
Сметана |
2.2 |
ОГУРЕЧИК |
ферма |
Сир |
1. |
ОГУРЕЧИК |
ферма |
Борошно |
0.5 |
УРОЖАЙ |
коопторг |
Цукор |
0.94 |
ТУЛЬСЬКИЙ |
універсам |
Цукор |
1. |
УРОЖАЙ |
коопторг |
Він отриманий у такий спосіб: СУБД послідовно формує рядки декартового добутку таблиць, перерахованих у фразі FROM, перевіряє, чи задовольняють дані сформованого рядка умовам фрази WHERE, і якщо задовольняють, то включає у відповідь на запит ті поля, які перераховані у фразі SELECT.
Варто підкреслити, що в SELECT й WHERE (щоб уникнути двозначності) посилання на всі (*) або окремі стовпці можуть (а іноді й повинні) уточнюватися ім'ям відповідної таблиці, наприклад, Поставки.ПС, Постачальники.ПС, Меню.*, Склад.БЛ, Блюда.* і т.п.
Очевидно, що за допомогою з'єднання нескладно сформувати запит на обробку даних з декількох таблиць. Крім того, у такий запит можна включити будь-які частини пропозиції SELECT. Отже, з'єднання дозволяють обробляти безліч взаємозалежних таблиць як єдину таблицю, у якій перемішана інформація про велику кількість сутностей. Тому починаючий проектант бази даних може спокійно створювати маленькі нормалізовані таблиці, тому що він завжди може одержати з них будь-яку "більшу" таблицю.
Крім механізму з'єднань в SQL є механізм вкладених підзапитів, що дозволяє об'єднати кілька простих запитів у єдиній пропозиції SELECT. Іншими словами, вкладений підзапит - це вже знайомий нам підзапит (з невеликими обмеженнями), що вкладений в WHERE фразу іншого вкладеного підзапита або WHERE фразу основного запиту.
Для ілюстрації вкладеного підзапита повернемося до попереднього приклада й спробуємо одержати перелік тих постачальників продуктів для Сирників, які поставляють потрібні продукти за мінімальну ціну.
SELECT Продукт, Ціна, Назва, Статус
FROM Продукти, Склад, Блюда, Поставки, Постачальники
WHERE Продукти.ПР = Склад.ПР
AND Склад.БЛ = Блюда.БЛ
AND Поставки.ПР = Склад.ПР
AND Поставки.ПС = Постачальники.ПС
AND Блюдо = 'Сирники'
AND Ціна = ( SELECT MIN(Ціна)
FROM Поставки X
WHERE X.ПР = Поставки.ПР );
Результат запиту має вигляд
Продукт |
Ціна |
Назва |
Статус |
Яйця |
1.8 |
ПОРТОС |
кооператив |
Цукор |
0.94 |
ТУЛЬСЬКИЙ |
універсам |
Борошно |
0.5 |
УРОЖАЙ |
коопторг |
Сметана |
2.2 |
ОГУРЕЧИК |
ферма |
Сир |
1. |
ОГУРЕЧИК |
ферма |
Тут за допомогою підзапита, розміщеного в трьох останніх рядках запиту, описується процес визначення мінімальної ціни кожного продукту для Сирників і пошук постачальника, що пропонує цей продукт за таку ціну.
Запити, що використають з'єднання
Декартовий добуток таблиць
У літературі показано, що з'єднання - це підмножини декартова добутку. Тому що декартово добуток 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 Меню, Трапези, Вид_блюд, Блюда;
утвориться таблиця (рис 2.5), що містить 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 Вид Трапеза;
що дозволяє вибрати з отриманого в декартова добутку таблиць Вид_блюд і Трапези лише ті рядки, у яких значення трапези "менше" (за алфавітом) значення виду блюда:
В |
Вид |
Т |
Трапеза |
З |
Закуска |
1 |
Сніданок |
С |
Суп |
1 |
Сніданок |
С |
Суп |
2 |
Обід |
Н |
Напій |
1 |
Сніданок |
З'єднання таблиць із додатковою умовою
При формуванні з'єднання створюється робоча таблиця, до якої застосовні всі операції: відбір потрібних рядків з'єднання (WHERE фраза), упорядкування одержуваного результату (ORDER BY фраза) і агрегатування даних (SQL-функції й GROUP BY фраза).
Наприклад, для одержання переліку блюд, пропонованих у меню на сніданок, можна сформувати запит на основі композиції:
SELECT Вид, Блюдо, Основа, Вихід, 'Номер -', БЛ
FROM Меню, Трапези, Вид_блюд, Блюда
WHERE Меню.Т = Трапези.Т
AND Меню.В = Вид_блюд.В
AND Меню.БЛ = Блюда.БЛ
AND Трапеза = ’Сніданок’;
Одержимо
Вид |
Блюдо |
Основа |
Вихід |
'Номер -' |
БЛ |
Закуска |
Салат вітамінний |
Овочі |
200. |
Номер - |
3 |
Закуска |
М'ясо з гарніром |
М'ясо |
250. |
Номер - |
6 |
Гаряче |
Омлет з луком |
Яйця |
200. |
Номер - |
19 |
Гаряче |
Пудинг рисовий |
Крупу |
160. |
Номер - |
21 |
Напій |
Молочний напій |
Молоко |
200. |
Номер - |
31 |
Напій |
Кава чорний |
Кава |
100. |
Номер - |
32 |
З'єднання таблиці зі своєю копією
У ряді додатків виникає необхідність одночасної обробки даних якої-небудь таблиці й однієї або декількох її копій, створюваних на час виконання запиту.
Наприклад, при створенні списків студентів (таблиця Студенти) можливий повторне уведення даних про якого-небудь студента із присвоєнням йому другого номера залікової книжки. Для виявлення таких помилок можна з'єднати таблицю Студенти з її тимчасовою копією, установивши в WHERE фразі рівність значень всіх однойменних стовпців цих таблиць крім стовпців з номером залікової книжки (для останніх треба встановити умову нерівності значень).
Тимчасову копію таблиці можна сформувати, указавши ім'я псевдоніма за ім'ям таблиці у фразі FROM. Так, за допомогою фрази
FROM Блюда X, Блюда Y, Блюда Z
будуть сформовані три копії таблиці Блюда з іменами X, Y й Z.
Як приклад з'єднання таблиці з нею самої сформуємо запит на висновок таких пар блюд таблиці Блюда, у яких збігається основа, а назва першого блюда пари менше (за алфавітом), чим номер другого блюда пари. Для цього можна створити запит з однією копією таблиці Блюда (Копія):
SELECT Блюдо, Копія.Блюдо, Основа
FROM Блюда, Блюда Копія
WHERE Основа = Копія.Основа
AND Блюдо < Копія.Блюдо;
або двома її копіями (Перша й Друга):
SELECT Перша.Блюдо, Друга.Блюдо, Основа
FROM Блюда Перша, Блюда Друга
WHERE Перша.Основа = Друга.Основа
AND Перша.Блюдо < Друга.Блюдо;
Одержимо результат виду
Перша.Блюдо |
Друга.Блюдо |
Основа |
Морква з рисом |
Помідори з луком |
Овочі |
Морква з рисом |
Салат літній |
Овочі |
Морква з рисом |
Салат вітамінний |
Овочі |
Помідори з луком |
Салат вітамінний |
Овочі |
Помідори з луком |
Салат літній |
Овочі |
Салат вітамінний |
Салат літній |
Овочі |
Бастурма |
Бефстроганов |
М'ясо |
Бастурма |
М'ясо з гарніром |
М'ясо |
Бефстроганов |
М'ясо з гарніром |
М'ясо |
Вкладені підзапити
Види вкладених підзапитів
Вкладений підзапит - це підзапит, ув'язнений у круглі дужки й вкладений в WHERE (HAVING) фразу пропозиції SELECT або інших пропозицій, що використають WHERE фразу. Вкладений підзапит може містити у своєї WHERE (HAVING) фразі інший вкладений підзапит і т.д. Неважко догадатися, що вкладений підзапит створено для того, щоб при відборі рядків таблиці, сформованої основним запитом, можна було використати дані з інших таблиць (наприклад, при відборі блюд для меню використати дані про наявність продуктів у коморі пансіонату).
Існують прості й корельовані вкладені підзапити. Вони включаються в WHERE (HAVING) фразу за допомогою умов IN, EXISTS або однієї з умов порівняння ( = | < | < | <= | | = ). Прості вкладені підзапити обробляються системою "знизу нагору". Першим обробляється вкладений підзапит самого нижнього рівня. Безліч значень, отримана в результаті його виконання, використається при реалізації підзапита більше високого рівня й т.д.
Запити з корельованими вкладеними підзапитами обробляються системою у зворотному порядку. Спочатку вибирається перший рядок робочої таблиці, сформованої основним запитом, і з неї вибираються значення тих стовпців, які використаються у вкладеному підзапиті (вкладених підзапитів). Якщо ці значення задовольняють умовам вкладеного підзапита, то обраний рядок включається в результат. Потім вибирається другий рядок і т.д., поки в результат не будуть включені всі рядки, що задовольняють вкладеному підзапиту (послідовності вкладених підзапитів).
Слід зазначити, що SQL має велику надмірність у тому розумінні, що він часто надає кілька різних способів формулювання того самого запиту. Тому в багатьох прикладах будуть використані вже знайомі нам концептуальні формулювання запитів. І незважаючи на те, що частина з них успішно реалізується за допомогою з'єднань, тут все-таки будуть наведені їхні варіанти з використанням вкладених підзапитів. Це пов'язане з необхідністю детального знайомства зі створенням і принципом виконання вкладених підзапитів, тому що існує чимало завдань (особливо на видалення й зміну даних), які не можуть бути реалізовані іншим способом. Крім того, різні формулювання того самого запиту вимагають для свого виконання різних ресурсів пам'яті й можуть значно відрізнятися за часом реалізації в різних СУБД.
Прості вкладені підзапити
Прості вкладені підзапити використаються для подання безлічі значень, дослідження яких повинне здійснюватися в якому-небудь предикаті IN, що ілюструється в наступному прикладі: видати назву й статус постачальників продукту з номером 11, тобто помідорів.
|
Результат: |
||
SELECT Назва, Статус FROM Постачальники WHERE ПС IN ( SELECT ПС FROM Поставки WHERE ПР = 11 ); |
Назва |
Статус |
|
СИТНИЙ |
ринок |
||
УРОЖАЙ |
коопторг |
||
ЛІТО |
агрофірма |
||
КОРЮШКА |
кооператив |
Як ми вже відзначали при обробці повного запиту система виконує насамперед вкладений підзапит. Цей підзапит видає безліч номерів постачальників, які поставляють продукт із кодом ПР = 11, а саме безліч (1, 5, 6, 8). Тому первісний запит еквівалентний такому простому запиту:
SELECT Назва, Статус
FROM Постачальники
WHERE ПС IN (1, 5, 6, 8);
Підзапит із декількома рівнями вкладеності можна проілюструвати на тім же прикладі. Нехай потрібно довідатися не постачальників продукту 11, як це робилося в попередніх запитах, а постачальників помідорів, що є продуктом з номером 11. Для цього можна дати запит
SELECT Назва, Статус
FROM Постачальники
WHERE ПС IN
( SELECT ПС
FROM Поставки
WHERE ПР IN
( SELECT ПР
FROM Продукти
WHERE Продукт = 'Помідори' ));
У цьому випадку результатом самого внутрішнього підзапита є тільки одне значення (11). Як уже було показано вище, підзапит наступного рівня у свою чергу дає в результаті безліч (1, 5, 6, 8). Останній, самий зовнішній SELECT, обчислює наведений вище остаточний результат. Взагалі допускається будь-яка глибина вкладеності підзапитів.
Той же результат можна одержати за допомогою з'єднання
SELECT Назва, Статус
FROM Постачальники, Поставки, Продукти
WHERE Постачальники.ПС = Поставки.ПС
AND Поставки.ПР = Продукти.ПР
AND Продукт = 'Помідори';
При виконанні цього компактного запиту система повинна одночасно обробляти дані із трьох таблиць, тоді як у попередньому прикладі ці таблиці обробляються по черзі. Природно, що для їхньої реалізації потрібні різні ресурси пам'яті й часу, однак цього неможливо відчути при роботі з обмеженим обсягом даних в ілюстративній базі ПАНСІОН.
Використання однієї й тієї ж таблиці в зовнішньому й вкладеному підзапиті
Видати номера постачальників, які поставляють хоча б один продукт, що поставляє постачальником 6.
|
Результат: |
SELECT DISTINCT ПС FROM Поставки WHERE ПР IN ( SELECT ПР FROM Поставки WHERE ПС = 6); |
ПС |
1 |
|
3 |
|
5 |
|
6 |
|
8 |
Відзначимо, що посилання на Поставки у вкладеному підзапиті означає не те ж саме, що посилання на Поставки в зовнішньому запиті. У дійсності, два імені Поставки позначають різні значення. Щоб цей факт став явним, корисно використати псевдоніми, наприклад, X й Y:
SELECT DISTINCT X.ПС
FROM Поставки X
WHERE X.ПР IN
( SELECT Y.ПР
FROM Поставки Y
WHERE Y.ПС = 6 );
Тут X й Y - довільні псевдоніми таблиці Поставки, обумовлені у фразі FROM і використовувані як явні уточнювачі у фразах SELECT й WHERE. Нагадаємо, що псевдоніми визначені лише в межах одного запиту.
Вкладений підзапит із оператором порівняння, відмінним від IN
Видати номера постачальників, що перебувають у тім же місті, що й постачальник з номером 6.
|
Результат: |
SELECT ПС FROM Постачальники WHERE Місто = ( SELECT Місто FROM Постачальники WHERE ПС = 6 ); |
ПС |
1 |
|
4 |
|
6 |
У подібних запитах можна використати й інші оператори порівняння (<, <=, <, = або ), однак, якщо вкладений підзапит повертає більше одного значення й не використається оператор IN, буде виникати помилка.
Корельовані вкладені підзапити
Видати назва й статус постачальників продукту з номером 11.
SELECT Назва, Статус
FROM Постачальники
WHERE 11 IN
( SELECT ПР
FROM Поставки
WHERE ПС = Постачальники.ПС );
Такий підзапит відрізняється від розглянутого тим, що вкладений підзапит не може бути оброблений перш, ніж буде оброблятися зовнішній підзапит. Це пов'язане з тим, що вкладений підзапит залежить від значення Постачальники.ПС а воно змінюється в міру того, як система перевіряє різні рядки таблиці Постачальники. Отже, з концептуальної точки зору обробка здійснюється в такий спосіб:
-
Система перевіряє перший рядок таблиці Постачальники. Припустимо, що це рядок постачальника з номером 1. Тоді значення Постачальники.ПС буде в цей момент має значення, рівне 1, і система обробляє внутрішній запит
( SELECT ПР
FROM Поставки
WHERE ПС = 1 );
одержуючи в результаті безліч (9, 11, 12, 15). Тепер система може завершити обробку для постачальника з номером 1. Вибірка значень Назва й Статус для ПС=1 (СИТНИЙ і ринок) буде проведена тоді й тільки тоді, коли ПР=11 буде належати цій безлічі, що, мабуть, справедливо.
-
Далі система буде повторювати обробку такого роду для наступного постачальника й т.д. доти, поки не будуть розглянуті весь рядки таблиці Постачальники.
Подібні підзапити називаються коррелированными, тому що їхній результат залежить від значень, певних у зовнішньому підзапиті. Обробка коррелированного підзапита, отже, повинна повторюватися для кожного значення витягає із зовнішнього підзапита, а не виконуватися раз і назавжди.
Розглянемо приклад використання однієї й тієї ж таблиці в зовнішньому підзапиті й коррелированном вкладеному підзапиті.
Видати номера всіх продуктів, що поставляють тільки одним поставщиком.
|
Результат: |
SELECT DISTINCT X.ПР FROM Поставки X WHERE X.ПР NOT IN ( SELECT Y.ПР FROM Поставки Y WHERE Y.ПС <> X.ПС ); |
X.ПР |
17 |
Дія цього запиту можна пояснити в такий спосіб: "По черзі для кожного рядка таблиці Поставки, скажемо X, виділити значення номера продукту (ПР), якщо й тільки якщо це значення не входить у деякий рядок, скажемо, Y, тієї ж таблиці, а значення стовпця номер постачальника (ПС) у рядку Y не дорівнює його значенню в рядку X".
Відзначимо, що в цьому формулюванні повинен бути використаний принаймні один псевдонім - або X, або Y.
Запити, що використовують EXISTS
Квантор EXISTS (існує) - поняття, запозичене з формальної логіки. У мові SQL предикат із квантором існування представляється вираженням EXISTS (SELECT * FROM ...).
Таке вираження вважається щирим тільки тоді, коли результат обчислення "SELECT * FROM ..." є непустою безліччю, тобто коли існує який-небудь запис у таблиці, зазначеної у фразі FROM підзапита, що задовольняє умові WHERE підзапита. (Практично цей підзапит завжди буде коррелированным безліччю.)
Розглянемо приклади. Видати назви постачальників, що поставляють продукт із номером 11.
|
Результат: |
SELECT Назва FROM Постачальники WHERE EXISTS ( SELECT * FROM Поставки WHERE ПС= Постачальники.ПС AND ПР = 11 ); |
Назва |
СИТНИЙ |
|
УРОЖАЙ |
|
КОРЮШКА |
|
ЛІТО |
Система послідовно вибирає рядки таблиці Постачальники, виділяє з них значення стовпців Назва й ПС, а потім перевіряє, чи є вірною умова існування, тобто існуе в таблиці Поставки хоча б один рядок зі значенням ПР=11 і значенням ПС, рівним значенню ПС, обраному з таблиці Постачальники. Якщо умова виконується, то отримане значення стовпця Назва включається в результат.
Припустимо, що перші значення полів Назва й ПС рівні, відповідно, 'СИТНИЙ' й 1. Тому що в таблиці Поставки є рядок із ПР=11 і ПС=1, то значення 'СИТНИЙ' повинне бути включене в результат.
Хоча цей перший приклад тільки показує інший спосіб формулювання запиту для завдання, розв'язуваної й іншими шляхами (за допомогою оператора IN або з'єднання), EXISTS являє собою одну з найбільш важливих можливостей SQL. Фактично будь-який запит, що виражається через IN, може бути альтернативним образом сформульований також за допомогою EXISTS. Однак зворотне висловлення несправедливо.
Видати Назва й Статус постачальників, що не поставляють продукт із номером 11.
|
Результат: |
||
SELECT Назва, Статус FROM Постачальники WHERE NOT EXISTS ( SELECT * FROM Поставки WHERE ПС = Постачальники.ПС AND ПР = 11 ); |
Назва |
Статус |
|
ПОРТОС |
кооператив |
||
ШУШАРЫ |
радгосп |
||
ТУЛЬСЬКИЙ |
універсам |
||
ОГУРЕЧИК |
ферма |
Функції в підзапиті
Тепер, після знайомства з різними формулюваннями вкладених підзапитів і псевдонімами легше зрозуміти текст й алгоритм реалізації запиту на одержання тих постачальників продуктів для Сирників, які поставляють ці продукти за мінімальну ціну:
SELECT Продукт, Ціна, Назва, Статус
FROM Продукти, Склад, Блюда, Поставки, Постачальники
WHERE Продукти.ПР = Склад.ПР
AND Склад.БЛ = Блюда.БЛ
AND Поставки.ПР = Склад.ПР
AND Поставки.ПС = Постачальники.ПС
AND Блюдо = 'Сирники'
AND Ціна = ( SELECT MIN(Ціна)
FROM Поставки X
WHERE X.ПР = Поставки.ПР );
Природно, що це корельований підзапит: тут спочатку визначається мінімальна ціна продукту, що входить до складу Сирників, і тільки потім з'ясовується його постачальник.
Об'єднання (UNION)
Реляційна операція "Об'єднання", що дозволяє одержати відношення, що складається із всіх рядків, що входять в одне або обоє поєднуваних відносини. Але при цьому вихідні відносини або їхні поєднувані проекції повинні бути сумісними по об'єднанню. Для SQL це означає, що дві таблиці можна поєднувати тоді й тільки тоді, коли:
-
вони мають однакове число стовпців, наприклад, m;
-
для всіх i (i = 1, 2, ..., m) i-й стовпець першої таблиці й i-й стовпець другої таблиці мають у точності однаковий тип даних.
Наприклад, видати назви продуктів, у яких немає жирів, або вхідних до складу блюда з кодом БЛ = 1:
|
Результат: |
Продукт |
SELECT Продукт FROM Продукти WHERE Жири = 0 UNION SELECT Продукт FROM Соста WHERE БЛ = 1 |
Майонез |
|
Лук |
||
Помідори |
||
Зелень |
||
Яблука |
||
Цукор |
Із цього простого приклада видно, що надлишкові дублікати завжди виключаються з результату UNION. Тому, хоча в розглянутому прикладі Помідори, Зелень й Яблука вибираються обома із двох складові пропозиції SELECT, в остаточному результаті вони з'являються тільки один раз.
Пропозицією з UNION можна об'єднати будь-яке число таблиць (проекцій таблиць). Так, до попереднього запиту можна додати (перед крапкою з коми) конструкцію
UNION
SELECT Продукт
FROM Продукти
WHERE Ca < 250
що дозволяє додати до списку продуктів Масло, Рис, Борошно й Кава. Однак той же результат можна одержати простою зміною фрази WHERE першої частини вихідного запиту
WHERE Жири = 0 OR Ca <250
Реалізація операцій реляційної алгебри пропозицією SELECT
За допомогою пропозиції SELECT можна реалізувати будь-яку операцію реляційної алгебри.
Селекція (горизонтальна підмножина) таблиці створюється з тих її рядків, які задовольняють заданим умовам. Приклад:
SELECT *
FROM Блюда
WHER Основа = 'Молоко'
AND Вихід 200;
Проекція (вертикальна підмножина) таблиці створюється із зазначених її стовпців (у заданому порядку) з наступним виключенням надлишкових дублікатів рядків. Приклад:
SELECT DISTINCT Блюдо, Вихід, Основа
FROM Блюда;
Об'єднання двох таблиць містить ті рядки, які є або в першої, або в другий, або в обох таблицях. Приклад:
SELECT Блюдо, Основа, Вихід
FROM Блюда
WHER Основа = 'Овочі'
UNION
SELECT Блюдо, Основа, Вихід
FROM Блюда
WHER В = 'Г';
Перетинання двох таблиць містить тільки ті рядки, які є й у першої, і в другий. Приклад:
SELECT БЛ
FROM Склад
WHERE БЛ IN
( SELECT БЛ
FROM Меню);
Різниця двох таблиць містить тільки ті рядки, які є в першої, але відсутні в другий. Приклад:
SELECT БЛ
FROM Склад
WHERE БЛ NOT IN
( SELECT БЛ
FROM Меню);
Декартово добуток таблиць і різні види з'єднань були докладно розглянуті раніше.