Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Структурована мова запитів SQL.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
663.04 Кб
Скачать

Використання вкладених запитів

У самому загальному випадку, запити можуть управляти іншими запитами - це робиться шляхом розміщення запиту усередину предиката іншого, котрий використає висновок внутрішнього запиту для встановлення вірного або невірного значення предиката. Наприклад, потрібна інформація про успішність студента із прізвищем Поляков, однак у силу яких-небудь причин невідомий номер цього студента. Тоді необхідно витягти цей номер з таблиці з даними про студентів, і після цього застосовувати результат до таблиці успішності. Це можна реалізувати шляхом наступної конструкції:

SELECT *

FROM USP

WHERE SNUM =

(SELECT SNUM

FROM STUDENTS

WHERE SFAM = 'Поляков');

Результат цього запиту буде наступний:

UNUM OCENKA UDATE SNUM PNUM

--------------------------------

1001 5 10/06/1999 3412 2001

1004 4 12/06/1999 3412 2003

Щоб виконати основний запит, SQL спочатку повинен оцінити внутрішній запит (його називають підзапитом) усередині пропозиції WHERE. Відбувається це традиційним чином, тобто виконується вкладений запит, що витягає необхідні для визначення значення предиката дані, а тільки потім -основний. Зрозуміло, підзапит повинен вибрати тільки одне поле, а тип даних цього поля повинен збігатися з тим значенням, з яким він буде рівнятися в предикаті.

З іншого боку, можлива ситуація, коли подзапрос видає як результат кілька різних значень, що може зробити нездійсненним оцінку предиката основного запиту, і команда видасть помилку. При використанні, подзапросов у предикатах, заснованих на реляційних операторах, обов'язково потрібно переконатися, що використано підзапит, що буде відображати тільки один рядок висновку. Крім того, при використанні підзапиту, що взагалі не виводить ніяких значень, основний запит не виведе ніяких значень: його предикат буде мати невідоме значення.

У деяких випадках варто використати DISTINCT для того, щоб у подзапросе одержати одиночне значення. Припустимо, що викладачі можуть вести заняття по різних дисциплінах. Тоді для одержання відповіді на питання про те, які дисципліни веде викладач Викулина, можна скористатися запитом:

SELECT *

FROM PREDMET

WHERE TNUM =

(SELECT DISTINCT TNUM

FROM TEACHERS

WHERE TFAM = 'Викулина');

У результаті буде отримано:

PNUM PNAME TNUM HOURS COURS

-------------------------------------

2001 Фізика 4001 34 1

Подзапрос установив, що значення поля TNUM збіглося із прізвищем Викулина при значенні 4001, а потім основний запит виділив всі записи із цим значенням TNUM з таблиці предметів. Т.к., загалом кажучи, могло вийти, що викладач веде кілька предметів, то фраза DISTINCT у цьому випадку обов'язкова - якщо подзапит повернув би більше одного значення, те це викликало б помилку.

Варто мати на увазі, що предикати з підзапитами є необоротними, тобто предикати, що включають підзапити, використають конструкцію в наступному порядку:

<ВИРАЗ> <ОПЕРАТОР> <ПІДЗАПИТ>,

і в жодному разі не

< ПІДЗАПИТ > <ОПЕРАТОР> <ВИРАЗ>,

або

< ПІДЗАПИТ > <ОПЕРАТОР> < ПІДЗАПИТ >,

Інакше кажучи, що попередній приклад, записаний у такий спосіб:

SELECT *

FROM PREDMET

WHERE (SELECT DISTINCT TNUM

FROM TEACHERS

WHERE TFAM 'Викулина') = TNUM;

є невірним.

Зрозуміло, у підзапитах допускається використання агрегатних функцій - це зручно й з тієї причини, що вони автоматично роблять одиночне значення для будь-якого числа рядків, що може бути використане в основному предикаті. Наприклад, якщо виникає необхідність у висновку всіх оцінок по навчальних дисциплінах, значення яких вище за середнє, то для цього скористаємося запитом:

SELECT *

FROM USP

WHERE OCENKA >

(SELECT AVG (OCENKA)

FROM USP);

Висновок такого запиту наступний:

UNUM OCENKA UDАТЕ SNUM PNUM

----------------------------------------

1001 5 10/06/1999 3412 2001

1005 5 12/06/1999 3416 2004

Середня оцінка за наявним даними становить 4.2, отже, основний запит вибирає тільки ті записи, у яких значення поля OCENKA більше, ніж 4.2.

Не варто забувати, що згруповані агрегатні функції, певні в термінах пропозиції GROUP BY, можуть видавати численні значення, а виходить, не допускаються в підзапитах такого характеру. Навіть якщо GROUP BY або HAVING використаються так, що тільки одна група значень виводиться за допомогою подзапроса, однаково команда буде відхилена. До речі кажучи, можна використати підзапити, які роблять будь-яке число рядків, якщо використовується спеціальний оператор IN (оператори BETWEEN, LIKE, і IS NULL не можуть використатися з підзапитами). IN визначає набір значень предиката, одне йз яких повинне збігатися з іншим один по одному. При використанні IN з підзапитом, SQL просто формує цей набір з висновку підзапиту, а виходить, допускається використання IN для того, щоб виконати такий самий підзапит. Наприклад, запит

SELECT *

FROM PREDMET

WHERE PREDMET.TNUM IN

(SELECT TEACHERS.TNUM

FROM TEACHERS

WHERE TEACHERS.TFAM

BETWEEN 'И' AND 'С');

Висновок цього запиту такий:

PNUM PNAME TNUM HOURS COURS

------------------------------

2002 Хімія 4002 68 1

2003 Математика 4003 68 1

2005 Економіка 4004 17 3

Такий запит набагато зрозуміліше, ніж виконаний за допомогою об'єднання, хоча результат, що дає такий же:

SELECT PREDMET. РNUМ, PREDMET.PNAME,

TEACHERS.TNUM, PREDMET.HOURS,

PREDMET.COURS

FROM PREDMET, TEACHERS

WHERE TEACHERS.TNUM = PREDMET.TNUM

AND TEACHERS.TFAM BETWEEN 'И' AND 'C';

Загалом кажучи, швидкість виконання того або іншого запиту, наприклад, як у розглянутому випадку, залежить від реалізації тієї СКБД, що для цього використовується. Вище вже піднімалася проблематика, пов'язана з роботою оптимізатора запитів, що намагається знайти найбільш ефективний спосіб виконання. Традиційно гарний оптимізатор найчастіше перетворить варіант об'єднання в підзапит, що звичайно виконується швидше.

І ще один цікавий момент: у будь-якій ситуації, де застосовується реляційний оператор рівності (=), можна використати IN. На відміну від першого, IN не може змусити запит зазнати невдачі, якщо підзапитом обрано більше чим одне значення. Це може бути або перевагою або недоліком. Наприклад, уже розглянутий нами вище запит можна переписати в такий спосіб:

SELECT *

FROM PREDMET

WHERE TNUM IN

(SELECT TNUM

FROM TEACHERS

WHERE TFAM = 'Викулина');

Таким чином, підзапити завжди визначають одиночні стовпці - це обов'язково, оскільки обраний висновок рівняється з одиночним значенням. Підтвердженням цьому є те. Що команда SELECT * не може використатися в підзапиті. У підзапиті допускається використати вираження, засноване на поле, а не просто саме поле, у пропозиції SELECT. Це може бути виконане за допомогою реляційних операторів або при використанні IN. Прикладом може служити наступний запит:

SELECT *

FROM PREDMET

WHERE PNUM =

(SELECT PNUM-1

FROM PREDMET

WHERE PNAME ='Філософія')

Висновок цього запиту такий:

PNUM PNAME TNUM HOURS COURS

------------------------------------

2003 Математика 4003 68 1

Цей запит знаходить інформацію про навчальний предмет, код якого на 1 менше коду філософії. Зрозуміло, поле PNUM не повинне містити повторюваних значень, інакше запит викличе помилку.

Можна також використати підзапити усередині пропозиції HAVING. Ці підзапити можуть використати свої власні агрегатні функції, якщо вони не роблять численних значень, або використати GROUP BY або HAVING. Наприклад, розглянемо запит:

SELECT OCENKA, COUNT (DISTINCT SNUM)

FROM USP

GROUP BY OCENKA

HAVING OCENKA > =

(SELECT AVG (OCENKA)

FROM USP

WHERE PNUM = 2003);

Висновок для цього запиту наступний:

OCENKA

---------

5 2

4 2

Даний запит підраховує кількість студентів з оцінками вище середньої, чим по дисципліні з PNUM = 2003.

Тепер кілька слів про об'єднання запитів і використанні при цьому підзапити. Операція, що буває часто корисне - це об'єднання із двох запитів, у якому другий запит вибирає рядка, виключені першим. Найбільше часто це роблять для того, щоб не виключати рядка, які не задовольнили предикату при об'єднанні таблиць. Цей процес прийнятий називати зовнішнім об'єднанням.

Припустимо, що деякі зі студентів ще не одержали оцінку, однак уже внесені в таблицю USP. Наприклад, у цю таблицю доданий запис {1006, NULL, NULL, 3416, NULL}. Якщо виникає необхідність у перегляді успішності студентів по дисципліні, не з огляду на тих, хто ще не одержав оцінку. Цього можна досягти, формуючи об'єднання із двох запитів, один із яких виконує об'єднання, а іншої вибирає студентів з NULL значеннями поля OCENKA. Цей останній запит повинен вставляти повідомлення в поля, що відповідають полю PNAME, і значення 0 у поле OCENKA у першому запиті. Як було розглянуто раніше, можна вставляти текстові рядки у висновок, щоб ідентифікувати запит, що вивів даний рядок. Використання цієї методики в зовнішнім об'єднанні дає можливість застосовувати предикати для класифікації, а не для виключення. Наступний запит виконує ці дії:

SELECT USP.SNUM, STUDENTS.SFAM,

PREDMET . PNAME , USP. OCENKA.

FROM USP, STUDENTS, PREDMET

WHERE USP.SNUM = STUDENTS.SNUM

AND USP.PNUM = PREDMET.PNUM

UNION

SELECT USP.SNUM, STUDENTS.SFAM,

'НЕМАЄ ', 0

FROM USP, STUDENTS

WHERE USP.SNUM = STUDENTS.SNUM

AND NOT USP.OCENKA = ANY

(SELECT OCENKA

FROM USP)

ORDER BY 2 ASC;

Висновок цього запиту наступний:

-------------------------------------------------

3414 Гриценко Економіка 3

3416 Нагорний Філософія 5

3416 Нагорний НЕМАЄ 0

3412 Поляков Фізика 5

  1. Поляков Математика 4

  2. Старова Математика 4

Зверніть увагу на те, що рядок 'НЕМАЄ ' була доповнена пробілами, щоб одержати збіг поля PNAME по довжині. Другий запит вибирає навіть ті рядки, які були виключені першими.

Кілька слів про використаного оператора ANY, більш докладно про яке будемо говорити нижче. Оператор ANY бере всі значення, виведені підзапитом (для нашого випадку - це всі значення, OCENKA у таблиці USP), і оцінює їх як вірні, якщо кожне з них рівняється значенню оцінки поточного запису зовнішнього запиту.

На закінчення розмови про вкладені запити, поговоримо про так званих співвіднесений підзапит. Коли використаються підзапити, в SQL є можливість звернутися до внутрішнього запиту таблиці в пропозиції зовнішнього запиту FROM, за допомогою співвіднесеного підзапиту. При цьому підзапит виконується неодноразово, по одному разі для кожного запису таблиці основного запиту. З використанням співвіднесеного підзапиту можна знайти дані на всіх студентів, які одержували оцінки 10/06/1999:

SELECT *

FROM STUDENTS FIRST

WHERE 10/06/1999 IN

SELECT UDATE

FROM USP SECOND

WHERE FIRST.SNUM = SECOND.SNUM);

Висновок цього запиту такий:

SNUM SFAM SIMA SOTCH STIP

-------------------------------------------------------------------------------------

3412 Поляков Анатолій Олексійович 25.50

3413 Старова Любов Михайлівна 17.00

У цьому прикладі FIRST й SECOND - псевдоніми таблиць, при цьому виходить, що значення в поле SNUM зовнішнього запиту міняється, а виходить, внутрішній запит повинен виконуватися окремо для кожного рядка зовнішнього запиту.

Рядок зовнішнього запиту, для якої внутрішній запит щораз буде виконуватися, будемо називати поточним рядком. З обліком цього, процедура оцінки, виконуваної співвіднесеним підзапитом:

  • вибір рядка з таблиці в зовнішньому запиті - це поточний рядок;

  • збереження значення поточного рядка в псевдонімі, ім'я якого визначено в пропозиції FROM зовнішнього запиту;

  • виконання підзапиту, при цьому скрізь, де знайдений псевдонім із зовнішнього запиту, використається значення з поточного рядка (це прийнято називати зовнішнім посиланням):

  • оцінка предиката зовнішнього запиту на основі результатів підзапиту;

  • описана вище послідовність повторюється для наступного рядка з таблиці зовнішнього запиту, і так доти, поки всі рядки не будуть перевірені.

Загалом кажучи, останній приклад міг бути реалізований, використовуючи об'єднання наступного виду:

SELECT STUDENTS.SNUM, STUDENTS.SFAM,

STUDENTS.SIMA, STUDENTS.SOTCH,

STUDENTS.STIP

FROM STUDENTS, USP

WHERE STUDENTS.SNUM = USP.SNUM

AND USP.UDATE = 10/06/1999;

Припустимо, що є необхідність у висновку прізвища й номера всіх студентів, які одержали більше однієї оцінки. Це реалізується наступним запитом:

SELECT SNUM, SFAM

FROM STUDENTS FIRST

WHERE 1 <

(SELECT COUNT(*)

FROM USP

WHERE SNUM = FIRST. SNUM) ;

Висновок цього запиту наведений нижче:

SNUM SFAM

----------------------------

3412 Поляков

Варто звернути увагу на те, що пропозиція FROM підзапиту в цьому прикладі не використає псевдонім. При відсутності імені таблиці або префікса псевдоніма, SQL може для початку прийняти, що будь-яке поле виводиться з таблиці з ім'ям, зазначеним у пропозиції FROM поточного запиту. Якщо поле із цим ім'ям відсутній у цій таблиці, SQL буде перевіряти зовнішні запити. Саме із цієї причини префікс імені таблиці звичайно необхідний у співвіднесених підзапитах - для скасування цього припущення. Псевдоніми також часто необхідні для того, щоб дати можливість зсипатися до тієї ж самої таблиці у внутрішньому й зовнішньому запиті без якої-небудь неоднозначності.

Часто співвіднесений підзапит використають на основі тієї ж самої таблиці, що й основний запит. Це дає можливість витягти складні форми інформації. Наприклад, за допомогою наступного запиту можна знайти всі оцінки по дисципліні зі значеннями, вище середньої по цій же дисципліні:

SELECT *

FROM USP FIRST

WHERE OCENKA >

(SELECT AVG (OCENKA)

FROM USP SECOND

WHERE SECOND.PNUM = FIRST.PNUM);

У цьому випадку висновку в запиту не буде, тому що в таблиці USP немає записів для студентів, що мають по якому-небудь навчальному предметі оцінку вище середньої. Якщо ж умова зовнішнього предиката змінити на >=, то як результат будуть виведені всі дані таблиці успішності.

Пропозиція HAVING може також працювати й зі співвіднесеними підзапитами. При використанні співвіднесеного підзапиту в пропозиції HAVING необхідно пам'ятати, що предикат оцінюється для кожної групи із зовнішнього запиту, а не для кожного рядка. Отже, підзапит буде виконуватися один раз для кожної групи, виведеної із зовнішнього запиту, а не для кожного рядка. Наприклад, для встановлення середнього значення оцінок за кожний день, причому такий, що це середнє повинне бути більше або дорівнює хоча б на половину бала, чим мінімальне значення оцінки в цей день, можна скористатися запитом:

SELECT UDATE, AVG (OCENKA)

FROM USP FIRST

GROUP BY UDATE

HAVING AVG (OCENKA) >=

(SELECT MIN(OCENKA) +0.5

FROM USP SECOND

WHERE FIRST.UDATE = SECOND.UDATE);

Результат запиту буде такою:

UDATE

------------

10/06/1999 4.5

12/06/1999 4.5

Підзапит обчислює значення MIN для всіх записів з тією же самою датою, що й у поточної агрегатної групи основного запиту.

Співвіднесені підзапити по своїй суті близькі до об'єднань - обидві конструкції включають перевірку кожного запису однієї таблиці з кожним записом інший або із псевдонімом з тієї ж таблиці, при цьому більшість операцій у них схожі. Із цієї причини докладно зупинятися на цьому матеріалі не будемо.

Таким чином, застосування вкладених запитів з метою використання їхнього результату для керування іншим запитом розширює можливості SQL, дозволяючи виконати більша кількість функцій.