Загрузка данных


КОНСПЕКТ ПО ТЕОРИИ СУБД ДЛЯ ЭКЗАМЕНА

Файл сделан для быстрого поиска и копирования.
Темы идут по тем вопросам, которые встречаются в старых билетах.
Почти в каждом блоке есть пример, потому что на экзамене могут снять баллы за ответ без примера.


1. ПРЕДМЕТНАЯ ОБЛАСТЬ, ФОРМАЛИЗАЦИЯ, БИЗНЕС-ПРАВИЛА

Предметная область - часть реального мира, данные о которой нужно хранить и обрабатывать в базе данных.

Изучение предметной области обычно включает:
1. определение основных объектов;
2. определение связей между объектами;
3. определение атрибутов объектов;
4. выявление ограничений и бизнес-правил;
5. построение концептуальной модели.

Формализация - это перевод описания предметной области на строгий язык модели данных. Например, фразу "у каждого билета есть пассажир" можно формализовать как сущность tickets с атрибутами ticket_no, passenger_id, passenger_name.

Смысловое описание - обычное текстовое описание предметной области. Оно помогает понять, что именно хранится в БД и какие операции должны выполняться.

Бизнес-правила - ограничения и правила работы предметной области. Они могут быть реализованы ключами, CHECK-ограничениями, внешними ключами, триггерами или логикой приложения.

Пример для авиаперевозок:
1. пассажир может иметь несколько билетов;
2. билет может включать несколько перелетов;
3. рейс выполняется на одном самолете;
4. место в посадочном талоне должно относиться к конкретному рейсу и билету;
5. стоимость билета не должна быть отрицательной.

Пример бизнес-правила в SQL:

ALTER TABLE ticket_flights
ADD CONSTRAINT amount_positive CHECK (amount >= 0);


2. КОНЦЕПТУАЛЬНОЕ ПРОЕКТИРОВАНИЕ И ER-МОДЕЛЬ

Концептуальное проектирование - этап, на котором строится модель предметной области без привязки к конкретной СУБД.

Основные элементы ER-модели:
1. сущность - класс объектов предметной области;
2. атрибут - свойство сущности;
3. связь - отношение между сущностями;
4. ключ - атрибут или набор атрибутов, однозначно определяющий экземпляр сущности;
5. кардинальность - сколько экземпляров одной сущности связано с экземплярами другой;
6. обязательность - должна ли связь существовать всегда.

Пример:
Сущности: Самолет, Рейс, Билет, Пассажир.
Связь: один самолет выполняет много рейсов.
Кардинальность: aircrafts 1:M flights.
Обязательность: у рейса должен быть самолет, значит связь со стороны flights обязательная.

Типы связей:
1. 1:1 - один экземпляр связан максимум с одним экземпляром другой сущности;
2. 1:M - один экземпляр связан со многими;
3. M:N - многие связаны со многими, обычно преобразуется в промежуточную таблицу.

Пример M:N:
Пассажиры и рейсы связаны многие ко многим. В базе это решается через tickets и ticket_flights.


3. УТОЧНЕНИЕ, ОБОБЩЕНИЕ, КОМПОЗИЦИЯ, АГРЕГИРОВАНИЕ

Обобщение - выделение общей сущности для нескольких похожих сущностей.
Пример: самолет, автомобиль и поезд можно обобщить до сущности Транспорт.

Уточнение - обратный процесс, когда общая сущность делится на подклассы.
Пример: сущность Билет можно уточнить как Билет эконом-класса, Билет бизнес-класса, Билет комфорт-класса.

Суперкласс - общая сущность.
Подкласс - более конкретная сущность.

Композиция - сильная связь "часть-целое", где часть не имеет смысла без целого.
Пример: посадочный талон как часть перелета пассажира. Без конкретного билета и рейса такой талон не нужен.

Агрегирование - представление связи как самостоятельного объекта.
Пример: связь "пассажир летит рейсом" можно рассматривать как объект ticket_flights, у которого есть атрибут amount.


4. НОТАЦИЯ IDEF1X

IDEF1X - графическая нотация для моделирования данных. Она используется для описания сущностей, атрибутов, ключей и связей.

Основные элементы IDEF1X:
1. сущность - прямоугольник;
2. атрибуты - перечисляются внутри сущности;
3. первичный ключ - обычно отделяется верхней частью сущности;
4. связь - линия между сущностями;
5. идентифицирующая связь - дочерняя сущность зависит от родительской, ключ родителя входит в ключ потомка;
6. неидентифицирующая связь - дочерняя сущность имеет собственный ключ, а ключ родителя хранится как внешний ключ.

Пример:
aircrafts(aircraft_code) и flights(flight_id, aircraft_code).
Самолет связан с рейсами как 1:M.
Если flight_id является собственным ключом рейса, то связь неидентифицирующая: aircraft_code просто внешний ключ.

Что важно сказать на экзамене:
IDEF1X помогает перейти от предметной области к структуре таблиц. В модели сразу видно, какие таблицы нужны, где первичные ключи, где внешние ключи и какие связи между таблицами.


5. ПЕРЕХОД К ДАТАЛОГИЧЕСКОЙ И ФИЗИЧЕСКОЙ МОДЕЛИ

Даталогическая модель - логическая структура базы данных в терминах таблиц, столбцов, ключей и связей.

Физическая модель - реализация даталогической модели в конкретной СУБД. Здесь выбираются типы данных, индексы, ограничения, способы хранения.

Переход ER-модели к таблицам:
1. сущность становится таблицей;
2. атрибут становится столбцом;
3. первичный ключ становится PRIMARY KEY;
4. связь 1:M реализуется внешним ключом на стороне M;
5. связь M:N реализуется промежуточной таблицей.

Пример:
aircrafts(aircraft_code PRIMARY KEY)
flights(flight_id PRIMARY KEY, aircraft_code REFERENCES aircrafts(aircraft_code))


6. ПОДЗАПРОСЫ

Подзапрос - это запрос внутри другого запроса.

Где может использоваться подзапрос:
1. в WHERE;
2. в FROM;
3. в SELECT;
4. в HAVING;
5. вместе с EXISTS, IN, ANY, ALL.

Виды подзапросов:
1. скалярный - возвращает одно значение;
2. строковый - возвращает одну строку;
3. табличный - возвращает таблицу;
4. коррелированный - зависит от строки внешнего запроса.

Пример скалярного подзапроса:

SELECT book_ref, total_amount
FROM bookings
WHERE total_amount > (
    SELECT avg(total_amount)
    FROM bookings
);

Пример EXISTS:

SELECT t.ticket_no, t.passenger_name
FROM tickets t
WHERE EXISTS (
    SELECT 1
    FROM ticket_flights tf
    WHERE tf.ticket_no = t.ticket_no
);

Коррелированный подзапрос выполняется с учетом строки внешнего запроса. Он удобен, но иногда медленный.

Пример коррелированного подзапроса:

SELECT a.aircraft_code,
       a.model,
       (
           SELECT count(*)
           FROM flights f
           WHERE f.aircraft_code = a.aircraft_code
       ) AS flight_count
FROM aircrafts_data a;


7. CTE

CTE - common table expression, общее табличное выражение. Это временный именованный результат внутри одного SQL-запроса.

Синтаксис:

WITH name AS (
    SELECT ...
)
SELECT ...
FROM name;

Зачем нужны CTE:
1. сделать сложный запрос читаемым;
2. переиспользовать промежуточный результат;
3. разбить задачу на шаги;
4. писать рекурсивные запросы.

Пример CTE:

WITH flight_passengers AS (
    SELECT flight_id, count(*) AS cnt
    FROM boarding_passes
    GROUP BY flight_id
)
SELECT f.flight_id, f.flight_no, fp.cnt
FROM flights f
JOIN flight_passengers fp ON fp.flight_id = f.flight_id;

Пример CTE для поиска максимума по группам:

WITH delays AS (
    SELECT flight_id,
           departure_airport,
           actual_departure - scheduled_departure AS delay
    FROM flights
    WHERE actual_departure IS NOT NULL
),
max_delays AS (
    SELECT departure_airport, max(delay) AS max_delay
    FROM delays
    GROUP BY departure_airport
)
SELECT d.*
FROM delays d
JOIN max_delays m
  ON m.departure_airport = d.departure_airport
 AND m.max_delay = d.delay;

Важно:
В PostgreSQL CTE может быть раскрыто оптимизатором. Если написать MATERIALIZED, результат CTE будет сначала вычислен и сохранен, а потом прочитан.


8. ПРЕДСТАВЛЕНИЯ И МАТЕРИАЛИЗОВАННЫЕ ПРЕДСТАВЛЕНИЯ

Представление - сохраненный SQL-запрос, к которому можно обращаться как к таблице.

Синтаксис:

CREATE VIEW view_name AS
SELECT ...
FROM ...;

Пример:

CREATE VIEW flight_info AS
SELECT f.flight_id,
       f.flight_no,
       dep.airport_name AS departure_airport,
       arr.airport_name AS arrival_airport
FROM flights f
JOIN airports dep ON dep.airport_code = f.departure_airport
JOIN airports arr ON arr.airport_code = f.arrival_airport;

SELECT *
FROM flight_info
WHERE flight_no = 'PG0010';

Материализованное представление хранит результат запроса физически.

CREATE MATERIALIZED VIEW route_income AS
SELECT f.departure_airport, f.arrival_airport, sum(tf.amount) AS total_amount
FROM flights f
JOIN ticket_flights tf ON tf.flight_id = f.flight_id
GROUP BY f.departure_airport, f.arrival_airport;

Плюс materialized view - быстрее читать.
Минус - данные могут устаревать, нужно делать REFRESH MATERIALIZED VIEW.


9. ОКОННЫЕ ФУНКЦИИ

Оконная функция считает значение не по всей таблице, а по окну строк, связанному с текущей строкой.

Синтаксис:

function(...) OVER (
    PARTITION BY ...
    ORDER BY ...
    ROWS BETWEEN ... AND ...
)

PARTITION BY делит строки на группы.
ORDER BY задает порядок внутри группы.
ROWS или RANGE задает рамку окна.

Примеры оконных функций:
1. row_number() - номер строки;
2. rank() - ранг с пропусками;
3. dense_rank() - ранг без пропусков;
4. lag() - значение из предыдущей строки;
5. lead() - значение из следующей строки;
6. sum() over - накопительная сумма;
7. avg() over - среднее по окну.

Пример ранжирования самолетов по дальности:

SELECT aircraft_code,
       model,
       range,
       rank() OVER (ORDER BY range DESC) AS rnk,
       dense_rank() OVER (ORDER BY range DESC) AS dense_rnk
FROM aircrafts_data;

Пример накопительной выручки:

SELECT f.departure_airport,
       f.arrival_airport,
       f.scheduled_departure::date AS day,
       sum(tf.amount) AS day_amount,
       sum(sum(tf.amount)) OVER (
           PARTITION BY f.departure_airport, f.arrival_airport
           ORDER BY f.scheduled_departure::date
       ) AS running_amount
FROM flights f
JOIN ticket_flights tf ON tf.flight_id = f.flight_id
GROUP BY f.departure_airport, f.arrival_airport, f.scheduled_departure::date;

Отличие оконных функций от GROUP BY:
GROUP BY сворачивает строки.
Оконная функция сохраняет строки и добавляет вычисленное значение.


10. ФУНКЦИИ И ПРОЦЕДУРЫ PL/PGSQL

Функция возвращает значение или таблицу. Ее можно использовать в SELECT.

Синтаксис функции:

CREATE OR REPLACE FUNCTION function_name(param type)
RETURNS type AS $$
BEGIN
    ...
    RETURN ...;
END;
$$ LANGUAGE plpgsql;

Пример функции, которая возвращает рейсы из аэропорта:

CREATE OR REPLACE FUNCTION get_flights_from_airport(p_airport bpchar)
RETURNS TABLE(flight_id integer, flight_no char(6), scheduled_departure timestamptz) AS $$
BEGIN
    RETURN QUERY
    SELECT f.flight_id, f.flight_no, f.scheduled_departure
    FROM flights f
    WHERE f.departure_airport = p_airport;
END;
$$ LANGUAGE plpgsql;

SELECT *
FROM get_flights_from_airport('SVO');

Процедура не обязана возвращать значение. Ее вызывают через CALL. В процедуре можно делать действия с данными.

CREATE OR REPLACE PROCEDURE cancel_flights_by_aircraft(p_aircraft bpchar) AS $$
BEGIN
    UPDATE flights
    SET status = 'Cancelled'
    WHERE aircraft_code = p_aircraft;
END;
$$ LANGUAGE plpgsql;

CALL cancel_flights_by_aircraft('733');

Разница:
Функция - обычно для получения результата.
Процедура - обычно для выполнения действия.


11. ТРИГГЕРЫ И ТРИГГЕРНЫЕ ФУНКЦИИ

Триггер - объект БД, который автоматически вызывает функцию при событии INSERT, UPDATE, DELETE или TRUNCATE.

Виды по моменту выполнения:
1. BEFORE - до операции;
2. AFTER - после операции;
3. INSTEAD OF - вместо операции, обычно для представлений.

Виды по уровню:
1. FOR EACH ROW - для каждой строки;
2. FOR EACH STATEMENT - один раз для всей команды.

Триггерная функция должна возвращать trigger.
Внутри доступны OLD и NEW.
NEW - новая строка при INSERT или UPDATE.
OLD - старая строка при UPDATE или DELETE.

Пример проверки дальности самолета:

CREATE OR REPLACE FUNCTION check_aircraft_range()
RETURNS trigger AS $$
BEGIN
    IF NEW.range < 0 THEN
        RAISE EXCEPTION 'range must be positive';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_check_aircraft_range
BEFORE INSERT OR UPDATE ON aircrafts_data
FOR EACH ROW
EXECUTE FUNCTION check_aircraft_range();

Пример журнала удаления:

CREATE TABLE airports_delete_log (
    airport_code bpchar,
    deleted_at timestamptz DEFAULT now()
);

CREATE OR REPLACE FUNCTION log_airport_delete()
RETURNS trigger AS $$
BEGIN
    INSERT INTO airports_delete_log(airport_code)
    VALUES (OLD.airport_code);
    RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_log_airport_delete
AFTER DELETE ON airports_data
FOR EACH ROW
EXECUTE FUNCTION log_airport_delete();


12. ТРАНЗАКЦИИ И ACID

Транзакция - логическая единица работы с базой данных. Она либо выполняется полностью, либо не выполняется.

ACID:
1. Atomicity - атомарность. Все действия транзакции выполняются как единое целое.
2. Consistency - согласованность. После транзакции БД остается в корректном состоянии.
3. Isolation - изолированность. Параллельные транзакции не должны некорректно влиять друг на друга.
4. Durability - долговечность. После COMMIT изменения сохраняются.

Основные команды:

BEGIN;
UPDATE bookings
SET total_amount = total_amount + 100
WHERE book_ref = '00000F';
COMMIT;

Если нужно отменить:

BEGIN;
UPDATE bookings
SET total_amount = total_amount + 100
WHERE book_ref = '00000F';
ROLLBACK;

Сериализация транзакций - такой результат параллельного выполнения, как будто транзакции выполнялись последовательно.


13. УРОВНИ ИЗОЛЯЦИИ ТРАНЗАКЦИЙ

Уровень изоляции определяет, какие изменения других транзакций видит текущая транзакция.

Основные уровни:
1. READ UNCOMMITTED;
2. READ COMMITTED;
3. REPEATABLE READ;
4. SERIALIZABLE.

В PostgreSQL READ UNCOMMITTED фактически работает как READ COMMITTED.

READ COMMITTED:
Каждый оператор видит только зафиксированные данные на момент начала оператора. Один и тот же SELECT в одной транзакции может увидеть разные данные, если другая транзакция сделала COMMIT.

REPEATABLE READ:
Транзакция видит снимок данных на момент начала транзакции. Повторный SELECT дает тот же результат.

SERIALIZABLE:
Самый строгий уровень. СУБД старается обеспечить результат, эквивалентный последовательному выполнению транзакций. Возможны ошибки сериализации, тогда транзакцию нужно повторить.

Пример:

BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT total_amount FROM bookings WHERE book_ref = '00000F';
SELECT total_amount FROM bookings WHERE book_ref = '00000F';
COMMIT;


14. БЛОКИРОВКИ

Блокировка нужна, чтобы параллельные транзакции не портили данные друг другу.

Блокировки бывают:
1. строковые;
2. табличные;
3. явные;
4. неявные.

Пример строковой блокировки:

BEGIN;
SELECT *
FROM bookings
WHERE book_ref = '00000F'
FOR UPDATE;
COMMIT;

FOR UPDATE блокирует выбранные строки для изменения другими транзакциями.

Пример табличной блокировки:

BEGIN;
LOCK TABLE bookings IN SHARE MODE;
COMMIT;

Важно:
Строковая блокировка обычно точнее и лучше, потому что не мешает работе со всей таблицей.
Табличная блокировка сильнее и может заблокировать больше операций.


15. ИНДЕКСЫ И ИНДЕКСНЫЕ СТРУКТУРЫ

Индекс - структура данных, которая ускоряет поиск строк, но требует места и замедляет INSERT, UPDATE, DELETE.

Основные виды индексов:
1. B-tree;
2. Hash;
3. GiST;
4. GIN;
5. BRIN;
6. bitmap-индексы как подход в некоторых СУБД.

B-tree - основной индекс в PostgreSQL. Хорошо подходит для:
1. =;
2. <, >, <=, >=;
3. BETWEEN;
4. ORDER BY;
5. LIKE 'abc%'.

Пример:

CREATE INDEX ticket_flights_amount_idx
ON ticket_flights(amount);

SELECT *
FROM ticket_flights
WHERE amount > 200000;

Bitmap Scan:
Планировщик может сначала найти подходящие строки по индексу, построить bitmap, а потом прочитать страницы таблицы. Это удобно, когда строк не одна-две, но и не вся таблица.

Index Only Scan:
Если все нужные данные есть в индексе и видимость строк известна, PostgreSQL может не читать таблицу.

Пример:

SELECT amount
FROM ticket_flights
ORDER BY amount DESC
LIMIT 1;

После индекса по amount план может использовать Index Only Scan Backward.


16. EXPLAIN И ЧТЕНИЕ ПЛАНА ЗАПРОСА

EXPLAIN показывает, как PostgreSQL планирует выполнить запрос.

Основной синтаксис:

EXPLAIN
SELECT ...

EXPLAIN ANALYZE реально выполняет запрос и показывает фактическое время.

EXPLAIN (ANALYZE, BUFFERS)
SELECT *
FROM ticket_flights
WHERE amount > 200000;

Что смотреть:
1. тип сканирования: Seq Scan, Index Scan, Bitmap Heap Scan;
2. тип соединения: Nested Loop, Hash Join, Merge Join;
3. Sort, HashAggregate, GroupAggregate;
4. cost - оценочная стоимость;
5. rows - оценка строк;
6. actual time - фактическое время;
7. loops - сколько раз узел выполнялся;
8. Buffers - чтение из памяти и с диска;
9. Batches и Memory Usage у Hash.

Пример пояснения:
Если в плане есть Parallel Seq Scan, Gather, Partial Aggregate и Finalize Aggregate, запрос выполняется параллельно. Рабочие процессы считают частичные результаты, а главный процесс собирает итог.


17. СОЕДИНЕНИЯ ТАБЛИЦ

Основные алгоритмы соединений:
1. Nested Loop;
2. Hash Join;
3. Merge Join.

Nested Loop:
Для каждой строки внешней таблицы ищутся строки во внутренней таблице. Хорошо работает, если внешняя таблица маленькая или есть индекс.
Сложность без индекса примерно O(n * m).

Пример:

SELECT *
FROM flights f
JOIN airports a ON a.airport_code = f.departure_airport
WHERE f.flight_id = 1;

Hash Join:
Для одной таблицы строится hash-таблица, потом строки второй таблицы ищутся по hash. Хорошо для больших неотсортированных наборов.
Сложность примерно O(n + m).

Пример:

SELECT f.flight_id, a.airport_name
FROM flights f
JOIN airports a ON a.airport_code = f.arrival_airport;

Merge Join:
Обе таблицы должны быть отсортированы по ключу соединения. Затем данные сливаются в одном проходе.
Сложность примерно O(n + m), но может добавиться стоимость сортировки.

Пример:

SELECT t.ticket_no, tf.flight_id
FROM tickets t
JOIN ticket_flights tf ON tf.ticket_no = t.ticket_no
ORDER BY t.ticket_no;


18. РАСПРЕДЕЛЕННЫЕ СУБД

Распределенная СУБД хранит данные на нескольких узлах, но пользователь работает с ними как с единой базой.

Зачем нужны:
1. масштабирование;
2. отказоустойчивость;
3. распределение нагрузки;
4. хранение данных ближе к пользователям.

Основные способы организации:
1. репликация - копии данных на нескольких узлах;
2. фрагментация - разные части данных на разных узлах;
3. шардирование - горизонтальное разделение таблиц по ключу;
4. федерация - несколько БД объединяются общим интерфейсом.

Плюсы:
1. можно обрабатывать больше данных;
2. система устойчивее к отказам;
3. можно распределять нагрузку.

Минусы:
1. сложнее обеспечивать согласованность;
2. сложнее транзакции;
3. возможны сетевые задержки;
4. сложнее администрирование.

Примеры: PostgreSQL с репликацией и шардингом, CockroachDB, Cassandra, MongoDB cluster.


19. СИСТЕМЫ ХРАНЕНИЯ ДАННЫХ И ЗНАНИЙ

Основные типы систем хранения:
1. реляционные БД;
2. key-value хранилища;
3. документоориентированные БД;
4. колоночные БД;
5. графовые БД;
6. объектные БД;
7. хранилища знаний.

Реляционные БД:
Данные хранятся в таблицах. Есть SQL, ключи, ограничения, транзакции.
Примеры: PostgreSQL, MySQL, Oracle.

Key-value:
Данные хранятся как ключ и значение. Очень быстрый доступ по ключу.
Примеры: Redis, Riak, DynamoDB.

Документоориентированные:
Данные хранятся документами, обычно JSON или BSON. Удобно для гибкой структуры.
Примеры: MongoDB, CouchDB.

Колоночные:
Данные хранятся по столбцам. Хорошо для аналитики.
Примеры: ClickHouse, Cassandra, HBase.

Графовые:
Данные хранятся как вершины и ребра. Хорошо для связей.
Примеры: Neo4j.

Хранилища знаний:
Хранят не только данные, но и связи, правила, факты, онтологии.
Примеры: RDF-хранилища, OWL-онтологии, экспертные системы.


20. KEY-VALUE ХРАНИЛИЩА

Key-value хранилище хранит данные в виде ключ - значение.

Пример:
ключ: passenger:123
значение: JSON с данными пассажира

Особенности:
1. быстрый доступ по ключу;
2. простая модель данных;
3. хорошо подходит для кеша, сессий, счетчиков;
4. плохо подходит для сложных JOIN и аналитических запросов.

Пример Redis:

SET passenger:123 "Ivan Petrov"
GET passenger:123

Плюсы:
1. высокая скорость;
2. простота;
3. горизонтальное масштабирование.

Минусы:
1. нет полноценной реляционной модели;
2. сложные выборки нужно делать в приложении;
3. меньше ограничений целостности.


21. ДОКУМЕНТООРИЕНТИРОВАННЫЕ БАЗЫ ДАННЫХ

Документоориентированная БД хранит данные как документы, чаще всего JSON или BSON.

Пример документа:

{
  "ticket_no": "0005432000987",
  "passenger_name": "Ivan Petrov",
  "flights": [
    {"flight_id": 10, "amount": 12500},
    {"flight_id": 11, "amount": 9300}
  ]
}

Особенности:
1. гибкая структура;
2. удобно хранить вложенные данные;
3. можно хранить разные документы в одной коллекции;
4. нет обязательной жесткой схемы как в реляционной БД.

Плюсы:
1. удобно для JSON-данных;
2. быстро разрабатывать;
3. хорошо масштабируется горизонтально.

Минусы:
1. сложнее поддерживать целостность;
2. JOIN ограничены или отсутствуют;
3. дублирование данных встречается чаще.

Пример: MongoDB.