Основы языка SQL

SQL как процедурный язык

В лекции последовательно раскрываются процедурные возможности SQL как полноценного языка программирования. Сначала рассматриваются представления (VIEW) — виртуальные таблицы, позволяющие инкапсулировать сложные запросы и многократно использовать их как обычные таблицы. Далее вводятся управляющие конструкции: условный оператор IF, циклы FOR и WHILE, а также операторы SUSPEND, BREAK и EXIT для управления возвратом данных и выполнением кода. Затем обсуждаются генераторы (GENERATOR) для получения уникальных последовательностей, триггеры (TRIGGER) для автоматической реакции на события изменения данных и обработка исключений (EXCEPTION) для устойчивости серверного кода. Завершают изложение хранимые процедуры (STORED PROCEDURE), события (EVENT) и краткое введение в оптимизацию запросов. Логика материала ведёт от пассивного переиспользования запросов к активному императивному программированию на стороне сервера.

Основные мысли

В результате изучения лекции слушатель будет способен:
1. Объяснять назначение представлений и перечислять их ключевые свойства.
2. Создавать представления для декомпозиции и повторного использования сложных запросов.
3. Применять условный оператор IF для ветвления логики в SQL-коде.
4. Использовать циклы FOR и WHILE для построчной и итеративной обработки данных.
5. Управлять возвратом результатов с помощью операторов SUSPEND, BREAK и EXIT.
6. Создавать и настраивать генераторы для формирования уникальных числовых последовательностей.
7. Различать типы триггеров (BEFORE/AFTER) и описывать их роль в проверке данных и реализации бизнес-логики.
8. Разрабатывать хранимые процедуры с входными и выходными параметрами.
9. Организовывать перехват и обработку исключений на стороне сервера.
10. Отправлять события клиентским приложениям из кода на SQL.
Показывать лекцию целиком
Краткое изложение

Представления (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) делает эту модель исключительно гибкой.

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

Практическая ценность всей цепочки инструментов — в переносе критически важной логики на сервер. Снижается сетевой трафик, повышается согласованность операций, упрощается поддержка, так как правила существуют в единственном экземпляре. В совокупности рассмотренные средства формируют фундамент для построения производительных и устойчивых приложений, где база данных выступает не пассивным хранилищем, а активным участником выполнения бизнес-процессов.
Представления (VIEW)

Представление — виртуальная таблица, сохраняющая запрос. К ней обращаются как к обычной таблице. Данные физически не дублируются, а кэшируются; при изменении исходных таблиц представление автоматически пересчитывается. Допустима вложенность — создание представления на основе другого представления.

Синтаксис:

sql
CREATE VIEW Имя (колонки) AS SELECT ...;

Пример:

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.

Вложенное представление для получения номера самой многочисленной группы:

sql
CREATE VIEW Broup (Namba) AS
SELECT Groups.Number FROM GSize
JOIN Groups ON GSize.GID = Groups.ID
WHERE ROWS 1; -- или TOP 1

Назначение: декомпозиция сложных запросов, упрощение клиентского кода.

Управляющие конструкции

IF — условный оператор:

sql
IF (условие) THEN действие; [ELSE действие;]

Цикл FOR SELECT … INTO :переменная DO — построчная обработка результата.
SUSPEND возвращает текущую строку клиенту и продолжает цикл.
Пример: вернуть имена длиннее 5 символов:

sql
FOR SELECT FirstName FROM Students WHERE GroupID IS NOT NULL
INTO :OutFname DO
IF (CHAR_LENGTH(:OutFname) > 5) THEN SUSPEND;

WHILE — цикл с предусловием:

sql
WHILE (A < 100) DO BEGIN INSERT ...; A = A + 1; END

BREAK прерывает цикл, EXIT — полностью завершает процедуру.

Генераторы (GENERATOR)

Объект для генерации уникальных чисел. Не привязан к таблицам.

sql
CREATE GENERATOR Gen;
SET GENERATOR Gen TO N;
GEN_ID(Gen, шаг) -- возвращает значение, увеличенное на шаг

Применение: создание суррогатных ключей, уникальных номеров в триггерах и процедурах.

Триггеры (TRIGGER)

Автоматически выполняются при INSERT, UPDATE, DELETE на таблице.

BEFORE — для валидации новых данных.
AFTER — для бизнес-логики, использующей уже зафиксированные изменения.

Доступ к изменяемым данным через INSERTED (новые значения) и DELETED (старые значения).

Обработка исключений

Перехват ошибок сервера предотвращает аварийное завершение приложения.

WHEN ANY DO … — перехват любой ошибки.
WHEN SQLCODE -код DO … — точная обработка по коду, позволяющая выдать информативное сообщение.

Хранимые процедуры (STORED PROCEDURE)

Именованный блок кода с входными и выходными параметрами. Вызывается из клиента, триггера или по расписанию.

Пример:

sql
CREATE PROCEDURE GroupSize (Namba VARCHAR(10))
RETURNS (S_Count INTEGER) AS
BEGIN
SELECT COUNT(*) FROM Groups JOIN Students ...
WHERE Groups.Number = :Namba
INTO :S_Count;
SUSPEND;
END;

Вызов: EXECUTE PROCEDURE GroupSize('123');

Процедура может возвращать несколько полей — тогда к ней обращаются как к таблице: SELECT * FROM ProcName(параметры);. Это позволяет инкапсулировать сложную логику и переиспользовать её.

События (EVENT)

POST_EVENT 'текст' отправляет сообщение клиенту. Используется в триггерах и процедурах для уведомления о значимых изменениях.

Оптимизация запросов

Вопросы производительности решаются через грамотное написание запросов; детально рассматриваются на практических занятиях.

Выводы

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. Какие преимущества даёт перенос бизнес-логики в хранимые процедуры и триггеры по сравнению с реализацией исключительно на клиенте?
Вернуться к учебному плану