Складна та агрегована вибірка даних за допомогою SQL

Аналіз інформації, що зберігається в реляційній базі даних, здійснюється за допомогою SELECT-запитів. В загальному випадку SELECT-запит має наступну структуру:
SELECT [DISTINCT]
вираз [AS псевдонім_колонки],…
FROM {таблиця|з’єднання_таблиць|підзапит} [[AS] псевдонім_таблиці]
[WHERE предикат]
[GROUP BY вираз,…
[HAVING предикат]]
[{UNION|INTERSECT|EXCEPT} [ALL]
{SELECT …}]
[ORDER BY вираз [ASC|DESC],…]

Цей запит складається з кількох частин, кожна з яких починається відповідним ключовим словом:

  • SELECT;
  • FROM;
  • WHERE;
  • GROUP BY;
  • HAVING;
  • ORDER BY.

В частині SELECT визначається структура майбутнього результату, тобто ця частина визначає набір стовпчиків результуючої таблиці. Частина FROM вказує на назви усіх таблиць чи підзапитів, які використовуються в SELECT-запиті. Частина WHERE містить предикат, який може включати кілька логічних виразів та приймати одне з трьох значень: TRUE, FALSE чи UNKNOWN (NULL). В результат SELECT-запиту включаються усі рядки, для яких предикат з частини WHERE приймає значення TRUE. Частина ORDER BY використовується для впорядкування вихідних даних. Рядки впорядковуються відповідно до виразів, вказаних в списку.
Для отримання агрегованих даних дуже важливу роль грають інші компоненти SELECT-запиту.
У виразах, які перераховуються після ключового слова SELECT, можуть використовуватися агрегуючі функції. Під агрегуючими функціями розуміють одну з наступних п’яти функцій:
агрегуюча_функція ::=
{COUNT(*) | {AVG | SUM | MAX | MIN | COUNT} (вираз)}

Існує два типи функції COUNT. Перший тип в якості аргументу використовує символ “*”. В цьому випадку функція обчислює кількість рядків, які повертає запит. Тут не враховуються окремі значення полів, результат не залежить чи задано параметр DISTINCT і чи трапляються значення NULL. Інший тип функції COUNT обчислює всі значення виразу, що не є NULL.
Усі інші функції також ігнорують значення NULL для виразу.
Функція AVG обчислює середнє арифметичне (середнє) для значень виразу. Аргументом цієї функції може бути лише числовий вираз.
Функція SUM обчислює суму значень числового виразу.
Функції MAX та MIN обчислюють відповідно максимальне та мінімальне значення виразу будь-якого типу.
Існують певні правила виконання агрегуючих функцій:

  • Якщо в результаті запиту не отримано жодного рядка, то результатом функції COUNT буде нуль, а результатом інших функцій – значення NULL.
  • В аргументах агрегуючих функцій не допускається використання інших агрегуючих функцій чи підзапитів.
  • Результатом виконання функції COUNT є ціле число. Інші агрегуючі функції наслідують типи даних від аргументу.

При побудові агрегуючих запитів допускається використання декількох таблиць (під запитів) в частині FROM, з’єднаних між собою. З’єднання можуть бути внутрішніми та зовнішніми. При цьому необхідно зважати на наступні особливості:

  • Операція з’єднання завжди виконується перед операціями агрегування.
  • При зовнішніх з’єднаннях в одній з таблиць може не існувати записів-відповідників, що приводить до появи NULL-значень у полях з цієї таблиці. Ці NULL-значення ігноруватимуться усіма агрегуючими функціями крім COUNT(*).

Якщо в частині SELECT запиту на вибірку зустрічається хоча б одна агрегуюча функція, то дуже часто в цьому запиті є присутня частина GROUP BY. Частина GROUP BY використовується для виділення груп вихідних рядків, до яких будуть застосовуватися агрегуючі функції. Якщо ця частина відсутня, то всі вирази частини SELECT, які базуються на полях таблиць, повинні включатися в агрегуючі функції, і ці функції будуть застосовуватися до всього набору рядків, які задовольняють умови запиту. В протилежному випадку всі вирази зі списку SELECT, які базуються на полях таблиць, що не ввійшли в жодну агрегуючу функцію, повинні бути перераховані в частині GROUP BY. Виняток становлять вирази константи. Наприклад, наступний запит не вимагає частини GROUP BY.
SELECT 1 AS optype, COUNT(*)
FROM …;

Усі вихідні рядки запиту, для яких вирази частини GROUP BY приймають однакове значення, утворюють єдину групу (для GROUP BY усі значення NULL вважаються рівними). Агрегуюча функція буде застосовуватися до кожної з таких груп.
Частина HAVING є аналогічною до частини WHERE. Однак, якщо частина WHERE визначає предикат для фільтрації рядків, то частина HAVING застосовується після здійснення групування для визначення предикату, що фільтрує групи по значеннях агрегуючих функцій. Тобто, частина HAVING використовується для перевірки значень, отриманих за допомогою агрегуючих функцій. В результат SELECT-запиту включаються усі рядки після агрегування, для яких предикат з частини HAVING приймає значення TRUE. Найчастіше предикат містить оператори порівняння =, <, <=, >, >= та <>. Для конструювання складних виразів дозволяється використовувати стандартні оператори *, /, +, - та || (конкатенація), спеціальні предикати BETWEEN, IN, LIKE, IS NULL, а також булеві оператори AND, OR та NOT.
SELECT-запит може формуватися на основі результатів декількох підзапитів на вибірку, об’єднаних за допомогою однієї з наступних множинних операцій:

  • UNION – об’єднання;
  • INTERSECT – перетин;
  • EXCEPT – різниця.

Такий складений запит має наступну структуру:
запит_A {UNION | INTERSECT | EXCEPT} [ALL] запит_B
Необов’язковий параметр ALL вказує на те, що в результат запиту включаються усі рядки-дублікати. При його відсутності (по замовчуванню) серед однакових рядків вибирається лише один. Слід відзначити, що Microsoft Access не підтримує операцій INTERSECT та EXCEPT в складених запитах на вибірку, а допускає лише використання UNION.
запит_A UNION [ALL] запит_B
Частина ORDER BY відноситься не до останнього SELECT-підзапиту, а до цілого складеного запиту на вибірку. Тобто, незалежно від кількості SELECT-підзапитів частина ORDER BY може зустрічатися в запиті на вибірку лише один раз, і записується останньою.
Розглянемо спроектовану за допомогою ER-діаграми схему бази даних (рис. 1).
Приклад ER-діаграми в нотації Баркера
Рис. 1. Приклад ER-діаграми в нотації Баркера.
Для виконання запиту на вибірку в MS Access слід скористатися закладкою Queries вікна бази даних (рис. 2).
Закладка Queries вікна бази даних
Рис. 2. Закладка Queries вікна бази даних.
За допомогою кнопки New створюється новий запит (в діалоговому вікні, яке при цьому висвітлюється, слід вибирати Design View). При створенні нового запиту MS Access пропонує скористатися графічним конструктором запиту. Для переходу в режим SQL необхідно скористатися пунктом меню View/SQL View (Вигляд/Вигляд SQL). Наприклад, MS Access дозволяє варіант запиту як показано на рис. 3.
Агрегований запит на вибірку
Рис. 3. Агрегований запит на вибірку.
Для виконання запиту на вибірку необхідно скористатися пунктом меню Query/Run (Запит/Виконати). В результаті на екран виводиться таблиця, яку повертає SELECT-запит.
Результат виконання агрегованого запиту на вибірку
Рис. 4. Результат виконання агрегованого запиту на вибірку.
Написання достатньо складних SELECT-запитів необхідно проводити достатньо обережно та продумано, оскільки одного й того ж результату можна отримати декількома способами. Серед усіх можливих варіантів необхідно вибирати оптимальний з точки зору часу його виконання конкретною СКБД. Особливо це стосується використання множинних операцій (UNION, INTERSECT, EXCEPT), які на практиці істотно сповільннють процес виконання SELECT-запитів.
Наприклад, наступні два запити повертають тотожний результат не залежно від вмісту бази даних.
Агрегований запит з використанням UNION
Рис. 5. Агрегований запит з використанням UNION.
Агрегований запит без використання UNION
Рис. 6. Агрегований запит без використання UNION.
Очевидно, що перший запит (див. рис. 5) є занадто громіздкий для розуміння, і він виконуватиметься довше (а при великих масивах даних в десятки чи сотні тисяч записів значно довше). Тому приктичне застосування має другий варіант запиту (див. рис. 6).

ПРИКЛАДИ

Запитання SELECT-запит
Порахувати всі записи з таблиці rf_course SELECT COUNT(*) FROM rf_course;
Для кожного предмету та типу заняття порахувати кількість тем SELECT course_id, lessontype, COUNT(*) AS theme_cnt
FROM rf_theme
GROUP BY course_id, lessontype;
Вибрати предмети, для яких типи занять мають більше 10 тем SELECT course_id, lessontype, COUNT(*) AS theme_cnt
FROM rf_theme
GROUP BY course_id, lessontype
HAVING COUNT(*)>10;
Для кожної групи визначити середнє значення номера заняття SELECT group_id, AVG(lessonnum) AS lessonnum_avg
FROM dc_lesson
GROUP BY group_id;
Вибрати всі групи, для яких середнє значення номера заняття є більше 3 SELECT group_id, AVG(lessonnum) AS lessonnum_avg
FROM dc_lesson
GROUP BY group_id;
HAVING AVG(lessonnum)>3;
Для кожної лекції і лабораторного заняття, та номера порахувати кількість занять SELECT 'Лек' AS lessontype, l.lessonnum, COUNT(*) AS lesson_count
FROM rf_theme AS t INNER JOIN dc_lesson AS l ON t.id-l.theme_id
WHERE t.lessontype='S’
GROUP BY l.lessonnum
UNION
SELECT 'Лаб' AS lessontype, l.lessonnum, COUNT(*) AS lesson_count
FROM rf_theme AS t INNER JOIN dc_lesson AS l ON t.id-l.theme_id
WHERE t.lessontype='L’
GROUP BY l.lessonnum;