Представления (VIEW)
Представление (VIEW) — это виртуальная таблица, результат сохранённого запроса, к которому можно обращаться как к обычной таблице. Оно не хранит физическую копию данных, а лишь определение запроса.
Представления удобны, когда регулярно используется одна и та же громоздкая склейка таблиц. Вместо повторения сложного кода создаётся представление, инкапсулирующее этот запрос, и далее все операции идут с ним.
Особенности представлений:
• Доступны только для чтения (в большинстве реализаций).
• Автоматически обновляются при изменении данных в исходных таблицах.
• Являются виртуальными: копия данных не создаётся. Результат может кэшироваться в оперативной памяти или на диске (зависит от СУБД), но физического дублирования строк нет.
• Поддерживается вложенность: можно создать представление на основе другого представления.
Синтаксис создания:
sql
CREATE VIEW ИмяПредставления (поле1, поле2, ...)
AS
SELECT ...
Пример 1. Создадим представление
GSize, показывающее размер каждой группы:
sql
CREATE VIEW GSize (GID, S_Count)
AS
SELECT Groups.ID, COUNT(Students.ID)
FROM Groups
JOIN Students ON Groups.ID = Students.GroupID
GROUP BY Groups.ID
ORDER BY COUNT(Students.ID) DESC;
Теперь можно обращаться
SELECT * FROM GSize; — не требуется повторять весь JOIN и группировку.
Пример 2. Вложенное представление
Broup, возвращающее номер самой многочисленной группы:
sql
CREATE VIEW Broup (Namba)
AS
SELECT Groups.Number
FROM GSize
JOIN Groups ON GSize.GID = Groups.ID
WHERE ROWS 1; -- или TOP 1 / FETCH FIRST 1 ROW ONLY
Здесь
GSize используется как обычная таблица, хотя сама является представлением. Склейка с таблицей
Groups нужна, чтобы по ID группы получить её номер.
Назначение представлений:
• Подготовка данных для клиентских приложений: сложная часть запроса выносится на сервер, клиент выполняет простые выборки.
• Декомпозиция сложных запросов: промежуточные шаги оформляются как представления, что упрощает отладку и понимание кода.
SQL как процедурный язык
Условный оператор IF
Синтаксис:
sql
IF (условие) THEN
действие;
[ELSE
альтернативное действие;]
ELSE опционально. Пример:
sql
IF (A > 10) THEN
A = 0;
Цикл FOR и оператор SUSPEND
Цикл
FOR SELECT ... INTO ... DO позволяет построчно обойти результат запроса. Перед именем переменной ставится двоеточие.
SUSPEND — оператор возврата текущей строки и продолжения цикла. Он не завершает процедуру, а отправляет очередную строку клиенту и переходит к следующей итерации.
Пример. Вернуть имена студентов, приписанных к группе, длина имени которых больше 5 символов:
sql
FOR SELECT FirstName FROM Students
WHERE GroupID IS NOT NULL
ORDER BY FirstName
INTO :OutFname
DO
IF (CHAR_LENGTH(:OutFname) > 5) THEN
SUSPEND;
Результат запроса заносится в переменную
OutFname. В теле цикла проверяется длина строки, и если условие выполняется, строка возвращается оператором
SUSPEND.
Цикл WHILE
Классический цикл с предусловием. Синтаксис:
sql
WHILE (условие) DO
действие;
Пример. Вставить 100 записей «Иван Иванов»:
sql
A = 0;
WHILE (A < 100) DO
BEGIN
INSERT INTO Students (FirstName, LastName) VALUES ('Иван', 'Иванов');
A = A + 1;
END
Проверка условия происходит перед каждым входом в тело цикла.
Операторы BREAK и EXIT
• BREAK — немедленно прерывает текущий цикл (FOR или WHILE), управление переходит к следующей за циклом инструкции.
• EXIT — полностью завершает выполнение хранимой процедуры или триггера.
Пример с BREAK:
sql
FOR SELECT Age, FirstName FROM Students
INTO :VarAge, :Name
DO
BEGIN
IF (:VarAge > 20) THEN
BEGIN
SUSPEND;
BREAK;
END
END
Здесь возвращается возраст и имя первого студента старше 20 лет, после чего цикл прерывается. Если заменить
BREAK на
EXIT, произойдёт немедленный выход из всей процедуры без возврата данных.
Генераторы (GENERATOR)
Генератор — самостоятельный объект базы данных, который выдаёт возрастающую последовательность целых чисел. Не привязан к конкретной таблице или первичному ключу.
Синтаксис:
sql
CREATE GENERATOR ИмяГенератора;
SET GENERATOR ИмяГенератора TO начальное_значение;
GEN_ID(ИмяГенератора, шаг); -- возвращает текущее значение, увеличенное на шаг
Пример:
sql
CREATE GENERATOR GenStatID;
SET GENERATOR GenStatID TO 45;
GenValue = GEN_ID(GenStatID, 1); -- результат 46
Генераторы удобны в триггерах и процедурах, когда требуется уникальный идентификатор для каждой новой строки. Они гарантируют неповторяемость значений даже при параллельной работе.
Триггеры (TRIGGER)
Триггер — хранимая процедура, автоматически вызываемая при наступлении определённых событий над таблицей:
INSERT,
DELETE,
UPDATE.
Триггеры бывают двух типов по моменту срабатывания:
•
BEFORE (до события) — обычно используется для проверки корректности данных перед вставкой, изменением или удалением.
•
AFTER (после события) — реализует бизнес-логику, требующую уже зафиксированных изменений (например, пересчёт итогов, распределение заказа).
Триггер имеет доступ к старым и новым данным через псевдотаблицы (в терминологии SQL Server —
inserted и
deleted):
•
INSERTED — новые значения, которые будут вставлены или уже вставлены.
•
DELETED — старые значения, которые удаляются или изменяются.
Такая возможность позволяет сравнивать состояния «до» и «после» и принимать точные решения.
Хотя написание триггеров относится к компетенциям более высокого уровня (требуется знание предметной области, параллельной работы пользователей и оптимизации), понимание их структуры и назначения необходимо.
Обработка исключений (EXCEPTION)
На стороне сервера могут возникать ошибки — например, попытка вставить некорректную дату. Если не перехватывать такие ошибки, клиентское приложение может аварийно завершиться или получить невразумительное сообщение.
Обработка исключений позволяет перехватить ошибку и выдать клиенту осмысленное уведомление, а также выполнить компенсирующие действия.
Базовый синтаксис:
sql
WHEN {любая_ошибка | код_ошибки} DO
действие;
• Обработка «любой ошибки» (
WHEN ANY DO) спасает приложение от краха, но не даёт подробностей.
• Обработка по конкретному коду ошибки позволяет точно идентифицировать проблему и предложить пользователю целенаправленное исправление.
Хранимые процедуры (STORED PROCEDURE)
Хранимая процедура — именованный блок кода на SQL, который может быть вызван явно из клиентского приложения, из триггера или по расписанию.
Особенности:
• Имеет входные (
IN) и выходные (
OUT) параметры.
• Входные параметры делают процедуру универсальной: одно и то же тело работает с разными аргументами.
• Выходные параметры и явный возврат значений позволяют передавать результаты вызывающей стороне.
Пример 1. Процедура
GroupSize, возвращающая количество студентов в группе по её номеру:
sql
CREATE PROCEDURE GroupSize (
Namba VARCHAR(10) -- вход: номер группы
) RETURNS (
S_Count INTEGER -- выход: количество студентов
)
AS
BEGIN
SELECT COUNT(*)
FROM Groups
JOIN Students ON Students.GroupID = Groups.ID
WHERE Groups.Number = :Namba
GROUP BY Groups.ID
INTO :S_Count;
SUSPEND;
END;
Вызов:
EXECUTE PROCEDURE GroupSize('123');.
Пример 2. Процедура
FillAvgMark для пересчёта средней оценки, запускаемая из AFTER-триггера при добавлении новой оценки.
Хранимая процедура может возвращать несколько значений (несколько выходных полей) — тогда она воспринимается как таблица, и её результат можно запросить через
SELECT * FROM ИмяПроцедуры(параметры). Например:
sql
CREATE PROCEDURE SomeProc (GroupNum VARCHAR(10))
RETURNS (ID INTEGER, FName VARCHAR(50), LName VARCHAR(50))
...
Вызов:
SELECT * FROM SomeProc('123'); — вернёт таблицу с колонками ID, FName, LName.
Ключевая задача хранимых процедур — реализация бизнес-логики на стороне сервера, снижение сетевого обмена и централизация кода.
События (EVENT)
Событие — сообщение, которое база данных посылает клиентским приложениям. Используется для уведомления о значимых изменениях состояния.
Отправка события:
sql
POST_EVENT 'текст_события';
Обычно вызов
POST_EVENT встраивается в тело триггера или хранимой процедуры, чтобы оповестить клиентов, например, о поступлении нового заказа или завершении пакетной обработки.
Оптимизация запросов
Оптимизация SQL-запросов — важнейший аспект производительности. Понимание того, как сервер выполняет запрос, позволяет писать эффективный код. В рамках данного курса детальный разбор оптимизации будет выполнен на практике в последующих занятиях.
Краткие итоги
Материал демонстрирует переход от декларативного извлечения данных к полноценному императивному программированию внутри базы данных. Отправной точкой служат представления — механизм, позволяющий инкапсулировать часто используемую логику выборки и скрыть сложность за простым интерфейсом таблицы. Такой подход не только сокращает объём повторяющегося кода, но и служит первым шагом к модульности серверного слоя: разработчик получает возможность строить иерархии абстракций, комбинируя представления друг с другом.
Далее вводятся конструкции управления потоком: условный оператор и циклы. Их появление принципиально расширяет арсенал SQL, позволяя реализовать ветвление и итеративную обработку без выхода за пределы серверного контекста. Оператор SUSPEND придаёт циклам новое качество — построчную потоковую выдачу данных, что особенно ценно при работе с большими наборами, когда клиенту не нужен весь результат сразу. Сочетание SUSPEND с BREAK даёт возможность прервать обход по бизнес-правилу, а EXIT — экстренно остановить всю операцию, сохраняя контроль над выполнением.
Следующий логический слой — управление идентификацией и реактивностью. Генераторы обеспечивают независимую от таблиц фабрику уникальных чисел, решая классическую проблему суррогатных ключей без риска коллизий в многопользовательской среде. Триггеры, напротив, связывают код с событиями изменения данных. Разделение на BEFORE и AFTER формирует двухфазную модель: сначала валидация и очистка входных данных, затем — автоматический запуск связанных процессов, таких как пересчёт агрегатов или распределение работ. Прямой доступ к снимкам «до» и «после» (INSERTED/DELETED) делает эту модель исключительно гибкой.
Вершиной абстракции становятся хранимые процедуры — именованные блоки с чёткими интерфейсами в виде параметров. Они позволяют упаковывать целые бизнес-сценарии в вызываемые подпрограммы. Возможность возвращать как скаляр, так и таблицу превращает процедуру в универсальный строительный блок, который можно комбинировать в запросах наравне с таблицами. Вкупе с обработкой исключений это даёт полноценную среду для создания надёжных, централизованных сервисов данных: ошибки перехватываются и транслируются клиенту в понятной форме, а события уведомляют внешние системы о значимых изменениях.
Практическая ценность всей цепочки инструментов — в переносе критически важной логики на сервер. Снижается сетевой трафик, повышается согласованность операций, упрощается поддержка, так как правила существуют в единственном экземпляре. В совокупности рассмотренные средства формируют фундамент для построения производительных и устойчивых приложений, где база данных выступает не пассивным хранилищем, а активным участником выполнения бизнес-процессов.
1. Представления позволяют сохранить сложный запрос как виртуальную таблицу и использовать его многократно.
2. Виртуальность представлений исключает дублирование данных, а кэширование предотвращает лишние пересчёты.
3. Вложенность представлений даёт возможность строить многоуровневые абстракции над сырыми таблицами.
4. Оператор IF добавляет процедурному SQL ветвление по условиям.
5. Цикл FOR SELECT … INTO … DO обеспечивает построчную обработку результирующего набора.
6. SUSPEND позволяет возвращать строки порционно, не завершая цикл, а BREAK — прервать итерации досрочно.
7. EXIT полностью останавливает выполнение процедуры или триггера.
8. Генераторы создают независимые последовательности чисел, гарантируя уникальность без привязки к таблицам.
9. Триггеры BEFORE выполняют проверку корректности, AFTER — запускают связанную бизнес-логику.
10. Хранимые процедуры инкапсулируют многократно используемый код и могут возвращать как одиночные, так и табличные значения.
11. Перехват исключений позволяет обрабатывать ошибки сервера и давать клиенту осмысленную обратную связь.
12. События служат механизмом асинхронного уведомления клиентов об изменениях в базе данных.
1. Какую проблему решает использование представлений вместо повторяющихся сложных запросов?
2. Можно ли создать представление, которое базируется на другом представлении, и если да — зачем это нужно?
3. В чём разница между операторами SUSPEND и BREAK внутри цикла FOR?
4. Для каких сценариев применяется цикл WHILE, а для каких — цикл FOR?
5. Как с помощью генератора обеспечить сквозную нумерацию заказов, не опираясь на автоинкремент поля таблицы?
6. Какие события могут активировать триггер, и в чём различие между BEFORE- и AFTER-триггерами?
7. Как в триггере получить доступ к данным, которые будут изменены или удалены?
8. Зачем хранимой процедуре нужны входные параметры, и как они повышают универсальность кода?
9. Опишите способ вернуть из хранимой процедуры несколько значений и обратиться к ним как к таблице.
10. Почему перехват «любой ошибки» менее информативен, чем обработка исключения по конкретному коду?
11. Каким образом событие, отправленное из базы данных, достигает клиентского приложения?
12. Какие преимущества даёт перенос бизнес-логики в хранимые процедуры и триггеры по сравнению с реализацией исключительно на клиенте?