Реляційні SQL бази даних
Який синтаксис запиту в SQL
У SQL існує безліч ключових слів, які використовуються для створення, зміни, керування та отримання даних з баз даних. Наведу основні категорії та ключові слова, що використовуються в SQL:
DML (Data Manipulation Language) - Мова маніпуляції даними:
Ключові слова для роботи з даними, такими як вставка, оновлення та видалення даних.
SELECT:
вибирає дані з таблиці.
SELECT *FROM usersWHERE age > 30;
SELECT COUNT(*)FROM usersWHERE age > 30;
INSERT:
додає нові записи до таблиці.
INSERTINTO users (name, age)VALUES ('John', 25);
UPDATE:
оновлює наявні записи в таблиці.
UPDATE usersSET age = 26WHERE name = 'John';
DELETE:
видаляє записи з таблиці.
DELETEFROM usersWHERE age < 18;
MERGE:
об'єднує дані з двох таблиць на основі умов.
MERGE INTO employees eUSING employees_updates euON (e.employee_id = eu.employee_id)WHEN MATCHED THEN UPDATE SET e.salary = eu.salaryWHEN NOT MATCHED THEN INSERT (employee_id, salary) VALUES (eu.employee_id, eu.salary);
CALL:
викликає збережену процедуру.
CALL calculate_salary(12345);DDL (Data Definition Language) — Мова визначення даних:
Ключові слова для створення та модифікації структури бази даних (таблиць, індексів та інших об'єктів).
CREATE:
створює нові об'єкти бази даних (таблиці, індекси тощо).
CREATE TABLE products ( id INT PRIMARY KEY, name VARCHAR(100), price DECIMAL(10, 2));
ALTER:
змінює структуру наявних об'єктів бази даних.
ALTERTABLE productsADD COLUMN description TEXT;
DROP:
видаляє об'єкти бази даних (таблиці, індекси тощо).
DROP TABLE products;
TRUNCATE:
видаляє всі рядки з таблиці, але не видаляє саму таблицю.
TRUNCATE TABLE users;
COMMENT:
додає коментарі до об'єктів бази даних.
COMMENTON TABLE productsIS 'Table storing product details';
RENAME:
перейменовує об'єкт бази даних.
RENAMETABLE old_productsTO new_products;DCL (Data Control Language) — Мова керування доступом:
Ключові слова для керування правами доступу до даних.
GRANT:
надає користувачам або ролям права на об'єкти бази даних.
GRANTSELECT, INSERTON ordersTO user_admin;
REVOKE:
відкликає права на об'єкти бази даних у користувачів або ролей.
REVOKEINSERT ON ordersFROM user_admin;TCL (Transaction Control Language) — Мова керування транзакціями:
Ключові слова для керування транзакціями в базі даних.
BEGIN TRANSACTION:
починає транзакцію.
BEGIN TRANSACTION;UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;COMMIT;
COMMIT:
зберігає зміни, зроблені в межах поточної транзакції.
BEGIN TRANSACTION;INSERT INTO orders (order_id, customer_id, order_total) VALUES (101, 5, 250.00);INSERT INTO order_items (order_id, product_id, quantity) VALUES (101, 20, 2);COMMIT;
ROLLBACK:
скасовує зміни, зроблені в межах поточної транзакції.
BEGIN TRANSACTION;DELETE FROM employees WHERE employee_id = 45;UPDATE departments SET manager_id = NULL WHERE manager_id = 45;ROLLBACK;
SAVEPOINT:
створює точку відновлення в транзакції.
BEGIN TRANSACTION;UPDATE products SET price = price * 0.9 WHERE category = 'Electronics';SAVEPOINT discount_applied;DELETE FROM products WHERE discontinued = true;ROLLBACK TO discount_applied;COMMIT;
SET TRANSACTION:
встановлює параметри для поточної транзакції.
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;BEGIN TRANSACTION;SELECT * FROM sales WHERE sale_date BETWEEN '2024-01-01' AND '2024-01-31';COMMIT;Ключові слова для керування даними (Constraints):
Ключові слова, що використовуються для задання обмежень на дані в таблицях.
PRIMARY KEY:
визначає унікальний ідентифікатор для кожного рядка таблиці.
CREATE TABLE customers ( customer_id INT PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50));
FOREIGN KEY:
встановлює зв'язок між двома таблицями.
CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE, FOREIGN KEY (customer_id) REFERENCES customers(customer_id));
UNIQUE:
вимагає, щоб усі значення в стовпці або групі стовпців були унікальними.
CREATE TABLE users ( user_id INT PRIMARY KEY, username VARCHAR(50) UNIQUE, email VARCHAR(100) UNIQUE);
NOT NULL:
забороняє використання порожніх значень для цього стовпця.
CREATE TABLE products ( product_id INT PRIMARY KEY, product_name VARCHAR(100) NOT NULL, price DECIMAL(10, 2) NOT NULL);
CHECK:
визначає умову, яку мають виконувати дані.
CREATE TABLE employees ( employee_id INT PRIMARY KEY, salary DECIMAL(10, 2) CHECK (salary > 0), age INT CHECK (age >= 18));
DEFAULT:
встановлює значення за замовчуванням для стовпця.
CREATE TABLE orders ( order_id INT PRIMARY KEY, order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status VARCHAR(20) DEFAULT 'PENDING');Ключові слова для роботи з умовами:
Ключові слова, які використовуються для фільтрації, сортування та агрегування даних.
WHERE:
фільтрує рядки на основі умови.
SELECT *FROM employeesWHERE department = 'Sales'AND salary > 50000;
GROUP BY:
групує рядки за значеннями одного або кількох стовпців.
SELECT department, COUNT(*) AS total_employeesFROM employeesGROUP BY department;
HAVING:
фільтрує рядки після групування.
SELECT department, AVG(salary) AS average_salaryFROM employeesGROUP BY departmentHAVING AVG(salary) > 60000;
ORDER BY:
сортує результат за одним або кількома стовпцями.
SELECT first_name, last_name, hire_dateFROM employeesORDER BY hire_date DESC;
DISTINCT:
повертає унікальні значення.
SELECT DISTINCT countryFROM customers;
LIMIT:
обмежує кількість рядків, що повертаються (у деяких СУБД, наприклад MySQL).
SELECT * FROM productsORDER BY price DESCLIMIT 10;
OFFSET:
визначає, з якого рядка почати виведення результату.
SELECT * FROM productsORDER BY price DESCLIMIT 10 OFFSET 10;Ключові слова для роботи з об'єднанням таблиць:
Ключові слова для об'єднання даних з кількох таблиць.
JOIN:
об'єднує рядки з двох або більше таблиць на основі відношення між ними.
SELECT o.order_id, c.first_name, c.last_nameFROM orders oJOIN customers c ON o.customer_id = c.customer_id;
INNER JOIN:
повертає рядки зі збіжними значеннями в обох таблицях.
SELECT e.first_name, e.last_name, d.department_nameFROM employees eINNER JOIN departments d ON e.department_id = d.department_id;
LEFT JOIN (або LEFT OUTER JOIN):
повертає всі рядки з лівої таблиці, навіть якщо в правій таблиці немає збігів.
SELECT c.first_name, o.order_idFROM customers cLEFT JOIN orders o ON c.customer_id = o.customer_id;
RIGHT JOIN (або RIGHT OUTER JOIN):
повертає всі рядки з правої таблиці, навіть якщо в лівій таблиці немає збігів.
SELECT o.order_id, c.first_nameFROM orders oRIGHT JOIN customers c ON o.customer_id = c.customer_id;
FULL JOIN (або FULL OUTER JOIN):
повертає всі рядки з обох таблиць із заповненням значень NULL, якщо немає збігів.
SELECT a.account_id, t.transaction_idFROM accounts aFULL JOIN transactions t ON a.account_id = t.account_id;
CROSS JOIN:
повертає декартів добуток двох таблиць (кожен рядок з першої таблиці об'єднується з кожним рядком з другої таблиці).
SELECT p.product_name, s.store_nameFROM products pCROSS JOIN stores s;
SELF JOIN:
з'єднує таблицю саму з собою.
SELECT e1.first_name AS employee, e2.first_name AS managerFROM employees e1JOIN employees e2 ON e1.manager_id = e2.employee_id;Ключові слова для керування послідовностями та індексами:
Ключові слова для створення та керування послідовностями та індексами.
CREATE INDEX:
створює індекс для стовпців таблиці для підвищення продуктивності запитів.
CREATE INDEX idx_last_name ON employees(last_name);
CREATE SEQUENCE:
створює послідовність для генерації унікальних чисел.
CREATE SEQUENCE order_seqSTART WITH 1000INCREMENT BY 1;
DROP INDEX:
видаляє індекс.
DROP INDEX idx_last_name;// для деяких СУБД, наприклад PostgreSQLDROP INDEX IF EXISTS idx_last_name;// у MySQL, індекс видаляється інакшеALTER TABLE employees DROP INDEX idx_last_name;
DROP SEQUENCE:
видаляє послідовність.
DROP SEQUENCE order_seq;Ключові слова для роботи з представленнями (views):
Ключові слова для створення та керування представленнями.
CREATE VIEW:
створює представлення.
CREATE VIEW active_customers ASSELECT customer_id, first_name, last_name, emailFROM customersWHERE status = 'ACTIVE';
DROP VIEW:
видаляє представлення.
DROP VIEW active_customers;
ALTER VIEW:
змінює наявне представлення.
ALTER VIEW active_customers ASSELECT customer_id, first_name, last_name, email, phoneFROM customersWHERE status = 'ACTIVE';Ключові слова для роботи зі збереженими процедурами та тригерами:
Ключові слова для створення та керування збереженими процедурами та тригерами.
CREATE PROCEDURE:
створює збережену процедуру.
CREATE PROCEDURE increase_salary(IN emp_id INT, IN percent DECIMAL)BEGIN UPDATE employees SET salary = salary + (salary * percent / 100) WHERE employee_id = emp_id;END;
CREATE TRIGGER:
створює тригер, який автоматично виконується при настанні певної події (наприклад, INSERT, UPDATE, DELETE).
CREATE TRIGGER set_created_atBEFORE INSERT ON usersFOR EACH ROWSET NEW.created_at = CURRENT_TIMESTAMP;
EXECUTE:
виконує збережену процедуру.
CALL increase_salary(101, 10);
DROP PROCEDURE:
видаляє збережену процедуру.
DROP PROCEDURE increase_salary;
DROP TRIGGER:
видаляє тригер.
DROP TRIGGER set_created_at;Функції агрегації та аналітики:
Ключові слова для агрегування та аналізу даних.
COUNT():
повертає кількість рядків.
SELECT COUNT(*) AS total_employeesFROM employees;
SUM():
повертає суму значень.
SELECT SUM(order_total) AS total_revenueFROM ordersWHERE order_date >= '2024-01-01';
AVG():
повертає середнє значення.
SELECT AVG(salary) AS average_salaryFROM employeesWHERE department = 'IT';
MIN():
повертає мінімальне значення.
SELECT MIN(price) AS cheapest_productFROM products;
MAX():
повертає максимальне значення.
SELECT MAX(price) AS most_expensive_productFROM products;Інші ключові слова та оператори:
EXISTS:
перевіряє, чи існують рядки, що відповідають умові.
SELECT first_name, last_nameFROM customersWHERE EXISTS ( SELECT 1 FROM orders WHERE orders.customer_id = customers.customer_id);
IN:
перевіряє, чи міститься значення в списку значень.
SELECT *FROM productsWHERE category_id IN (1, 3, 5);
BETWEEN:
перевіряє, чи перебуває значення в певному діапазоні.
SELECT *FROM ordersWHERE order_date BETWEEN '2024-01-01' AND '2024-12-31';
LIKE:
шукає рядкові дані за шаблоном.
SELECT *FROM usersWHERE email LIKE '%@example.com';
IS NULL:
перевіряє, чи містить значення NULL.
SELECT *FROM employeesWHERE manager_id IS NULL;
CAST():
перетворює одне значення на інший тип даних.
SELECT CAST(order_total AS INT) AS total_as_integerFROM orders;
COALESCE():
повертає перше ненульове значення в списку.
SELECT COALESCE(phone, 'No phone') AS contact_numberFROM customers;
CASE:
повертає значення на основі умов (аналог оператора if-else).
SELECT first_name, last_name, CASE WHEN salary >= 100000 THEN 'High' WHEN salary >= 50000 THEN 'Medium' ELSE 'Low' END AS salary_levelFROM employees;
WITH:
використовується для створення тимчасових результатів у запиті (CTE — Common Table Expression).
WITH high_salary_employees AS ( SELECT * FROM employees WHERE salary > 80000)SELECT department, COUNT(*)FROM high_salary_employeesGROUP BY department;
UNION:
використовується для об'єднання результатів двох або більше запитів. Він дозволяє об'єднувати рядки з кількох SELECT запитів в один набір результатів. При цьому рядки з різних запитів не повинні дублюватися, якщо явно не використовується оператор ALL
SELECT first_name, email FROM customersUNIONSELECT name, email FROM newsletter_subscribers;Це ключові слова, які найчастіше зустрічаються в SQL. Вони використовуються для роботи з даними та керування структурою бази даних. Кожна СУБД може мати свої унікальні ключові слова, але перелічені вище — це стандартні команди SQL.
Що таке VIEW (представлення) в SQL
VIEW (представлення) в SQL — це віртуальна таблиця, яка формується на основі результату запиту. Представлення не зберігають дані фізично, вони лише зберігають SQL-запит, який виконується щоразу при зверненні до представлення. Це зручний інструмент для спрощення складних запитів, забезпечення безпеки та підвищення зручності роботи з даними.
Представлення можуть використовуватися для відображення даних з однієї або кількох таблиць, а також для об'єднання, фільтрації та агрегації даних.
Приклад створення VIEW:
CREATE VIEW active_users ASSELECT id, name, emailFROM usersWHERE status = 'active';Тут представлення active_users містить лише тих користувачів з таблиці users, які мають статус "active". Тепер можна використовувати це представлення як звичайну таблицю:
SELECT * FROM active_users;Це запитає дані з представлення, і фактично виконається збережений запит.
Переваги використання представлень:
Спрощення складних запитів:
Представлення дозволяють приховати складні запити за простим іменем, що спрощує роботу з ними. Замість того щоб щоразу писати довгі SQL-запити, можна просто звернутися до представлення.
Підвищення безпеки:
Представлення можуть бути використані для обмеження доступу до даних. Наприклад, можна надати користувачам доступ лише до певних стовпців або рядків через представлення, а не до вихідної таблиці.
Aбстракція даних:
Представлення можуть допомогти абстрагуватися від складних структур бази даних. Наприклад, можна створити представлення, яке об'єднує дані з кількох таблиць, і користувачі можуть не знати, як ці таблиці пов'язані.
Забезпечення сумісності:
Якщо структура таблиці змінюється (наприклад, додаються нові стовпці), представлення може слугувати шаром сумісності, який продовжує надавати старий інтерфейс для наявних застосунків.
Оновлювані представлення:
Представлення можуть підтримувати оновлення, вставку та видалення даних (якщо це можливо з погляду SQL-сервера та логіки запиту). Але не всі представлення оновлювані. Наприклад, якщо представлення використовує агрегатні функції (SUM, COUNT, AVG тощо) або об'єднання (JOIN), то такі представлення зазвичай не можна оновлювати напряму.
Приклад оновлюваного представлення:
CREATE VIEW basic_users ASSELECT id, nameFROM users;Можна оновлювати дані в цьому представленні:
UPDATE basic_usersSET name = 'New Name'WHERE id = 1;Обмеження представлень:
Не зберігають дані:
Представлення не зберігають дані. Це означає, що щоразу, коли ви робите запит до представлення, сервер бази даних наново виконує запит, на основі якого воно створене.
Не всі представлення можуть бути оновлюваними:
Деякі представлення неможливо використовувати для зміни даних, особливо якщо вони включають складні операції, такі як агрегації або підзапити.
Продуктивність:
Залежно від складності запиту, на основі якого побудоване представлення, використання представлень може сповільнити виконання запитів, особливо якщо воно звертається до великих таблиць або робить складні операції.
Видалення представлення:
Якщо представлення більше не потрібне, його можна видалити за допомогою команди DROP VIEW:
DROP VIEW active_users;VIEW — це потужний інструмент для спрощення роботи з даними, підвищення безпеки та приховування складності структури бази даних. Воно дозволяє створювати віртуальні таблиці, які можуть об'єднувати дані, фільтрувати їх та пропонувати користувачам зручніші способи взаємодії з базою даних.
Що таке INDEX (індекси) в SQL
Індекси в SQL — це спеціальні структури даних, які використовуються для прискорення пошуку рядків у таблицях. Індекси створюються на одному або кількох стовпцях таблиці, і вони працюють як вказівники, що дозволяють базі даних знаходити дані швидше, ніж якби вона виконувала повне сканування таблиці (тобто перегляд усіх рядків).
Як працюють індекси:
Структура індексу:
Індекси часто організовані у вигляді дерев (наприклад, B-дерево або B+ дерево), що дозволяє ефективно знаходити дані. Ці структури дозволяють виконувати пошук, вставку та видалення даних за логарифмічний час, що значно швидше порівняно з повним скануванням таблиці.
Індекси містять відсортовані дані стовпців, за якими вони побудовані. Це дозволяє базі даних швидко знаходити потрібні рядки.
Основна ідея:
Замість того щоб сканувати всю таблицю при виконанні запиту (особливо якщо таблиця містить багато рядків), база даних звертається до індексу, який містить відсортовані значення. За допомогою індексу база даних знаходить потрібні рядки з мінімальними витратами часу.
Приклад роботи індексу:
Припустимо, у нас є таблиця employees, яка містить інформацію про співробітників. У таблиці є стовпець name, за яким часто виконуються запити для пошуку.
Без індексу:
Запит на кшталт SELECT * FROM employees WHERE name = 'Alice' призведе до того, що база даних повинна буде перевірити кожен рядок у таблиці (повне сканування таблиці), щоб знайти рядки з іменем "Alice". Це особливо витратно для великих таблиць.
З індексом:
Якщо ми створимо індекс на стовпець name за допомогою команди:
CREATE INDEX idx_employee_name ON employees(name);То запит SELECT * FROM employees WHERE name = 'Alice' буде використовувати цей індекс. База даних уже знатиме, де перебувають записи з іменем "Alice", завдяки індексу, що значно прискорить виконання запиту.
Переваги індексів:
Прискорення запитів на вибірку даних:
Індекси значно прискорюють виконання запитів SELECT, особливо тих, які включають умови WHERE, JOIN, ORDER BY або GROUP BY.
Підвищення продуктивності з'єднань (JOIN):
Якщо використовуються індекси на стовпцях, за якими таблиці з'єднуються, це також прискорює виконання запитів з JOIN.
Упорядкування даних:
Індекси можуть допомогти впорядкувати дані в запитах з ORDER BY, оскільки вони вже містять відсортовані значення стовпців
Недоліки індексів:
Сповільнення операцій вставки, оновлення та видалення:
Щоразу, коли відбувається вставка, оновлення або видалення рядка в таблиці, база даних повинна оновлювати індекс, що вимагає додаткових ресурсів. Чим більше індексів у таблиці, тим більше накладних витрат на ці операції.
Використання пам'яті:
Індекси займають додатковий простір на диску та в пам'яті. Якщо таблиця містить кілька індексів, то для кожного з них необхідно зберігати копію даних стовпців у відсортованому вигляді.
Типи індексів у SQL:
Звичайний індекс (Non-Unique Index):
Це індекс, який може містити неунікальні значення. Підходить для стовпців, за якими виконуються часті запити, але де значення можуть повторюватися.
CREATE INDEX idx_name ON employees(name);Унікальний індекс (Unique Index):
Цей тип індексу вимагає, щоб усі значення в індексованому стовпці були унікальними. Він часто використовується для забезпечення унікальності даних, наприклад, для стовпців, які повинні містити унікальні ідентифікатори.
CREATE UNIQUE INDEX idx_unique_email ON employees(email);Первинний ключ (Primary Key):
Первинний ключ автоматично створює унікальний індекс на відповідний стовпець або групу стовпців. В одній таблиці може бути лише один первинний ключ.
CREATE TABLE employees ( id INT PRIMARY KEY, name VARCHAR(100));Складений індекс (Composite Index):
Індекс, який створюється на кількох стовпцях таблиці. Такий індекс корисний, коли запити часто фільтрують дані за кількома стовпцями одночасно.
CREATE INDEX idx_name_department ON employees(name, department);Повнотекстовий індекс (Full-text Index):
Спеціальний тип індексу для швидкого пошуку текстової інформації у великих стовпцях, таких як текстові поля.
CREATE FULLTEXT INDEX idx_fulltext_description ON articles(description);Унікальний складений індекс:
Унікальний індекс на кілька стовпців, який гарантує, що комбінація значень у цих стовпцях унікальна.
CREATE UNIQUE INDEX idx_unique_name_dept ON employees(name, department);Коли індекси не використовуються:
Індекси не завжди використовуються, навіть якщо вони існують. Існує кілька ситуацій, коли індекси можуть бути проігноровані:
Малий обсяг таблиці:
Якщо таблиця дуже маленька, база даних може вирішити, що сканування всієї таблиці швидше, ніж використання індексу.
Використання функцій на індексованих стовпцях:
Якщо в запиті використовується функція (наприклад, LOWER(name)), індекс може бути не використаний, оскільки перетворення даних робить його непридатним для індексації.
Неправильні запити:
Якщо запити написані так, що не дозволяють використовувати індекс (наприклад, використання від'ємних умов NOT, <>, !=), це може знизити ефективність індексу.
Часто оновлювані або видалювані таблиці:
У таблицях з частими операціями вставки, видалення або оновлення використання індексів може знижувати продуктивність через необхідність їх постійного оновлення.
Як уникнути конкуренції при доступі до запису в SQL
Припустимо, 2 користувачі відкрили один і той самий запис у базі даних, як можна уникнути ситуації, коли перший користувач оновив дані раніше за другого, і другий працює з неактуальними даними
У цій ситуації виникає класична проблема конкуренції при роботі з одним і тим самим записом бази даних, яка може призвести до того, що другий користувач перезапише зміни, внесені першим. Щоб уникнути таких конфліктів, існує кілька стратегій керування конкурентним доступом до даних. Ось основні підходи:\
Блокування на рівні бази даних (Pessimistic Locking)
Песимістичне блокування передбачає, що щойно один користувач починає працювати із записом, запис блокується, щоб інші користувачі не могли його змінювати до завершення поточної транзакції. Це дозволяє уникнути проблем з конкурентним доступом, але може знижувати продуктивність і призводити до блокувань при високому навантаженні.
Приклад використання:
Один користувач виконує SELECT … FOR UPDATE, що блокує запис для зміни іншими користувачами.
Поки транзакція першого користувача не завершена (через COMMIT або ROLLBACK), інші користувачі не можуть його оновити.
Приклад на SQL:
BEGIN TRANSACTION;
SELECT * FROM recordsWHERE id = 1FOR UPDATE;
-- Зміна даних...
UPDATE recordsSET field = 'new_value'WHERE id = 1;
COMMIT;При використанні блокувань база даних не дозволить другому користувачеві отримати доступ до зміни запису до завершення роботи першого.
Оптимістичне блокування (Optimistic Locking)
Оптимістичне блокування передбачає, що конфлікти при доступі до запису рідкісні, тому зміни дозволяються, але при оновленні перевіряється, що запис не був змінений з моменту його читання. Якщо запис був змінений, другий користувач повинен повторити операцію.
Це досягається шляхом зберігання спеціального поля, такого як версія запису або часова мітка останньої зміни. При оновленні запису застосунок перевіряє, що версія запису збігається з тією, що була прочитана в момент початку роботи з ним. Якщо версія змінилася — операція скасовується або пропонується оновити дані.
Кроки роботи:
Користувач зчитує запис разом з його поточною версією.
При оновленні дані надсилаються на сервер разом з версією.
Сервер перевіряє, чи збігається версія в базі з переданою версією.
Якщо версії збігаються, дані оновлюються і версія інкрементується.
Якщо версії не збігаються, сервер відхиляє запит, і користувачеві показується повідомлення про конфлікт.
Приклад таблиці з полем версії:
CREATE TABLE records ( id INT PRIMARY KEY, name VARCHAR(100), version INT);Приклад на SQL:
UPDATE recordsSET name = 'New Name', version = version + 1WHERE id = 1 AND version = 3;-- Якщо це оновлення торкнулося 0 рядків, отже, версія змінилася.Використання часових міток (Timestamp-Based Concurrency Control)
Схожий підхід на оптимістичне блокування, але замість зберігання версії запису використовується часова мітка (наприклад, поле updated_at або last_modified). Коли користувач оновлює дані, база даних перевіряє, чи не змінилася часова мітка запису з моменту його останнього читання.
Приклад таблиці з часовою міткою:
CREATE TABLE records ( id INT PRIMARY KEY, name VARCHAR(100), updated_at TIMESTAMP);Оновлення з перевіркою часової мітки:
UPDATE recordsSET name = 'New Name', updated_at = CURRENT_TIMESTAMPWHERE id = 1 AND updated_at = '2024-09-03 12:00:00';
-- Якщо оновлення не відбулося (0 рядків змінено), отже, запис був оновлений іншим користувачем.Застосування версій даних на рівні застосунку:
На рівні застосунку можна реалізувати логіку, яка відстежує стан даних. Наприклад, при редагуванні даних користувачеві може бути показана версія запису, яку він редагує. Якщо інший користувач вніс зміни, застосунок може або попередити користувача, що дані застаріли, або запропонувати автоматичне злиття змін.
Комбінування підходів:
Часто оптимальний варіант — це комбінація методів. Наприклад:
При роботі з критично важливими даними можна використовувати песимістичне блокування.
Для менш критичних операцій використовувати оптимістичне блокування або перевірку часових міток.
Які існують види відношень у SQL
У реляційних базах даних відношення між таблицями описують, як дані в одній таблиці пов'язані з даними в іншій. Основні види відношень у SQL базах даних:
Один до багатьох (One-to-Many)
Це найпоширеніший тип відношень у базах даних. Він позначає ситуацію, коли один запис у таблиці може бути пов'язаний з кількома записами в іншій таблиці, але кожен з цих записів пов'язаний лише з одним записом у першій таблиці.
Приклад:
Таблиця authors (автори) і таблиця books (книги). Один автор може написати кілька книг, але кожна книга має лише одного автора.
Реалізація:
У таблицю books додається зовнішній ключ, який посилається на первинний ключ таблиці authors.
CREATE TABLE authors ( id INT PRIMARY KEY, name VARCHAR(100));
CREATE TABLE books ( id INT PRIMARY KEY, title VARCHAR(100), author_id INT, FOREIGN KEY (author_id) REFERENCES authors(id));Багато до багатьох (Many-to-Many)
Цей тип відношення позначає, що один запис у першій таблиці може бути пов'язаний з кількома записами в другій таблиці, і навпаки. Однак такі відношення не можуть бути виражені напряму в SQL. Для реалізації такого відношення використовується проміжна таблиця (сполучна таблиця), яка розбиває відношення "багато до багатьох" на два відношення "один до багатьох".
Приклад:
Таблиці students (студенти) і courses (курси). Один студент може бути записаний на кілька курсів, і на один курс може бути записано кількох студентів.
Реалізація:
Проміжна таблиця students_courses, яка пов'язує таблиці students і courses.
CREATE TABLE students ( id INT PRIMARY KEY, name VARCHAR(100));
CREATE TABLE courses ( id INT PRIMARY KEY, title VARCHAR(100));
CREATE TABLE students_courses ( student_id INT, course_id INT, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES students(id), FOREIGN KEY (course_id) REFERENCES courses(id));Один до одного (One-to-One)
Цей тип відношення позначає, що один запис в одній таблиці пов'язаний лише з одним записом в іншій таблиці, і навпаки. Такі відношення використовуються, коли дані можна розділити на дві таблиці для покращення структури або для зберігання додаткової інформації, яка використовується рідше.
Приклад:
Таблиці users (користувачі) і user_profiles (профілі користувачів). Кожен користувач має лише один профіль, і кожен профіль пов'язаний лише з одним користувачем.
Реалізація:
У таблиці user_profiles зовнішній ключ, який також є унікальним (або первинним), посилається на users.
CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(100));
CREATE TABLE user_profiles ( user_id INT PRIMARY KEY, address VARCHAR(255), phone VARCHAR(50), FOREIGN KEY (user_id) REFERENCES users(id));Самопосилальне відношення (Self-Referencing Relationship)
Це відношення використовується, коли запис у таблиці посилається на інший запис у цій самій таблиці. Таке відношення може описувати ієрархічні структури даних.
Приклад:
Таблиця employees (співробітники), де один співробітник може бути начальником іншого співробітника.
Реалізація:
У таблицю employees додається зовнішній ключ, який посилається на первинний ключ тієї самої таблиці.
CREATE TABLE employees ( id INT PRIMARY KEY, name VARCHAR(100), manager_id INT, FOREIGN KEY (manager_id) REFERENCES employees(id));Приклади використання відношень:
Один до багатьох:
Це найчастіше вживаний тип зв'язку. Використовується, коли є основна сутність (наприклад, замовник) і пов'язані з нею сутності (наприклад, замовлення).
Приклад: Клієнти та їхні замовлення (один клієнт — багато замовлень).
Багато до багатьох:
Використовується, коли об'єкти обох таблиць можуть бути пов'язані з багатьма об'єктами іншої таблиці. Наприклад, студенти та курси (один студент може записатися на кілька курсів, а один курс може відвідувати багато студентів).
Один до одного:
Використовується для розділення сутностей за окремими таблицями для покращення структури бази даних. Наприклад, користувач і його профіль.
Самопосилальні відношення:
Дуже корисні при моделюванні ієрархій (наприклад, співробітники та їхні начальники, категорії товарів і підкатегорії).
Як зробити співвідношення Many-to-Many в SQL
Для створення відношення Many-to-Many (багато до багатьох) у реляційних базах даних використовується проміжна (сполучна) таблиця, яка пов'язує два інші об'єкти (таблиці). Це робиться, тому що реляційні бази даних напряму не підтримують зв'язок "багато до багатьох" між двома таблицями.
Приклад:
Припустимо, у нас є дві таблиці
Students (студенти)
Courses (курси)
Кожен студент може записатися на кілька курсів, а на кожен курс може бути записано багато студентів. Це типова ситуація зв'язку "багато до багатьох".
Створюємо таблицю students:
CREATE TABLE students ( id INT PRIMARY KEY, name VARCHAR(100) NOT NULL);Створюємо таблицю courses:
CREATE TABLE courses ( id INT PRIMARY KEY, title VARCHAR(100) NOT NULL);Створюємо проміжну таблицю students_courses:
Ця таблиця пов'язує студентів і курси через їхні ідентифікатори (student_id і course_id). Вона міститиме по одному запису на кожну участь студента в конкретному курсі.
CREATE TABLE students_courses ( student_id INT, course_id INT, PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES students(id), FOREIGN KEY (course_id) REFERENCES courses(id));Що відбувається в проміжній таблиці:
Таблиця students_courses містить два стовпці: student_id і course_id. Разом вони складають складений первинний ключ, що запобігає дублюванню записів (тобто один студент не може записатися на один і той самий курс двічі).
Зовнішні ключі (FOREIGN KEY) використовуються для забезпечення цілісності даних, вказуючи, що значення в student_id і course_id мають відповідати наявним записам у таблицях students і courses відповідно.
Приклад вставки даних:
Додаємо студентів:
INSERT INTO students (id, name) VALUES (1, 'Alice'), (2, 'Bob');Додаємо курси:
INSERT INTO courses (id, title) VALUES (101, 'Mathematics'), (102, 'Biology');Пов'язуємо студентів з курсами:
INSERT INTO students_courses (student_id, course_id) VALUES (1, 101), (1, 102), (2, 101);Приклад запиту для отримання даних:
Щоб дізнатися, на які курси записаний студент, можна використати запит з JOIN:
SELECT students.name, courses.titleFROM studentsJOIN students_courses ON students.id = students_courses.student_idJOIN courses ON students_courses.course_id = courses.id;Цей запит покаже імена студентів і назви курсів, на які вони записані.
Як у SQL об'єднати запит до кількох таблиць
У SQL існує кілька підходів для об'єднання даних з кількох пов'язаних таблиць в один результуючий набір.
Використовуємо приклад таблиць:
CREATE TABLE customers ( customer_id INTEGER PRIMARY KEY, name VARCHAR(100), city VARCHAR(100));CREATE TABLE orders ( order_id INTEGER PRIMARY KEY, customer_id INTEGER, order_date DATE, total_amount DECIMAL(10,2), FOREIGN KEY (customer_id) REFERENCES customers(customer_id));CREATE TABLE archived_orders ( order_id INTEGER PRIMARY KEY, customer_id INTEGER, order_date DATE, total_amount DECIMAL(10,2));+-------------+------------------+---------------+| customer_id | name | city |+-------------+------------------+---------------+| 1 | Alice Johnson | New York || 2 | Bob Smith | Los Angeles || 3 | Charlie Brown | Chicago || 4 | Diana Prince | Miami |+-------------+------------------+---------------+
+----------+-------------+------------+--------------+| order_id | customer_id | order_date | total_amount |+----------+-------------+------------+--------------+| 101 | 1 | 2024-05-01 | 250.00 || 102 | 2 | 2024-05-03 | 150.00 || 103 | 1 | 2024-06-01 | 300.00 || 104 | 3 | 2024-06-15 | 200.00 |+----------+-------------+------------+--------------+
+----------+-------------+------------+--------------+| order_id | customer_id | order_date | total_amount |+----------+-------------+------------+--------------+| 201 | 2 | 2023-12-20 | 180.00 || 202 | 3 | 2023-11-30 | 220.00 || 203 | 4 | 2023-10-10 | 100.00 |+----------+-------------+------------+--------------+Об'єднання за допомогою JOIN
Спосіб «з'єднання» таблиць за спільним полем.
INNER JOIN
Лише ті рядки, де значення в полях-з'єднувачах збігаються в обох таблицях.
SELECT c.name, o.order_id, o.total_amountFROM customers cINNER JOIN orders o ON c.customer_id = o.customer_id;| name | order_id | total_amount |+------------------+----------+--------------+| Alice Johnson | 101 | 250.00 || Alice Johnson | 103 | 300.00 || Bob Smith | 102 | 150.00 || Charlie Brown | 104 | 200.00 |LEFT JOIN
Усі рядки з лівої таблиці та збіжні рядки з іншої таблиці.
SELECT c.name, o.order_id, o.total_amountFROM customers cLEFT JOIN orders o ON c.customer_id = o.customer_id;| name | order_id | total_amount |+------------------+----------+--------------+| Alice Johnson | 101 | 250.00 || Alice Johnson | 103 | 300.00 || Bob Smith | 102 | 150.00 || Charlie Brown | 104 | 200.00 || Diana Prince | NULL | NULL |RIGHT JOIN
SELECT c.name, o.order_id, o.total_amountFROM customers cRIGHT JOIN orders o ON c.customer_id = o.customer_id;| name | order_id | total_amount |+------------------+----------+--------------+| Alice Johnson | 101 | 250.00 || Alice Johnson | 103 | 300.00 || Bob Smith | 102 | 150.00 || Charlie Brown | 104 | 200.00 || Diana Prince | NULL | NULL |FULL OUTER JOIN
Об'єднує рядки з обох таблиць, включно з тими рядками, де відповідностей немає.
SELECT c.name, o.order_id, o.total_amountFROM customers cFULL OUTER JOIN orders o ON c.customer_id = o.customer_id;| name | order_id | total_amount |+------------------+----------+--------------+| Alice Johnson | 101 | 250.00 || Alice Johnson | 103 | 300.00 || Bob Smith | 102 | 150.00 || Charlie Brown | 104 | 200.00 || Diana Prince | NULL | NULL |CROSS JOIN
Утворює декартів добуток двох таблиць, кожен рядок з першої таблиці поєднується з кожним рядком з другої.
SELECT c.name, o.order_idFROM customers cCROSS JOIN orders o;| name | order_id |+------------------+----------+| Alice Johnson | 101 || Alice Johnson | 102 || Alice Johnson | 103 || Alice Johnson | 104 || Bob Smith | 101 || Bob Smith | 102 || Bob Smith | 103 || Bob Smith | 104 || Charlie Brown | 101 || Charlie Brown | 102 || Charlie Brown | 103 || Charlie Brown | 104 || Diana Prince | 101 || Diana Prince | 102 || Diana Prince | 103 || Diana Prince | 104 |Загальні моменти щодо швидкодії JOIN:
Наявність індексів на полях з'єднання суттєво покращує продуктивність.
Вибір конкретного алгоритму з'єднання (nested loop, hash join, merge join) залежить від СУБД, обсягу даних і наявності відповідних індексів.
Оптимізатор запитів може по-різному реагувати на складні ланцюжки з'єднань; іноді варто переписувати запити або застосовувати підказки для оптимізації.
Об'єднання результатів за допомогою UNION / UNION ALL
UNION
Об'єднує SELECT в один результат і видаляє дублікати.
SELECT order_id, customer_id, order_date, total_amount FROM ordersUNIONSELECT order_id, customer_id, order_date, total_amount FROM archived_orders;| order_id | customer_id | order_date | total_amount |+----------+-------------+------------+--------------+| 101 | 1 | 2024-05-01 | 250.00 || 102 | 2 | 2024-05-03 | 150.00 || 103 | 1 | 2024-06-01 | 300.00 || 104 | 3 | 2024-06-15 | 200.00 || 201 | 2 | 2023-12-20 | 180.00 || 202 | 3 | 2023-11-30 | 220.00 || 203 | 4 | 2023-10-10 | 100.00 |UNION ALL
Об'єднує SELECT в один результат.
SELECT customer_id FROM ordersUNION ALLSELECT customer_id FROM archived_orders;| customer_id |+-------------+| 1 || 2 || 1 || 3 || 2 || 3 || 4 |Використання підзапитів і похідних таблиць
Замість прямого об'єднання таблиць можна використовувати підзапити, які повертають дані з інших таблиць.
| Тип вкладеного запиту | Де використовується | Підтримка |
|---|---|---|
| У SELECT | Повертає скаляр | ✅ Зазвичай |
| У FROM | Табличний підзапит | ✅ Стандартно |
| У WHERE / HAVING | Фільтрація | ✅ Стандартно |
| EXISTS / NOT EXISTS | Перевірка наявності | ✅ Часто |
| IN / NOT IN | Порівняння з множиною | ✅ Стандартно |
| Порівняння з ANY, ALL | Скалярні порівняння | ✅ Часто |
| Корельовані підзапити | Використовують зовнішні поля | ✅ Стандартно |
Підзапит у SELECT
SELECT nameAS name,(SELECT AVG(total_amount) FROM orders)AS global_avgFROM customers;| name | global_avg |+------------------+-------------+| Alice Johnson | 225.00 || Bob Smith | 225.00 || Charlie Brown | 225.00 || Diana Prince | 225.00 |Підзапит у FROM
SELECT sub.customer_id, sub.avg_amountFROM (SELECT customer_id, AVG(total_amount) AS avg_amount, COUNT(*) AS cntFROM orders GROUP BY customer_id) AS subWHERE sub.cnt > 1;Підзапит у WHERE
SELECT nameFROM customersWHERE customer_id IN ( SELECT DISTINCT customer_id FROM orders);Підзапит у EXISTS / NOT EXISTS
SELECT nameFROM customers cWHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);Підзапит у IN / NOT IN
SELECT nameFROM customersWHERE customer_id IN ( SELECT customer_id FROM orders);Підзапит у ALL
SELECT order_id, total_amountFROM ordersWHERE total_amount > ALL ( SELECT total_amount FROM archived_orders);Підзапит у ANY / SOME
SELECT order_id, total_amountFROM ordersWHERE total_amount > ANY ( SELECT total_amount FROM archived_orders);Вкладений підзапит залежить від рядка зовнішнього запиту
SELECT order_id, customer_id, total_amountFROM orders o1WHERE total_amount > ( SELECT AVG(total_amount) FROM orders o2 WHERE o2.customer_id = o1.customer_id);Загальні табличні вирази (CTE) з конструкцією WITH
CTE дозволяють створювати тимчасові іменовані результати (як «віртуальні таблиці»), які можна використовувати в основному запиті. Це підвищує читабельність запиту та спрощує структурування складної логіки об'єднання.
Швидкодія:
- CTE по суті є аліасами для підзапитів. Їхня продуктивність часто аналогічна підзапитам у секції FROM, але в деяких СУБД може бути недостатня оптимізація, якщо CTE не інлайниться.
- При використанні рекурсивних CTE слід враховувати, що вони можуть бути менш ефективними, якщо глибина рекурсії висока.
Приклад: CTE для обчислення кількості замовлень
Спочатку створимо CTE, яке агрегує замовлення, а потім виконаємо LEFT JOIN з клієнтами:
WITH OrderCounts AS ( SELECT customer_id, COUNT(*) AS total_orders FROM orders GROUP BY customer_id)SELECT c.customer_id, c.name, c.city, COALESCE(oc.total_orders, 0) AS total_ordersFROM customers cLEFT JOIN OrderCounts ocON c.customer_id = oc.customer_id;Коментар щодо швидкодії:
- Якщо CTE інлайниться оптимізатором, то вони працюють так само ефективно, як і підзапити в секції FROM.
- У деяких СУБД CTE не оптимізуються («materialized»), що може вплинути на продуктивність при великому наборі даних.
Додаткові моменти та рекомендації щодо швидкодії
Оптимізація запитів:
Незалежно від обраного методу, наявність відповідних індексів – ключовий фактор швидкодії. Варто також вивчити план виконання запиту (EXPLAIN PLAN або аналог) для виявлення «вузьких місць».
Розміри таблиць і розподіл даних:
При роботі з великими таблицями всі методи вимагають ретельного тестування. Наприклад, якщо одна таблиця містить мільйони записів, а інша – тисячі, то оптимізатор може обрати інший порядок з'єднань для підвищення ефективності.
Особливості СУБД:
Різні системи (PostgreSQL, MySQL, Oracle, SQL Server) можуть по-різному обробляти JOIN, UNION або CTE. Тому варто протестувати запити в конкретному середовищі і, за потреби, застосовувати спеціалізовані налаштування або підказки для оптимізації (hints).
Читабельність vs. продуктивність:
Іноді читабельніша конструкція запиту (наприклад, з використанням CTE) може поступатися за продуктивністю переписаному з JOIN’ами. Тому рекомендується балансувати між підтримуваністю і швидкістю виконання, проводячи бенчмаркінг у реальному робочому середовищі.
Висновок
JOIN (INNER, LEFT/RIGHT, FULL, CROSS):
Найпоширеніший спосіб з хорошою оптимізацією за наявності індексів.
Хороший для безпосереднього об'єднання пов'язаних даних, особливо при правильному індексуванні.
UNION / UNION ALL:
Чудово підходить для об'єднання кількох SELECT-запитів; UNION ALL кращий для продуктивності, якщо усунення дублікатів не потрібне.
Корисні для об'єднання результатів з таблиць з однаковою структурою (наприклад, активні та архівні замовлення).
Підзапити та похідні таблиці:
Універсальний інструмент, але слід уникати корельованих підзапитів у випадках великого обсягу даних.
Можуть бути зручні для агрегування або фільтрації даних, але слід бути обережним з корельованими підзапитами.
CTE (WITH):
Підвищують читабельність складних запитів, однак не завжди гарантують покращену швидкодію.
Покращують читабельність складних запитів і дозволяють перевикористовувати проміжні результати, хоча іноді можуть впливати на швидкодію, якщо не оптимізуються інлайн.
Як використовувати план запиту в SQL
План запиту (execution plan) — це докладний опис того, як СУБД збирається виконати SQL-запит. Він дозволяє побачити, які операції виконуються (сканування таблиць, використання індексів, з'єднання, сортування, агрегації тощо), в якому порядку вони відбуваються і як розподіляються витрати за часом і ресурсами. Це важливий інструмент для оптимізації запитів і діагностики проблем з продуктивністю.
PostgreSQL
EXPLAIN:
EXPLAIN SELECT * FROM employees WHERE department_id = 10;Вивід:
QUERY PLAN--------------------------------------------------------------- Seq Scan on employees (cost=0.00..35.50 rows=5 width=50) Filter: (department_id = 10)(2 rows)Пояснення:
Seq Scan on employees: послідовне сканування таблиці employees.
cost=0.00..35.50: оцінка витрат від початку до завершення операції.
rows=5: передбачувана кількість рядків, що задовольняють умову.
width=50: середній розмір рядка в байтах.
Filter: (department_id = 10): умова фільтрації, що застосовується при скануванні.
EXPLAIN ANALYZE:
EXPLAIN ANALYZE SELECT * FROM employees WHERE department_id = 10;Вивід:
QUERY PLAN-------------------------------------------------------------------------------------------------------------------------- Seq Scan on employees (cost=0.00..35.50 rows=5 width=50) (actual time=0.021..0.040 rows=2 loops=1) Filter: (department_id = 10) Rows Removed by Filter: 3Planning Time: 0.123 msExecution Time: 0.060 ms(5 rows)Пояснення:
actual time=0.021..0.040: фактичний час початку та закінчення сканування.
rows=2: фактична кількість рядків, що пройшли фільтр.
Rows Removed by Filter: 3: кількість рядків, відсіяних фільтром.
Planning Time і Execution Time: дають уявлення про час планування запиту та його виконання відповідно.
MySQL
EXPLAIN:
EXPLAIN SELECT * FROM employees WHERE department_id = 10;Вивід (у вигляді таблиці):
+----+-------------+-----------+------+---------------+-----------+---------+-------+------+--------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+-----------+------+---------------+-----------+---------+-------+------+--------------------------+| 1 | SIMPLE | employees | ref | dep_index | dep_index | 4 | const | 5 | Using where; Using index |+----+-------------+-----------+------+---------------+-----------+---------+-------+------+--------------------------+Пояснення:
id: ідентифікатор запиту (для складніших запитів з підзапитами або з'єднаннями).
select_type: тип запиту (SIMPLE означає відсутність підзапитів).
table: ім'я таблиці.
type: тип доступу (наприклад, ref означає використання індексу для пошуку).
possible_keys: індекси, які могли б бути використані.
key: фактично використаний індекс.
rows: оцінна кількість рядків, яка буде прочитана.
Extra: додаткові відомості, наприклад, Using where вказує на застосування фільтра.
Oracle
Для Oracle зазвичай виконують два кроки- спочатку формують план, потім його переглядають.
Крок 1:
Формування плану
EXPLAIN PLAN FORSELECT * FROM employees WHERE department_id = 10;Крок 2:
Перегляд плану
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);Вивід (у вигляді таблиці):
--------------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost |--------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | | | 3 ||* 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 5 | 120 | 3 ||* 2 | INDEX RANGE SCAN | DEP_INDEX | 5 | | 2 |--------------------------------------------------------------------------------Predicate Information (identified by operation id):--------------------------------------------------------- 1 - filter("DEPARTMENT_ID"=10) 2 - access("DEPARTMENT_ID"=10)Пояснення:
TABLE ACCESS BY INDEX ROWID: доступ до рядків через індекс.
INDEX RANGE SCAN: використання діапазонного сканування за індексом DEP_INDEX.
Cost: оцінка витрат на виконання кожної операції.
Predicate Information: уточнює, яка умова застосовується для кожної операції.
Microsoft SQL Server
У SQL Server існує можливість отримання плану виконання як у графічному інтерфейсі SSMS, так і через T-SQL.
Приклад:
використання SHOWPLAN
SET SHOWPLAN_ALL ON;GOSELECT * FROM employees WHERE department_id = 10;GOSET SHOWPLAN_ALL OFF;Вивід (у вигляді таблиці):
------------------------------------------------------------------------------------------------------------------| stmtid | nodeid | parent | physical_op | logical_op | argument |------------------------------------------------------------------------------------------------------------------| 1 | 0 | NULL | Clustered Index Scan | Clustered Index Scan | Table: employees, Index: PK_employees || 1 | 1 | 0 | Filter | Filter | Predicate: department_id = 10 |------------------------------------------------------------------------------------------------------------------Пояснення:
Clustered Index Scan: сканування з використанням кластерного індексу.
Filter: застосування умови фільтрації.
stmtid, nodeid, parent: ідентифікатори, що визначають ієрархію операцій у плані виконання.
Як використовувати індекси в SQL
Індекси суттєво підвищують продуктивність запитів, однак їх надмірне або недоречне використання може сповільнити операції вставки, оновлення та видалення через додаткове обслуговування індексу. Вибір типу індексу визначається конкретними завданнями:
B-Tree індекс:
стандарт для більшості випадків.
Унікальний індекс:
для забезпечення унікальності.
Композитний індекс:
для запитів з фільтрами за кількома стовпцями.
Повнотекстовий індекс:
для роботи з текстовими даними.
Просторовий індекс:
для географічних і геометричних даних.
Функціональний індекс:
для оптимізації запитів із застосуванням функцій.
Звичайний (B-Tree) індекс:
Стандартний тип індексу, який використовується для більшості пошукових операцій. Він будується за принципом бінарного дерева, що дозволяє швидко знаходити рядки за значенням стовпця.
CREATE INDEX idx_employee_department ON employees(department_id);Як використовується:
При виконанні запиту з умовою за стовпцем department_id (наприклад, WHERE department_id = 10) оптимізатор запиту обирає індекс замість повного сканування таблиці.
Унікальний індекс:
Забезпечує унікальність значень у стовпці або наборі стовпців. При спробі вставити дубльоване значення СУБД видає помилку.
CREATE UNIQUE INDEX idx_unique_email ON employees(email);Як використовується:
Якщо в таблиці employees не можна мати двох співробітників з однаковим email, унікальний індекс гарантує виконання цього обмеження та прискорює пошук за стовпцем email.
Композитний (мультиколонковий) індекс:
Створюється за двома або більше стовпцями. Він корисний, коли запити використовують кілька стовпців в умовах фільтрації або з'єднання. Важливо пам'ятати, що порядок стовпців в індексі має значення.
CREATE INDEX idx_employee_dept_lastname ON employees(department_id, last_name);Як використовується:
При запиті індекс дозволить швидко вибрати рядки за обома стовпцями.
SELECT * FROM employeesWHERE department_id = 10 AND last_name = 'Ivanov';Повнотекстовий індекс:
Використовується для швидкого пошуку за текстовими даними (наприклад, у полях з описом або вмістом статей). Він будується з урахуванням морфології та стоп-слів, що дозволяє виконувати пошук за словами та фразами.
MySQL:
Використовується для стовпців типу TEXT або VARCHAR.
CREATE FULLTEXT INDEX idx_fulltext_description ON products(description);Після створення індексу можна виконувати запити з оператором MATCH … AGAINST:
SELECT * FROM productsWHERE MATCH(description) AGAINST('електроніка');PostgreSQL:
використовує індекс типу GIN з функцією to_tsvector для повнотекстового пошуку.
CREATE INDEX idx_fts_content ON articles USING gin(to_tsvector('russian', content));Пошук виконується так:
SELECT * FROM articlesWHERE to_tsvector('russian', content) @@ to_tsquery('електроніка');SQL Server:
має вбудований механізм повнотекстового пошуку. Спочатку необхідно створити повнотекстовий каталог, потім повнотекстовий індекс.
-- Створення каталогу повнотекстового пошукуCREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
-- Створення повнотекстового індексу за таблицею productsCREATE FULLTEXT INDEX ON products(description)KEY INDEX PK_products ON ftCatalog;Просторовий індекс:
Просторові індекси використовуються для прискорення запитів за геометричними даними (точки, лінії, полігони). Вони оптимізовані для пошуку за двовимірними (або багатовимірними) просторовими координатами.
MySQL:
При використанні типів даних GEOMETRY, POINT тощо. Тут geom – стовпець, що містить геометричні дані.
CREATE SPATIAL INDEX idx_spatial_location ON locations(geom);PostgreSQL з розширенням PostGIS:
Для просторових даних використовується індекс GIST. Такий індекс прискорює просторові запити, наприклад, пошук об'єктів у певному радіусі.
CREATE INDEX idx_spatial_location ON locations USING gist(geom);Oracle:
Підтримує просторові індекси через Oracle Spatial. Приклад створення:
CREATE INDEX idx_spatial_location ON locations(geom)INDEXTYPE IS MDSYS.SPATIAL_INDEX;Функціональний (обчислюваний) індекс:
Функціональні індекси створюються на основі виразів або функцій, що застосовуються до стовпців. Це зручно, якщо запити часто використовують функції, наприклад, для приведення до нижнього регістру, обрізки рядків тощо.
Oracle і PostgreSQL:
Припустимо, потрібен індекс для швидкого пошуку за email без урахування регістру:
CREATE INDEX idx_lower_email ON employees(LOWER(email));Запит буде використовувати індекс, якщо умова записана так:
SQL Server:
У SQL Server можна створити індекс на обчислюваний стовпець. Спочатку додають обчислюваний стовпець, а потім створюють за ним індекс:
ALTER TABLE employees ADD lower_email AS LOWER(email);CREATE INDEX idx_lower_email ON employees(lower_email);