Найбільш складною, але в той же час найбільш цікавою темою є підзапити. Це досить потужний засіб отримання необхідних даних, а з іншого боку, це засіб дуже сильно б'є по продуктивності обробки запиту сервером. Спочатку ми навчимося працювати з підзапитах, тому що з їх допомогою можна швидко вирішити поставлене завдання, а потім будемо вчитися позбавлятися від підзапитів, що ідентично оптимізації.
Це завдання досить просто вирішується за допомогою підзапитів:
В даному прикладі ми вибираємо всі записи з таблиці tbPhoneNumbers. При цьому, поле "idPhoneType" порівнюється з результатом підзапиту, який пишеться в круглих дужках. Так як стоїть знак рівності, то результат підзапиту повинен бути з одного поля і одного рядка. Якщо результатом буде два поля або більше одного рядка, то сервер поверне нам помилку.
Спробуємо виконати наступний запит:
Цей запит поверне два рядки з двома значеннями первинного ключа. У відповідь на це, сервер поверне помилку:
Subquery returned more than 1 value. This is not permitted when the subquery follows =. =, <, <=. &qt;, &qt;= or when the subquery is used as an expression.
(Підзапитів повертає більш ніж 1 значення. Це не дозволено, коли підзапит слід після знаків =. =, <, <=. &qt;, &qt;= или когда подзапрос используется как выражение)
А що ж тоді можна використовувати? Якщо трохи подумати, то для такого запиту знак рівності потрібно замінити на оператор IN:
Спочатку SQL виконає внутрішній запит, який розташований в дужках і результат підставить під зовнішній запит.
Я вже натякнув на те, що результат підзапиту повинен складатися тільки з однієї колонки. Це означає, що ви не можете написати у внутрішньому запиті SELECT *, а можна тільки SELECT ІмяОдногоПоля. Пам'ятайте, що ім'я має бути тільки одне і тип його повинен збігатися з типом порівнюваного значення. Підзапити потрібно використовувати дуже акуратно, тому що вони можуть привести до помилки.
Дуже важливо, що підзапит перебувати в дужках і праворуч від знака рівності. Стандарт не дозволяє писати підзапити зліва. Це означає, що наступний запит хибний:
У цьому прикладі спочатку йде підзапит, потім знак рівності і тільки після цього вказується поле, з яким необхідно провести порівняння.
Основний запит (так само званий зовнішнім) може звертатися до підзапитів (внутрішній запит). Для цього таблицями необхідно вказати псевдоніми. Подивимося на наступний запит:
Зверніть увагу на передостанню рядок:
Тут відбувається порівняння співаючи "vcPhoneNumber" таблиці ot з шаблоном. Найцікавіше тут в тому, що ot - це псевдонім таблиці tbPhoneNumbers, яка описана в секції FROM зовнішнього запиту. Але, не дивлячись на це, ми можемо з підзапиту звертатися за псевдонімом до стовпців зовнішніх запитів. Таким чином, можна наводити досить складні зв'язки між запитами.
Такий запит буде виконуватися за наступним алгоритмом:
- Вибрати рядок з таблиці tbPhoneNumbers в зовнішньому запиті. Це буде поточна рядок-кандидат.
- Зберегти значення з цього рядка-кандидата в псевдонім з ім'ям ot.
- Виконати підзапит, при цьому, під час пошуку бере участь і зовнішній запит.
- Оцінити "idPhoneType" зовнішнього запиту на основі результатів підзапиту виконуваного в попередньому кроці. Він визначає - вибирається рядок-кандидат для виведення на екран.
Підзапити можуть бути не тільки в секції WHERE, але і в секції SELECT і в секції FROM. Давайте розглянемо спочатку підзапити з FROM, для цього поставимо досить складну, але цікаву задачу. Під час угруповання ми змогли навчитися визначати кількість користувачів з ім'ям Андрій. А що якщо створити таблицю, яка буде складатися з трьох колонок:
У вигляді двох запитів ця задача вирішується досить просто. Наступний запит визначає кількість кожного імені в таблиці:
Такий запит ми вже розбирали.
У нас вийшло дві різні таблиці. А як тепер їх об'єднати в одне ціле? Спробуйте самостійно вирішити цю задачу. Моє рішення можна побачити в лістингу 2.3.
У секції FROM, замість вказівки таблиць стоять вищеописані запити, укладені в круглі дужки. Для кожного запиту вказується псевдонім, інакше неможливо працювати з полями запитів. Виходить, що замість того, щоб отримати дані безпосередньо з таблиці, ми отримуємо їх із запиту.
Для таких запитів, є тільки одне обмеження - у кожного поля в підзапиті секції FROM має бути ім'я. У нас є поля, що підраховують кількість записів і для таких полів ім'я не встановлюється, тому я встановив псевдоніми. У першому підзапиті колонка з кількістю записів названа як PeopleNumber, а в другому підзапиті - PhoneNum.
Зовнішній об'єднує запит пов'язує обидві отримані таблиці через поле імені "vcName", а результатом буде загальна таблиця (див. Рис. 2.7), що складається з чотирьох колонок - ім'я і кількість з першого запиту, і ім'я і кількість з другого запиту. Одну з колонок імен можна прибрати, тому що вони ідентичні, але я вирішив залишити, щоб ви могли побачити зв'язок.
Результат об'єднання двох таблиць
У секції SELECT спочатку запитуємо все колонки з таблиці tbPeoples (pl. *). Після цього, замість чергового поля в дужках вказується підзапит, який вибирає дані. При цьому в підзапиті в секції WHERE наводитися зв'язок між таблицями. Виходить, що і з цього підзапиту ми можемо звертатися до полів зовнішнього запиту.
Єдине обмеження - підзапит в секції SELECT повинен повертати тільки один рядок. Якщо результатом буде декілька рядків, то запит повертає помилку.
Всі приклади, які ми розглядали вище, досить просто реалізувати, без використання підзапитів. Наприклад, подивимося на наступний запит:
Це ж завдання вирішується таким чином:
Просто пов'язуємо обидві таблиці і вказуємо те ж саме умова. Таким чином, ми позбулися підзапиту, і тепер сервер зможе виконати завдання швидше. Більшість завдань можна вирішити без підзапитів, якщо правильно пов'язати таблиці. Саме тому, в даній книзі я постараюся мінімально користуватися підзапитах, і все постараємося вирішувати одним оператором SELECT.
А ось наступний запит, досить складно зробити без підзапитів. Припустимо, що вам потрібно визначити дані останньої, доданої в таблицю рядки. Якщо в якості первинного ключа використовується автоматично що збільшується поле, то необхідно дізнатися найбільше значення первинного ключа за допомогою оператора MAX, а потім знайти рядок з цим ключем. Ось як визначається останній рядок в таблиці tbPeoples:
Такий запит нам дуже допоможе, коли ми будемо вчитися додавати записи в таблицю, щоб швидко можна було побачити результат роботи.