
Современные СУБД
..pdf
Рисунок 19 – Применение миграций
После выполнения миграций можно увидеть, что модели преобразовались в таблицы (рисунок 20).
Рисунок 20 – Результат выполнения миграций
Контрольные вопросы
1.Что такое объектно-реляционные отображения?
2.Для чего нужны файлы миграции?
3.Какие преимущества и недостатки имеют объектно-реляционные отображения?
1.5Лабораторная работа «Разработка пользовательских процедур в PL/pgSQL»
Цель работы
Познакомиться с процедурными расширениями языка SQL, разработать процедуры и триггеры для PostgreSQL с применением процедурного языка PL/pgSQL.
21
Форма проведения
Выполнение индивидуального задания.
Форма отчетности
На проверку должен быть представлен триггер, реализованный с помощью процедурного расширения языка SQL.
Теоретические основы
PL/pgSQL – это процедурный язык для СУБД PostgreSQL. Целью проектирования PL/pgSQL было создание загружаемого процедурного языка, который позволяет реализовывать следующий функционал и обладает необходимыми свойствами:
–используется для создания функций и триггеров;
–добавляет управляющие структуры к языку SQL;
–может выполнять сложные вычисления;
–наследует все пользовательские типы, функции и операторы;
–может быть определён как доверенный язык;
–прост в использовании.
Функции PL/pgSQL могут использоваться везде, где допустимы встроенные функции. Например, можно создать функции со сложными вычислениями и условной логикой, а затем использовать их при определении операторов или в индексных выражениях.
В версии PostgreSQL 9.0 и выше, PL/pgSQL устанавливается по умолчанию. Тем не менее, это, по-прежнему, загружаемый модуль и администраторы, особо заботящиеся о безопасности, могут удалить его при необходимости.
PostgreSQL и большинство других СУБД используют SQL в качестве языка запросов. SQL хорошо переносим и прост в изучении. Однако каждый оператор SQL выполняется индивидуально на сервере базы данных.
Каждое объявление и каждый оператор в блоке должны завершаться символом «;» (точка с запятой). Блок, вложенный в другой блок, должен иметь точку с запятой после END, как показано выше. Однако финальный END, завершающий тело функции, не требует точки с запятой.
Метка требуется только тогда, когда нужно идентифицировать блок в операторе EXIT, или дополнить имена переменных, объявленных в этом блоке. Если метка указана после END, то она должна совпадать с меткой в начале блока.
Ключевые слова не чувствительны к регистру символов. Как и в обычных SQL-коман- дах, идентификаторы неявно преобразуются к нижнему регистру, если они не взяты в двойные кавычки.
Это значит, что ваше клиентское приложение должно каждый запрос отправлять на сервер, ждать пока он будет обработан, получать результат, делать некоторые вычисления, затем отправлять последующие запросы на сервер. Всё это требует межпроцессного взаимодействия, а также несёт нагрузку на сеть, если клиент и сервер базы данных расположены на разных компьютерах.
В PL/pgSQL можно создавать триггерные процедуры, которые будут вызываться при изменениях данных или событиях в базе данных. Триггерная процедура создаётся командой CREATE FUNCTION, при этом у функции не должно быть аргументов, а типом возвращаемого значения должен быть trigger (для триггеров, срабатывающих при изменениях данных) или event_trigger (для триггеров, срабатывающих при событиях в базе). Для триггеров автоматически определяются специальные локальные переменные с именами вида TG_имя, описывающие условие, повлёкшее вызов триггера.
Триггер при изменении данных объявляется как функция без аргументов и с типом результата trigger. Заметьте, что эта функция должна объявляться без аргументов, даже если
22

ожидается, что она будет получать аргументы, заданные в команде CREATE TRIGGER – такие аргументы передаются через TG_ARGV, как описано ниже.
Когда функция на PL/pgSQL срабатывает как триггер, в блоке верхнего уровня автоматически создаются несколько специальных переменных:
NEW – тип данных RECORD. Переменная содержит новую строку базы данных для команд INSERT/UPDATE в триггерах уровня строки. В триггерах уровня оператора и для команды DELETE этой переменной значение не присваивается.
OLD – тип данных RECORD. Переменная содержит старую строку базы данных для команд UPDATE/DELETE в триггерах уровня строки. В триггерах уровня оператора и для команды INSERT этой переменной значение не присваивается.
TG_NAME – тип данных name. Переменная содержит имя сработавшего триггера. TG_WHEN – тип данных text. Строка, содержащая BEFORE, AFTER или INSTEAD
OF, в зависимости от определения триггера.
TG_LEVEL – тип данных text. Строка, содержащая ROW или STATEMENT, в зависимости от определения триггера.
TG_OP – тип данных text. Строка, содержащая INSERT, UPDATE, DELETE или TRUNCATE, в зависимости от того, для какой операции сработал триггер.
TG_RELID – тип данных oid. OID таблицы, для которой сработал триггер. TG_RELNAME – тип данных name. Имя таблицы, для которой сработал триггер. Эта
переменная устарела и может стать недоступной в будущих релизах. Вместо неё нужно ис-
пользовать TG_TABLE_NAME.
TG_TABLE_NAME – тип данных name. Имя таблицы, для которой сработал триггер. TG_TABLE_SCHEMA – тип данных name. Имя схемы, содержащей таблицу, для ко-
торой сработал триггер.
TG_NARGS – тип данных integer. Число аргументов в команде CREATE TRIGGER, которые передаются в триггерную процедуру.
TG_ARGV[] – тип данных массив text. Аргументы от оператора CREATE TRIGGER. Индекс массива начинается с 0. Для недопустимых значений индекса ( < 0 или >= tg_nargs) возвращается NULL.
Триггерная функция должна вернуть либо NULL, либо запись/строку, соответствующую структуре таблице, для которой сработал триггер.
Порядок выполнения работы
Для примера напишем триггерную процедуру, которая будет записывать изменения в таблице «Студенты» в новую таблицу.
Для начала необходимо создать таблицу, в которую будут записываться данные логгирования об изменениях. В поле «text» будем записывать произведенную операцию и ФИО студента, а в поле «added» время операции (рисунок 21).
Рисунок 21 – Создание таблицы для логгирования
23

Затем необходимо написать функцию логгирования. При помощи переменной TG_OP, можно узнать какой запрос был выполнен, при помощи переменных NEW вновь созданную либо отредактированную запись, OLD удаленную либо запись до редактирования (рисунок
22).
Рисунок 22 – Процедура логгирования
Далее необходимо написать сам триггер, который будет работать на сервере базы данных (рисунок 23).
Рисунок 23 – Триггер
Далее необходимо выполнить триггер, после чего он будет записывать изменения в новую таблицу. Получим мы примерно следующее (рисунок 24).
Рисунок 24 – Результат работы
Контрольные вопросы
1.Какие виды процедурных расширений бывают?
2.Основные операторы процедурных расширений?
24
3.Что такое хранимые процедуры?
4.Что такое триггер?
5.Какие переменные хранит триггер?
1.6Лабораторная работа «Реализация многоуровневых запросов SQL
иих оптимизация»
Цель работы
Познакомиться с многоуровневыми SQL-запросами и правилами оптимизации SQL-за- просов, применить на практике полученные знания.
Форма проведения
Выполнение индивидуального задания.
Форма отчетности
На проверку должны быть представлены два многоуровневых запросов на выборку и добавление данных, которые должны соответствовать правилам оптимизации.
Теоретические основы
Вложенный запрос – это запрос, который находится внутри другого SQL запроса и встроен внутри условного оператора WHERE.
Данный вид запросов используется для возвращения данных, которые будут использоваться в основном запросе, как условие для ограничения получаемых данных.
Вложенные запросы должны следовать следующим правилам:
–вложенный запрос должен быть заключён в родительский запрос;
–вложенный запрос может содержать только одну колонку в операторе SELECT;
–оператор ORDER BY не может быть использован во вложенном запросе. Для обеспечения функционала ORDER BY, во вложенном запросе может быть использован GROUP BY;
–вложенные запросы, возвращающие более одной записи могут использоваться с операторами нескольких значений, как оператор IN;
–вложенный запрос не может заканчиваться в функции;
–SELECT не может включать никаких ссылок на значения BLOB, ARRAY, CLOB и
NCLOB;
–оператор BETWEEN не может быть использован вместе с вложенным запросом. Примеры:
Вложенный запрос имеет следующий вид:
SELECT имя_колонки [, имя_колонки2 ] FROM таблица1 [, таблица2 ]
WHERE имя_колонки ОПЕРАТОР
(SELECT имя_колонки [, имя_колонки2 ] FROM таблица1 [, таблица2 ]
[WHERE])
Предположим, что имеется таблица Программисты, которая содержит следующие записи:
25
ИД |
Имя |
Специальность |
Опыт |
Стоимость часа |
|
|
|
|
|
1 |
Программист 1 |
Java |
2 |
2500 |
|
|
|
|
|
2 |
Программист 2 |
Java |
3 |
3500 |
|
|
|
|
|
3 |
Программист 3 |
C++ |
3 |
2500 |
|
|
|
|
|
4 |
Программист 4 |
C# |
2 |
2000 |
|
|
|
|
|
5 |
Программист 5 |
Python |
2 |
1800 |
|
|
|
|
|
6 |
Программист 6 |
Python |
2 |
1800 |
|
|
|
|
|
7 |
Программист 7 |
C# |
1 |
900 |
|
|
|
|
|
Предположим, что имеется есть клон таблицы Программисты, который имеет имя Программисты_Копия и имеет структуру как у таблицы Программисты и не содержит данные.
Теперь попробуем выполнить для этой же таблицы следующий запрос:
INSERT INTO Программисты_Копия
SELECT * FROM developers
WHERE ИД IN (SELECT ИД
FROM Программисты);
В результате выполнения данного запроса таблица Программисты_Копия будет содержать следующие данные:
ИД |
Имя |
Специальность |
Опыт |
Оклад |
|
|
|
|
|
1 |
Программист 1 |
Java |
2 |
2500 |
|
|
|
|
|
2 |
Программист 2 |
Java |
3 |
3500 |
|
|
|
|
|
3 |
Программист 3 |
C++ |
3 |
2500 |
|
|
|
|
|
4 |
Программист 4 |
C# |
2 |
2000 |
|
|
|
|
|
5 |
Программист 5 |
Python |
2 |
1800 |
|
|
|
|
|
6 |
Программист 6 |
Python |
2 |
1800 |
|
|
|
|
|
7 |
Программист 7 |
C# |
1 |
900 |
|
|
|
|
|
Другими словами, мы скопировали все данные из таблицы Программисты в таблицу
Программисты_Копия.
Теперь изменим данные в таблице Программисты, воспользовавшись данными из таблицы Программисты_Копия с помощью следующего запроса:
26
UPDATE Программисты
SET Оклад = Оклад * 1.25 WHERE Опыт IN (SELECT Опыт
FROM Программисты_Копия
WHERE Опыт >= 2);
В результате этого наша таблица, содержащая изначальные данные, будет хранить следующие данные:
ИД |
Имя |
Специальность |
Опыт |
Стоимость часа |
|
|
|
|
|
1 |
Программист 1 |
Java |
2 |
3125 |
|
|
|
|
|
2 |
Программист 2 |
Java |
3 |
4375 |
|
|
|
|
|
3 |
Программист 3 |
C++ |
3 |
3125 |
|
|
|
|
|
4 |
Программист 4 |
C# |
2 |
2500 |
|
|
|
|
|
5 |
Программист 5 |
Python |
2 |
2250 |
|
|
|
|
|
6 |
Программист 6 |
Python |
2 |
2250 |
|
|
|
|
|
7 |
Программист 7 |
C# |
1 |
900 |
|
|
|
|
|
И наконец, попробуем выполнить удаление данных из таблицы с помощью вложенного запроса:
DELETE |
FROM Программисты |
|
|
|
||
WHERE |
Опыт ERIENCE IN |
|
|
|
||
(SELECT Опыт FROM Программисты_Копия |
|
|||||
|
|
|
|
WHERE Опыт >= 2); |
||
В результате таблица Программисты содержит следующие записи: |
||||||
|
|
|
|
|
|
|
|
|
ИД |
Имя |
Специальность |
Опыт |
Стоимость часа |
|
|
|
|
|
|
|
|
|
7 |
Программист 7 |
C# |
1 |
900 |
|
|
|
|
|
|
|
Оптимизация SQL-запросов
1.Используйте конкретные имена столбцов после оператора select, вместо «*» – это позволит увеличить быстроту отработки запроса и уменьшению сетевого трафика.
Сведите к минимуму использование подзапросов.
2.Например, запрос:
Select Column_A From Table_1
Where Column_B = (Select max (Column_B From Table_2) And Column_C = (Select max (Column_C From Table_2)
And Column_D = ‘position_2’
27
выглядит значительно хуже на фоне аналогичного запроса:
Select Column_A From Table_1
Where (Column_B, Column_C) = (Select max (Column_B), max (Column_C) From Table_2)
3.Используйте оператор IN аккуратно, поскольку на практике он имеет низкую производительность и может быть эффективен только при использовании критериев фильтрации в подзапросе.
4.Соединение таблиц в запросе также является критичным: в случае, когда соединение таблиц происходит в правильном порядке, то общее число строк, необходимых к обработке, значительно сократится.
При соединении основной и уточняющей таблиц убедитесь, что первой будет основная таблица, в противном случае вы рискуете получить обработку гораздо большего числа строк, чем необходимо.
5.При соединении таблиц EXIST предпочтительнее distinct (таблицы отношения «один-ко-многим»).
6.Избыточность при работе с SQL – это критичная необходимость, используйте в разделе WHERE как можно больше ограничивающих условий.
Например, если указан:
WHERE Column_А=Column_В and Column_А=425
Вы сможете вывести результат, где Column_В=425, однако при задании условий:
WHERE Column_А=Column_В and Column_B=Column_C
оператор не сможет определить, что Column_A=Column_C.
7.Пишите простые запросы. Больше упрощайте. Оптимизатор может не справиться со слишком сложными операторами. Кроме того, иногда выполнение нескольких простых до невозможности операторов дает лучший результат по сравнению со сложными и позволяет добиться лучшей эффективности.
8.Помните, что одного и того же результата можно добиться разными способами. Например, оператор MINUS выполняется гораздо быстрее, чем запросы с оператором WHERE NOT EXIST. Запрос с данным оператором в самом общем виде выглядит следующим образом:
Select worker_id
From workers
MINUS
Select worker_id
From orders
Этот пример показывает все значения worker_id, которые содержаться в таблице workers, не в таблице orders. Другими словами, если бы значение worker_id одновременно присутствовало в таблицах workers и orders, то значение worker_id не вывелось в результат, поскольку нет конкретики, содержание какой именно таблицы вывести как результат отработки запроса.
9. Оформляйте повторяющиеся коды в пользовательскую процедуру. Это может значительно ускорить работу, уменьшить сетевой трафик.
28
Таким образом, рассмотренные нами моменты работы с SQL операторами и запросами значительно ускоряют работу с СУБД.
В заключение хочется отметить, что очень важно при работе с SQL – мыслить шире, чем границы поставленной перед вами задачи
Всегда старайтесь оптимизировать запрос, какой бы не была мощной инфраструктура, даже ее производительность может снизиться при выполнении неоптимизированных запросов. Учитывайте все необходимые условия при работе с операторами таким образом, чтобы нагрузка на базу была минимальной.
Контрольные вопросы
1.Какие правила вложенных запросов используют?
2.Какие правила оптимизации запросов используют?
1.7Лабораторная работа «Разработка программных систем для базы данных»
Цель работы
Разработать многозвенную архитектуру, в которой клиентская часть имеет графический интерфейс и была реализована реляционная база данных с применением процедурных расширений.
Форма проведения
Выполнение индивидуального задания.
Форма отчетности
На проверку должна быть представлена программная система, использующая БД и имеющая графический интерфейс.
Теоретические основы
СУБД по типу назначения делятся на 3 вида.
Промышленные универсального назначения. Универсальные рассчитаны «на все слу-
чаи жизни» и, как следствие, либо очень сложны в использовании и требуют от пользователя специальных знаний, либо просты, но ограничены в возможностях.
Примеры:
–Access;
–FoxPro;
–Oracle;
–DB2.
Промышленные специализированные. Специализированные направлены на выполнения узких задач и поэтому создаются так, чтобы они были просты в использовании для профессионалов в своей области.
Примеры:
–1С Предприятие;
–БЭСТ;
–Правовая система Гарант.
Разрабатываемые под конкретного заказчика. Максимально учитывают нужды по-
требителя, его ситуацию и не требуют дополнительных знаний от пользователя. Они весьма дороги и требуют времени для создания, отладки и внедрения.
В настоящее время все чаще и чаще люди приходят за разработкой ПО для работы с какими-либо данными.
29

Порядок выполнения работы
С предыдущих работ у нас остались модели данных ORM, созданные с использованием Django-Rest-Framework. Используя модели, напишем сериалайзеры (рисунок 25). Они необходимы для преобразования данных из json в понятный языку программирования формат.
Рисунок 25 – Сериалайзеры для БД
В классе «Meta» выбираем модель данных и поля которые собираемся использовать. Далее, вспоминая паттерн MVC, добавляем представления (рисунок 26).
Рисунок 26 – Представления для БД
Далее необходимо прописать адреса url для конкретных представлений (рисунок 27).
30