/
Author: Ткачев О.А.
Tags: программирование на эвм компьютерные программы программирование информационные технологии компьютерные технологии язык программирования pl/pgsql
ISBN: 978-5-907592-32-2
Year: 2024
Text
Ткачев О. А.
РобтсреЗОЬ
ЗОЬ+РирдЭОЬ
ДЛЯ ТЕХ, КТО ХОЧЕТ СТАТЬ ПРОФЕССИОНАЛОМ
^вд
Х^йздатёльство^Г
"Издательство Наука и Техника"
Санкт-Петербург
УДК 004.42
ББК 32.973
Ткачев О. А.
PostgreSQL: SQL + PL/pgSQL для тех, кто хочет стать профессионалом
— СПб.: Издательство Наука и Техника, 2024. — 480 с., ил.
ISBN 978-5-907592-32-2
В этой книге содержится описание синтаксиса и правил применения всех
основных конструкций SQL и PL/pgSQL.
Теоретическая часть сопровождается большим количеством наглядных при
меров, разноплановых практических задач и детальным разбором их реше
ний. Это позволяет понять, при решении каких задач целесообразно исполь
зовать тот или иной рассматриваемый элемент.
Также к каждой главе прилагается список заданий различной степени слож
ности для самостоятельного решения, что позволит закрепить теоретиче
ские знания и получить практические навыки программирования
Книга может быть использована как в учебном процессе, при изучении дис
циплины «Базы данных» студентами IT-специальностей, так и для самостоя
тельного освоения программирования в среде СУБД PostgreSQL. Материал,
изложенный в этой книге, доступен для читателей с любым уровнем под
готовки, и поэтому, приложив определенные усилия, вы станете ближе к за
данной цели - стать профессионалом.
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев
авторских прав.
Издательство не несет ответственности за возможный ущерб, причиненный в ходе использования материалов данной книги, а также за доступность
материалов, ссылки на которые вы можете найти в этой книге.На момент подготовки книги к изданию все ссылки на интернет-ресурсы были действую
щими. Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные. Тем не менее, имея в виду
возможные человеческие или технические ошибки, издательство не может гарантировать абсолютную точность и полноту приводимых сведений и не
несет ответственности за возможные ошибки, связанные с использованием книги.
ISBN 978-5-907592-32-2
Контактные телефоны издательства:
(812)412 70 26
Официальный сайт: www.nit.com.ru
© Ткачев О. А.
9 785907 592322 >
© Издательство Наука и Техника
Содержание
ВВЕДЕНИЕ................................................................................................. 13
ГЛАВА 1. ВИЗУАЛЬНАЯ СРЕДА РАЗРАБОТКИ DBEAVER.. 19
1.1. Главное окно DBeaver......................................................................... 21
1.1.1. Меню.............................................................................................................21
1.1.2. Панель инструментов ............................................................................... 21
1.1.3. Рабочая область.......................................................................................... 22
1.1.4. Окно проектов............................................................................................. 23
1.2. Подключение к базе данных............................................................. 23
1.2.1. Настройка соединения............................................................................... 24
1.2.2. Загрузка схемы базы данных из резервной копии............................... 26
1.3. Создание пользователей и предоставление им прав для
работы с базой данных...............................................................................28
1.3.1. Создание роли............................................................................................. 29
1.3.2. Предоставление привилегий.................................................................... 31
1.3.3. Отзыв привилегий..................................................................................... 34
1.3.4. Предоставление и отзыв привилегий в графическом режиме..........35
1.3.5. Пример создания новой базы данных и предоставления
привилегий.............................................................................................................36
1.4. Навигатор базы данных.....................................................................38
1.4.1. Режимы просмотра объектов базы данных........................................... 39
1.4.2. Свойства объектов..................................................................................... 40
1.4.3. Копирование таблиц.................................................................................. 43
1.5. Редактор SQL........................................................................................ 45
1.5.1. Окно редактора SQL.................................................................................. 46
1.5.2. SQL-терминал............................................................................................. 48
3
PostgreSQL: SQL + PL/pgSQL
--------------------------------- , PostgreSQL
1.5.3. Панель результатов..................................................................................... 49
1.5.4. Управление скриптами............................................................................... 51
1.5.5. План выполнения запроса......................................................................... 53
1.6. Диаграммы сущность - связь...........................................................55
1.6.1. Диаграммы таблиц и схем......................................................................... 55
1.6.2. Пользовательские диаграммы.................................................................. 57
1.6.3. Описание используемой схемы базы данных....................................... 60
ГЛАВА 2. СТРУКТУРА ОПЕРАТОРА SELECT И
ФОРМИРОВАНИЕ УСЛОВИЙ ВЫБОРА........ 63
2.1. Структура оператора SELECT..........................................................65
2.2. Условия выбора...................................................................................... 68
2.3. Выражения выбора............................................................................... 70
2.3.1. Выражение LIKE..........................................................................................71
2.3.2. Выражение BETWEEN............................................................................... 72
2.3.3. Выражение IN.............................................................................................. 75
2.3.4. Выражение IS NULL....................................................
77
2.4. Вычисляемые столбцы........................................................................ 78
2.5. Операция конкатенации......................................................................80
2.6. Условные выражения........................................................................... 81
2.6.1. Выражение CASE с параметром..............................................................82
2.6.2. Выражение CASE с условием.................................................................. 85
2.7. Сортировка............................................................................................85
2.8. Ограничение количества строк в результате выполнения
запроса.............................................................................................................. 87
Задачи для самостоятельного решения................................................. 89
О............................................................. *
Содержание
ГЛАВА 3 ТИПЫ ДАННЫХ И ВСТРОЕННЫЕ ФУНКЦИИ ...91
3.1. Числовые типы........................................... ........................................ 92
3.2. Символьные типы.............................................................................. 98
3.3. Типы даты и времени........................................................................ 104
3.3.1. Тип INTERVAL.......................................................................................... 107
3.4. Логический тип......................................... ........................................ 112
3.5. Функции преобразования типов данных................................. 114
3.5.1. Преобразование чисел в строку символов.......................................... 115
3.5.2. Преобразование типов даты и времени в строку символов............116
3.5.3. Преобразование строки символов в число......................................... 119
3.5.4. Преобразование строки символов к типам даты и времени........... 120
3.5.5. Преобразование значений NULL........................................................... 121
3.5.6. Функция COALESCE ............................................................................. 122
Задачи для самостоятельного решения.............................................. 125
ГЛАВА 4 АГРЕГАТНЫЕ ФУНКЦИИ И ГРУППИРОВКА
ДАННЫХ..................................................................................... 127
4.1. Агрегатные функции........................................................................ 128
4.2. Группировка......................................................................................... 131
4.2.1. Группировка по нескольким столбцам................................................. 133
4.2.2. Использование условий на группу.............................................................. 134
4.3. Использование специальных операторов группировки....... 137
4.3.1. Оператор GROUP BY ROLLUP............................................................. 137
4.3.2. Оператор GROUP BY CUBE................................................................ 140
4.3.3. Оператор GROUP BY GROUPING SETS............................................ 141
Задачи для самостоятельного решения............................................. 143
5
PostgreSQL: SQL + PL/pgSQL
ф PostgreSQL.
ГЛАВА 5. МНОГОТАБЛИЧНЫЕ ЗАПРОСЫ.............................145
5.1. Условия соединения таблиц в предложении WHERE............ 147
5.2. Условия соединения таблиц в предложении FROM............... 150
5.2.1. Внутренние соединения........................................................................... 150
5.2.2. Внешние соединения................................................................................ 154
Левое внешнее соединение..................................................................... 154
Правое внешнее соединение................................................................... 156
Полное внешнее соединение.................................................................. 157
5.3. Декартово произведение таблиц.................................................... 158
5.4. Самосоединение таблицы................................................................. 159
Задачи для самостоятельного решения............................................... 161
ГЛАВА 6. ПОДЗАПРОСЫ (ВЛОЖЕННЫЕ ЗАПРОСЫ)....... 163
6.1. Простые подзапросы.......................................................................... 164
6.1.1. Подзапросы и значение Null................................................................... 166
6.1.2. Использование выражения IN................................................................ 167
6.1.3. Использование выражений ALL и ANY............................................... 169
6.1.4. Выражения ALL, ANY и значение NULL............................................. 170
6.1.5. Выражения ALL, ANY и пустые подзапросы..................................... 172
6.1.6. Многостолбцовые подзапросы............................................................... 173
6.2. Коррелированные подзапросы....................................................... 177
6.2.1. Выражение EXISTS.................................................................................. 179
6.3. Использование оператора WITH................................................... 181
6.4. Составные запросы............................................................................ 183
Задачи для самостоятельного решения............................................... 187
6
Содержание
ГЛАВА 7. ОПЕРАТОРЫ МОДИФИКАЦИИ ДАННЫХ......... 189
7.1. Оператор INSERT............................................................................... 191
7.1.1. Вставка значений, заданных по умолчанию....................................... 192
7.1.2. Вставка нескольких строк....................................................................... 193
7.2. Оператор UPDATE.............................................................................. 195
7.2.1. Присвоение значений, заданных по умолчанию ...............................196
7.2.2. Обновление строк с использованием коррелированного подзапроса.. 197
7.3. Оператор MERGE............................................................................... 198
7.4. Оператор DELETE............................................................................. 204
7.5. Ошибки нарушения ограничения ссылочной целостности
при выполнении операторов изменения данных............................ 206
Задачи для самостоятельного решения.............................................. 209
ГЛАВА 8. ОПЕРАТОРЫ ОПРЕДЕЛЕНИЯ ДАННЫХ..............211
8.1. Создание таблиц базы данных....................................................... 214
8.1.1. Значения по умолчанию.......................................................................... 215
8.2. Ограничения........................................................................................ 215
8.2.1. Ограничение NOT NULL........................................................................217
8.2.2. Ограничение UNIQUE............................................................................. 217
8.2.3. Ограничение EXCLUDE.........................
218
8.2.4. Ограничение CHECK..............................................................................219
8.2.5. Ограничение первичного ключа (PRIMARY KEY)........................... 220
8.2.6. Ограничение внешнего ключа (FOREIGN KEY)............................... 221
8.3. Экспорт данных.................................................................................. 224
8.4. Резервное копирование базы данных.......................................... 228
8.5. Редактирование таблиц.................................................................... 230
7
PostgreSQL: SQL + PL/pgSQL
....................................... Ф PostgreSQL.
8.5.1. Добавление и удаление столбцов......................................................... 231
8.5.2. Изменение столбцов................................................................................. 231
8.5.3. Изменение ограничений.......................................................................... 232
8.5.4. Удаление таблицы..................................................................................... 233
8.5.5. Получение информации о таблицах базы данных............................. 234
8.6. Представления..................................................................................... 235
8.7. Последовательности........................................................................... 240
8.7.1. Функции для работы с последовательностями...................................242
8.7.2. Использование последовательностей................................................... 243
8.7.3. Изменение последовательности............................................................ 244
8.7.4. Получение информации о последовательностях................................245
8.8. Индексы.................................................................................................. 245
8.8.1. Создание индексов на базе функций..................................................... 247
8.8.2. Перестроение индекса.............................................................................248
8.8.3. Переименование индекса........................................................................ 249
8.8.4. Удаление индекса..................................................................................... 249
8.8.5. Получение информации об индексах.................................................... 249
Задачи для самостоятельного решения...............................................251
ГЛАВА 9. СТРУКТУРА ПРОГРАММ PL/PGSQL........................ 253
9.1. Структура блока PL/pgSQL............................................................. 255
9.2. Анонимные блоки............................................................................... 256
9.3. Переменные, константы и тины данных.................................... 259
9.3.1. Использование переменных числового типа...................................... 260
9.3.2. Использование переменных символьного типа.................................. 261
9.3.3. Использование переменных типа Date ................................................263
9.3.4. Неявное объявление типа переменной................................................. 264
9.3.5. Область действия переменных...............................................................266
9.4. Операторы SQL в PL/SQL............................................................... 269
Содержание
9.4.1. Использование оператора SELECT...................................................... 270
9.4.2. Использование оператора INSERT........................................................273
9.4.3. Использование оператора UPDATE......................................................275
9.4.4. Использование оператора DELETE..................................................... 279
9.4.5. Использование оператора MERGE...................................................... 280
Задачи для самостоятельного решения.............................................. 282
ГЛАВА 10. ОПЕРАТОРЫ УПРАВЛЕНИЯ....................................285
ЮЛ. Условные операторы....................................................................... 286
10.1.1. Использование инструкции ELSIF......................................................290
10.1.2. Вложенные операторы IF..................................................................... 292
10.2. Использование команд и выражений CASE........................... 293
10.2.1. Команда CASE....................................................................................... 294
10.2.2. Выражение CASE.................................................................................. 298
10.3. Операторы цикла............................................................................. 301
10.3.1. Простые циклы LOOP........................................................................... 301
10.3.2. Циклы WHILE........................................................................................307
10.3.3. Циклы FOR............................................................................................. 311
10.3.4. Вложенные циклы.................................................................................. 314
Задачи для самостоятельного решения............................................ 317
ГЛАВА
11. КУРСОРЫ........................................................................ 319
11.1. Использование курсоров................................................................ 320
11.1.1. Объявление курсора............................................................................... 320
11.1.2. Открытие курсора и получение данных............................................ 322
11.1.3. Закрытие курсора................................................................................... 323
11.2. Циклы для курсоров........................................................................ 328
11.3. Использование курсоров для изменения данных.................. 332
9
PostgreSQL: SQL + PL/pgSQL
............................................. f PostgreSQL
11.4. Курсорные переменные.................................................................. 335
Задачи для самостоятельного решения.............................................. 337
ГЛАВА 12. МАССИВЫ.......................................................................... 339
12.1. Объявление масси ва......................................................................... 340
12.2. Функции для работы с массивами..............................................342
12.3. Циклы для массивов........................................................................ 344
12.4. Многоуровневые массивы............................................................. 347
12.5. Использование массивов в столбцах таблицы...................... 350
12.6. Использование массивов и курсоров для повышения
эффективности обработки данных....................................................... 357
Задачи для самостоятельного решения...............................................363
ГЛАВА 13. ОБРАБОТКА ОШИБОК................................................ 365
13.1. Раздел обработки ошибок............................................................... 366
13.2. Обработка ошибок, определяемых программистом.............374
13.3. Инициирование ошибок, определяемых сервером............... 379
13.4. Распространение ошибок............................................................... 381
Задачи для самостоятельного решения.............................................. 385
ГЛАВА 14. ХРАНИМЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ......... 387
14.1. Хранимые процедуры......................................................................388
14.2. Хранимые функции.......................................................................... 396
Содержание
14.3. Использование массивов в качестве параметров процедур и
функций....................................................................................................... 400
14.4. Хранимые функции, возвращающие таблицу....................... 404
Задачи для самостоятельного решения.............................................. 409
ГЛАВА 15. ТРИГГЕРЫ........................................................................ 411
15.1. DML-триггеры................................................................................... 413
Порядок активизации DML-триггеров ..........................................................414
15.2. Триггерные функции...................................................................... 414
15.3. Примеры использования DML-триггеров.............................. 416
15.4. Триггеры для оператора MERGE.............................................. 423
15.5. Триггеры с моментом срабатывания INSTEAD OF............ 428
15.5. Триггеры событий........................................................................... 430
15.6. Управление триггерами..................................................................435
Задачи для самостоятельного решения.............................................. 437
ГЛАВА 16. ВСТРОЕННЫЙ ДИНАМИЧЕСКИЙ SQL............. 439
16.1. Выполнение динамических операторов SELECT.................. 440
16.2. Использование динамических DML-операторов.................. 445
16.3. Формирование имени вызываемой процедуры или функции. 450
16.4. Использование динамических DDL-операторов................... 453
Задачи для самостоятельного решения.............................................. 456
PostgreSQL: SQL + PL/pgSQL
Ф PostgreSQL
ГЛАВА 17. УПРАВЛЕНИЕ ТРАНЗАКЦИЯМИ ......................... 457
17.1. Требования к транзакциям............................................................458
17.2. Команды управления транзакциями......................................... 461
17.2.1. Команда COMMIT ................................................................................. 462
17.2.2. Команда SAVEPOINT............................................................................ 462
17.2.3. Команда ROLLBACK ТО SAVEPOINT............................................. 462
17.2.4. Команда ROLLBACK............................................................................ 463
17.3. Примеры использования команд управления транзакциями..464
17.4. Использование команд COMMIT и ROLLBACK в блоках
PL/pgSQL....................................................................................................... 467
17.5. Управление транзакциями в DBeaver....................................... 472
17.5.1. Режим интеллектуальной фиксации (Smart Commit Mode).......... 473
17.5.2. Журнал транзакций................................................................................. 474
Задачи для самостоятельного решения...............................................476
Список использованных источников................................................... 477
12
ВВЕДЕНИЕ
Ро5І§ге8ОЬ: 80Ь + PL/pgSQL
РозІдгевОЬ
Использование эффективных средств хранения и обработки данных явля
ется одним из определяющих факторов успеха в любой сфере деятельности
современного общества. Для решения этой задачи создаются информацион
ные системы. Информационная система содержит данные о некоторой пред
метной области.
Предметная область — это та часть реального мира,
данные о которой необходимы для решения заданных
прикладных задач. Для хранения данных в этих системах
используются базы данных.
База данных представляет собой совокупность
специальным образом организованных данных и
программного обеспечения, предназначенного для создания
объектов базы данных и управления ими.
Это программное обеспечение получило название системы управления
базами данных (СУБД).
Любая предметная область содержит бесконечное количество различных
данных. Задача проектирования базы данных состоит в выявлении необхо
димых данных и определении их представления, удовлетворяющего опре
деленным критериям.
Важным аспектом применения баз данных является использование моделей
представления данных. Большинство современных СУБД используют реля
ционную модель данных.
14
Введение
Реляционная модель предполагает представление
всех необходимых данных о предметной области в виде
совокупности взаимосвязанных таблиц (отношений).
Считается общепризнанным факт, что только часть данных, которые необхо
димо обрабатывать, являются структурированными, поэтому сейчас разра
батываются и уже разработаны СУБД, способные обрабатывать различные
виды данных. Однако задача обработки структурированных данных акту
альна в настоящее время и будет оставаться таковой в ближайшее время. По
явившиеся новые способы представления данных и манипулирования ими
расширяют возможности обработки данных.
Информационная система состоит из сервера базы данных, где располага
ются база данных и СУБД, и клиентских компьютеров, на которых распола
гаются приложения - программные средства, предназначенные для решения
задач пользователей. Обработка данных может осуществляться как сред
ствами самой СУБД, так и средствами приложений, взаимодействующих с
базой данных.
Для обработки данных на сервере СУБД используются запросы на выполне
ние действий с данными, хранимые процедуры и функции, триггеры. При
ложение может передать на сервер запрос на выборку данных и после их
получения обработать эти данные на клиентском компьютере.
Ро51дге$Ок — это мощная объектно-реляционная СУБД с
открытым исходным кодом, которая позволяет создавать
высоконагруженные базы данных большого объема.
РоэІдгеЗОЬ может работать на всех основных операционных
системах.
PostgreSQL была создана на основе некоммерческой СУБД Postgres, разра
ботанной в Калифорнийском университете Беркли под руководством про
фессора Майкла Стоунбрейкера в период с 1986 по 1994 год.
В настоящее время разработка PostgreSQL осуществляется командой,
состоящей из добровольных разработчиков, распространенных по всему
миру и общающихся через Интернет. Координацией их деятельности за
нимается международная группа разработчиков — PostgreSQL Global
PostgreSQL: SQL + PL/pgSQL
............................................
w
PostgreSQL
Development Group (PGDG)1 в которую входят как непосредственно про
граммисты, так и те, кто отвечает за продвижение PostgreSQL, за поддержа
ние серверов и сервисов, написание и перевод документации.
PostgreSQL является свободно распространяемым программным обеспече
нием. Это позволяет пользователям делать с кодом все, что они хотят, в том
числе перепродавать двоичные файлы без исходного кода. С текстом лицен
зии можно ознакомиться по следующей ссылке2.
PostgreSQL эффективно использует архитектуру многоядерных процессо
ров, обеспечивая параллельное выполнение запросов, позволяет распарал
леливать чтение данных и соединение таблиц.
PostgreSQL обеспечивает высокий уровень надежности путем горячего ре
зервирования с использованием различных видов репликации.
В PostgreSQL используются самые современные средства обеспечения без
опасного доступа к данным. Российская версия этой системы — Postgres
PRO, разработанная компанией Postgres Professional, получила сертификат
ФСТЭК (Федеральная служба по техническому и экспортному контролю)
для систем обработки конфиденциальной информации и персональных данных.
Версия языка структурированных запросов СУБД PostgreSQL в высокой
степени соответствует стандартам ANSI SQL. Для создания хранимых про
цедур, функций и триггеров используется встроенный процедурный язык
PL/pgSQL, во многом аналогичный языку PL/SQL, используемому в СУБД
Oracle. Это позволяет использовать опыт серверного программирования
Oracle и облегчает переход с Oracle на PostgreSQL. Процедурный язык
PL/pgSQL будет подробно рассмотрен в этой книге.
Характерной особенностью PostgreSQL является возможность использовать
в серверном программировании языки PL/Perl, PL/Python, PL/Tcl. Можно ис
пользовать и другие языки программирования, например PHP, Java, Ruby, но
это требует установки дополнительных пакетов.
При работе с любой базой данных необходимо иметь возможность создавать
и редактировать объекты базы данных, разрабатывать и выполнять запросы,
осуществлять функции администрирования.
Есть два способа решения этих задач:
1. Использовать интерфейс командной строки (CLI);
2. Использовать графический пользовательский интерфейс (GUI).
1
2
https://www.postgresql.org/community/contributors/
https://www.postgresql.org/about/licence/
16
Ц
Введение
Графический интерфейс — это приложение, которое
позволяет пользователю создавать и редактировать объекты
базы данных, управлять ими, разрабатывать и выполнять
запросы и программы.
Многие разработчики СУБД разрабатывают и приложения, которые позво
ляют осуществлять разработку баз данных и управление ими в графическом
режиме. Для СУБД Microsoft SQL Server разработано приложение Manage
ment Studio. Для СУБД Oracle можно использовать Oracle SQL Developer.
Существует большое количество универсальных приложений, которые
позволяют создавать базы данных различного типа и управлять ими.
В этой книге для работы с PostgreSQL использовалось приложение DBeaver.
DBeaver3 - это мультиплатформенный инструмент для
создания баз данных и управления ими.
DBeaver поддерживает более 80 СУБД (как реляционных, так
и нереляционных), включая PostgreSQL, MySQL, SQLite, SQL
Server, DB2, Sybase, Phoenix, MS Access, Teradata, Apache Hive
и др.
DBeaver использует интерфейс прикладного программирования JDBC (API)
для взаимодействия с реляционными базами данных. Для нереляционных
баз данных и баз данных NoSQL используются проприетарные драйверы
баз данных.
Существуют различные версии DBeaver: бесплатная - Community Edition
(СЕ) и коммерческие - Enterprise Edition (ЕЕ), Ultimate Edition(UE), Team
Edition (ТЕ).
При работе над книгой были использованы СУБД PostgreSQL версии 15.1-4
и DBeaver (СЕ) 23.1.1-5. В сети можно найти много рекомендаций по скачи
ванию, установке и настройке этих программ4.
В конце каждой главы предлагаются для самостоятельного решения задачи
различной степени сложности.
3
4
https://github.com/dbeaver/dbeaver/wiki
См., например: https://techviewleo.com/manage-postgresql-database-server-using-dbeaver/
17
Ро§1§ге89Е: 80Ь + PL/pgSQL
РозідгѳЗОк
Решение этих задач позволит лучше понять правила использования рассма
триваемого элемента языка и получить практические навыки программиро
вания.
Надеюсь, что материал, изложенный в этой книге, доступен для читателей с
любым уровнем подготовки. Однако следует быть готовым к тому, что осво
ение материала и решение задач потребует определенных усилий, необходи
мых для достижения заданной цели — стать профессионалом в выбранной
области.
Глава 1.
ВИЗУАЛЬНАЯ СРЕДА
РАЗРАБОТКИ ИВЕАѴЕК
PostgreSQL: SQL + PL/pgSQL
DBeaver — это приложение для работы с базами данных, которое предо
ставляет разработчикам и пользователям удобный способ выполнения сле
дующих задач:
• создание подключения к базе данных;
• просмотр и управление объектами базы данных;
• ввод, отладка и выполнение SQL-операторов;
• создание и редактирование хранимых процедур, функций и триггеров;
• экспорт и импорт данных в нужных форматах;
• резервное копирование и восстановление баз данных.
После запуска DBeaver на экране появляется главное окно, представленное
на рис. 1.1.
Рис. 1.1. Главное окно DBeaver
20
Глава 1. Визуальная среда разработки DBEAVER
1.1. Главное окно DBeaver
Главное окно DBeaver содержит строку меню, панель инструментов, окно
навигатора, окно проектов и рабочую область.
1.1.1. Меню
Строка меню содержит следующие элементы:
• Файл — содержит пункты меню для создания файлов, папок, проектов,
подключений к базе данных, проектов баз данных и диаграмм ER, а так
же элементов импорта и экспорта.
• Редактирование — содержит основные команды, предназначенные для
активного элемента.
• Навигация — позволяет перемещаться по скриптам и объектам базы
данных.
• Поиск — предоставляет опции для поиска среди файлов, объектов базы
данных и конкретных данных.
• Редактор SQL — используется для открытия редактора запросов SQL и
скриптов.
• База данных — позволяет управлять драйверами базы данных, подклю
чениями и транзакциями, подключаться к базе данных и отключаться от
нее.
• Окна — включает в себя пункты для управления внешним видом окна
DBeaver.
• Справка — содержит ссылки на информационные и справочные ресурсы.
1.1.2. Панель инструментов
Панель инструментов (рис. 1.2) содержит кнопки для исполнения основных
и часто используемых команд.
PostgreSQL: 8рЬ + PL/pgSQL
^Врь^гевсх
Рис. 1.2. Панель инструментов ИВеаѵег
Некоторые кнопки включены (выделены цветом), другие отключены (се
рые). Наборы включенных и отключенных кнопок меняются в зависимости
от того, какой редактор в данный момент активен в рабочей области. Только
включенные кнопки применимы к активному виду или редактору.
Рассмотрим назначение наиболее важных и часто используемых кнопок:
• Новое соединение — позволяет создать новое соединение с базой
данных.
•
Открыть скрипт — предназначена для открытия ранее созданного
скрипта или создания нового окна в рабочей области для ввода скрипта.
• Выбор соединения — предназначена для выбора соединения и базы дан
ных, с которыми мы будем работать.
• Выбор схемы — позволяет выбрать схему, для которой будут выполнены
команды, содержащиеся в скрипте.
1.1.3. Рабочая область
В рабочей области может быть одновременно открыто несколько представ
лений и редакторов, но активным может быть только один из них.
Представления — это окна в рабочей области, которые
используются для отображения объектов базы данных.
Редакторы — это окна, в которых можно редактировать
объекты базы данных, создавать, редактировать и
выполнять БОк-запросы, процедуры, функции, триггеры.
Глава 1. Визуальная среда разработки ВВЕАѴЕИ
1.1.4. Окно проектов
В окне проектов отображаются все проекты, созданные в системе, и предо
ставляются инструменты для управления ими. Обычно это файлы, хранящи
еся в файловой системе.
1.2. Подключение к базе данных
Чтобы иметь возможность управлять базой данных, используя ОВеаѵег,
необходимо создать подключение к этой базе данных. Подключение вклю
чает в себя драйвер и ряд параметров конфигурации, включая расположение
базы данных и учетные данные для доступа к ней.
Для создания подключения нужно щелкнуть по кнопке Новое соединение
на панели инструментов ОВеаѵег (рис. 1.2) и в появившемся окне (рис. 1.3)
выбрать СУБД, которую вы собираетесь использовать. Мы будем использо
вать СУБД PostgreSQL, поэтому нужно выбрать эту СУБД и нажать кнопку
Далее. На экране появится окно для ввода параметров соединения (рис. 1.4).
Рис. 1.3. Выбор СУБД
При установке СУБД PostgreSQL автоматически создаются база данных
postgres и пользователь postgres, который является владельцем этой базы
23
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
данных и обладает правами администратора, также при установке требуется
ввести пароль, который будет являться паролем пользователя роь^гех. Эти
данные нужно ввести в окне для ввода параметров соединения (рис. 1.4).
Для проверки правильности ввода этих данных рекомендуется нажать кноп
ку Тест соединения. Если все в порядке, то на экране должно появиться
окно, представленное на рис. 1.5. В этом случае для завершения создания
соединения следует нажать кнопку Готово (рис. 1.4).
X
Настройки соединения
Xl PtetgreSQL
Свойства соединения с РойдгеЗОІ
Главное PostgreSQL ; Свойства драйвера ' SSH
Proxy [ SSL
Connect by: ® Host О URL
jdbeтройней?!:/ Лос aih ost 54 З 2/postgres
Порт:
lecalhost
Вам данных:
5432
рсйдгеі
Аутентификация
Аутентификация; Database Native
Пользователь:
pcstgres
>н
Пароль:
; Й Сохранять пароль локально
Advanced
Роль сессии.'
Локальный клиент
Ройдте$01 15
© Вы можете использовать системные переменные в параметрах.
Драйвер: Po5tgreSQL
Рис. 1.4. Окно для ввода параметров соединения
Рис. 1.5. Тест соединения
1.2.1. Настройка соединения
Чтобы изменить параметры конфигурации подключения к базе данных, нуж
но в Навигаторе базы данных щелкнуть правой кнопкой мыши на имени
соединения и в появившемся контекстном меню (рис. 1.6) выбрать команду
Редактировать объект «Соединение».
24
Глава 1. Визуальная среда разработки DBEAVER
Рис. 1.6. Выбор команды "Редактировать объект «Соединение»"
В результате этих действий на экране появится окно Конфигурация соединения (рис. 1.7).
Формат данных
^JtWgreSQL
команде: ОС
Идентификация клие
Transactors
Метаданные
Обработка ошибок
Редактор давив»
Бинарный Редактор
Формат данных
Представление
Рис. 1.7. Окно "Конфигурация соединения "
В качестве примера рассмотрим настройку формата отображения даты и
времени.
Ро«1§ге8ОЕ: SQL + PL/pgSQL
Для этого нужно в окне Конфигурация соединения нажать кнопку
Глобальные настройки. На экране появится окно Параметры (рис. 1.8).
В этом окне следует выбрать параметр Формат данных, тип данных и из
менить шаблон отображения данных.
Рис. 1.8. Настройка отображения даты и времени
1.2.2. Загрузка схемы базы данных из резервной копии
Резервную копию схемы НК РОС можно скачать с сайта издательства, в раз
деле «Материалы к книгам» (прямая ссылка на материал на Яндекс-диске1).
Для того чтобы загрузить в базу данных postgres схему НК РОС, следует в
навигаторе выбрать базу данных postgres, щелкнуть правой кнопкой и в
появившемся контекстном меню (рис. 1.9) выбрать команду Инструменты,
а в следующем меню выбрать команду Восстановить.
На экране появится окно Настройка восстановления (рис. 1.10). В этом
окне следует выбрать файл, который содержит резервную копию базы дан
ных, и нажать кнопку Готово.
1
https://disk.yandex.rU/d/PMbLjOnCT4JbOQ
26
Глава 1. Визуальная среда разработки DBEAVER
В результате этих действий все таблицы схемы НК РОС будут извлечены из
резервной копии и загружены в базу данных postgres.
Результат этого процесса показан на рис. 1.11.
Рис. 1.9. Загрузка данных из резервной копии
Настройки восстановления
Настройки восстановления
Настройки
S Настройся восстановления
Восстановление » процесс?: і
_.................
Формат. Custom ѵ
Q Уничтожит» (DROP) обметы БД перед их восстановлением
□ Gert* database
П Пропустит» указание на владельца
Ввод
O.Pcstgfc’.dump^^
Файл резерва:
В ^
Доп. аргументы команды:
Безопасность
Перезаписать пользовательские данные (’postures') для обмета ”.
Внешня# программа вроде pspl или pg.dump может требовать другой расстановки разрешений.
Аутентификация
Вернуть к стандартному
Сохранить задачу : Д ;
Локальный Клиент ...
Старт
Отмен»
Рис. 1.10. Настройка восстановления
27
PostgreSQL: SQL + PL/pgSQL
Файл
Редактирование
Навигация
Поиск
Редактор SQL
База данных
fr ж I # & ^ I П SQL *; La -w-^’* Q ^ЫсOS Базы данных X
1г ▼ Д
® Проекты
Введите часть имени объекта для поиска
Ь fl? Newer Sample Database (SQLite)
a t^ postgres - !<xalfwst:5432
а П Базы данных
a § postgres
a SB Схемы
* Й hf-poc
a &3 Таблицы
^Я customers
> О departments
& И employees
> В jobs
Рис. 1.11. Результат загрузки схемы HR POC
0» locations
> Я orders
24K
Загрузить схему HR POC можно с помо
щью встроенной утилиты pgrestore. Для
этого, нужно в командной строке перейти
в каталог BIN, программы PostgreSQL?
► Я products
24K
p Я order_items
0 ^ Представления
> S3 Мат. представления
p Ml Индексы
> К Функции
& ЯК Последовательности
> М Типы данных
? Я Агрегатные функции
C:\>cd C:\Program Files\
PostgreSQL\15\bin
и выполнить команду восстановления
>рд_геэЬоге -и роэЪдгез -сі роэЬдгез <
{путь}\hr_poc.дитр
Перед выполнением этой команды должна быть создана база данных
postgres. Этот способ восстановления можно применять и при использова
нии других средств разработки.
1.3. Создание пользователей и предоставление
им прав для работы с базой данных
Все действия с базой данных, включая создание базы и ее объектов, осу
ществляются пользователями, поэтому на первом этапе необходимо создать
пользователей и предоставить им необходимые права для работы с базой
данных. Эту операцию должен выполнить администратор базы данных.
В СУБД PostgreSQL для реализации этого правила используются понятия
роль и привилегия. Для каждой роли задается набор атрибутов и предостав
ляются привилегии для работы с объектами базы данных.
При установке PostgreSQL автоматически создается роль postgres, которая
имеет все возможные атрибуты и привилегии. Пользователь, который об
ладает ролью postgres, является администратором базы данных и должен
создавать другие роли и предоставлять им привилегии.
Глава 1. Визуальная среда разработки DBEAVER
1.3.1. Создание роли
Для создания новой роли используется команда:
CREATE ROLE {имя роли}
{список атрибутов}
Описание некоторых часто используемых атрибутов:
• SUPERUSER — определяет, будет ли эта роль суперпользователем,
который может переопределить все ограничения доступа в базе дан
ных. Создать нового суперпользователя может только суперпользова
тель. В отсутствие этих предложений по умолчанию подразумевается
NOSUPERUSER.
• CREATEDB — определяет то, что эта роль сможет создавать базы дан
ных. По умолчанию подразумевается NOCREATEDB.
• CREATEROLE — определяет, сможет ли роль создавать новые роли, по
умолчанию подразумевается NOCREATEROLE.
• INHERIT — определяет, будет ли роль «наследовать» права ролей, чле
ном которых она является. По умолчанию подразумевается INHERIT.
• LOGIN — определяет, разрешается ли роли вход на сервер, то есть может
ли эта роль стать начальным авторизованным именем при подключении
клиента. По умолчанию подразумевается вариант NOLOGIN.
•
BYPASSRLS — определяет, будут ли для роли игнорироваться все по
литики защиты на уровне строк (RLS). Создавать роли с атрибутом
BYPASSRLS разрешено только суперпользователям, по умолчанию под
разумевается вариант NOBYPASSRLS.
•
CONNECTION LIMIT — определяет предел подключений. Если роли
разрешён вход, этот параметр определяет, сколько параллельных под
ключений может установить роль. Значение -1 (по умолчанию) снимает
ограничение.
•
PASSWORD 'пароль' — задаёт пароль роли. Если проверка подлинности
по паролю не будет использоваться, этот параметр можно опустить.
Пример команды создания роли суперпользователя с «положительными»
значениями рассмотренных атрибутов:
CREATE ROLE userl SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN
BYPASSRLS PASSWORD 'useri’;
Используя DBeaver, можно создавать роли в графическом режиме. Для это
го нужно установить соединение для роли postgre, выбрать базу данных
postgre, нажать правую кнопку и выбрать Создать —> Роль (рис. 1.12).
29
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
В появившемся окне (рис. 1.13) ввести имя и пароль роли, после этого
появится окно, изображенное на рис. 1.14, в котором можно выбрать атрибу
ты роли и привилегии для схемы.
Рис. 1.12. Выбор команды Создать —> Роль
Настройки
Название
Пароль:
Рис. 1.13. Ввод имени и пароля роли
useri
| •••••
@ Является пользователем
Рис. 1.14. Выбор атрибутов и прав доступа роли
Глава 1. Визуальная среда разработки DBEAVER
1.3.2. Предоставление привилегий
Роли определяются для всей базы данных, но для того, чтобы пользователь,
обладающий определенной ролью, мог осуществлять операции с ее объекта
ми ему должны быть предоставлены соответствующие привилегии. Приме
чание: если роль имеет атрибут SUPERUSER, то все привилегии предостав
ляются автоматически. Привилегии предоставляются для различных видов
объектов базы данных.
Для работы с различными объектами базы данных могут быть предоставле
ны следующие привилегии:
•
SELECT — позволяет оператору SELECT извлекать данные из столбцов
таблицы или представления.
• INSERT — позволяет добавлять новые строки в таблицу, может быть
предоставлена для определенных столбцов таблицы.
• DELETE — разрешает удалять строки из таблицы.
• TRUNCATE — разрешает осуществлять очистку таблицы.
•
REFERENCES — позволяет создавать ограничение внешнего ключа для
таблицы.
• TRIGGER — позволяет создавать триггер для таблицы или представ
ления.
• CREATE — для баз данных позволяет создавать новые схемы, а для схем
позволяет создавать новые объекты внутри схемы.
• CONNECT — позволяет подключаться к базе данных.
• TEMPORARY — позволяет создавать временные таблицы при исполь
зовании базы данных.
• EXECUTE — позволяет вызывать функцию или процедуру. Это един
ственный тип привилегий, который применим к функциям и процедурам.
• USAGE — для процедурных языков позволяет использовать язык для
создания функций на этом языке. Это единственный тип привилегий, ко
торый применим к процедурным языкам. Для схем разрешает доступ к
объектам, содержащимся в схеме. Для последовательностей разрешает
использование функций currval и nextval. Для типов и доменов разрешает
использование типа или домена при создании таблиц, функций и других
объектов схемы.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
• ALL [PRIVILEGES] — предоставляет все привилегии, которые могут
быть предоставлены для определенного типа объекта. Ключевое слово
PRIVILEGES является необязательным в PostgreSQL, хотя в стандарте
SQL оно требуется.
В таблице 1.1 приведены рассмотренные привилегии и указаны типы
объектов, которым они могут быть предоставлены.
Таблица 1.1. Привилегии и типы объектов
Привилегия
Применимые типы объектов
SELECT
Таблица, столбец таблицы, последовательность
INSERT
Таблица, столбец таблицы
UPDATE
Таблица, столбец таблицы, последовательность
DELETE
Таблица
TRUNCATE
Таблица
REFERENCES
Таблица, столбец таблицы
TRIGGER
Таблица
CREATE
База данных, схема
CONNECT
База данных
TEMPORARY
База данных
EXECUTE
Хранимые процедуры и функции
USAGE
Процедурные языки, схема, последовательность, тип
Выдать привилегии можно с помощью команды GRANT,
которая имеет следующий формат:^
GRANT {привилегии} ON {объект} ТО {роль};
Глава 1. Визуальная среда разработки DBEAVER
Примеры предоставления привилегий:^
1. Предоставление привилегий SELECT, UPDATE, INSERT для всех
таблиц в схеме HR_POC роли userl.
GRANT SELECT, UPDATE, INSERT ON ALL TABLES IN SCHEMA HR_POC TO userl;
2. Предоставление всех привилегий для всех таблиц в схеме HR POC
роли userl.
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA HR_POC TO userl;
3. Предоставление всех привилегий для таблицы customers в схеме
hrpoc роли userl.
GRANT ALL PRIVILEGES ON TABLE customers IN SCHEMA HR_POC TO userl;
4. Предоставление привилегий на создание и просмотр объектов в схеме
hr_poc роли userl.
GRANT create,
usage ON SCHEMA HR_POC TO userl;
Данные о предоставленных привилегиях содержатся в
таблице information_schema.role_table_grants.
Пример просмотра предоставленных привилегий:^
SELECT table_catalog,table_schema,table_name, privilege_type,
is_grantable
FROM information_schema. role_table_grants
WHERE grantee='userl' and table_schema='hr_poc' AND table_name =
’customers'
table_catalog|table_schema|table_nameIprivilege_type|is_grantableI
—+------------- +------------------ -+------------------ --- +--postgres
postgres
postgres
postgres
Ihr_poc
|hr_poc
|hr_poc
I hr__poc
I
I
I
I
customers
customers
customers
customers
I INSERT
|SELECT
|UPDATE
I DELETE
I YES
I YES
I YES
I YES
33
PostgreSQL: SQL + PL/pgSQL
postgres
postgres
postgres
7 row(s)
w PostgreSQL
I customers
I customers
I customers
Ihr_poc
Ihr_poc
Ihr_poc
|TRUNCATE
|REFERENCES
|TRIGGER
I YES
I YES
I YES
|
|
|
fetched.
1.3.3. Отзыв привилегий
Для отзыва предоставленных привилегий используется команду
REVOKE {привилегии} ON {объект}
FROM {роль};
Примеры отзыва привилегий:
1. Отзыв привилегий INSERT для всех таблиц в схеме HR_POC у роли
userl.
REVOKE INSERT ON ALL TABLES IN SCHEMA HR_POC FROM userl;
2. Отзыв привилегий DELETE, TRUNCATE для одной таблицы у роли
userl.
REVOKE DELETE,
TRUNCATE ON TABLE customers FROM userl;
3. Отзыв привилегий UPDATE для столбца creditlimit таблицы customers
у роли userl.
REVOKE UPDATE
(credit_limit )
ON TABLE customers FROM userl;
Выведем данные о привилегиях, которые предоставлены роли userl для
таблицы customers после отзыва некоторых привилегий:/^
SELECT table_catalog,table_schema,table_name, privilege_type,is_
grantable
FROM information_schema.role_table_grants
WHERE grantee='userl' and table_schema='hr_poc' AND table_name =
'customers'
Глава 1. Визуальная среда разработки DBEAVER
table_catalog|table_schema|table_nameIprivilege_type|is_grantable|
------------------------------------ Р--------------------------------- 1----------------------------- 1---------------------------------------- 1----------------------------------- р
postgres
postgres
postgres
postgres
4 row(s)
1hr_poc
1hr_poc
1hr_poc
1hr_poc
I
I
I
I
customers
customers
customers
customers
(SELECT
|UPDATE
|REFERENCES
(TRIGGER
I
I
I
I
YES
YES
YES
YES
fetched.
1.3.4. Предоставление и отзыв привилегий в графическом
режиме
Используя ОВеаѵег, можно просматривать, предоставлять и отзывать при
вилегии в графическом режиме. Для этого нужно:
• Выбрать схему, для которой нужно посмотреть или изменить привилегии
(рис. 1.15).
• Сделать двойной щелчок на имени таблицы и в появившемся окне (рис.
1.16) выбрать Права доступа и роль userl.
Рис. 1.15. Выбор схемы и таблицы
В правой части окна отобразятся имеющиеся привилегии для выбранной
таблицы. Добавим привилегию INSERT. При нажатии на кнопку Сохра
нить появится окно с командой, которая будет выполнена.
35
PostgreSQL: SQL + PL/pgSQL
В рассматриваемом примере:
GRANT INSERT ON TABLE HR РОС.Customers TO useri;
Puc. 1.16. Просмотр и редактирование привилегий
1.3.5. Пример создания новой базы данных и предоставления
привилегий
Рассмотрим еще один пример, в котором будет создана новая база данных и
роль, которая обладает правами разработчика для этой базы данных.
Установим соединение для роли postgres, которая обладает правами адми
нистратора, и введем следующие команды
1. CREATE DATABASE test;
2. CREATE ROLE developer SUPERUSER CREATEDB LOGIN PASSWORD
’developer' ;
3. GRANT ALL PRIVILEGES ON DATABASE test TO developer;
Первая команда создает новую базу данных test. Вторая команда созда
ет роль developer, которая имеет атрибуты: SUPERUSER, CREATEDB,
LOGIN, PASSWORD. Третья команда предоставляет все привилегии роли
developer для базы данных test.
36
Глава 1. Визуальная среда разработки DBEAVER
Создадим новое соединение с базой данных test для роли developer (рис.
1.17).
я
Настройки соединения
Свойства соединения с PostgreSQL
Главное
PostgreSQL = Свойства драйвера
SSH
Proxy = SSL
Connect by: Ф Host О URL
URL:
jdbcpostg«&qM/k?< aihost 5*&/twt
Хост:
i localhost
Базз данных:
Порт
5432
test
Аутентифи ка ци я
Аутентифи ка ция:
Database Native
Пользователь:
developer
Пароль;
•••••••••
*
Й Сохранять пароль локально
Advanced
Роль сессии:
Локальный клиент.
Описание соединения (название, тип,... ) :
Вы можете использовать системные переменные в параметрах.
Настройки драйвера
Драйвер: PostgreSQL
Тест соединения... ;
ѵ
PostgreSQL 15
< Назад
Далее »
Лицензия драйвера
Готово
Отмена
Рис. 1.17. Создание нового соединения с базой данных test для роли developer
Создадим в базе данных test схему hr_poc3:
CREATE SCHEMA HR РОСЗ;
И в этой схеме создадим таблицу customers:
CREATE TABLE customers
customer_id
INTEGER PRIMARYKEY,
c_name
address
credit_limit
VARCHAR( 255 ) NOT
VARCHAR( 255 ),
NUMERIC( 10,2)
NULL,
);
Результат выполнения этих команд показан на рис. 1.18.
37
PostgreSQL: SQL + PL/pgSQL
PostgreSQL.
Puc. 1.18. Создание новой таблицы customers в схеме HRP0C3
1.4. Навигатор базы данных
Навигатор базы данных предназначен для работы со
структурой и содержимым баз данных.
Внешний вид окна навигатора показан на рис. 1.19.
г% Базы данных X
% Проекты
°
^ ’ ®*J ■“' ^
Введите часть имени объекта для поиска
V
О
§
ж
а
л ^ postgres - locathost:5432
а П Базы данных
л * postgres
* ЕВ Схемы
а Э hrpoc
л ЗВ Таблицы
рв customers
32К
t> И departments
24К
> S employees
72К
р Я jobs
24К
рЯ locations
24К
Р Я order_items
24К
р Я orders
24К
Р И products
24К
о И Представления
Р В Мат. представления
> М Индексы
Р ІЙ Функции
Рис. 1.19. Окно навигатора базы данных
Р В Последовательности
-В Типы данных
Р В Агрегатные функции
38
ѵ
Глава 1. Визуальная среда разработки DBEAVER
Навигатор базы данных содержит дерево объектов, панель инструментов и
меню просмотра. Каждый объект в дереве имеет свое собственное контекст
ное меню. Дерево содержит следующие объекты:
• папки —
;
• подключения к базе данных —
;
• объекты базы данных — таблицы, представления, столбцы и т.д.
1.4.1. Режимы просмотра объектов базы данных
Для выбора режима просмотра нужно щелкнуть правой кнопкой на имени
соединения и в появившемся контекстном меню выбрать команду Свойства
view (рис. 1.20).
Рис. 1.20. Выбор режима просмотра объектов базы данных
Можно использовать следующие режимы:
•
Простой (Simple) — показывает только схемы и таблицы.
•
Расширенный (Advanced) — показывает все системные объекты и все
поддерживаемые объекты базы данных.
39
PostgreSQL: SQL + PL/pgSQL
PostgreSQL.
• Пользовательский (Custom) — в этом режиме можно установить следу
ющие параметры:
»
показывать системные объекты (например, системную схему
pg_catalog или SYSTEM);
»
показывать служебные объекты (например, внутренние базы данных
шаблонов, необходимые для создания новой базы данных);
» показывать только схемы и таблицы;
»
скрыть папки - скрываются все промежуточные папки, и отображает
ся только содержимое папок.
Окно настройки пользовательского режима показано на рис. 1.21.
Рис. 1.21. Окно настройки пользовательского режима
Мы будем использовать расширенный режим про
смотра объектов базы данных. При изменении ре
жима просмотра нужно повторно подключиться к
базе данных.
1.4.2. Свойства объектов
Выбрав некоторый объект, вы можете просмотреть и отредактировать его
свойства. Открыть редактор объектов можно с помощью команды Открыть
объект в контекстном меню или сделав двойной щелчок на выбранном
объекте. На рис. 1.22 показано окно редактора объектов для схемы Ьг_рос.
Окно редактора объектов делится на две части, в верхней
части отображаются свойства выбранного объекта, а в
нижней части отображаются свойства дочерних объектов.
В нижней части окна, представленного на рис. 1.22, отображаются данные
о таблицах, которые входят в схему Ьгрос. Выбрав таблицу и свойство
Права доступа, можно просмотреть и изменить привилегии, которые име
ет выбранная роль для работы с этой таблицей.
40
Глава 1. Визуальная среда разработки DBEAVER
Если сделать двойной щелчок на имени таблицы, то откроется окно редакто
ра для работы с этой таблицей. В этом окне можно посмотреть и отредакти
ровать свойства имеющихся столбцов, удалить и добавить столбцы. На рис.
1.23 показано окно редактора для таблицы Employees.
Рис. 1.22. Свойства схемы HRPOC
Г" * ©
(2’<ten> Scrt-
Auto
іО *
jJ<t«t>Sca~
W postgres ▼ i^ pubhc^postgres ’ u^ A ’ Д. ’
Д}Нгдюс
^customcn
^enpioyees X *15
° □
Г? СВОЙСТеЗ • ~”. Данные: ^% Диаграмма
*?’. postgres
Г? Базы данных » 5 pos*gr*>
^ Схемы ’ ’ •' hrpc<
^Таблицы * ^ employees
25120
дож»
int4
varchanZO}
varchar(2S)
vaf<han23)
date
varchafOS)
питегкрС, 2}
• T 0 * ^ «
И
-
^Мио.™
Рис. 1.23. Свойства таблицы Employees
41
PostgreSQL: SQL + PL/pgSQL
Если выбрать вкладку Данные в верхней части этого окна, то на экране
отобразятся данные, содержащиеся в выбранной таблице (рис. 1.24), их
можно просматривать и редактировать.
2 «postées
^•<ocstgres>
У) «•«» 5сгі.
^ portgret П Ваш данных * В postgres
1
Ц} employee,rd
* <»cf,r«.name
101 Neena
103 Alexander
Kcchha
De Haan
Hunold
Ernst
06 V«W
107 Diana
'« Nancy
Palatal»
’10 ЗоЬл
Chen
' 12 Зом Manuel
115 Alexander
•16 Shell!
Popp
Raphael
Khoo
Baida
himuro
1.20 Matthew
’22 Payam
'24 Kevin
Wets
F"PP
KaufUng
Vollman
Meurgos
МйЛйпепі
127 James
^ Обчоаить
Ж email * ; йК phone.
SONG ......... здздде
NKOCHHAR
515.12 3 4566
I DEHAAN
515 123.4569
AHUNOLD
590 4234567
BERNST
590.423.4568
DAUSTiN
590.42 3.4569
VPATABAL
590 423 4 560
DLORENTZ
590 423 556?
NGREENBE
515124.4569
DFAVIET
515.124.4169
KHEN
515 124.4269
ISCIARRA
515.124.4369
JMURMAN
515 124.4469
515.124.4567
LPOPP
DRAPHEAL
515.127.4561
AKHOO
515.127 4562
SBAfDA
515127.4563
STOBIAS
515 127.4564
GMMURO
515.127.4565
KCOlMENA 515.127 4566
MWEISS
550 123.1234
AFRIPP
650123.2234
PKAUfUN
650.123.3234
SVOLLMAN
650.123 42 34
KMOURGGS
650123.5234
JNAYER
650 124.1214
650-241224
IMIKKILI
RANORY
650.124.1334
Ï7-Ô6-Ï967
21-09-1969
13-01-1993
03-01-1990
21-05-1991
25-06-1997
05-02-1998
07-02-1999
17-36-1994
16-06-1994
28-09-199?
30-09-1997
074»-1998
07-12-1999
07-12-1994
18-05-1995
24-12-1997
24-07-1997
15-11-1998
10-08-1999
10-07-1996
10-04-1997
01-05-1995
10-Ю-1997
10-11-1999
16-07-1997
28-09-1996
14-01-1999
ÀOJHtfS
AD.VP
AD.VP
IT.PROG
T.PROG
IT.PROG
IT.PROG
IT.PROG
R.MGR
Fl ACCOUNT
Fl.AC COUNT
Fl.ACCOUNT
Fl.ACCOUNT
Fl.ACCOUNT
PU.MAN
PU.CIERK
PU.CIERK
PU.CIERK
PU.CIERK
PU.CIERK
ST.MAN
ST.MAN
ST.MAN
ST.MAN
ST.MAN
5T.CIERK
ST.CtERK
ST.CtERK
$ 200
^ Схемы * ^ hr.pcc ^ Таблицы » ® employees
24000
17000
17000
9000
6000
90
90#
103#
12 00C
9 000
106#
108
108;.
100#
3100
2900
2 800
2600
2500
8000
8200
7 900
6500
5 800
3 200
2 700
2400
100#
100#
100#
100 #
1®#
100 ..
100#
120
120#
10? строе получено 11mt (8ms получ.). 2023-04-23 а 11:1&О9
Рис. 1.24. Данные, содержащиеся в таблице Employees
Панели предоставляют дополнительное пространство в
редакторе данных, в котором вы можете манипулировать
данными.
Панели отображаются в виде вкладок на дополнительной панели в правой
части вкладки Данные (рис. 1.25). Это пространство появляется только при
открытии одной из пяти панелей:
1. Группы.
2. Значение.
3. Метаданные.
4. Ссылки.
5. Функции.
Для отображения нужной панели следует нажать соответствующую кнопку на панели инструментов, которая расположена в правой части вкладки
Данные (рис. 1.25). Панель Функции служит для получения статистики по
данным, содержащимся в нескольких столбцах или строках.
Глава 1. Визуальная среда разработки DBEAVER
Ф Свойстве Г. Данные Л .Диграмме
^Обновить ” : Q А-: * £Г
If, postgres
г/ ^ :W S- : К <
> Я A
Пі Базы данных ’ g postgres
ill Экспорт данных.
»
й Схемы ’ ^ hr_poc
^Таблицы »$? employees
$ 200
107 строк получено - 11ms (8ms волуч.), 2025-04-23 в 11:1609
Рис. 1.25. Панель "Функции" со статистикой для столбца salary
По умолчанию панель отображает результаты только для двух функций:
Count и Count Distinct. Чтобы добавить другие функции, нажмите кнопку
Добавить функцию на панели инструментов.
1.4.3. Копирование таблиц
Для того чтобы создать копию существующей таблицы, нужно выбрать ее в
окне Навигатора, щелкнуть правой кнопкой и в появившемся контекстном
^ DBeaver
меню (рис. 1.26) выбрать команду
Файл Редактирование Навиг
Создать
’I в t * I gsa^
Копировать.
объект Таблица”
23.1.0- <postgres> Script-38
r.
‘
Открыть
^ Базы данных X Ц Проект: Y
Фильтр
-------------------------------------------- ^
Введите часть имени объекта дл ^,
View Diagram
View Data
> ^ DBeaver Sample Database С
Сравнить/Мигрировать
v 1| postgres
Экспорт данных
ѵ § postgees
v ^) Схемы
* ^ hr_рос
•Я
Инструменты
ѵ 53 Таблицы
> ® customer
> ® departmt
Генерация SQL
л
"r departme !
> æ emp1
> æ locations
Копировать
CtrK
см+ѵ
Колировать полную информацию
І
; ^ order.iter /-
Рис. 1.26. Выбор команды "Копиро
вать " (таблицу)
Read data in SQL editor
Вставить
> ?В employer
: æ jobs
Импорт данных
S order iter A
> ж orders
Delete
Удалить
Переименовать
F2
Refresh
F5
> Ф product.sales
56К
> 35 products
24К
> ® products.ns
CW*Shift+C
8К
> 3? products.nsl
8К
.:> Ж products.total
24К
43
Ф
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
После этого нужно снова щелкнуть правой кнопкой и в появившемся меню
выбрать команду Вставить. В результате этих действий на экране появится
окно свойств таблицы (рис. 1.27). В этом окне можно изменить имя таблицы
и другие свойства.
Д <postgres> ...
Д «ройдги» ...
Д <КЯ> Seri...
Я Свойства 57, Данные ^ Диаграмма
-£~~—~“-----------а Колонки
Щ роядге»
Наманив
Тип данных
■ ^order_id
int4
j ’^custcmerjd
^ status
* Индексы
Д <postgres>...
Д «postgres» _
“| Базы данных
Автсувеличение
: И *o«ies_1 X
Д <postgre$> „
» g postgres
Щ Схемы
Правило сортировки
» g) ht_poc
Not Null
©Таблицы
“
□
» Я orders. 1
По умолчанию
М
м
int4
[v]
default
verchart 20)
' &3 salesman_id
int4
И
^ order.date
date
M
‘Pending‘:;character varying
CURRENT-DATE
«Ссылки
Ш Триггеры
IB Правила
» Polices
Statistics
5 элементов
<X
: Y Ф
-^
З
І ^ Сохранитъ ~
[?* Вернуть ^ Обновитъ
Рис. 1.27. Окно свойств таблицы
После настройки свойств таблицы следует нажать кнопку Сохранить (рис.
1.27). На экране появится окно Сохранить изменения (рис. 1.28). Это окно
содержит ВПЬ-оператор создания таблицы. Оператор можно скопировать,
но нельзя изменить. Для завершения процесса копирования следует нажать
кнопку Сохранить (рис. 1.28).
Рис. 1.28. Окно "Сохранить изменения "
Созданная таблица не будет содержать данных. Ее можно открыть и запол
нить. Для вставки новых строк нужно выбрать вкладку Данные и нажимать
кнопку Добавить запись (рис. 1.29). На рис. 1.30 показан результат запол
нения таблицы Огбегв і данными.
44
Глава 1. Визуальная среда разработки DBEAVER
, j order.id
В 123 customer id
«sc status
—
2
3
” I 123 salesman_id
■»
І О order date
Pending
145
2019-09-26
5
Pending
146
2019-09-26
5
5
Pending
156
2019-09-09
8
28
Pending
3
2019-09-09
Рис. 1.30. Результат заполнения таблицы Ordersl
1.5. Редактор SQL
Редактор SQL используется для создания, редактирования
и выполнения запросов SQL и скриптов.
Скрипт — это последовательность запросов или команд
языка программирования.
В этой книге рассматривается язык PL/pgSQL, и скрипты будут состоять из
команд этого языка.
4S
PostgreSQL: SQL + PL/pgSQL
1.5.1. Окно редактора SQL
Для одного соединения можно открыть несколько окон редактора SQL.
Открыть окно этого редактора можно различными способами. Мы будем ис
пользовать раскрывающийся список Открыть скрипт SQL, в котором сле
дует выбрать команду Новый редактор SQL (рис. 1.31), или комбинацию
клавиш Ctrl + ].
£J SQL ▼ Q Commit ГЇ Rollback
П
Открыть SQL скрипт
£
Открыть последний скрипт
і ГТ
0
If" ’ j| ■
F3
*
Ctrl
]
Новый редактор SQL
Open SQL console
Ctrl+Alt+ShiftsF
Быстрый поиск
Default command
►
Рис. 1.31. Выбор команды "Новый редактор SQL "
Выбор схемы
Выбор соединения
Auto
о ’
*t2.~
<postg«
w postgrts
-
й) hi_poc1®postgr«
£2 <postgr«l-
’
® й •
fj rpostgres»...
<t ’
£J споле» Script-7
О.
•
£2'<postgres> .
X
£J <postgre»2.-
tt
*
® [Д
=
О
►
- select • tree customers
.~fc~-——M-«e limit >1000000;
J$_
Выполнит. SQL запрос і ~
—u
3
E
0
P customers 1 X
Рис. 1.32. Окно редактора SQL
В результате на экране появится окно редактора SQL (рис. 1.32). Для того
чтобы выполнить новый запрос, нужно:
•
выбрать соединение и схему;
46
Глава 1. Визуальная среда разработки DBEAVER
• ввести текст запроса;
•
нажать кнопку Выполнить SQL-запрос.
^ Базы данных
В нижней части окна редактора появится
панель результатов, которая будет содер
жать результаты выполнения запроса.
Д Проекты X
В ^ •= ^ $ ^
§
°
л И General
Connections
: i^ Bookmarks
;: ^ Diagrams
4 В Scripts
Д Script-1,sql
Вы можете просмотреть все ваши
сохраненные скрипты в представлении
обозреватель проектов в папке Scripts
(рис. 1.33).
Д Script-W.sql
Д Script-1 l.sql
Д Script-12.sql
Д Script-13,sql
Д Script-14,sql
Д Script-15.sql
Д Script-16,sql
Д Script-17,sql
Д Script-18,sql
Д Script-2.sql
Рис. 1.33. Сохраненные скрипты
Д Script-3,sql
------------------- —►
Редактор SQL имеет настраиваемую па
нель инструментов. В таблице 1.2 содер
жится описание основных кнопок этой
панели.
Д Script-4,sql
Д Script-5,sql
Д Script-6,sql
Д Script-7,sql
Д Script-8,sql
Д Script-9,sql
Д Script.sql
Таблица 1.2. Описание кнопок панели инструментов редактора SQL
Кнопка
Описание
►
Выполнить инструкцию SQL (Ctrl + Enter). Происходит вы
полнение запроса, и полученные результаты отображаются
на панели результатов или в окне SQL-терминала
►+
Выполнить инструкцию SQL в новой вкладке (Ctrl +\). Эта
кнопка аналогична предыдущей кнопке, но результаты запроса
отображаются на новой вкладке панели результатов. Эту кноп
ку удобно использовать для сравнения результатов 2 запросов
JET
Выполнить скрипт (Alt+X). Происходит выполнение скрип
та, содержащегося в окне редактора SQL, и его результаты
отображаются в окне вывода (консоли) и/или окне SQLтерминала
л
Показать план выполнения запроса (Shift + Ctrl +Е)
0
SQL-терминал. Результаты выполнения SQL-запроса или
скрипта будут выведены на специальной вкладке панели ре
зультатов в текстовом формате
*
PostgreSQL: SQL + PL/pgSQL
Панель инструментов редактора SQL является настраиваемой, можно до
бавить или скрыть кнопки, отображаемые на этой панели. Для настройки
панели инструментов нужно выбрать в меню команду Окна —* Настройки
и в появившемся окне (рис. 1.34) выбрать Интерфейс —> Toolbar
Customization.
Рис. 1.34. Окно настройки панели инструментов
1.5.2. SQL-терминал
SQL Terminal — это вкладка результатов редактора SQL,
где отображаются результаты выполненных запросов в
текстовом формате.
Это очень удобный способ вывода результатов. Для того чтобы открыть SQL
Terminal, нужно нажать соответствующую кнопку на левой панели инстру
ментов редактора SQL. На рис. 1.35 показано окно редактора SQL, которое
содержит скрипт и результаты его выполнения.
По умолчанию SQL Terminal не используется. Для того чтобы его включить
и настроить, нужно нажать кнопку Настройки на панели инструментов ре
дактора SQL и в появившемся окне (рис. 1.36) выбрать команду Редакторы
—► SQL Terminal.
48
Глава 1. Визуальная среда разработки DBEAVER
£J <poatgr«s2...
g
™
£T
^
£J cpostgresi..
ELSIE
Q
X
£J <postgre5>...
”5
= D
BEGIN
SELECT SWI(olt.quantity*unlt_prlce) As Sales INTO v_sum_sal
FROM Employees enp
JOIN Orders ord ON (employee_id«salesman_xd)
JOIN Order_Itenss oit ON (ord.CRDER_ID ” ait.order_id)
WHERE ersployee_ld“V_eiEp_id;
Настройки
r^
fj <po;tgres>
DO $5
DECLARE
v_erp_id Employees.essployee_idtTYPE:“15S;
v_sum_sal numeric(10,2);
v_bonus numeric(10,2);
1 > 1000000 THEN v_bonus :~ 50000;
_sum_sal > 500000 THEN v bonus :- 25000;
v_sunT’»l > 200000 THEN v_bonus :■ 10000;
v_bonus :“0;
ELSE
END IF;
RAISE notice ’ v__sum_sal” » ’, v_sum_sal;
RAISE notice ’ v_bonus« V,v_bonus;
end И;
В Статистик» 1
:15 SQL Terminal X
0 row(s) modified.
v_sum_sal” 526380.00
v_bonus“ 25000.00
Рис. 1.35. Отображение результатов на вкладке SQL Terminal
Рис. 1.36. Настройка SQL Terminal
1.5.3. Панель результатов
На панели результатов отображаются вкладки с результатами
в различных форматах.
49
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Вкладка Таблица (рис. 1.37) содержит результаты выполнения SQL-зaпpoca,
представленные в виде таблицы.
Рис. 1.37. Результаты выполнения SQL-запроса в виде таблицы
Вкладка SQL Terminal (рис. 1.38) содержит результаты выполнения SQLзапроса в текстовом формате.
В employees 1
ГЯ SQL Terminal
X | Q Лог выполнения
ercployee_idIflrst_name|last_nanie|salary
1001 Steven
1011 Neena
102|Lex
3 row(s)
I King
IKochhar
1 De Haan
|department_id|
124000.001
117000.001
117000.001
90 1
90 1
90 1
fetched.
Рис. 1.38. Результаты выполнения SQL-запроса в текстовом формате
Вкладка Лог выполнения (рис. 1.39) содержит все запросы, выполненные в
текущем редакторе SQL.
Для того чтобы отобразить эту вкладку, необходимо нажать кнопку Лог
выполнения на панели инструментов.
ІЗ
L| Лог аыпслнения |
>
Й table_prtvUeges 1
Д Лог выполнения X j
Ввести часть запроса для поиска в журнале запросов
Время
Тип
Текст
ПродоЛЖИТЄЛ...
апр.-01 11:...
SQL/User
select * from information_schema.table_privileges
493
200
anp-01 11:...
SQL / User
SELECT emplcyee.id, first.name, last, name, salary, department.idUFROM Employees*WHERE salary > 10000
64
15
Успешно
anp.-01 11:...
SQL / User
SELECT employee.id. first.name, last.name, departmentjcflFRCM EmployeesHWHERE (job_id='ST_CLERK)
3
20
Успешно
Результат
Рис. 1.39. Все запросы, выполненные в текущем редакторе SQL
Успешно
Глава 1. Визуальная среда разработки DBEAVER
Для того чтобы показать или скрыть панель результатов, нужно нажать ком
бинацию клавиш СТКЫ-6 или щелкнуть правой кнопкой мыши в любом
месте на панели сценариев и в контекстном меню (рис. 1.40) выбрать коман
ду Расположение —► Переключить панель результатов.
fj «postgres 3...
£J <postgres>...
£J «postgres»...
X
fj <postgr«> ...
fj <postgr«> ...
”4
* select * from customers
where credit limit
►
>300000;
Выполнить
Файл
Форматирование
Panels
Расположение
SQL помощник
SQL шаблоны
•
Ctrl+Пробел
CW* АИ+Пробел
Горизонтальный
Вертикальный
Отдельный
Контекстный помощник SQL
F2
Переключить панель результатов
Ctrk6
Редактировать объект БД
F4
Восстановить панель результатов
Ctrl+Shift.6
Копировать как исходный код
Ctrf+Shrft+C
Переключить панель
АИ*6
Copy selected query
Search selected text with Google
і
Ctrl+X
Копировать
Ctrl» C
Вставить
Ctrl+V
<У
Отменить
Ctrf+Z
J
Сохранить
Ctrl+S
I Q
I
Вырезать
$
Параметры...
Рис. 1.40. Контекстное меню панели сценариев
1.5.4. Управление скриптами
Можно сохранять скрипты в текущем активном проекте или где-нибудь в
файловой системе. Чтобы сохранить сценарий в текущем проекте, нажмите
Ctrl+S или выберите команду Сохранить в контекстном меню панели сце
нариев.
Скрипт, сохраненный таким образом, можно найти в папке Scripts проекта
(рис. 1.33).
Чтобы сохранить скрипт в файловой системе, выберите команду Файл —►
Сохранить SQL-скрипт в контекстном меню (рис. 1.41), а затем выберите
папку в файловой системе и введите имя файла (рис. 1.42).
PostgreSQL: SQL + PL/pgSQL
fj <postgres> »
fj *<postgres>... X fj <ройдг«-> ~
3<postgnes>«.
2<postgres>
3 <postgt«>,.,
- SELECT евф1оуее_ій, first_name. last_naree, salary, departjnent_id
К
ÎJ
FROM Employees
WERE salary > 10000'-
>
’1
Выполнить
Файл
Переименовать скрипт
Форматирование
Вернуться
Panels
> 8?
Расположение
►
SQL помощник
SQL шаблоны
Контекстный помощник SQL
Ctrl* Пробел
Загрузить SQL скрипт
CtrkF2
Ctrl-Ait-Shift-0
Сохранить SQL скрипт
Disable SQL syntax parser
Ctrl* Alt* Пробел
F2
Редактировать объект БД
Копировать как исходный код
Ctrt-Shift-C
Copy selected query
Search selected text with Google
Вырезать
^
$
0
Копировать
Ct*<
Вставить
сы»ѵ
Отменить: Напечатать
CtrkZ
Сохранить
Ctrl*S
Параметры...
Рис. 1.41. Сохранение скрипта в файловой системе
Рис. 1.42. Выбор папки и ввод имени файла
Чтобы загрузить скрипт, хранящийся в файловой системе, выберите команду
Файл —► Загрузить SQL-cкpипт в контекстном меню (рис. 1.41), а затем
выберите папку в файловой системе и имя файла.
Глава 1. Визуальная среда разработки DBEAVER
Если вы хотите отменить последнее изменение, внесенное в текущий ЗРЬскрипт, выберите команду Отменить: Напечатать в контекстном меню
(рис. 1.41).
1.5.5. План выполнения запроса
РозІдгеЗОЕ разрабатывает план выполнения для каждого
запроса. Выбор правильного плана, соответствующего
структуре запроса и свойствам данных, сокращает время
выполнения запроса, поэтому система включает в себя
планировщик для формирования оптимального плана.
Структура плана запроса представляет собой дерево узлов плана. Узлы
на нижнем уровне дерева являются узлами сканирования: они возвращают
строки из таблиц. Если запрос требует объединения, агрегирования, сорти
ровки или других операций над строками, то над узлами сканирования будут
дополнительные узлы для выполнения этих операций. Вывод плана запро
са содержит одну строку для каждого узла в дереве плана, показывающую
базовый тип узла плюс оценки затрат, сделанные планировщиком для вы
полнения этого узла плана. Стоимость узла верхнего уровня включает стои
мость всех его дочерних узлов.
Для того чтобы вывести план выполнения запроса, нужно нажать кнопку
Получить план выполнения запроса на панели инструментов редактора
SQL.
На экране появится окно (рис. 1.43) для выбора параметров, с их описани
ем можно ознакомиться в официальной документации. После этого следует
нажать кнопку ОК, и на панели результатов появится вкладка с планом вы
полнения запроса (рис. 1.44).
Для каждого узла будут выведены:
• Стоимость — оценка затрат на выполнение этого узла в условных еди
ницах.
• Строки — ожидаемое число строк, которое должен вывести этот узел.
•
Время — значение ожидаемого времени для выполнения операций этого
узла в миллисекундах.
53 1
___ j
PostgreSQL. SQL + PL/pgSQL
PostgreSQL
Puc. 1.43. Окно для выбора параметров
Д <postgres> ...
к
и
S'
В
$
Д <postgres> ...
Д <postgr«3...
Д <postgr«>...
Д <postgres> ...
X
”4
^SELECT d.department_id, d.department_name, d.manager_id, e.count_emp
FROM Departments d JOIN
(SELECT department_id, COUNT(*) AS count_emp
FROM Emp1oyee s
GROUP BY department_id
HAVING COUNT(•) > 10) e
ON (d.department_idwe.department_id);
В Результат 1
І’З План выполнения запроса -1
X
Сущность
4 Hash Join
Время
Условие
2
0.096
(d. department-id = e.departmentjd)
0.00-127
Стоимость
27
0.018
3.78 - 3.78
2
0.066
Subquery Scan
3.61 - 3.78
2
0.064
J Aggregate
3.61 - 3.74
2
0.062
0.00 - 3.07
107
0.016
Seq Scan
departments
j Hash
j
Строки
3.83-5.18
Seq Scan
employees
(countfl > 10)
<
SELECT d.departmentjd, d.department_name, d.manager_id, e.count_emp
FROM Departments d JOIN
(SELECT department-id, COUNT(*) AS count_emp
FROM Employees
GROUP BY department.id
HAVING COUNTS > W) e
0 N (d. depa rtment. i d=e. d epartm ent.i d)
Puc. 1.44. План выполнения запроса
План выполнения запроса можно получить, используя команду EXPLAN.
На рис. 1.45 показаны пример и результат использования этой команды.
Сравнивая планы выполнения двух запросов для решения одной задачи,
следует учитывать то, что оценка времени выполнения запроса существен
но зависит от размера таблиц, и запрос, который выполняется быстрее для
небольших таблиц, может выполняться медленнее альтернативного запроса
при увеличении количества строк в таблицах.
Глава 1. Визуальная среда разработки DBEAVER
£j <postgres> ...
fj <postgres> ...
£J <postages 3..
П <postgre$> ...
Д <postgres> ...
= □
Д *<postgres>... X
"EXPLAIN (analyse,costs, timing)
SELECT d.department_id, d.department_name, d.manager_id, e. count__enrp
FROM Departments d JOIN
(SELECT department^id, COUNT(*) AS count_emp
FROM Employees
GROUP BY department_id
HAVING COUNT(*) > 10) e
ON (d.departreent_id-e.department_id);
IF
1
$
В Результат 1 X
д План выполнения запроса • 1
0 План выполнения запроса - 2
<>Т EXPLAIN (analyze,costs, timing) SELECT
Ж QUERY PLAN
Hash Cond: (d.departmentjd = e.d epart ment.id)
-> Seq Scan on departments d (cost=0.00..1.27 row$=27 widths 1019 (actual time=0.025..0.028 rows=27 loopssl)
-> Hash (cost=3.78..3.78 rows=4 widths 13) (actual time=0.111.0.114 rows=2 loopssl)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Subquery Scan on e (costs3.61..3.78 rows=4 width= 13) (actual time=0.107..0.111 rcws=2 loops=1j
-> HashAggregate (costs3.61.3.74 rows=4 widths 13) (actual times0.107..0.109 rows=2 loopssl)
Group Key: employees.department_id
8
9
Fitter: (count(*) > 10)
10
Batches: 1 Memory Usage: 24kB
Rows Removed by Filter: 10
_и
Л
14
-> Seq Scan on employees (cost=0.00.3.07 rows=107 width=5) (actual time=0.013..0.027 rows=107 loops=1)
Planning Time: 0.293 ms
Execution Time: 0.219 ms
^ Обмовить
Î , Экспорт данных...
*
$
200
X 14
14 строк получено • Oms, 2023-04-02 в 12:33:40
Рис. 1.45. Результат использования команды EXPLAN
1.6. Диаграммы сущность - связь
Диаграммы сущность-связь (ЕРО) — это графические
представления таблиц (сущностей) базы данных и связей
между ними.
ОВеауег позволяет просматривать диаграммы существующих таблиц, схемы
всей базы данных и создавать пользовательские диаграммы.
1.6.1. Диаграммы таблиц и схем
Если выбрать вкладку Диаграмма, в верхней части окна Свойства табли
цы (рис. 1.23), то на экране будет отображено графическое представление
таблицы и таблиц, с которыми эта таблица связана (рис. 1.46).
55
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Puc. 1.46. Диаграмма сущность-связь таблицы Orders
Если выбрать вкладку Диаграмма в верхней части окна Свойства схемы
(рис. 1.22), то на экране будет отображена диаграмма (рис. 1.47), которая
представляет собой графическое представление всех таблиц (сущностей)
этой схемы и связей между ними.
В нижней части окна на рис. 1.47 имеется панель инструментов, кнопки
которой позволяют изменить внешний вид диаграммы, распечатать ее или
сохранить во внешнем файле.
Рис. 1.47. Диаграмма сущность - связь схемы НК РОС
56
Глава 1. Визуальная среда разработки DBEAVER
Линии, представляющие связи между таблицами, могут выглядеть поразному, в зависимости от типа связи. Описание типов связей содержится в
таблице 1.3.
Таблица 1.3. Типы связей
Обозначения
Описание
Идентифицирующая связь — означает, что столбец
внешнего ключа одновременно является частью пер
вичного ключа
Обычная связь
Черная точка указывает таблицу, которая содержит
внешний ключ
Ромб используется в конце линии, когда столбец, яв
ляющийся внешним ключом, может иметь значения
NULL
1.6.2. Пользовательские диаграммы
ОВеаѵег позволяет создавать пользовательские
диаграммы, которые будут содержать только часть таблиц
схемы и связей между ними. Пользовательские диаграммы
могут содержать только реально существующие таблицы.
Для создания такой диаграммы нужно выбрать в окне проекта узел
Diagrams, щелкнуть правой кнопкой и появившемся контекстном меню
(рис. 1.48) выбрать команду Создать новую диаграмму.
В появившемся окне (рис. 1.49) нужно выбрать:
• соединение;
• базу данных;
• схему;
• таблицы.
Е
PostgreSQL: SQL + PL/pgSQL
□
S Project - General X
Название
Источник данных
>^ Bookmarks
i> ^ Diagrams
t> Si Scripts
Рис. 1.48. Выбор команды "Создать новую диаграмму"
Создать новую диаграмму
Управление содержимым диаграмм.
Настройки
Employees
Начальное содержимое (опционально):
і> П ^ DBeever Sample Database (SQLrte)
‘ □, postgr»
4 Qa Бады данных
Д □ 8 portgrei
< ПЙ Схемы
' DU b'-pod
j
Q® Таблицы
>□«customers
^ Й в departments
> ® ® employees
^ ® ® jobs
> й В locations
Ш» order_ items
t> D ® orders
> D в products
> □ 80 Представления
Рис. 1.49. Выбор схемы и таблиц
Готово
Отмена
После этого следует ввести имя диаграммы и нажать кнопку Готово. Новая
диаграмма появится в отдельном редакторе (рис. 1.50). Можно добавлять в
диаграмму новые таблицы. Для этого нужно выбрать таблицу в окне Нави
гатора объектов (рис. 1.51) и перетащить ее в окно редактора. Для редак
тирования диаграммы можно использовать панель инструментов, которая
расположена в правой части окна. Для выбора редактируемого элемента ис
пользуется команда Select этой панели.
58
Глава 1. Визуальная среда разработки DBEAVER
п <postg«s> .м
П <postgr«>...
Д <postgres> ...
Д <postgres> ...
^ Employees-1erd X
**8
“О
> 4* Palette
® locations
№ employees
1^ employeeJd
BBC first_name
nac last_name
fisc email
в departments
1-У department Jd
fisc postal_code
123 managerjd
ABC city
123 location_id
abc
co
[^ Select
C@> Pan Diagram
fisc street_addres5
ABC department_name
ABC phone_number
Q Tools
iy locationjd
♦** Connection
Q Note
state.province
fiBC ountryjd
; ^ postgres
co
$ hire_date
® departments
abc jobjd
® jobs
® employees
123 salary
^ job Jd
123 commission_pct
® jobs
123 managerJd
ABC job.title
123 departmentjd
123 min_salary
123 rating_e
123 max.salary
4 objects
W locations
Q
100%
V G< O' M SB « І І й I •Bl#
Рис. 1.50. Пользовательская диаграмма
Рис. 1.51. Редактирование пользовательской диаграммы
Созданную диаграмму можно сохранить в виде отдельного файла, имею
щего графический формат. Для этого используется кнопка Сохранить
диаграмму во внешнем файле, рис. 1.51.
59
PostgreSQL: SQL + PL/pgSQL
.Ф PostgreSQL
1.6.3. Описание используемой схемы базы данных
При создании SQL-запросов и программ PL/pgSQL нужно иметь четкое
представление о схеме базы данных, с которой вы работаете, и знать бизнесправила и ограничения, которые существуют в предметной области.
На рисунке ранее представлена E-R диаграмма схемы HR_POC, которую
мы будем использовать в этой книге. Рассмотрим назначение таблиц этой
схемы и сформулируем бизнес-правила, которые необходимо соблюдать.
• В таблице Employees содержатся данные о сотрудниках. Каждый сотруд
ник компании имеет уникальный идентификационный номер (employee_
id), номер должности (job_id), ставку заработной платы (salary) и код ме
неджера (manager id). Некоторые сотрудники в дополнение к зарплате
получают комиссионные (commission_pct). Размер комиссионных опре
деляется как часть от заработной платы. Столбец job_id используется для
установления связи с таблицей Jobs, и для него определено ограничение
внешнего ключа. Следствием этого является то, что значение данного
столбца должно совпадать с одним из значений столбца job id в табли
це Jobs или иметь неопределенное значение NULL. Это ограничение
обеспечивается средствами СУБД. Аналогичными свойствами обладает
столбец department_id, который используется для установления связи с
таблицей Departments.
• В таблице Jobs содержится информация обо всех возможных должностях
организации. Каждая должность имеет уникальный идентификационный
номер (job_id), наименование (job_title), минимальную (min_salary) и
максимальную ставку заработной платы (max_salary). При изменении
заработной платы сотрудника, занимающего определенную должность,
нужно следить, чтобы новая зарплата не выходила за заданные границы.
Средствами СУБД это ограничение не обеспечивается.
• Данные об отделах содержатся в таблице Departments. Каждый отдел
имеет уникальный код (departmentid), код руководителя (manager_
id), наименование (department_name), а также место расположения
(locationid).
• Компания имеет распределенную структуру, поэтому в таблице Locations
хранятся данные о местонахождении отделов, которые состоят из адреса
(streetaddress), почтового индекса (postal_code), названия города (city),
названия штата (state_province) и кода страны (country id). В таблице
Locations содержатся данные о населенных пунктах, в которых пока нет
отделов.
60
Глава 1. Визуальная среда разработки DBEAVER
В таблице Customers хранятся данные о клиентах. Столбец customer_id
содержит код клиента и является ключом к этой таблице. В этой таблице
есть столбцы: c_name - имя клиента, address - адрес клиента, credit_
limit - кредитный лимит. Используя столбец credit_limit, можно сформу
лировать следующее правило: запретить оформление заказа, если общая
сумма заказов клиента, находящихся в состоянии ожидания, превышает
его кредитный лимит.
Таблица Order содержит данные о заказах и имеет следующие столбцы:
customer_id - код клиента, для которого оформлен заказ, salesman_id
- код сотрудника, который оформил заказ, order_date - дата оформле
ния заказа. Столбец status таблицы Order определяет состояние заказа и
может принимать следующие значения: Pending - в ожидании, Shipped
- отправлен, Canceled - отменен. Используя этот столбец, сформулиру
ем следующее бизнес-правило: можно изменить содержимое заказа, ко
торый находится в состоянии Pending, но нельзя изменить содержимое
заказа, который находится в состоянии Shipped.
Заказ может содержать несколько товаров. Для хранения данных о то
варах в каждом заказе служит таблица Order items. Разберем назначе
ние столбцов: order_id - номер заказа; item id - строка (пункт) заказа;
product_id - код товара; quantity - количество товара в заказе; unit_price
- продажи.
В таблице Products содержатся следующие данные о товарах: product_id
- код товара; product name - наименование товара; rating р - рейтинг
товара, price - цена товара.
»
Столбец price в таблице Products содержит текущую цену товара, а
столбец unit_price в таблице Order items - цену, по которой он был
продан. Разница между значениями этих столбцов может возникать
из-за того, что клиенту была предоставлена скидка, также со временем
значение price может измениться, а значение unit_price - нет.
В таблице Employees имеется столбец rating_e. Значения элементов это
го столбца целочисленные и должны лежать в диапазоне от 1 до 5. Будем
считать, что значение столбца rating_e отражает квалификацию сотруд
ника.
В таблице Products содержится столбец rating_p. Значения элементов
этого столбца также должны лежать в диапазоне от 1 до 5 и отражать
сложность товара.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Используя столбцы rating„e и ratingp, можно
сформулировать следующее бизнес-правило: сотрудник
имеет право продавать только те товары, рейтинг которых не
превышает его рейтинга.
Это бизнес-правило мы будем неоднократно использовать при решении
задач.
Глава 2.
СТРУКТУРА ОПЕРАТОРА
SELECT И ФОРМИРОВАНИЕ
УСЛОВИЙ ВЫБОРА
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
SQL (Structured Query Language) - язык
структурированных запросов, является основным языком
определения, манипулирования и управления данными в
современных СУБД.
В начале семидесятых годов двадцатого столетия сотрудником фирмы IBM
Эдгаром Коддом были разработаны реляционная модель представления данных
и реляционная алгебра для манипулирования такими данными. Компания IBM
обратила внимание на эту работу и создала СУБД IBM System/R, в рамках кото
рой был разработан язык структурированных запросов.
Эту разработку оценили другие компании и стали разрабатывать свои вер
сии СУБД, использующие реляционную модель данных, и создавать свои
версии языка запросов.
Для того чтобы обеспечить переносимость программного обеспечения с од
ной СУБД на другую, было принято решение разработать стандарт языка
SQL.
Международная организация по стандартизации (ISO) и Американский на
циональный институт стандартов (ANSI) в 1986 году разработали стандарт
SQL-86.
Этот стандарт постоянно совершенствовался, и к настоящему време
ни известны следующие стандарты: SQL-86, SQL-89, SQL-92, SQL: 1999,
SQL:2003, SQL:2006, SQL:2008, SQL:2011, SQL:2016, SQL:2019. При этом
реализация SQL, используемая в конкретной версии СУБД, лишь отчасти
соответствует тому или иному стандарту.
Глава 2. Структура оператора SELECT и формирование условий выбора
Операторы SQL разделены на три группы:
1. Операторы манипулирования данными (Data Manipulation Language,
DML) — предназначены для выборки и изменения данных: SELECT,
INSERT, UPDATE, MERGE, DELETE.
2. Операторы определения данных (Data Definition Language, DDL) —
предназначены для создания и модификации объектов базы данных. Ос
новными операторами этой группы являются: CREATE, ALTER, DROP.
3. Операторы управления данными (Data Control Language, DCL) —
предназначены для предоставления пользователям прав на выполнение
определенных действий с базой данных: GRANT, REVOKE.
2.1. Структура оператора SELECT
Оператор SELECT предназначен для выборки данных из
таблиц, то есть он реализует одно из основных назначений
базы данных — предоставлять пользователю информацию.
Результатом выполнения оператора SELECT является
таблица.
Структура этого оператора может быть представлена в следующем виде:
SELECT [ALL(DISTINCT] {список столбцов или выражений}
[FROM
{список таблиц}]
[WHERE {условия выбора}]
[GROUP BY {столбцы группировки}]
[HAVING {условия на группу}];
[ORDER BY {столбцы сортировки [ASC|DESC]]}
[LIMIT {N} ] [OFFSET{М}];
(Квадратными скобками отмечены необязательные элементы).
Дадим предварительное описание элементов данного оператора.
• Оператор SELECT начинается со списка столбцов или выражений, зна
чения которых будет отображаться в результате выполнения запроса. По
умолчанию SELECT не исключает дублирование строк. Для исключения
дублирования следует использовать ключевое слово DISTINCT.
PostgreSQL: SQL + PL/pgSQL
(^ PostgreSQL
• В предложении FROM указываются источники данных. В качестве таких
источников можно использовать таблицы базы данных, которые возвра
щают подзапросы или представления. В тех запросах, где используется
несколько таблиц, необходимо указывать условия соединения. Если этого
не сделать, то будет осуществляться декартово произведение таблиц.
• Предложение WHERE содержит условия выбора строк, а также может
содержать условия соединения таблиц в многотабличных запросах.
• В предложении GROUP BY можно указать столбцы, по которым следу
ет осуществить группировку. Группировка состоит в том, что несколько
строк, имеющих совпадающие значения столбцов, по которым осущест
вляется группировка, объединяются в одну строку. Обычно группировка
используется в запросах, использующих агрегатные функции, например:
Sum(), Мах().
• При наличии группировки в предложении HAVING можно указать ус
ловия на группу. Результат выполнения запроса будет содержать данные
только о тех группах записей, которые удовлетворяют этому условию.
• Результат выполнения запроса может быть упорядочен по значениям од
ного или нескольких столбцов. В предложении ORDER BY указываются
имена столбцов, по значению которых следует отсортировать результат
выполнения запроса. По умолчанию строки упорядочиваются в порядке
возрастания значений столбца. Для сортировки в порядке убывания после
имени столбца следует указать параметр DESC. Если указать несколько
столбцов, то результат будет упорядочиваться сначала по значению пер
вого столбца; строки, имеющие одинаковые значения первого столбца,
упорядочиваются по значению второго столбца, и так далее.
• Предложение LIMIT N позволяет ограничить количество строк в ре
зультате выполнения запроса. При наличии этого предложения будет
выводиться не более первых N строк результата. Как правило, это пред
ложение используется в запросах с сортировкой результата. При исполь
зовании LIMIT N можно использовать OFFSET М. В этом случае сна
чала пропускаются первые М строк результата, после этого выводится N
следующих строк.
При изучении SQL следует обратить внимание на то, что для создания
запроса необходимо:
1. Определить структуру запроса, соответствующую заданной задаче обра
ботки данных.
Глава 2. Структура оператора SELECT и формирование условий выбора
2. Синтаксически правильно записать запрос.
Перейдем к рассмотрению примеров, которые должны научить правильно
решать обе задачи. Сначала будут рассмотрены запросы, структура которых
очевидна, поэтому основное внимание будет уделяться синтаксису, затем мы
перейдем к рассмотрению более сложных запросов, где основной задачей
будет являться определение структуры запроса.
В своей простейшей форме оператор SELECT должен включать в себя сле
дующее:
• предложение SELECT, где указываются имена столбцов, значение кото
рых будет отображаться в результате выполнения запроса;
• предложение FROM, в котором указывается имя таблицы, содержащей
данные.
SELECT {список столбцов}
FROM {таблица};
Запрос 2.1. Вывод содержимого одного столбца
SELECT employee_id
FROM Employees;
Запрос 2.2. Вывод содержимого нескольких столбцов
SELECT employee_id, first_name, last_name, department_id
FROM Employees
Если нужно вывести значения всех столбцов, то вместо списка столбцов ука
зывается символ *.
Запрос 2.3. Вывод значений всех столбцов
SELECT *
FROM Employees;
Исключение дублирования данных
Рассмотрим запрос, который должен вывести коды должностей сотрудников.
Запрос 2.4. Вывод значений столбца job_id
SELECT job_id
FROM Employees;
67
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Так как одну должность могут занимать несколько сотрудников, то коды
должностей будут повторяться. Для того чтобы исключить повторения зна
чений, следует добавить ключевое слово DISTINCT.
Запрос 2.5. Вывод значений столбца job_ id таблицы Employees,
без дублирования
SELECT DISTINCT job_id
FROM Employees;
2.2. Условия выбора
Для того чтобы выводить только те данные, которые
удовлетворяют определенным условиям, оператор SELECT
должен содержать предложение WHERE, которое содержит
условное выражение:^
SELECT {список столбцов}
FROM {таблица}
WHERE {условное выражение};
Условное выражение для каждой строки таблицы может принимать
значения: ИСТИНА (TRUE), ЛОЖЬ (FALSE), НЕ ОПРЕДЕЛЕНО
(UNKNOWN). Результат выполнения запроса будет содержать только те
строки, для которых условное выражение будет иметь значение ИСТИНА
(TRUE).
Запрос 2.6. Вывод данных о сотрудниках,
которых больше 15000
зарплата
SELECT employee_id, first_name, last_name, salary, department_id
FROM Employees
WHERE salary > 15000;
employee_id I first_name I last_name I salary
------------------ ---.-.^— —
1001 Steven
101|Neena
1021 Lex
-I- —----------- — —
1 King
1Kochhar
1 De Haan
I department_id I
1- —------------- — — -J- —---------------- —--------------- 1-
124000.001
117000.001
117000.001
90 1
90|
901
Глава 2. Структура оператора SELECT и формирование условий выбора
Запрос 2.7. Вывод данных о сотрудниках, принятых на
работу 20.08.1997
SELECT employee_id, first_name, last_name, salary, department_id
FROM Employees
WHERE hire_date = '20.08.1997';
employee_id | first_name | last_name | salary | department_id |
----------- +---------- +---------- +------- +-------------- +
129|Laura
152|Peter
|Bissot
IHall
50|
80|
13300.00|
19000.001
В условных выражениях предложения WHERE могут быть использованы
операторы сравнения =, >, < и логические операторы NOT, AND, OR. Ло
гические операторы используются для формирования сложных условий вы
бора и имеют разный приоритет. Сначала выполняются все операторы NOT,
потом операторы AND; операторы OR выполняются в последнюю очередь.
Для исключения возможных ошибок при формировании сложных запросов
следует использовать скобки. Выражения внутри скобок выполняются пер
выми, слева направо.
Рассмотрим примеры запросов, использующих логические операторы при
формировании условий выбора.
Запрос 2.8. Вывод данных о сотрудниках, которые
работают в отделе 50 и занимают должность ST_MAN
SELECT employee_id, first_name, last_name, department_id, job_id
FROM Employees
WHERE (department_id = 50) AND (job_id= 'ST_MAN');
employee_id I first_name I last_name I department_id I j ob_id I
----------- +--------- +--------------------- +—
--- +
-----
1201 Matthew
1211 Adam
1221Payam
1231Shanta
1241 Kevin
I Weiss
|Fripp
| Kaufling
IVollman
IMourgos
1
1
1
1
50
50
50
50
50
1
1
1
1
1
ST MANI
ST MAN 1
ST__MAN 1
ST MAN 1
ST__MAN 1
Использование скобок при формировании условий выбора может суще
ственным образом изменять логику выполнения запроса.
69
PostgreSQL: SQL + PL/pgSQL
.............................................. f PostgreSQL
Запрос 2.9. Вывод данных о договорах сотрудника 155,
заключенных 15.03.2018 или 02.11.2019
SELECT * FROM Orders
WHERE (salesman_id = 155) AND (order_date ='15.03.2018'
OR order_date ='02.11.2019');
order_idIcustomer_idI status
Isalesman_idIorder_dateI
---------------------- -I----------------------------------- 1-----------------------1---------------------------------- 1------------------------------- p
101 j
49|
50|
3|Pending|
61|Shipped|
62(Pending|
15512018-03-151
15512019-11-021
15512019-11-021
Если в предложении WHERE скобки поставить так, как это показано в
запросе 2.10, то запрос будет иметь совсем другой смысл.
Запрос 2.10. Вывод данных о договорах сотрудника
155, заключенных 15.03.2018, и обо всех договорах,
заключенных 02.11.2019
SELECT * FROM Orders
WHERE (salesman_id = 155) AND (order_date ='15.03.2018')
OR (order_date ='02.11.2019');
order_idIcustomer_id|status |salesman_id|order_dateI
--------- +---------------- +-------------- +-------------------- +-------------------- +
101 1
49|
50 1
511
52 1
3|Pending|
61|Shipped|
62|Pending|
63 I Shipped|
64|Shipped|
15512018-03-151
15512019-11-021
15512019-11-021
15912019-11-021
16012019-11-021
2.3. Выражения выбора
Для формирования условий выбора можно использовать специальные вы
ражения, представленные в таблице 2.1.
Таблица 2.1. Специальные выражения
Выражение
Описание
Пример
LIKE 'шаблон'
Совпадает ли часть строкового
значения с заданным шаблоном
first_name LIKE '_а%'
2 70 ]
Глава 2. Структура оператора SELECT и формирование условий выбора
BETWEEN V_MIN
AND V_MAX
Находится ли значение столбца в
диапазоне от УМЕЧ до У МАХ
rating_e BETWEEN 2
And 4
IN(список)
Совпадает ли значение столбца
с одним из элементов списка
значений
rating_e IN (2;4)
IS NULL
Значение столбца не определено
rating_e IS NULL
2.3.1. Выражение LIKE
Выражение LIKE применяется при работе со строками. Оно
проверяет, совпадет ли часть строки с заданным шаблоном.
Если совпадение найдено, то оператор возвращает значение
TRUE, в противном случае возвращается FALSE.
Для создания шаблонов в выражении LIKE используются следующие
символы:
• символ подчеркивания "_" — обозначает один символ;
• символ процента "%" — обозначает любую, в том числе и пустую, по
следовательность символов.
Синтаксис:
{имя столбца} LIKE
'шаблон'
Примеры использования выражения LIKE.
Вывод данных о сотрудниках, имена которых
начинаются на букву L
Запрос 2.11.
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE first_name LIKE ' L% ' ;
Вывести имена сотрудников, вторым
символом которых является буква "а"
Запрос 2.12.
71
PostgreSQL: SQL + PL/pgSQL
SELECT DISTINCT first_name
FROM Employees
WHERE first_name LIKE '_a%’;
Запрос 2.13. Вывести имена сотрудников, которые
состоят из четырех символов, начинаются на букву J и
заканчиваются буквой п
SELECT DISTINCT first_name
FROM Employees
WHERE first_name LIKE 'J__ n';
first_name |
----------- +
Jean
John
I
I
Для поиска в строке символов _ и % при построении шаблона используется
опция ESCAPE 7'. Символ, который в шаблоне будет располагаться после /,
будет рассматриваться как символ поиска. Вместо символа / можно исполь
зовать и другие символы, например !.
Запрос 2.14. Вывести имена и адреса клиентов,
address которых содержит символ "_"
столбец
SELECT c_name, address,
FROM Customers
WHERE address LIKE '%/_%' ESCAPE '/'
c_name
I address
I
---------------------------- +------------------------------------------- +
DAIKIN INDUSTRIES
|1304 Kanaoka-cho, Osaka 591_8511, Japan
|
SAKAI HEAVY INDUSTRIES, LTD| Seiwa Bldg., 1-4-8, Minato_ku
|
Microsoft Corporation
(Microsoft Way Redmond, WA 98052_7329 USA |
2.3.2. Выражение BETWEEN
Выражение BETWEEN используется для того, чтобы
результат запроса содержал только те строки, в которых
значение проверяемого столбца находится в заданном
диапазоне, включая его границы.
72
Глава 2. Структура оператора SELECT и формирование условий выбора
Синтаксис:
{имя столбца} BETWEEN V_MIN AND V_MAX
• V MIN — нижняя граница диапазона;
• V_MAX — верхняя граница диапазона.
Выражение BETWEEN эквивалентно двум операциям
сравнения, объединенным логическим оператором AND.
({имя столбца} >= V_MIN) AND ({имя столбца} >= Ѵ_МАХ)
Для определения границ диапазона можно использовать числа, даты и строки.
Запрос 2.15. Вывести данные о сотрудниках, зарплата
которых находится в определенном диапазоне
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE salary BETWEEN 6000 AND 8000;
Запрос 2.16. Получить данные о договорах, дата
заключения которых лежит в определенном диапазоне
SELECT * FROM Orders
WHERE order_date BETWEEN '01.09.2019' AND '30.09.2019';
Выражение BETWEEN можно использовать совместно с логическим опера
тором NOT.
Запрос 2.17. Получить данные о договорах, дата
заключения которых не лежит в определенном диапазоне
SELECT * FROM Orders
WHERE order date NOT BETWEEN '01.09.2019' AND '30.09.2019';
При использовании в качестве границ диапазона строчных значений нужно
учитывать особенности сортировки строк. Например, нужно получить дан
ные о сотрудниках, имена которых начинаются с букв в диапазоне от А до
73
f
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
В включительно. На первый взгляд может показаться, что данную задачу
должен решить следующий запрос.
Запрос 2.18. Получить данные о сотрудниках, работающих
в отделе 50, имена которых начинаются с букв в
диапазоне от А до В (содержит ошибку)
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id =50 AND
first_name BETWEEN
'A' AND ’B’;
employee_idI first_nameIlast_nameIdepartment_idI
------------- 1— --------- 1------------ 1---------------- 1_
1211 Adam
1851 Alexis
1871 Anthony
196|Alana
1 Fripp
IBull
1 Cabrio
IWalsh
1
1
1
1
50
50
50
50
1
1
1
1
Анализ результатов этого запроса показывает, что данные о сотрудниках,
чьи имена начинаются на букву В, в результат выполнения запроса не по
пали, хотя такие сотрудники есть, например Britney.
Это происходит потому, что значение строки ’В’ меньше значения строки
'Britney', поэтому данные о сотрудниках, чьи имена начинаются на букву В,
в результат выполнения запроса не попали. Эту проблему можно решить,
указывая в качестве верхнего диапазона следующую букву.
Запрос 2.19. Получить данные о сотрудниках, работающих
в отделе 50, имена которых начинаются с букв в
диапазоне от А до В
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id =50 AND
first_name BETWEEN
'A' AND ’C ;
employee_idI first_nameIlast_nameIdepartment_idI
------------ +----------- +---------- +--------------- +
121|Adam
185|Alexis
1871 Anthony
1931 Britney
1961 Alana
74
1 Fripp
IBull
1 Cabrio
1 Everett
IWalsh
1
1
1
1
1
50
50
50
50
50
1
1
1
1
1
Глава 2. Структура оператора SELECT и формирование условий выбора
2.3.3. Выражение IN
Выражение Ш используется для того, чтобы результат
запроса содержал только те строки, в которых значение
проверяемого столбца совпадает с одним из значений,
указанных в списке.
Синтаксис:
{имя столбца} IN {список значений}
Список значений может формироваться в результате выполнения оператора
SELECT (подзапроса).
Запрос 2.20. Вывести данные о сотрудниках, которые
работают в отделах с определенными номерами
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id IN (40, 10, 110);
employee_idI first_nameIlast_nameIdepartment_id|
------------ +----------- +---------- +--------------- +
200 1 Jennifer
203 1 Susan
205 1 Shelley
206|William
1 Whalen
IMavris
1 Higgins
IGietz
1
1
1
1
10
40
110
110
Запрос 2.21. Вывести данные о договорах, заключенных
в определенные даты
SELECT * FROM Orders
WHERE order_date IN('07.09.19','14.09.19','19.09.19');
order_idIcustomer__idI status
|salesman_idIorder_dateI
--------------- +---------------------- +--------------+----------------------+-------------------- +
23
24
95
96
1
1
1
1
231
41 1
451
4 61
Shipped 1
Shipped 1
Shipped 1
Shipped 1
15212019-09-07|
15212019-09-07|
179|2019-09-191
179|2019-09-14|
PostgreSQL: SQL + PL/pgSQL
? PostgreSQL
Выражение IN можно использовать вместе с логическим оператором NOT. В
этом случае результат запроса будет содержать строки, в которых значение
проверяемого столбца не совпадает ни с одним из значений, указанных в
списке.
Запрос 2.21. Вывести данные о сотрудниках, которые не
работают в отделах с определенными номерами
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id NOT IN (30,50,60,80,90,100);
Условия выбора, формируемые оператором IN, можно объединять с другими
условиями выбора.
Запрос 2.22. Вывести названия городов, которые расположены
в США (country_id =’US’) или Канаде (country_id =’СА’) и
имеют почтовый индекс, заканчивающийся цифрой 2
SELECT city FROM Locations
WHERE (country_id IN ('US','CA')) AND (postal_code LIKE '%2');
Следует иметь в виду то, что если список значений в IN будет содержать
NULL, то результат выполнения оператора не будет содержать строк, у кото
рых проверяемый столбец имеет значение NULL, так как результат сравне
ния с NULL имеет значение НЕ ОПРЕДЕЛЕНО (UNKNOWN).
Запрос 2.23. Вывести данные о сотрудниках, которые работают
в отделах с определенными номерами, и о сотрудниках, у
которых не задан номер отдела (содержит ошибку)
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id IN (40, 10, 110, NULL);
employee_id I first_name | la s t__name | department_id |
------------ +----------- +--------- +-------------- +
200 1 Jennifer
203 1 Susan
205 1 Shelley
206|William
1 Whalen
IMavris
1 Higgins
IGietz
1
1
|
1
10|
40 1
110 1
110 1
Глава 2. Структура оператора SELECT и формирование условий выбора
При этом в таблице Employees есть строки, у которых столбец department_
id имеет значение NULL. Правильным решением этой задачи является
Запрос 2.27.
Рассмотрим особенности использования оператора NOT IN ().
Запрос 2.24. Вывести данные о сотрудниках, которые не
работают в отделах с определенными номерами
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id NOT IN (30,50,60,80,90,100,NULL);
Результат выполнения этого запроса не будет содержать строк. Это произой
дет, потому что оператор
X NOT IN (Al, А2,
AN)
эквивалентен выражению
XOAl AND XOA2 AND ...... XOAN
Если одно из AI будет иметь значение NULL, то результат этого выражения
будет иметь значение НЕ ОПРЕДЕЛЕНО (UNKNOWN).
2.3.4. Выражение IS NULL
Выражение IS NULL используется для определения строк с
неопределенным значением заданного столбца.
Синтаксис:
{имя столбца} IS NULL
Это выражение возвращает значение TRUE, если значение проверяемого
столбца будет NULL.
77
PostgreSQL: SQL + PL/pgSQL
........................................... Ф PostgreSQL
Запрос 2.25. Получить данные о сотрудниках сотрудников,
для которых неизвестен номер руководителя
SELECT employee__id, first_name, last_name, department_id
FROM Employees
WHERE manager_id IS NULL;
employee_idI first_nameIlast_name|department_id|
------------ +----------- +---------- +--------------- +
1001 Steven
I King
I
901
•
Запрос 2.26. Вывести данные о сотрудниках, у которых
не задан номер отдела
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id IS NULL;
employee_id| first_name|last_nameIdepartment_id|
------------ +----------- +---------- +--------------- +
178|Kimberely
|Grant
|
I
Запрос 2.27. Вывести данные о сотрудниках, которые
работают в отделах с определенными номерами, и о
сотрудниках, у которых не задан номер отдела
SELECT employee_id, first_name, last_name, department_id
FROM Employees
WHERE department_id IN (40, 10, 110)
OR department_id IS NULL;
employee_id I first_name I last_name I department_id I
----- +------------------- -+--------------- _ +---- --------------- +
178|Kimberely
200|Jennifer
203 1 Susan
205|Shelley
2061 William
1 Grant
1 Whalen
1Mavris
1 Higgins
1Gietz
1
1
1
1
1
10
40
110
110
1
1
1
1
1
2.4. Вычисляемые столбцы
В предложении SELECT, кроме списка столбцов таблиц, участвующих в
запросе, могут присутствовать вычисляемые столбцы, которые представ78
Глава 2. Структура оператора SELECT и формирование условий выбора
ляют собой выражения, состоящие из имен столбцов, констант, функций и
арифметических операций.
Значению вычисляемого столбца можно присвоить имя. Для этого использу
ется следующая конструкция:^
{Выражение} As {псевдоним}
При вычислении выражения, содержащего несколько
арифметических операций, сначала выполняются операции
умножения и деления, которые имеют одинаковый
приоритет, потом сложения и вычитания, которые также
относительно друг друга имеют одинаковый приоритет.
Если операции в выражении имеют одинаковый приоритет, то их выполне
ние производится слева направо.
Рассмотрим примеры использования вычисляемых столбцов. Значение
столбца commission_pct в таблице Employees обозначает надбавку к зарпла
те как часть от заработной платы.
Общая зарплата с учетом комиссионных может быть вычислена с использо
ванием выражения:^
salary*(l+commission_pct) As Total_Salary
Следует иметь в виду то, что у некоторых сотрудников значение столбца
commission_pct равно NULL. А если один из элементов арифметического
выражения равен NULL, то и все выражение будет иметь значение NULL.
Данную проблему можно решить, используя специальные функции, которые
мы рассмотрим позже.
Запрос 2.28. Вывести данные и сумму комиссионных для
тех сотрудников, у которых значение комиссионных > 3500
SELECT employee_id, first_name, last_name, department_id,
commission_pct*salary AS commission
FROM Employees
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
WHERE commission_pct IS NOT NULL
AND commission_pct*salary>3500;
employee id I first_nameI last name I department_id|commission|
---- —------- 1------------ 1----------- 1---------------- 1------------ 11 Russell
1
1 Partners 1
1Errazuriz1
145|John
14б|Karen
147|Alberto
80 1
80 1
80|
5600.0000
4050.0000
3600.0000
Запрос 2.29. Вывести данные о продажах, в которых
сумма одной продажи превышала 400000
SELECT product_id, order_id, item_id, quantity, unit_price,
quantity*unit_price
FROM Order_iterns
WHERE quantity*unit__price > 400000;
product_ id|order_id|item_id|quantity Iunit_price|?column?
------------------- +-----26|
28 1
28 1
27 1
-----+--20 1
79|
811
24 1
|
----------------------+------------------ +
11
11
11
61
105
145
133
99
1
1
1
1
5500.001577500.00|
3250.001471250.00 |
3250.001432250.001
4140.001409860.001
2.5. Операция конкатенации
Операция конкатенации (слияния) используется для того,
чтобы объединить при выводе данных два или несколько
столбцов или литералов в один столбец.
Синтаксис:
{столбеці/литерал 1}
| |
{столбец2 / литерал2}... Аз {псевдоним}
Операцию конкатенации можно применять для строк, чисел и дат. Даты и
числа при слиянии конвертируются в строковые значения.
Запрос 2.30. Вывести данные о статусе заказов,
оформленных сотрудником 165
80
Глава 2. Структура оператора SELECT и формирование условий выбора
SELECT 'Order '||order_id||' from
AS Order_Status
FROM Orders
WHERE salesman_id =165;
'||order_date||' is '|(status
I
order_statys
-------------------------------------- +
Order
Order
66 from
67 from
2020-01-23 is
2018-10-22 is
Pending!
Shipped!
Для выполнения слияния можно использовать функцию
СОМСАТ, которая имеет следующий синтаксис:
CONCAT({столбеці/литерал 1}, {столбец2/литерал2},...)
Запрос 2.31. Вывод данных о статусе заказов, оформленных
сотрудником 165, с использованием функции CONCAT
SELECT CONCAT('Order ',order_id,' from ',order_date,' is ',status)
AS Order_Statys
FROM Orders
WHERE salesman_id =165;
Оператор конкатенации можно использовать в шаблоне оператора LIKE.
Запрос 2.32. Вывести значения столбца street_address
в таблице Locations, которые содержат название города
(city)
SELECT city, street_address
FROM Locations
WHERE street_address LIKE '%'|I city I I'%';
city
|street_address
I
------ +------------------------------------------- +
Oxford|Magdalen Centre,
The Oxford Science Park!
2.6. Условные выражения
Довольно часто значение столбца, которое должен вернуть SQL-запрос, за
висит от условий, которые нужно проверять для каждой строки. Для реа
лизации подобного выбора используются выражение CASE. Используя это
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
выражение, можно реализовать условную логику if-then-else в операторе
SELECT. Можно использовать два варианта выражения CASE:
1. Выражение CASE с параметром;
2. Выражение CASE с условием.
2.6.1. Выражение CASE с параметром
Выражение CASE с параметром имеет следующий синтаксис:
CASE {параметр}
WHEN {значениеі} THEN {результаті}
[WHEN {значение2} THEN {результат2}
WHEN {значениеИ} THEN {результат^ ]
[ELSE {результат_ЕЬ8Е}]
END
Выражение CASE выполняется следующим образом:
• Сравнивается значение {параметр} со значениями {значение і} в пред
ложениях WHEN, и возвращается результат {результат!} первого пред
ложения, в котором будет выполнено условие {параметр} = {значение!}.
• Если ни в одном из предложений WHEN не выполняется условие
{параметр} = {значение!}, то возвращается значение {результат_
ELSE}. Если предложение ELSE отсутствует, то выражение CASE вер
нет результат NULL.
• Возвращаемый результат может быть значением или выражением. Вы
ражения {параметр} и {значение} должны иметь один и тот же тип дан
ных. Все возвращаемые значения должны иметь одинаковый тип данных.
ПРИМЕЧАНИЕ. Выражение CASE может содержать другие вы
ражения CASE.
Запрос 2.33. Вывести данные о сотрудниках и размере
их премии, которая задана в виде фиксированной суммы,
размер которой зависит от отдела, где работает сотрудник
Глава 2. Структура оператора SELECT и формирование условий выбора
SELECT department_id, employee_id, first_name, last_name, job_id,
salary,
CASE department_id
WHEN 10 THEN 1000
WHEN 30 THEN 1200
WHEN 40 THEN 1500
ELSE 500
END AS bonus
FROM Employees
WHERE department_id IN (10,20,30,40)
ORDER BY department_id;
department_id|employee_id | first_name|last_name |job_id
|salary
|bonus|
---------------------- +------- ----------- +---------------- -+---------------- +—--------- + ------------- +. ------- +
10|
20|
20|
30 1
30|
30|
30|
30|
30|
40|
2001 Jennifer
201|Michael
202|Pat
1171Sigal
118|Guy
1191 Karen
114|Den
1151 Alexander
1161Shelli
2031 Susan
1 Whalen
|AD ASST |
1Hartstein |MK MAN |
|MK REP |
IFay
1 Tobias
1 PU CLERK|
1Himuro
1 PU CLERK|
1Colmenares 1 PU CLERK|
1Raphaely |PU MAN |
1 Khoo
1 PU CLERK|
1 Baida
|PU CLERK|
|HR__REP |
IMavris
4400.001
13000.001
6000.001
2800.001
2600.001
2500.001
11000.001
3100.001
2900.001
6500.001
10001
5001
5001
12001
12001
12001
12001
12001
12001
15001
Запрос 2.34. Вывести данные о сотрудниках и размере
их премии, которая задана как часть заработной платы,
размер которой зависит от отдела, где работает сотрудник
SELECT department_id, employee_id, first_name, last_name, job_id,
salary,
CASE department_id
WHEN 10 THEN 0.l*salary
WHEN 30 THEN 0.2*salary
WHEN 40 THEN 0.3*salary
END AS bonus
FROM Employees
WHERE department_id IN (10,20,30,40)
ORDER BY department_id;
department_id I employee_id | first_name I last_name |job_id
10|
20|
20|
30|
30|
200|Jennifer
2011 Michael
202|Pat
117|Sigal
118|Guy
IWhalen
|Hartstein
IFay
I Tobias
|Himuro
|salary
I
|bonus
|AD ASST | 4400.001 440.0001
|MK MAN 113000.001
|
IMK REP | 6000.001
I
|PU CLERK| 2800.001 560.0001
|PU_CLERK| 2600.001 520.0001
83
PostgreSQL: SQL + PL/pgSQL
30|
30|
30|
30|
40|
119|Karen
114|Den
115(Alexander
116|Shelli
203|Susan
PostgreSQL
(ColmenaresIPU_CLERK| 2500.001 500.0001
IRaphaely |PU_MAN |11000.00|2200.000|
|Khoo
|PU_CLERK| 3100.001 620.0001
|Baida
|PU_CLERK| 2900.001 580.0001
IMavris
|HR_REP ( 6500.00|1950.000|
В этом примере отсутствует предложение ELSE, поэтому размер премии для
сотрудников отделов, номеров которых нет в предложении WHERE, имеет
значение NULL.
Размер премии может зависеть как от отдела, в котором работает сотрудник,
так и от его должности. Для решения этой задачи необходимо использовать
вложенные выражения CASE.
Запрос 2.35. Вывести данные о сотрудниках и размере их
премии, которая зависит как от отдела, где работает
сотрудник, так и от его должности
SELECT department_id, employee_id, first_name, last_name, job_id,
salary,
CASE department_id
WHEN 20 THEN CASE job_id
WHEN 'MK_MAN' THEN 2000
WHEN 'MK_REP' THEN 1000
End
WHEN 30 THEN CASE job_id
WHEN 'PU MAN' THEN 3000
ELSE 1500
End
END AS bonus
FROM Employees
WHERE department_id IN (10,20,30)
ORDER BY department_id;
department_id| employee_id|first_name | last_name |job_id
.—+---
10|
20|
20|
30|
30|
30 (
30(
30]
30|
(salary
I bonus|
------ +--------- --------- .+— -----+ ------- +. ---- +
200|Jennifer
201(Michael
202(Pat
117|Sigal
114|Den
1191 Karen
118|Guy
115|Alexander
116|Shelli
|Whalen
|AD ASST ( 4400.00|
IHartstein |MK MAN | 13000.00|
|MK REP | 6000.001
IFay
1 Tobias
|PU CLERK| 2800.001
(Raphaelу |PU MAN | 11000.001
IColmenares|PU CLERK| 2500.001
IHimuro
I PU CLERK| 2600.00|
I Khoo
|PU_ CLERK| 3100.001
I Baida
|PU_ CLERK| 2900.001
|
20001
10001
1500 |
30001
1500 |
15001
15001
15001
Глава 2. Структура оператора SELECT и формирование условий выбора
2.6.2. Выражение CASE с условием
Синтаксис:
CASE
WHEN {условие!.} THEN {результаті}
[WHEN {условие2} THEN {результат2}
WHEN {условней} THEN {результати}]
[ELSE {результат_ЕЬЕЕ}]
END;
При использовании этой разновидности оператора CASE последовательно
поверяются значения условных выражений в предложениях WHEN, и воз
вращается результат из первого предложения, в котором это выражение бу
дет иметь значение TRUE.
Запрос 2.36. Вывести данные о сотрудниках и размере их
премии, которая зависит от зарплаты сотрудника
SELECT department_id, employee_id, first_name, last_name, job_id,
salary,
CASE
WHEN salary > 10000 THEN 5000
WHEN salary > 7000 THEN 3000
WHEN salary > 5000 THEN 2000
ELSE 1000
END AS bonus
FROM Employees
WHERE department_id in (10,20,30,40)
ORDER BY department_id;
2.7. Сортировка
Результат выполнения оператора SELECT может быть
упорядочен по значению одного или нескольких столбцов.
Для этого служит предложение ORDER BY, которое имеет
следующий синтаксис:^
ORDER BY {имя столбца | номер столбца
[ASC|DESC]}
85
PostgreSQL: SQL + PL/pgSQL
Запрос 2.37. Вывести данные о сотрудниках отдела 30,
упорядочив их в порядке убывания зарплаты
SELECT employee_id, first_name, last_name, department_id, salary
FROM Employees
WHERE department_id =30
ORDER BY salary DESC;
employee_id | first_name | last_name |department_id|salary
|
------------ +----------- +----------- +-------------- +---------+
114|Den
115|Alexander
1161Shelli
117|Sigal
118|Guy
119|Karen
IRaphaely
|
I Khoo
|
I Baida
I
I Tobias
|
IHimuro
|
IColmenares|
30|11000.00|
30| 3100.001
30| 2900.001
30| 2800.001
30| 2600.001
30| 2500.001
Отсортировать результат можно по значениям нескольких столбцов.
Сначала строки упорядочиваются по значению первого столбца. Строки,
имеющие одинаковые значения первого столбца, упорядочиваются по зна
чению второго столбца и т.д. Для каждого столбца можно указать свой по
рядок сортировки.
Запрос 2.38. Вывести данные о сотрудниках отделов 10, 20,
30, расположив их в порядке возрастания номеров отделов,
где они работают. Данные о сотрудниках, которые работают
в одном отделе, вывести в порядке убывания зарплаты
SELECT employee_id, first_name, last_name, department_id, salary
FROM Employees
WHERE department_id in (10,20,30)
ORDER BY department_id, salary DESC;
employee_id | first_name I last_name I department_id I salary
------------ +----------
I
--------- +----- ------- +.------- +
200|Jennifer
201|Michael
202|Pat
114|Den
115|Alexander
1161Shelli
117|Sigal
118|Guy
119|Karen
86
I Whalen
|
IHartstein |
1 Fay
|
IRaphaely
|
I Khoo
|
I Baida
|
[Tobias
|
IHimuro
|
IColmenaresI
10| 4400.00|
20Ц3000.00І
20 | 6000.00|
30Ц1000.00І
30 | 3100.001
30 | 2900.00|
30 | 2800.001
30 | 2600.00|
30 | 2500.001
Глава 2. Структура оператора SELECT и формирование условий выбора
Можно сортировать строки по столбцам, не указанным в предложении
SELECT.
Запрос 2.39. Вывести данные о сотрудниках, которые
работают в отделе 30, расположив их в порядке убывания
рейтинга
SELECT employee_id, first_name, last_name, depar tment_id, salary
FROM Employees
WHERE department_id = 30
ORDER BY rating_e DESC;
employee_id I first_name | last_name
I department_id | salary
|
-------------------- +------------------- +-------------------+------------------------ +--------------- +
1151 Alexander I Khoo
|
117ISigal
I Tobias
|
1181 Guy
IHimuro
|
1191 Karen
IColmenaresI
1161 Shell!
I Baida
|
1141 Den
IRaphaely
|
30| 3100.001
30 1 2800.001
30 1 2600.001
30 1 2500.001
30 1 2900.001
30 1 11000.00 1
2.8. Ограничение количества строк в результате
выполнения запроса
Используя предложения LIMIT N и OFFSET М, можно ограничить количе
ство строк в результате выполнения запроса. Если оператор SELECT содер
жит предложение LIMIT N, то будет выводиться не более первых N строк
результата. При использовании LIMIT N можно использовать OFFSET М.
В этом случае сначала пропускаются первые М строк результата, после это
го выводится N следующих строк.
Запрос 2.40. Вывести данные о 7 сотрудниках с
наибольшими значениями заработной платы
SELECT employee_id, first_name, last_name, department_id, salary
FROM Employees
ORDER BY salary desc
LIMIT 7;
employee_idI first_name|last_name|department_idI salary
|
------------ +
+
+-------------- +-------- +
lOOISteven-----|King------ I
101|Neena
IKochhar
|
90|24000.00|
90117000.00|
87
PostgreSQL: SQL + PL/pgSQL
102|Lex
145|John
146|Karen
201|Michael
108|Nancy
PostgreSQL
I De Haan
|
IRussell
|
I Partners I
IHartstein|
(Greenberg|
90 117000.00|
80|14000.00|
80Ц3500.001
20Ц3000.001
100Ц2000.00|
Запрос 2.41. Вывести данные о сотрудниках отдела 30,
зарплата которых занимает места с 3 по 5
SELECT employee_id, first_name, last_name, department_id, salary
FROM Employees
WHERE department_id =30
ORDER BY salary DESC
LIMIT 3 OFFSET 2;
employee_id I first_name | last_name | department_id | salary |
------------+----------- +---------- +-------------- +------- +
116|Shelli
117|Sigal
118 1 Guy
88
I Baida
(Tobias
IHimuro
|
|
|
30(2900.00|
3012800.001
3012600.001
Глава 2. Структура оператора SELECT и формирование условий выбора
Задачи для самостоятельного решения:
Задача 2.1. Вывести locationjd городов, в которых расположены
отделы фирмы.
Задача 2.2. Вывести данные о товарах, у которых столбец
гаИпд_р имеет значение 3 или 4, а рисе > 700.
Задача 2.3. Вывести данные о договорах сотрудников 155 и 160,
оформленных 02.11.2019.
Задача 2.4. Вывести 1ас^пате сотрудников, у которых /аз^пате
содержит две и более буквы е.
і
і
} Задача 2.5. Вывести названия городов, которые расположены в |
! США (countryjd ='US') или Канаде (countryjd ='СА') и почтовый I
; индекс которых заканчивается цифрой 2.
і
L- — -- ——
Г
!
;
]
!
J
і
———____________ —————————————————————————————————————————J
Задача 2.6. Вывести значения столбцов employeejd,
departmentjd, first_name, last_name, salary, job_id сотрудников,
которые получают зарплату > 1100, но не являются менеджерами. Менеджерами являются те сотрудники, у которых столбец
jobjd содержит подстроку 'MAN'.
'
;
;
!
I
I
L —— — — — — — — — ___ ——____— — — _. — — — — — — — — — — __ — _ — — -. — — — — — — — — — — — — — — — — — — — — — — — _ —-I
Задача
2.7.
Вывести
first_name,
last_name,
jobjd
и суммарную зарплату за год в следующем виде:
Michael Hartstein занимает должность MK_MAN, и зарплата за
год составляет 156000.
Задача 2.8. Вывести значения столбцов employeejd, department
id, first_name, last_name, salary, jobjd и столбец level, который
должен принимать следующие значения: low - если зарплата
сотрудника <= 5000; midi - если зарплата сотрудника >5000, но
<=10000; high - если зарплата сотрудника >10000.
89
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Задача 2.9. Вывести данные о 3 сотрудниках с наибольшим
размером премии (bonus), которую они должны получить. Раз
мер премии сотрудника рассчитывается по формуле bonus =
0.1 *salary*rating_e.
Задача 2.10. Вывести данные о сотрудниках, зарплата которых с
учетом комиссионных лежит в диапазоне от 5000 до 7000.
Глава 3.
ТИПЫ
ДАННЫХ
ВСТРОЕННЫЕ
И
ФУНКЦИИ
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Каждый столбец таблицы реляционной базы данных
должен содержать данные только одного типа. Тип данных
определяет значения, которые могут быть присвоены
элементам данного столбца, и операции, в которых могут
участвовать элементы данного столбца.
PostgreSQL имеет широкий набор встроенных типов данных. Пользователи
могут создавать и использовать собственные типы.
Количество типов данных достаточно велико, и в этой главе будут рассмо
трены основные встроенные типы, которые далее будут использоваться в
примерах. При изучении каждого типа будут приведены основные встроен
ные функции, аргументы которых могут иметь рассматриваемый тип.
Для вывода результатов выполнения функций и выражений используется
оператор SELECT. Согласно стандарту, данный оператор должен обязатель
но содержать предложение FROM, но в PostgreSQL данное требование не
является обязательным.
3.1. Числовые типы
Группа числовых типов включает:
• Целочисленные типы;
• Вещественные типы формата с фиксированной точкой;
• Вещественные типы формата с плавающей точкой;
• Последовательные типы.
Глава 3. Типы данных и встроенные функции
• Целочисленными типами являются: smallint, integer, bigint. Данные этих
типов могут принимать только целочисленные значения, которые долж
ны входить в заданный диапазон.
• К вещественным типам формата с фиксированной точкой относят
ся: питегіс(п,т) и decimal(n,m).
Эти типы являются абсолютно идентичными по своим характеристикам.
Они имеют два параметра: п - общее число десятичных разрядов в записи
числа пт- число десятичных разрядов справа от десятичной точки. Данные
этого типа обладают высокой точностью, но операции с такими данными вы
полняются медленнее по сравнению с другими числовыми типами.
• Вещественным типами формата с плавающей точкой являются: real
и double precision. Эти типы могут использоваться при работе с данными,
обозначающими значение веса, времени и т.д.
• Последними из числовых типов являются последовательные: smallserial,
serial, bigserial. Это особые типы, которые присваиваются столбцам,
являющимся суррогатными ключами таблиц. Характеристики числовых
типов - размер и диапазон значений - приведены в таб. 3.1.
Таблица 3.1. Характеристики числовых типов данных
Имя
Размер
Диапазон значений
smallint
2 байта
От -32768 до +32767
integer
4 байта
От -2147483648 до +2147483647
bigint
8 байт
От -9223372036854775808 до
+9223372036854775807
numeric(n,m)
Переменный
До 131072 цифр до десятичной точки; до
16383 цифр после десятичной точки
decimal(n,m)
Переменный
До 131072 цифр до десятичной точки; до
16383 цифр после десятичной точки
real
4 байта
От 1Е-37 до 1Е+37 с точностью до 6 десятич
ных разрядов
double precision
8 байт
От 1Е-307 до 1Е+308 с точностью до 15 деся
тичных разрядов
PoStgreSQL: SQL + PL/pgSQL
Ш PostgreSQL
Имя
Размер
Диапазон значений
smallserial
2 байта
От 1 до 32767
serial
4 байта
От 1 до 2147483647
bigserial
8 байт
От 1 до 9223372036854775807
В таблице 3.2 приведены основные функции, которые можно использовать
при обработке данных числового типа.
Таблица 3.2. Основные функции для работы с данными числовых типов
Функция
Описание
ROUND(x,n)
Выполняет округление числа х до ближайшего числа с за
данной точностью п
TRUNC(x, n)
Усекает (отбрасывает) значащие цифры числа х справа без
округления, с заданной точностью п
DIV(n,m)
Целая часть результата при делении пнат
MOD(n,m)
Возвращает остаток от деления пнат
POWER(x,n)
Возводит число х в степень п
SQRT(x)
Возвращает квадратный корень от числа х
EXP(n)
Возвращает значение экспоненты (результат возведения
е=2,718281 в степень п)
LN(n)
Вычисляет натуральный логарифм от числа п
LOG(n,m)
Производит вычисление логарифма числа и по основанию т
FACTORIAL(n)
Факториал числа п
RANDOM()
Возвращает случайное значение в диапазоне 0.0 <= х < 1.0
94
Глава 3. Типы данных и встроенные функции
Задает начальное значение для последующих random ()
вызовов; аргумент у должен быть в диапазоне от -1.0 до 1.0
включительно
SETSEED(Y)
GENERATE SERIES
(start,stop[,step])
Генерирует ряд значений от start до stop с шагом, равным
step. Step по умолчанию равен 1; start, stop, step могут иметь
тип integer или numeric
Рассмотрим примеры использования этих функций.
Запрос 3.1. Пример использования функции ROUND
SELECT ROUND(246.67), ROUND(246.67,1), ROUND(246.67,-1);
round I round I round|
----- +----- +----- +
2471246.71
2501
Запрос 3.2. Вывести значение зарплаты сотрудников из
отдела 60, округленные до 1000
SELECT employee_id, first_name, last_name, department_id, salary,
ROUND(salary,-3)
FROM Employees
WHERE department_id=60;
employee_id I first_name I last_name I department_id I salary I round I
----------- +----------- +--------- +-------------- +------- +----- +
103|Alexander
104|Bruce
105|David
106|Valli
107|Diana
IHunold
|
1 Ernst
|
(Austin
|
IPataballa1
(Lorentz
|
6019000.001
6016000.001
6014800.001
6014800.00 1
60 1 4200.001
90001
60001
5000 1
5000 1
4000 1
Запрос 3.3. Пример использования функции TRUNC
SELECT TRUNC(246.67), TRUNC(246.67,1), TRUNC(246.67,-1);
truncItruncItruncI
----- +----- +----- +
2461246.61
2401
95
PoStgreSQL: SQL + PL/pgSQL
^g PostgreSQL
Запрос 3.4. Пример использования функции DIV
SELECT DIV(5,2), DIV(6.5,1),DIV (6.5,2.1);
div I div I div I
21
6|
3|
Запрос 3.5. Пример использования функции MOD
SELECT mod(5,2), mod(6.5,1),mod(6.5,2.1);
mod I mod|mod|
--- +--- +--- +
Ц0.510.21
Запрос 3.6. Вывести данные о сотрудниках из отдела 60,
имеющих нечетный рейтинг
SELECT employee_id, first_name, last_name, department_id, rating_e
FROM Employees
WHERE department_id=60 and MOD(rating_e,2)=l;
employee_id| first_nameIlast_name|department_idIrating_e|
------------ 1-------- - —।---------- 1---------------- 1--------- 1-
103
104
105
107
I Alexander
I Bruce
I David
I Diana
IHunold
I Ernst
(Austin
I Lorentz
|
|
|
|
60|
60|
601
60|
31
31
51
31
Запрос 3.7. Вывести ту часть зарплаты сотрудников из
отдела 60, которая меньше 1000
SELECT employee_id, first_name, last_name, department_id, salary,
MOD(salary,1000)
FROM Employees
WHERE department_id=60;
employee _id I first _name | last_name | department_ id | salary |mod
I
------------+----------- +---------- +-------------- +------- +------ +
103|Alexander
104|Bruce
1051 David
106|Valli
107|Diana
IHunold
|
I Ernst
|
(Austin
|
IPataballa|
(Lorentz
|
6019000.001
0.001
6016000.001
0.001
6014800.001800.001
60|4800.00|800.00|
60(4200.001200.001
Глава 3. Типы данных и встроенные функции
Запрос 3.8. Пример использования функции POWER
SELECT POWER(2,2),POWER(9,0.5),POWER(10,-1);
power|power
I power I
----- +--------------------- +----- +
4.013.00000000000000001
0.1|
Вместо функции POWER можно использовать операцию возведения в
степень хЛа.
Запрос 3.9. Пример использования операции возведения в
степень
SELECT 2А2, 9А0.5, 10Л(-1);
?column?|?column?
|?column?I
--------- +--------------------- +--------- +
4.013.00000000000000001
0.1|
Загрос 3.10. Пример использования функции EXP (),LN(), FACTORIAL ()
SELECT EXP(2.0),LN(7.38906),FACTORIAL(4)
exp
I In
I factorial|
------------------- +------------------- +---------- +
7.3890560989306502 12.000000527 9521860|
24 |
В PostgreSQL можно использовать функцию GENERATE.
SERIES(), которая генерирует ряд значений между
заданными начальной и конечной точками.
Это может быть последовательность чисел или последовательность вре
менных значений. В запросе 3.11 эта функция совместно с функцией
ЯАКООМ() используется для моделирования подбрасываний игрального
кубика.
Запрос 3.11. Генерация последовательности случайных чисел
с равномерным распределением, имеющих значения от 1 до 6
97
PostgreSQL: SQL + PL/pgSQL
Postgr© SQL-
SELECT s.i, TRUNC(RANDOM()*6)+1 as rnd
FROM GENERATE_SERIES(1,6) s (і) ;
і IrndI
_+--- +
11 5.0 I
2Ц.0І
ЗЦ.0І
4 15.01
516.01
615.01
3.2. Символьные типы
Символьными типами являются:
• Строки фиксированной длины;
• Строки переменной длины;
•
Строки неограниченной длины.
Тип строки фиксированной длины имеет обозначение
сКагасІег(п), где п - максимальное число символов, которое
может содержать строка. Для обозначения этого типа обычно
используется псевдоним сИаг(п).
Если присваиваемое значение будет короче заявленной длины п, то осталь
ные разряды будут заполнены пробелами. Добавленные пробелы являются
семантически незначимыми и не учитываются при сравнении двух значе
ний, имеющих тип сЬагасГег(п).
Попытка присвоить значение, которое будет длиннее заявленной длины п,
приведет к возникновению ошибки.
Тип строки переменной длины имеет обозначение character
varying(n), где п - максимальное число символов, которое
может содержать строка. Для обозначения этого типа обычно
используется псевдоним varchar(n).
98
Глава 3. Типы данных и встроенные функции
Отличием от предыдущего типа является то, что при присвоении значения,
которое будет короче заявленной длины и, дополнение пробелами не произ
водится.
В PostgreSQL можно использовать тип строки неограниченной длины, кото
рый имеет обозначение text. Этого типа нет в стандарте SQL, но он исполь
зуется во многих современных СУБД.
Конечные пробелы являются семантически значимыми в значениях, имею
щих типы varchar(n) и text.
Для работы с данными, имеющими строковые типы, можно использовать
большое количество встроенных функций, некоторые из них приведены в
таблице 3.3.
Таблица 3.3. Основные функции для работы с данными символьных типов
Функция
Описание
CONCAT(strl, str2...stri)
Выполняет конкатенацию строк strl, str2...stri
UPPER (str)
Осуществляет преобразование строки str в
верхний регистр
LOWER (str)
Осуществляет преобразование строки str в
нижний регистр
INITCAP (str)
Осуществляет преобразование начальных букв
каждого слова в верхний регистр
LPAD(str, n [, char])
Возвращает строку str, дополненную слева
символом char, до достижения строкой дли
ны в п символов. Если char отсутствует, то
добавляет пробелы
RPAD(str, n [, char])
Возвращает строку str, дополненную справа
символом char, до достижения строкой дли
ны в п символов. Если char отсутствует, то
добавляет пробелы
LTRIM(str [, set])
Удаляет все символы с начала строки до перво
го символа, которого нет в наборе символов
set. Если set отсутствует, то удаляет пробелы
99
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
RTRIM(str [, set])
Удаляет символы, начиная от конца строки
до первого символа, которого нет в наборе
символов set. Если set отсутствует, то удаляет
пробелы
LENGTH(str)
Возвращает длину строки str в символах
Осуществляет поиск образца search str в стро
REPLACE(str, search str [,
ке str и каждое найденное вхождение заменяет
replacestr])
на replace str
SUBSTRING (str[FROM n]
[FOR m] )
Возвращает фрагмент строки str, начиная с
символа п длиной т
STRPOS(str, search str)
Возвращает позицию первого вхождения стро
ки search str в строку str
CHR(n)
Возвращает символ по его коду
Запрос 3.12. Вывести название товара, используя
различные функции преобразования регистра
SELECT
UPPER(Product_name) As UPPER,
LOWER(Product_name) As LOWER,
INITCAP(Product_name)As INITCAP
FROM Products
WHERE product_id = 50;
I lower
upper
Iinitcap
I
-----------------------------------------______-]--------- ----- ---------------------- —----- —------------ 1--------------------------------- -------------------------- 1-
MSI 24" OPTIX MAG241C|msi 24" optix mag241c|Msi 24" Optix Mag241c|
Довольно часто столбец, имеющий символьный тип, содержит значения в
различных регистрах. Например, столбец firstname может содержать как
значение 'DAVID', так и значение 'David'. В этом случае запрос, содержащий
условие выбора
first name =
100
’DAVID’,
Глава 3. Типы данных и встроенные функции
или
first name
’David'
выведет только часть необходимых данных. Эту проблему можно решить,
используя функции преобразования регистра.
Запрос 3.13. Вывести данные о сотрудниках, у которых
столбец first_name имеет значение 'DAVID', или 'David',
или 'david'
SELECT employee_id, first_name, last_name, depar tment_id, salary
FROM Employees
WHERE UPPER (first_name) = ' DAVID ' ;
employee_id I first_name | last_name I department_id I salary |
-------------------- +-------------------+----------------- +------------------------ +------------- +
151|David
165 I David
105 I DAVID
I Bernstein I
I Lee
I
(Austin
I
80 I 9500.00 I
80 I 6800.00 I
6014800.001
Функции 1РАО() и ПРАВО можно использовать для
отображения результата выполнения запроса в виде, который
более удобен для восприятия.
Запрос 3.14. Вывод данных о зарплате сотрудников,
зарплата которых больше 12000
SELECT first_name| | '
' | |last_name | |
|| salary || ' dollars.' AS Pay
FROM Employees
WHERE salary>12000;
' has a monthly salary of '
pay
I
--------------------------------------------------------------- ;—+
Steven King has a monthly salary of 24000.00 dollars.
|
Neena Kochhar has a monthly salary of 17000.00 dollars.
|
Lex De Haan has a monthly salary of 17000.00 dollars.
|
John Russell has a monthly salary of 14000.00 dollars.
|
Karen Partners has a monthly salary of 13500.00 dollars.
|
Michael Hartstein has a monthly salary of 13000.00 dollars.!
101
PostgreSQL. SQL + PL/pgSQL
PostgreSQL
Запрос 3.15. Вывод данных о зарплате сотрудников,
зарплата которых больше 12000, с использованием
функций ЬРАО()и ИРАО()
SELECT RPAD (first_name ||' '||last_name,20) ||
' has a monthly salary of ' || LPAD(TO_CHAR(salary,’99999D99'),9)
|| ' dollars.' AS Pay
FROM Employees
WHERE salary>12000;
pay
I
--------------------------------------------------------------------- +
Steven King
Neena Kochhar
Lex De Haan
John Russell
Karen Partners
Michael Hartstein
has
has
has
has
has
has
a
a
a
a
a
a
monthly
monthly
monthly
monthly
monthly
monthly
salary
salary
salary
salary
salary
salary
of
of
of
of
of
of
24000,00
17000,00
17000,00
14000,00
13500,00
13000,00
dollars.1
dollars.1
dollars.1
dollars.1
dollars.1
dollars.1
В этом примере следует обратить внимание на следующее:
• для того чтобы данные были выровнены по левому краю, была использо
вана функция RPAD();
• для выравнивания по правому краю использована функция LPAD().
Эти функции можно применять для данных только символьного типа,
поэтому значение столбца salary, который имеет тип numeric, было приве
дено к символьному типу с помощью функции TO_CHAR().
Запрос 3.16. Подсчитать, сколько раз символ 'е’
встречается в столбце first_name, рассматривать только
те значения, которые содержат символ 'е'
SELECT firs t_name, REPLACE (firs t_name, ' e ',•') ,
LENGTH (REPLACE (first_name,' e ','')) )
FROM Employees
WHERE first_name LIKE'%e%';
first_name | replace
I ?column? |
------------+----------+---------- +
Steven
Neena
Lex
Alexander
Bruce
3
1Stvn
1 Nna
ILx
lAlxandr
IBruc
I
1
1
1
1
2 1
21
11
21
11
(LENGTH (firs t_name) -
Глава 3. Типы данных и встроенные функции
Для того чтобы определить количество символов 'е' в строке, нужно из ис
ходной длины строки вычесть длину строки без символов 'е'. Для удаления
символов используется функция КЕРЬАСЕ(),
Рассмотрим более подробно функцию STRPOS(str, веагсЬ вІг), которая
часто используется при работе с символьными данными.
Функция STRPOS(str, search_str) возвращает номер
позиции в строке str, начиная с которой строка search str
входит в строку str. Если вхождений не найдено, то функция
возвращает значение 0.
Запрос 3.17. Использование функции STRPOS() для
нахождения позиции первого пробела в названии товара
SELECT product_name,STRPOS(product_name ,'
FROM Products;
')
product_name
Istrpos|
Corsair K70 RGB MK.2 Cherry MX Red (CH-9109010-RU)
Logitech G810 Orion Spectrum (920-007750)
Logitech G910 Orion Spectrum RGB (920-008019)
Samsung 24" S24F350FHI
G.Skill TridentZ RGB
Samsung 32" C32JG50QQI
8
9
9
8
8
8
Запрос 3.18. Извлечь первое слово в названии товара
SELECT SUBSTRING( product_name FOR (STRPOS(product_name,'
FROM Products;
substring
I
Corsair
Logitech
Logitech
Samsung
G.Skill
I
I
I
' )-l))
|
I
Используя функцию STRPOSO, можно осуществлять поиск по части строч
ного значения.
PostgreSQL: SQL + PL/pgSQL
---------- w PostgreSQL..
Запрос 3.19. Вывести названия товаров,
слово Core
в которых есть
SELECT product_name
FROM Products
WHERE STRPOS(product_name,'Core')>0;
product_name
I
---------------------------------------------------------------------- +
Intel
Intel
Intel
Intel
Intel
Intel
Core
i7 10700F OEM Comet Lake LGA1200 (CM8070104282329)
|
Core
i7 10700K BOX Comet Lake LGA1200 (BX8070110700K)
|
Core
i7 10700K OEM Comet Lake LGA1200 (CM8070104282436)
|
Core
i5 10600KF OEM Comet Lake LGA1200 (CM8070104282136)|
Core
i3 10100F OEM Comet Lake LGA1200 (CM8070104291318)
|
Core i7 9700F OEM Coffee Lake Refresh 1151v2
I
3.3. Типы даты и времени
PostgreSQL поддерживает все типы данных для представления даты и вре
мени, предусмотренные стандартом SQL. Характеристики этих типов при
ведены в таб. 3.4. Даты отображаются в соответствии с григорианским ка
лендарем, даже за годы до того, как этот календарь был введен.
При вводе значения этих типов заключаются в кавычки и формально пред
ставляют собой строковые значения. В процессе выполнения операторов
SQL эти значения приводятся к формату даты и времени. Это может осу
ществляться неявным образом или с использованием функций преобразо
вания формата. Использование функций преобразования формата является
более предпочтительным вариантом, позволяющим избежать многих оши
бок и недоразумений.
Таблица 3.4. Характеристики типов даты и времени
TIME
sEj
Имя
Размер
Описание
Диапазон значений
DATE
4 байта
Дата без указания времени
От 4713 до н.э. до
5874897 н.э.
8 байт
Время суток без указания
часового пояса, с точно
стью р после точки в се
кундах
[
(р)
]
От 00.00.00.000000 до
24.00.00.000000
Глава 3. Типы данных и встроенные функции
Имя
TIME [ (р) ] WITH
TIME ZONE
Размер
Описание
8 байт
Время суток с указанием
часового пояса, с точно
стью р после точки в се
кундах
Диапазон значений
От 00.00.00.000000
+1559 до
24.00.00.000000-1559
8 байт
Дата и время суток без
указания часового пояса, с
точностью р после точки в
секундах
От 4713 до н.э. до
294276 н.э.
TIMESTAMP [ (p) ]
WITH TIME ZONE
8 байт
Дата и время суток с ука
занием часового пояса, с
точностью р после точки в
секундах
От 4713 до н.э. до
294276 н.э.
TSTZRANGE
16 байт
Диапазон дат с подтипом
timestamp with time zone
От 4713 до н.э. до
294276 н.э
INTERVAL [FIELDS]
[ ( p) ]
16 байт
Временной интервал
От -178000000 лет до
+178000000 лет
TIMESTAMP [
(p) ]
Для работы с данными, имеющими тип даты и времени, можно использовать
большое количество встроенных функций. В таблице 3.5 приведены основ
ные функции, которые можно использовать при работе с данными этих типов.
Таблица 3.5. Основные функции для работы с данными типов даты и
времени
Функция
Описание и пример
AGE(X,Y)
Возвращает разницу между датами (Х-Y) в виде интерва
ла в годах, месяцах, днях, часах и т.д.
AGE( '2023-03-08',
AGE([timestamp ]X)
'2011-06-14') — 11 years 8 mens 24 days
Возвращает разницу между текущей датой и датой X в
виде интервала в годах, месяцах, днях, часах и т.д.
AGE( timestamp '2023-03-08') —1 mon 17 days
PostgreSQL. SQL + PL/pgSQL
PostgreSQL
Возвращает текущую дату
CURRENT_DATE
CURRENT_DATE - 25-04-2023
CURRENT_TIME
Возвращает текущее время суток с указанием часового
пояса
CURRENT_TIME - 13:43:14 +0300
CURRENT_TIMESTAMP
Возвращает текущую дату и время с указанием часового
пояса
CURRENT_TIMESTAMP - 25-04-2023 13:49:30.057 +0300
NOW ( )
Возвращает текущую дату и время с указанием часового
пояса
NOW() - 26-04-2023 14:03:04.695 +0300
Обрезает значение X до заданной точности М (second;
minute; hour; day; dow; month; year)
DATE_TRUNC(M,X)
SELECT CURRENT_DATE, DATE_TRUNC('MONTH', CURRENT_DATE).25-04-2023101-04-2023 00:00:00.000 +0300
Извлекает заданную часть M (second; minute; hour;
day; dow; month; year) из значения X
EXTRACT(M FROM X)
SELECT CURRENT_DATE, EXTRACT('MONTH' FROM CURRENT_DATE) 25-04-20231
JUSTIFY_
INTERVAL(INTERVAL)
4|
Преобразует значение interval в корректный формат даты и
времени TIMESTAMP
SELECT JUSTIFY_INTERVAL(INTERVAL'5000 hour 15
minute') — 6 mens 28 days 08:15:00
Стандарт ISO 8601 рекомендует использовать для вывода данных типа date
формат 'yyyy-mm-dd' (год-месяц-день), но PostgreSQL позволяет использо
вать и другие форматы. При настройке соединения postgres (рис. 1.8) был
установлен формат 'dd-mm-yyyy' (день-месяц-год).
Значения, имеющие типы даты и времени, могут участвовать в арифметиче
ских операциях, но с некоторыми ограничениями. Например, разница между
двумя датами равна количеству дней, прошедших между этими датами, но
нельзя непосредственно складывать значения, имеющие тип Date.
Глава 3. Типы данных и встроенные функции
Прибавление целого значения п к значению типа Date эквивалентно при
бавлению п дней к дате. Если в выражении участвует строка, содержащая
значение даты или времени, то ее рекомендуется преобразовать к значению
соответствующего типа, используя операцию приведения типа. Эта опера
ция осуществляется путем размещения символа двойного двоеточия (::) и
имени типа, к которому нужно привести строковое значение, например
'05-4-2023'::date.
Рассмотрим примеры, в которых значения, имеющие тип даты и времени,
участвуют в арифметических выражениях.
Запрос 3.20. Вывод заданного значения даты, увеличенного
на 45 дней
SELECT '05-4-2023'::DATE + 45 as nev_date;
nev_date
I
----------- +
20-05-20231
Запрос 3.21. Вывод значения текущего времени, увеличенного
на 1 час и 10 минут
SELECT CURRENT_TIME, CURRENT_TIME + INTERVAL '1 hour 10 minute' AS
new_time;
current_time
|new_time
I
--------------- +--------------- +
16:06:04 +0300117 :16 : 04 +0300 1
В этом запросе содержится значение, имеющее тип INTERVAL. Рас
смотрим синтаксис и правила использования этого типа данных.
3.3.1. Тип INTERVAL
Синтаксис:
INTERVAL [FIELDS][( р)]
где:
•
FIELDS — значение интервала;
• р — точность после точки в секундах.
PostgreSQL: SQL + PL/pgSQL
Ф PostgreSQL
Значение интервала FIELDS представляет собой выражение x
quantity unit [quantity unit]...
. ....... .......
где:
• quantity — количество, которое может быть как положительным, так и
отрицательным;
•
unit — единица измерения: year, month, day, hour, minute, second, micro
second и некоторые другие единицы.
Рассмотрим примеры использования этого типа данных.
Запрос 3.22. Вывести значение даты,
интервала
SELECT
заданной в виде
INTERVAL'10 day 4 month 2 year' AS new_date;
new_date
I
-------------------------- 1-
2 years 4 mons 10 days!
Запрос 3.23. Вычесть из текущей даты значение,
заданное в виде интервала
SELECT (CURRENTDATE - INTERVAL'10 day 4 month 2 year') AS new_date;
new_date
I
------------------------- +
16-12-2020 00:00:00.0001
Запрос 3.24. Умножить значение времени,
виде интервала
заданное в
SELECT 10*INTERVAL'5 hour 15 minute' as new_time;
new_timeI
--------- +
52:30:001
В этом примере следует обратить внимание на то, что полученный результат
не соответствует стандартному формату даты-времени. Для преобразования
к такому формату следует использовать функцию Л18Т1Е¥_ШТЕкУАЬ.
Глава 3. Типы данных и встроенные функции
Запрос 3.25. Умножить значение времени, заданное в
виде интервала, и полученный результат преобразовать к
стандартному формату даты-времени
Пример 1.
SELECT JUSTIFY_INTERVAL(1O*INTERVAL'O day 5 hour 15 minute') as
new time;
new time
2 days 04:30:001
Пример 2.
SELECT JUSTIFY_INTERVAL(INTERVAL '5000 hour 15 minute') as new_date
new date
6 mens 28 days 08:15:001
Используя функцию EXTRACT(), можно извлечь
определенное поле из значения, имеющего тип INTERVAL.
Запрос 3.26. Вывести количество дней в значении,
имеющем тип INTERVAL
SELECT EXTRACT(day FROM JUSTIFY_INTERVAL(INTERVAL '5000 hour 15
minute')) as days;
days I
28 I
Запрос 3.27. Для сотрудника employee_id=145 вывести
количество дней, прошедших между датой приема на работу
и сегодняшним днем
SELECT employee_id,hire_date, CURRENT_DATE,
hire_date) AS days
FROM Employees
WHERE employee_id=145;
(CURRENT_DATE -
PostgreSQL: SQL + PL/pgSQL
employee_idIhire_date Icurrent_dateI days I
------------ 1------------ 1-------------- 1----- 1145101-10-19961
26-04-2023197031
В этом запросе количество дней, которые проработал сотрудник, опреде
ляется путем вычитания из текущей даты, которую возвращает функция
CURRENT DATE, даты приема на работу hiredate. Для того чтобы выве
сти продолжительность работы в формате лет-месяцев-дней, нужно исполь
зовать функцию AGE(). В запросе 3.29 содержится пример использования
функции AGE().
Запрос 3.28. Для сотрудника employee_id=145 вывести период
времени между датой приема на работу и сегодняшним днем
SELECT AGE(hire_date)
FROM Employees WHERE employee_id=145;
age
I
--------------------------------------- +
26 years 6 mens 25 days I
Результат, который возвращает функция AGE(), имеет тип INTERVAL. Ис
пользуя функцию EXTRACTQ, можно выбрать и использовать определен
ную часть этого результата.
Запрос 3.29. Вывести данные о сотрудниках, которые
проработали более 30 лет
SELECT employee_id, first_name, last_name, hire_date, AGE(hire_date)
FROM Employees
WHERE EXTRACT(year FROM AGE(hire_date))>30;
employee_id|first_name I last_name I hire_date I age
I
----------+--------- +-------- +--------- +--------------------- +
100|Steven
|King
117-06-1987|35 years 10 mons 9 days I
101|Neena
IKochhar 121-09-19891 33 years 7 mons 5 days I
• 103|Alexander |Hunold
103-01-1990133 years 3 mons 23 days|
10'4|Bruce
|Ernst
|21-05-1991|31 years 11 mons 5 days I
200|Jennifer |Whalen
117-09-1987 1 35 years 7 mons 9 days I
Запрос 3.30. Вывести данные о договорах,
оформлены в воскресенье
ПО
которые были
Глава 3. Типы данных и встроенные функции
SELECT * FROM orders
WHERE EXTRACT(dow FROM order_date)=0;
order_idIcustomer_id|status
|salesman_idIorder_dateI
--------+---- ------------ +----------------+-------------------- +-------------------- +
1091
44 1
43|
30 1
311
42 1
62 1
89 1
100 1
29|Shipped
2|Pending
47 | Shipped
45|Shipped
46|Canceled
56 | Canceled
3|Shipped
7|Shipped
16|Pending
|
|
|
|
I
I
|
|
|
168125-10-2020|
155121-05-20171
162|03-05-2020|
153|12-08-2018|
153|12-08-2018|
164103-06-20181
162130-07-20171
155127-10-20191
154|15-11-2020|
В этом примере функция EXTRACT() извлекает численное значение дня не
дели (воскресенье - 0, понедельник - 1 и т.д.) из даты оформления заказа
order date.
Запрос 3.31. Вывести employee_id сотрудников,
работающих в 30-м отделе, и суммарную зарплату
каждого сотрудника за весь период их работы. Данные
расположить в порядке убывания суммарной зарплаты
SELECT employee_id, salary*(EXTRACT(YEAR FROM AGE(hire_date))*12 +
EXTRACT(MONTH FROM AGE(hire_date)))as sum_salary
FROM Employees
WHERE department_id = 30
ORDER BY sum_salary DESC
employee_idIsum_salary|
----------- +----------- +
114 I 3751000.00 I
11511041600.001
1161 881600.001
117| 865200.001
1181 764400.001
1191 712500.001
Суммарная зарплата сотрудника равна salary*N, где N - количество месяцев,
которые проработал каждый сотрудник:
N= 12*N1 + N2
111
PostgreSQL: SQL + PL/pgSQL
• N1 — количество лет;
• N2 — количество месяцев.
Используя функцию EXTRACT(), можно извлечь значения N1 и N2 из ре
зультата, который возвращает функция АСЕ(Ыге баіе).
Это решение предполагает, что зарплата сотрудников не изменялась. В рас
сматриваемой базе данных не хранится история изменения зарплат сотруд
ников.
3.4. Логический тип
Переменные и выражения, имеющие логический тип
BOOLEAN, могут принимать значения TRUE, FALSE и
UNKNOWN, которое можно представить значением NULL.
Результат логических операций AND и OR над значениями этого типа при
веден в таблицах 3.6 и 3.7 соответственно.
Таблица 3.6. Таблица истинности логической функции AND с учетом
значений NULL
AND
TRUE
FALSE
NULL
TRUE
TRUE
FALSE
NULL
FALSE
FALSE
FALSE
FALSE
NULL
NULL
FALSE
NULL
Таблица 3.7. Таблица истинности логической функции OR с учетом
значений NULL
JI2]
OR
TRUE
FALSE
NULL
TRUE
TRUE
TRUE
TRUE
FALSE
TRUE
FALSE
NULL
NULL
TRUE
NULL
NULL
Глава 3. Типы данных и встроенные функции
В предложении SELECT могут присутствовать выражения, которые имеют
логический тип. Рассмотрим небольшую модификацию запроса 3.30, в кото
рый добавлен вычисляемый столбец salary>l 0000. Значение этого столбца
будет иметь значение true, если значение зарплаты сотрудника будет больше
1000, и значение false в противном случае.
Запрос 3.32. Вывести данные о сотрудниках,
проработали более 30 лет
которые
SELECT employee_id, first_name, last_name, hire_date, salary>10000
FROM Employees
WHERE EXTRACT (year FROM AGE(hire_date))>30;
employee_id |first_name | last_name | hire_date | ?column? |
----------- +------------ +-------- +----------- +--------- +
100 I Steven
101|Neena
103|Alexander
104|Bruce
200|Jennifer
|King
IKochhar
IHunold
lErnst
IWhalen
117-06-1987|true
121-09-19891 true
|03-01-1990 I false
|21-05-1991|false
|17-09-1987|false
I
|
|
|
|
Значения UNKNOWN (NULL) выводятся в виде пустой строки. В запро
се 3.33 содержится выражение commission pet > 0.2. Если столбец
commission_pct будет иметь значение NULL, то результат этого выражения
будет UNKNOWN (NULL).
Запрос 3.33. Вывести данные о сотрудниках, которые
работают в отделах 50, 60 и получают зарплату более 8000
SELECT employee_id, first_name, last_name,hire_date,salary,
commission_pct>0 .2
FROM Employees
WHERE department_id IN (50,80)
AND salary>8000;
employee_id|first_name I last_name |hire_date [salary
) ?column? |
----------- +----------- +----------- +----------- +-------- +-------- +
121|Adam
145|John
146|Karen
147|Alberto
148|Gerald
149|Eleni
150|Peter
I Fripp
[Russell
I Partners
lErrazuriz
ICambrault
IZlotkey
[Tucker
Ц0-04-1997І 8200.001
|01-10-1996|14000.00|true
I 05-01-1997 113500.00 I true
I 10-03-1997 112000.00 I true
|15-10-1999|11000.00|true
I 29-01-2000 110500.00 I false
|30-01-1997|10000.00|true
I
|
I
I
I
|
|
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Рассмотрим пример использования столбцов логического типа. На рис. 3.1
представлена структура таблицы Empl, которая имеет столбец bonus логи
ческого типа. Если этот столбец имеет значение TRUE, то сотрудник получает доплату.
<postgres> ...
Название
етр!
ID объекта:
pg.defautt
Владелец:
■Индексы
Название
ВПравила
83 Таблицы
і О
postgres
*
Тип данных
1
int4
Автоувеличение
Правило сортировки
Not Null
varchar(20)
default
I 1
default
[V]
Oh.re.date
6
date
7
varchar(W)
default
M
M
8
numeric(10, 2)
[ ]
9
numeric(4, 3)
[ 1
10
int4
[ ]
11
int4
12
int4
I ]
( 1
[ 1
^commission.pct
і U- depsrtme
manager-ntld
id
k
ШгМІПд e
Г
О
■» ® employees. 1
varchar(25)
1123 salary
ВРЫюез
- § hrj»c
3
В Ссылки
•Триггеры
Е Схемы
2
, | вас job.id
Секции таблиц
°
‘first name
я[ІС|вйпвте
■ Зависимости
ff
» § postgres
И Базы данных
Табличное пространство:
В Внешние ключи
”21
И *emp1 X
Д *<postgresx„
Диаграмма
^ postgres
Колонки
Д <postgres> ...
п <postgres> ...
Д' <postgres> ...
® Свойства В?, Данные.
tonus
boolean
Statrst.es
Права доступа
Рис. 3.1. Структура таблицы Empl
Запрос 3.34. Вывести данные о сотрудниках, которые
содержат сведения о зарплате с учетом доплаты
SELECT employee_id,first_name, last_name,job_id,salary,bonus,
CASE WHEN bonus THEN salary + 3000
ELSE salary
END AS salary_bonus
FROM Empl
WHERE salary > 5000;
employee_idI first_nameI last_nameIj ob_idI salary |bonus Isalary_bonusI
---------- +--------- +-------- +----- +------ +---- +----------- +
120|Matthew
IWeiss
IST_MANI 8000.00 I true |
11000.001
121|Adam
|Fripp
IST_MANI 8200.00 I false I
8200.001
122|Payam
| Kaufling | ST_MAN | 7 900.00 I true |
10900.00 1
123|Shanta
IVollman |ST_MAN|6500.00|true |
9500.001
124|Kevin
IMourgos IST_MAN|5800.00 IfalseI
5800.001
3.5. Функции преобразования типов данных
Эти функции предназначены для преобразования различных типов данных
из одного формата в другой.
Глава 3. Типы данных и встроенные функции
3.5.1. Преобразование чисел в строку символов
Числа, хранящиеся в базе данных, не имеют форматирования. Это означает,
что они не имеют символов валюты, запятых, десятичных знаков и других
параметров форматирования. Чтобы добавить форматирование, необходимо
преобразовать число в строку символов. Для этого используется функция:
TO_CHAR(X,M)
где:
• X — число;
• М — маска преобразования.
Маска преобразования может содержать элементы формата, представлен
ные в таблице 3.8.
Таблица 3.8. Элементы числового форматирования
Элемент
Описание
9
Числовая позиция. Количество символов 9 определяет ширину вывода
0
Вывод начальных нулей до заданной ширины
.(D)
Десятичная точка (запятая)
, или G
Разделитель групп (тысяч)
L
Символ валюты (использует locale)
Запрос 3.35. Примеры преобразования числа в строку символов
SELECT TO_CHAR(1475.29,
TO_CHAR(1475.29,
TO_CHAR(1475.29,
TO_CHAR(1475.29,
TO_CHAR(1475.29,
TO_CHAR(1475.29,
TO_CHAR(1475.29,
9999.99
1999.99
'9999.99') As "9999.99",
'999.99') As "999.99",
'9999.9') As "9999.9",
'9999D99') As "9999D99",
'099999.90') As "099999.99",
'9,999.99') As "9,999.99",
'L9,999.99') As "L9,999.99";
19999.9 |9999D99 1099999.99
19,999.99
|L9,999.99
I
--------------------- +--------------------- +---------------- +-------------------- +--------------------------- +-------------------------+--------------------------- 4.
1475.291
###.##!
1475.31
1475,291
001475.291
1,475.291? 1,475.291
PostgreSQL: SQL + PL/pgSQL
f PostgreSQL
Если количество символов 9 в целой части шаблона будет меньше числа
цифр целой части числа, то результат преобразования будет выведен в виде
###.##.
Если количество символов 9 в дробной части шаблона будет меньше числа
цифр дробной части числа, то будет выполнено округление.
В этом примере символ валюты представляет собой ?, так как локализа
ция, определяемая параметром LCMONETARY, имеет значение Russian_
Russia. 1251.
Посмотреть значение этого параметра можно,
SHOW LC_MONETARY;
lc_monetary
|
-------------------------------------- +
Russian_Russia.12511
Значение параметра LC MONETARY можно изменить:
SET LCJMONETARY TO "de-DE"; -- символ валюты €
SET LCJMONETARY TO "en-US"; -- символ валюты $
После выполнения второй команды результат преобразования числа в де
нежную сумму будет иметь следующий вид:
SELECT TO_CHAR(1475.29,
'L9,999.99') As "L9,999.99";
L9,999.99 I
----------- +
$ 1,475.291
Установленное значение параметра LC MONETARY сохраняется только в
течение текущего сеанса соединения.
3.5.2. Преобразование типов даты и времени в строку
символов
Это преобразование выполняется для того, чтобы отобразить значение, име
ющее тип даты и времени в требуемом виде. Для осуществления этого пре
образования используется функция:
" 116 ]
Глава 3. Типы данных и встроенные функции
ТО_СНАИ(Х,М)
где:
• X — значение, имеющее тип даты и времени;
• М — маска преобразования.
В таблице 3.9 приведены наиболее часто используемые элементы формати
рования, которые может содержать маска преобразования.
Таблица 3.9. Основные элементы форматирования даты и времени
Элемент
Описание
YYYY
Все четыре цифры года
YY
Две последние цифры года
ММ
Двузначный номер месяца
MONTH
Полное текстовое название месяца
MON
Первые три буквы названия месяца в верхнем регистре
D
Номер дня недели с воскресенья (1) по субботу (7)
ID
Номер дня недели с понедельника (1) по воскресенье (7)
DD
Двузначный номер дня месяца
DDD
Трехзначный номер дня года
DAY
Полное название дня недели (SATURDAY)
DY
Первые три буквы названия дня недели (SAT)
HH
Часы (01-12)
TZH
Часы часового пояса
HH24
Часы (00-23)
MI
Минуты
SS
Секунды
MS
Миллисекунды
Гн?
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Значения, представляющие собой текст (MONTH, DAY), можно задавать в
различных регистрах: верхний, нижний и т.д.
Запрос 3.36. Примеры преобразования даты и времени в
строку символов
SELECT TO_CHAR(CURRENT_TIMESTAMP, 'DD-MM-YYYY HH24-MI-SS')
as "DD-MM-YYYY HH24-MI-SS",
TO_CHAR(CURRENT_TIMESTAMP, 'DD-MM-YYYY') as "DD-MM-YYYY",
TO_CHAR(CURRENT_TIMESTAMP, 'HH24-MI-SS') as "HH24-MI-SS",
TO_CHAR(CURRENT_TIMESTAMP, 'DAY') as "DAY",
TO_CHAR(CURRENT_TIMESTAMP, 'TZH') as "TZH";
DD-MM-YYYY
HH24-MI-SS|DD-MM-YYYY|HH24-MI-SS|DAY
07-05-2023
11-46-11
|TZH|
---------------------- +---------- +------- :—+--------- +—+
|07-05-2023|11-46-11
|SUNDAY
|+03|
Запрос 3.37. Вывести данные о сотрудниках, которые
были приняты на работу в 1999 году
SELECT employee_id, first_name, last_name, hire_date,
date, 'DAY')
FROM Employees
WHERE TO_CHAR(hire_date,'YYYY') = '1999';
employee^ id | first_name | last_name
107|Diana
113|Luis
119|Karen
124|Kevin
127|James
132|TJ
135|Ki
|hire_date
TO_CHAR(hire_
|to_char
|
|Lorentz
|07-02-1999|SUNDAY
I 07-12-1999|TUESDAY
1 Popp
IColmenares|10-08-1999|TUESDAY
|Mourgos
|16-11-1999|TUESDAY
I Landry
Ц4-01-1999(THURSDAY
| 10-04-1999(SATURDAY
I Olson
112-12-1999(SUNDAY
I Gee
|
|
|
|
|
|
|
Запрос 3.38. Вывести данные о сотрудниках, которые были
приняты на работу в 1999 году, в воскресенье (SUNDAY)
SELECT employee_id, first_name, last_name, hire_date, TO_CHAR(hire_
date, 'DAY')
FROM Employees
WHERE TO_CHAR(hi re_date,'YYYY') = '1999'
and RTRIM(TO_CHAR(hire_date,'DAY')) = 'SUNDAY';
employee_id | first_name | last_name | hire_date
| to_char
|
-------- —+----- -—+------- +------ ---+--- -—---+
jis]
J
Глава 3. Типы данных и встроенные функции
107|Diana
135|Ki
187|Anthony
191|Randall
|Lorentz
|Gee
ICabrio
|Perkins
|07-02-1999|SUNDAY
|12-12-1999|SUNDAY
|07-02-1999|SUNDAY
|19-12-1999|SUNDAY
|
|
|
|
В этом примере следует обратить внимание на необходимость использо
вания функции RTRIM() для удаления правых пробелов. Это необходимо
сделать, так как функция TO_CHAR(hire_date,'DAY') возвращает строку, со
держащую количество символов, равное длине самого длинного названия
дня недели - WEDNESDAY (среда), дополняя более короткие названия про
белами справа.
3.5.3. Преобразование строки символов в число
Для преобразования символьного значения в число используется функция
TO_NUMBER (X, М)
где:
• X — символьное представление числа;
• М — маска преобразования.
X может содержать цифры и символы, которые соответствуют заданному
формату.
М определяет, как нужно интерпретировать символьное представление чис
ла, может содержать те же элементы формата, что были определены для
функции ТО CHAR.
Если число символов в целой части символьного представления будет боль
ше числа элементов формата, то возникает ошибка.
Если число символов в дробной части символьного представления будет
больше числа элементов формата, то осуществляется усечение без округления.
Если число элементов формата будет больше числа символов в целой или
дробной части символьного представления, то ошибка не возникает.
Запрос 3.39. Примеры использования функции ТО_NUMBER()
SELECT TO_NUMBER ('1475.29',
TO_NUMBER ('1475.29',
TO_NUMBER ('1475.29',
'9999.99')As "9999.99",
'9999.9')As "9999.9",
'99999.999')As "99999.999";
Г 119 ’
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
9999.9919999.9199999.9991
------ +----- +-------- +
1475.29'11475.2 1
1475.291
Запрос 3.40. Использование функции TO_NUMBER в выражениях
SELECT salary, salary + TO_NUMBER ('$1475',
FROM Employees
WHERE employee_id=102;
'$9999') AS new
salary
I new
|
-------- +-------- +
17000.00 118475.00|
3.5.4. Преобразование строки символов к типам даты и времени
Для преобразования строки символов в значение, имеющее тип даты и вре
мени, используются функции:
TO_DATE(X, М)
TO_TIMESTAMP(X, М)
• X — содержит символьное значение даты и времени.
• М — маска преобразования, которая определяет, как нужно интерпрети
ровать символьное представление даты и времени.
Маска может содержать элементы формата, представленные в таблице 3.9.
При использовании функции ТО DATEQ следует использовать только те
элементы формата, которые соответствуют дате (день, месяц, год).
Запрос 3.41. Примеры использования функции TO_DATE
SELECT TO_DATE ('01-SEP-2018', 'DD-MON-YYYY')
As "DD-MON-YYYY"',
TO_DATE ('09/01/18 ' , 'MM/DD/YY') As "MM/DD/YY",
TO_DATE ('01092018' , 'DDMMYYYY') As "DDMMYYYY";
DD-MON-YYYY'IMM/DD/YY
(DDMMYYYY
|
--------------+----------- +----------- +
01-09-2018 I 01-0 9-2018|01-09-2018|
i 120 ]
Глава 3. Типы данных и встроенные функции
В этом примере строка преобразуется в дату, а дата выводится в установлен
ном формате даты.
Запрос 3.42. Примеры использования функций TO_DATE() и
TO_TIMESTAMP()
SELECT TO_CHAR(TO_DATE('01-SEP-2018, 14:45:51',
'DD-MON-YYYY HH24:MI:SS'),
'DD MON YYYY, HH24:MI:SS') As "Date",
TO_CHAR(TO_TIMESTAMP('01-SEP-2018, 14:45:51',
'DD-MON-YYYY HH24:MI:SS'),
'DD MON YYYY, HH24:MI:SS') As "Date Time";
Date
I Date Time
I
----------------------- +----------------------- +
01 SEP 2018,
00:00:00101 SEP 2018,
14:45:511
В этом примере следует обратить внимание на то, что строка содержит зна
чение даты и времени. При использовании функции ТО DATE() ошибки не
возникает, но значение времени не сохраняется.
3.5.5. Преобразование значений NULL
Если при вводе новой строки в таблицу столбцу не будет присвоено значе
ние, то этот столбец будет иметь значение NULL - не определено. Это может
происходить по двум основным причинам:
1. Первая причина: в момент ввода строки значение столбца неизвестно, в
этом случае значение будет присвоено позже.
2. Вторая причина: значение не может быть присвоено исходя из правил
предметной области. Для рассматриваемой базы данных вторую причину
можно пояснить на примере столбца commission_pct таблицы Employees.
Некоторым сотрудникам полагаются комиссионные, столбец commission_pct содержит значение комиссионных. Зарплата таких сотрудников
рассчитывается по формуле: Salary*(l + commission_pct). У сотрудников,
которым комиссионные не полагаются, значение столбца commission_pct
не может быть определено.
При работе с арифметическими выражениями следует иметь в виду следу
ющее: арифметическое выражение вернет значение NULL, если один или
несколько операндов будут иметь значение NULL. Результатом операции
ши
PostgreSQL: SQL + PL/pgSQL
..................................................
w
PostgreSQL
сравнения будет NULL, если один или оба операнда будут иметь значение
NULL.
Результат логических операций AND и OR с операндами, которые могут
иметь значение NULL, приведен в таблицах 3.6 и 3.7.
Для корректной обработки данных, которые могут иметь значения NULL,
следует использовать функцию COALESCE().
3.5.6. Функция COALESCE
Используется в выражениях, элементы которых могут иметь
значение NULL, и имеет следующий синтаксис:^
COALESCE (X1,X2,...XN)
Функция возвращает первое не-NULL значение. Если все ее аргументы рав
ны NULL, то функция возвращает NULL.
Запрос 3.43. Вывести данные о полной зарплате
сотрудников, которые работают в отделе 30. Значение
полной зарплаты равно salary*(1 + commission_pct)
SELECT employee_id, first_name, last_name, salary, commission_j>ct
as comjpct, ROUND(COALESCE(salary*(l+commission_pct),salary)) AS
total_salary
FROM Employees
WHERE department_id =30
ORDER BY total_salary DESC;
employee_id|first_nameIlast_name |salary
|com_pct|total_salary|
------------ +----------- +----------- +---------+------- +------------- +
114|Den
116 1 Shell!
1151 Alexander
117|Sigal
118|Guy
119 1 Karen
IRaphaely
| 11000.001
1 Baida
| 2900.001
1 Khoo
1 3100.00 1
1 Tobias
1 2800.001
1Himuro
1 2600.001
1Colmenares| 2500.001
0.2001
0.3001
0.1001
1
132001
3770 1
3100 1
3080 1
2600 1
2500 1
Без использования функции COALESCE() полная зарплата сотрудников, у
которых commission_pct имеет значение NULL, также имела бы значение
NULL.
122
Глава 3. Типы данных и встроенные функции
Запрос 3.44. Вывести данные о полной зарплате
сотрудников, которые работают в отделе 30 и полная
зарплата которых больше 3000. Данные расположить в
порядке убывания полной зарплаты
SELECT employee_id, first_name, last_name, salary,
commission_pct as com_pct,
ROUND (COALESCE (salary* (l+comission_pct) , salary)) AS total_salary
FROM Employees
WHERE department_id =30
AND COALESCE(salary*(1+commissionjpct),salary) > 3000
ORDER BY total_salary DESC;
employee_idI first_nameIlast_nameI salary
Icom_pctItotal_salary|
----------- +----------- +----------+-------- +------- +------------- +
114|Den
1161Shelli
1151 Alexander
117|Sigal
|11000.00|
1 2900.001
1 3100.001
1 2800.001
1Raphaely
1 Baida
1 Khoo
1 Tobias
0.2001
0.3001
0.1001
132001
3770 1
3100 1
3080 1
В этом запросе следует обратить внимание на то, что псевдонимы столбцов
(total_salary) можно использовать в предложении ORDER BY, но нельзя ис
пользовать в предложении WHERE.
Рассмотрим следующий запрос:
Запрос 3.45. Вывести данные об отделах, расположенных
не в Seattle ( 1осаtіon_id о 1700)
SELECT department_id, department_name, manager_id
FROM departments_l
WHERE location_id <> 1700;
department_id|department_name |manager_id|
-------------- +----------------- +----------- +
40 I Human Resources
60 I IT
70 I Public Relations
80|Sales
201 Marketing
501 Shipping
|
I
I
I
|
I
203|
103|
204|
145|
|
I
Из результатов этого запроса видно, что для отделов 20 и 50 начальник не
назначен. Рассмотрим другую версию этого запроса, в котором реализовано
следующее бизнес-правило: для отделов, которым не назначен начальник,
считать, что их начальником является Steven King (employee_id=100).
[ 123
PostgreSQL: SQL + PL/pgSQL
Запрос 3.46. Вывести данные об отделах, расположенных
не в Seattle (location id <> 1700)
SELECT department_id, department_name, COALESCE(manager_id,100)
"manager_id"
FROM departments_l
WHERE location id <> 1700;
department_idIdepartment_name Imanager_idI
------------------------------------ 1---------------------------------------------- p------------------------ p
40|Human Resources I
601 IT
|
70|Public Relations|
80|Sales
|
20(Marketing
|
50(Shipping
|
203
103
204
145
100
100
as
Глава 3. Типы данных и встроенные функции
Задачи для самостоятельного решения
Задача 3.1. Для сотрудников, зарплата которых больше 1200,
выведите столбец, который должен содержать полное имя
сотрудника, зарплату и несколько звездочек (*), по одной звез
дочке на каждые $1000 зарплаты.
Задача 3.2. Выведите названия городов (city), в которых 4-я бук
ва t, а последняя е.
Задача 3.3. Вывести данные о товарах, название которых со
держит слово AMD и не содержит слово RYZEN. Предусмотреть
то, что эти слова в названии товара могут быть представлены в
разных регистрах.
Задача 3.4. Вывести названия товаров, первое слово которых
состоит из 7 символов.
Задача 3.5. Выведите названия отделов, которые состоят более
чем из одного слова.
Задача 3.6. Вывести данные о сотрудниках, которые были при
няты на работу 21 апреля.
Задача 3.7. Вывести даты текущего месяца, которые выпадают
на воскресенье.
Задача 3.8. Вывести данные о сотрудниках и размере премии
(bonus), которую они должны получить. Размер премии зави
сит от количества лет, которые проработал сотрудник: bonus =
1000 х количество лет.
125
Ро8І§ге8ОЬ: 8рЬ + РЬ/рв89Ь
Задача 3.9. Вывести данные о размере премии сотрудников, ко
торые работают в отделе 30. Размер премии равен зарплате с
учетом комиссионных, если сотрудник получает комиссионные,
либо зарплате, умноженной на 1.2, если сотрудник не получает
комиссионные.
Задача 3.10. Вывести данные об отделах, названия которых
состоят более чем из одного слова. Результат выполнения за
проса должен содержать: берагІтепМсІ, бераг1теп1_пате, вто
рое слово в названии отдела.
126
Глава 4.
АГРЕГАТНЫЕ ФУНКЦИИ И
ГРУППИРОВКА ДАННЫХ
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
4.1. Агрегатные функции
В отличие от однострочных функций, агрегатные функции
обрабатывают группу строк и возвращают один результат для
группы. Группа строк может включать как всю таблицу, так и
часть таблицы.
В таблице 4.1 содержится описание основных агрегатных функций.
Таблица 4.1. Основные агрегатные функции
Функция
Возвращает
Тип аргумента
SUM(expr)
Сумму значений ехрг, игнорируя значе
ния NULL
Число
COUNT(expr|*})
Число строк, игнорируя значения
NULL. При использовании в качестве
аргумента * число строк
Любой
MAX(expr)
Максимальное значение ехрг, игнори
руя значения NULL
Число, строка, дата
MIN(expr)
Минимальное значение ехрг, игнорируя
значения NULL
Число, строка, дата
AVG(expr)
Среднее значение ехрг , игнорируя зна
чения NULL
Число
J28J
Глава 4. Агрегатные функции и группировка данных
Синтаксис агрегатных функций:
{имя функции}({ехрг})
где:
ехрг — аргумент агрегатной функции, который может содержать следую
щие элементы :
[DISTINCT]
{имя столбца}|{выражение}|{однострочная функция}
Запрос 4.1. Вывести обобщенные данные о зарплате сотрудников
SELECT MIN(salary) AS "MIN", MAX(salary) AS "MAX",
ROUND(AVG(salary),2) AS "AVG",
SUM(salary) As "SUM"
FROM Employees;
MIN
|MAX
|AVG
|SUM
|
------- +-------- +------- +--------- +
2200.00|24000.00|6479.05|680300.00|
Этот запрос не учитывает то, что некоторые сотрудники получают комисси
онные. Зарплата сотрудника с учетом комиссионных может быть вычислена
путем использования выражения:
COALESCE(salary*(l+commission_pct),salary)
Запрос 4.2. Вывести обобщенные данные о зарплате
сотрудников с учетом комиссионных
SELECT
MIN(ROUND(COALESCE(salary*(l+commission_pct),salary),2))
MAX(ROUND(COALESCE(salary*(l+commission_pct),salary),2))
ROUND(AVG(COALESCE(salary*(l+commission_pct),salary)),2)
SUM(ROUND(COALESCE(salary*(l+commission_pct),salary),2))
FROM Employees;
AS
AS
AS
As
"MIN",
"MAX",
"AVG",
"SUM"
MIN
|MAX
IAVG
I SUM
|
------- +-------- +------- +---------- +
2200.00 I 24000.00 I 7191.33 I 755090.00 I
129
PostgreSQL: SQL + PL/pgSQL
.................................................. f PostgreSQL-
ЗапрОС 4.3. Пример использования функции COUNT()
SELECT COUNT(*)as "COUNT(*)", COUNT(salary)as "COUNT(salary)",
COUNT(DISTINCT salary) as "COUNT(DISTINCT salary)"
FROM Employees;
COUNT(*) I COUNT(salary) |COUNT(DISTINCT salary) I
-------- +
.
+------------------------ +
107|------ 1051
56|
Анализ результатов этого запроса:
•
COUNT(*) — количество всех сотрудников;
•
COUNT(salary) — количество сотрудников, у которых значение столбца
salary не NULL;
•
COUNT(DISTINCT salary) — количество различных значений в столбце
salary.
Запрос 4.4. Вывести
данные о средней зарплате сотрудников
SELECT ROUND(AVG(salary),2) AS "AVG1", ROUND(SUM(salary)/
COUNT(salary),2)AS "AVG2",
ROUND(SUM(salary)/COUNT (*),2)AS "AVG3"
FROM Employees;
AVG1
|AVG2
IAVG3
|
------- +------- +------- +
6479.0516479.0516357.941
Анализ результатов этого запроса.
Первые два выражения вернули одинаковый результат, а значение, которое
вернуло третье выражение, отличается от первых двух. Причиной этого явля
ется то, что в первых двух выражениях сотрудники, зарплата которых имеет
значение NULL, не учитывались как при вычислении суммарной зарплаты,
так и при определении числа сотрудников. В третьем выражении функция
COUNT(*) вернула количество всех сотрудников.
Какой результат является правильным?
Ответ на этот вопрос не очевиден и зависит от правил предметной области.
Программист в подобных случаях не должен сам принимать решение, а дол
жен выяснить это у заказчика.
Глава 4. Агрегатные функции и группировка данных
Агрегатные функции нельзя использовать в предложении WHERE. Напри
мер, НЕЛЬЗЯ найти сотрудника с максимальной зарплатой, используя сле
дующий запрос.
Запрос 4.5а. Найти сотрудника, получающего максимальную
зарплату
Внимание: ЭТОТ ЗАПРОС НЕ БУДЕТ ВЫПОЛНЕН!
SELECT employee_id, salary
FROM Employees
WHERE salary = MAX(salary);
Данную задачу можно решить следующим образом.
Запрос 4.56. Найти сотрудника, получающего максимальную
зарплату
SELECT employee_id, salary AS maximum
FROM Employees
WHERE salary = (SELECT MAX(salary) FROM Employees);
employee_idI maximum |
---------- +------- +
100124000.001
Этот запрос содержит в предложении WHERE подзапрос.
4.2. Группировка
Чаще всего агрегатные функции используются в запросах с группировкой.
В общем виде запрос с группировкой может быть представлен в следующем
виде:^
SELECT {список столбцов*), {агрегатные функции)
FROM {таблица}
WHERE {условия}
GROUP BY {список столбцов*}
HAVING {условия на группу};
Списки столбцов в предложениях SELECT и GROUP BY должны совпадать.
Г 131 "
PostgreSQL: SQL + PL/pgSQL
Предложение GROUP BY разбивает данные на группы, и запрос выводит
обобщенные данные о каждой группе.
Рассмотрим примеры задач, для решения которых необходимо использовать
группировку и агрегатные функции.
Запрос 4.6. Для каждого отдела вывести количество и
суммарную зарплату сотрудников
SELECT department_id, COUNT(*), SUM(salary)
FROM Employees
GROUP BY department_id
ORDER BY department_id;
department_idI count I sum
|
--------------- +----- +---------- +
10|
20 1
30|
40|
50 1
4400.001
11
2| 19000.001
61 24900.001
6500.001
11
45| 154300.001
Запрос 4.7. Для отделов 30 и 50 вывести коды должности
(job_id) и количество сотрудников, занимающих каждую
должность
SELECT job-id, COUNT(*)
FROM Employees
WHERE department_id IN (30,50)
GROUP BY job_id
ORDER BY job_id;
job_id
I count I
------- +-----+
PU_CLERKI
PU_MAN
I
SH_CLERKI
ST_CLERKI
ST_MAN
I
31
51
II
20 I
17|
51
В этом примере следует обратить внимание на то, что в отделах 30 и 50 есть
сотрудники, у которых столбец job_id имеет значение NULL. Группировка
по этому значению выполнена, и функция COUNT(*) вернула количество
Глава 4. Агрегатные функции и группировка данных
таких сотрудников. Группировку можно осуществлять, используя вычисля
емые столбцы.
Запрос 4.8. Вывести количество заказов, оформленных в
течение каждого года
SELECT TO_CHAR(order_date, 'YYYY') as Year, COUNT(*)
FROM ORDERS
GROUP BY TO_CHAR(order_date, 'YYYY');
ORDER BY Year;
year I count I
---- +----- +
20171---- 33|
20181
31|
20191
22|
20201
13|
4.2.1. Группировка по нескольким столбцам
В предложении GROUP BY можно указать несколько столбцов. В этом слу
чае группу образуют строки с совпадающими значениями всех столбцов, по
которым осуществляется группировка. Рассмотрим задачи, в которых требу
ется группировка по нескольким столбцам.
Запрос 4.9. Для сотрудников, работающих в отделах 30 и
50, рейтинг которых >2, вывести код должности (job_id),
рейтинг (rating_e) и количество сотрудников, которые
имеют одинаковые пары значений job_id, rating_e
SELECT job_id, rating_e, count(*)
FROM Employees
WHERE department_id in (30,50)
and rating_e>2
GROUP BY department_id, job_id, rating_e
ORDER BY job_id,rating_e;
j ob_id
Irating_eI count|
PU_CLERK|
SH_CLERK|
SH CLERK|
41
31
31
4|
11
4 1
11
61
PostgreSQL: SQL + PL/pgSQL
SH_CLERKI
ST_CLERKI
ST_CLERKI
ST_CLERKI
ST_MAN
I
51
31
41
51
41
PostgreSQL..
51
61
21
41
11
В этом примере следует обратить внимание на то, что в группировке ис
пользуется столбец department_id, которого нет в предложении SELECT.
Условие ratinge>2 было добавлено для того, чтобы сократить число строк в
результате выполнения запроса.
4.2.2. Использование условий на группу
В запросах с группировкой можно использовать предложение
HAVING, которое содержит условия на группу. Результат
запроса будет содержать данные только о тех группах,
которые удовлетворяют этим условиям.
Запрос 4.10. Вывести суммарную зарплату для отделов, у
которых суммарная зарплата превышает 50000
SELECT department_id, SUM(salary)
FROM Employees
GROUP BY department_id
HAVING SUM(salary) > 50000
ORDER BY department_id;
department_id|sum
|
--------------- +---------- +
50Ц54300.00І
801295500.001
90| 58000.001
1001 51600.001
Запрос 4.11. В таблице Employees для каждой должности
определите разницу между максимальной и минимальной
зарплатой. Выведите данные только о тех должностях,
для которых эта разница > 0
SELECT job_id, MAX(salary)
і 134 ]
- MIN(salary)
as diff
Глава 4. Агрегатные функции и группировка данных
FROM employees
GROUP BY job_id
HAVING (MAX(salary)
- MIN(salary))>0;
job_id
I diff
I
----------- +-------- +
SH_CLERK
SA_MAN
IT_PROG
ST_CLERK
PU_CLERK
ST_MAN
SA_REP
I 1700.001
I 3500.00|
I 4800.00|
11400.00 |
I 600.001
12400.001
I 5400.00|
I 800.001
FI_ACCOUNT|2iOO.OO|
В этом запросе следует обратить внимание на то, что псевдонимы столб
цов (diff) можно использовать в предложениях ORDER BY и GROUP BY, но
нельзя использовать в предложении HAVING.
Запрос 4.12. Вывести номера клиентов, которые в
течение года оформили более 2 заказов
SELECT customer_id, TO_CHAR(order_date,
FROM orders
GROUP BY customer_id, year
HAVING COUNT(*)>2
ORDER BY customer_id, year;
'YYYY') as year, COUNT(*)
customer_id|year|count|
------------+---- +----- +
3120181
9|2017|
16|2017|
44|2017|
46120181
49120171
3|
3|
3|
3|
3|
3|
Запросы с группировкой и условиями на группу могут содержать предложе
ние WHERE. В этом случае сначала выбираются строки, удовлетворяющие
условиям предложения WHERE, после этого осуществляется группировка
полученных данных.
135
PostgreSQL: SQL + PL/pgSQL
Запрос 4.13. Вывести должности и количество
сотрудников, которые получают зарплату более 10000.
Вывести данные только о тех должностях, которые
занимают несколько сотрудников (более одного)
SELECT job_id, COUNT(*) As num_job
FROM Employees
WHERE salary > 10000
GROUP BY job_id
HAVING COUNT(*)>1
ORDER BY num_job DESC;
j ob_id|num_j obI
------ +-------- +
SA_MANI
SA_REPI
AD_VP I
5I
3 I
2I
В запросах с группировкой можно использовать выражение LIMIT N.
Запрос 4.14. Вывести номера (product_id)
наибольшим количеством во всех продажах
5 товаров с
SELECT product_id, SUM(quantity) AS sum_quantity
FROM Order_iterns
GROUP BY product_id
ORDER BY sum_quantity desc
LIMIT 5;
product_idIsum_quantityI
---------- +------------- +
78|
52|
19|
28|
18|
4581
4551
4441
411|
3921
Совместно с выражением LIMIT N можно использовать выражение OFF
SET M. В этом случае сначала пропускаются первые М строк результата,
после этого выводится N следующих строк.
Глава 4. Агрегатные функции и группировка данных
Запрос 4.15. Вывести номер товара, который занимает
3-е место в списке товаров с наибольшим количеством во
всех продажах
SELECT product_id, SUM(quantity) AS sum_quantity
from order_iterns
GROUP BY product_id
ORDER BY sum_quantity desc
LIMIT 1 OFFSET 2;
product_id|sum_quantity I
----------- +-------------- +
19|
444|
4.3. Использование специальных операторов
группировки
Рассмотрим специальные операторы группировки и функции, которые
позволяют существенно расширить возможности запросов, в которых ис
пользуется группировка данных.
4.3.1. Оператор GROUP BY ROLLUP
Расширяет возможности GROUP BY, возвращая для каждой
группы строку, содержащую итоги по группе, а также строку,
содержащую общий итог для всех групп, и имеет следующий
вид
GROUP BY ROLLUP {список столбцов)
Для демонстрации возможностей, которые предоставляет оператор GROUP
BY ROLLUP, рассмотрим следующую задачу:
Запрос 4.16. Для сотрудников, работающих в отделах
30 и 50, рейтинг которых >2, вывести код должности и
количество занимающих каждую должность
137
PostgreSQL: SQL + PL/pgSQL
SELECT depar tment_id, job_id, count(*)
FROM Employees
WHERE department_id IN (30,50)
and rating_e>2
GROUP BY ROLLUP (department_id, job_id)
ORDER BY department_id,job_id;
department_idIjob_id |count I
------------ +------- +---- +
30|PU CLERK|
30|
|
50|
|
50|SH_CLERK|
50|ST_CLERK|
50|ST_MAN
|
50|
|
I
I
4|
4|
1|
12|
12|
1|
26|
30 1
Решение этой задачи без использования ROLLUP содержится в запросе 4.10.
По сравнению с результатами, которые выводит запрос 4.10, этот запрос вы
водит данные о количестве сотрудников в каждом отделе и общем количе
стве сотрудников, работающих в рассматриваемых отделах.
Добавим в условие группировки столбец ratinge.
Запрос 4.17а. Для сотрудников, работающих в отделах
30 и 50, рейтинг которых >2, вывести код должности
(job_id), рейтинг (rating_e) и количество сотрудников,
которые имеют одинаковые пары значений job_id, rating_e
SELECT department_id, job_id, rating_e, count(*)
FROM Employees
WHERE department_id in (30,50)
and rating_e>2
GROUP BY rollup (department_id, job_id,rating_e)
ORDER BY department_id, job_id,rating_e;
department_idIj ob_id |rating_eI count|
------------ 1— ---- 1--------- 1----- j.
30|PU_CLERK|
30|PU_CLERK|
30|
I
50|
I
138
3 1
I
I
4|
4 I
4 I
4|
1|
Глава 4. Агрегатные функции и группировка данных
50 |
50 | SH__CLERK
50 | SH'_CLERK
50 | SH__CLERK
50 | SH'_CLERK
50 | ST__CLERK
50 | ST__CLERK
50 |ST__CLERK
50 |ST__CLERK
50 | ST__MAN
50 | ST MAN
50 |
1
1
|
|
|
|
|
|
|
|
|
|
31
4|
51
1
1
31
41
51
1
4|
1
1
6
5
12
6
2
4
12
1
1
26
30
В этом запросе выведены итоговые данные (количество сотрудников) не
только по каждому отделу, но и по каждой должности.
Для облегчения анализа этих данных можно использовать функцию
GROUPING0, которая принимает значение столбца и возвращает значение
1, если значение столбца равно NULL, и 0 в противном случае. Эта функция
может использоваться только в запросах с RULUP и CUBE.
Запрос 4.176. Решение задачи из запроса 4.17а с
использованием функции GROUPING()
SELECT department_id,j ob_id,
CASE GROUPING(job_id)
WHEN 1 THEN 'ALL DEP
'||department_id::text
ELSE '
'||rating_e::text
END rating_e,
CASE GROUPING(rating_e)
WHEN 1 THEN 'ALL JOB
'||job_id
ELSE '
END ALL_JOB,count(*)
FROM Employees
WHERE department_id in (30,50)
AND rating_e>2
GROUP BY rollup (department_id, job_id,rating_e)
ORDER BY department_id,job_id, rating_e;
department_id|job_id
|rating_e
|all_job
I count|
-------------- +-------- +------------ +------------------ +----- +
30|PU_CLERK|
30|PU_CLERK|
301
|ALL DEP
3|
|ALL JOB
301
I
PU_CLERK|
I
4|
4|
4 |
[ 139
PostgreSQL: SQL + PL/pgSQL
50|
50|
50|SH
50|SH
50|SH
50|SH
50 1ST
50 1ST
50 1ST
50 1ST
50 1ST
50 1ST
50|
1
PostgreSQL
I
I
CLERK|
CLERK|
CLERK|
CLERK|
CLERK|
CLERK|
CLERK|
CLERK|
MAN
|
MAN
|
|ALL DEP
1
41
1 ALL
31
41
51
|ALL
31
41
51
1 ALL
4|
1 ALL
50 1
1
1
JOB
1
JOB
SH CLERK|
1
1
JOB
ST CLERK|
JOB
ST MAN
|
1
1
11
11
К
61
51
12 1
61
21
41
12|
11
11
26| .
30|
4.3.2. Оператор GROUP BY CUBE
Возвращает предварительные итоги для всех комбинаций
столбцов и строку с общим итогом и имеет следующий вид:
GROUP BY CUBE {список столбцов}
Рассмотрим решение задачи из запроса 4.16 с использованием этого
оператора.
Запрос 4.18. Используя GROUP BY CUBE, вывести для
сотрудников, работающих в отделах 30 и 50, рейтинг которых
>2, код должности и количество занимающих каждую должность
SELECT depar tment_id, job_id, count(*)
FROM Employees
WHERE department_id IN (30,50)
AND rating_e>2
GROUP BY CUBE (department_id, job_id)
ORDER BY department_id,job_id;
department_id|job_id
I count I
— +--------- +— --- +
30|PU CLERK 1
30|
1
501
1
41
41
11
Глава 4. Агрегатные функции и группировка данных
501SH_CLERK1
501ST_CLERK|
50|ST_MAN
1
50|
1
1
1
1PU_CLERK1
1SH_CLERK1
1ST_CLERK1
1ST_MAN
1
1
1
12|
12 1
И
26|
И
41
12|
12|
И
30 1
Результаты этого запроса содержат данные, которые возвращал запрос 4.16,
и итоговые данные о сотрудниках, занимающих каждую должность. В ис
пользуемой схеме HR РОС должности для каждого отдела уникальны,
поэтому данные о количестве сотрудников, занимающих каждую долж
ность, повторяются.
Оператор GROUP BY CUBE выводит очень много строк, поэтому в ряде слу
чаев удобнее использовать оператор GROUP BY GROUPING SETS.
4.3.3. Оператор GROUP BY GROUPING SETS
Используется вместо оператора GROUP BY CUBE
в тех случаях, когда нужно вывести только строки с
промежуточными итогами.
Запрос 4.19. Для сотрудников, работающих в отделах 30 и 50,
рейтинг которых >2, вывести количество сотрудников в отделе
и количество сотрудников, занимающих каждую должность
SELECT department_id, job_id, count(*)
FROM Employees
WHERE department_id IN (30,50)
AND rating_e>2
GROUP BY GROUPING SETS(department_id, job_id)
ORDER BY department_id,job_id;
department_idIjob_id
-------------- +
30|
50|
|count|
+----- +
I
I
4|
26|
PostgreSQL: SQL + PL/pgSQL
|PU_CLERK|
|SH_CLERK|
|ST_CLERK|
|ST_MAN
|
II
41
12|
12|
II
Результат этого запроса содержит данные только о количестве сотрудников,
работающих в каждом отделе, и данные о количестве сотрудников, занима
ющих определенную должность, без учета отдела, в котором они работают.
Несколько столбцов в этом операторе можно заключить в скобки, в этом слу
чае они будут рассматриваться как один столбец, по которому нужно выве
сти промежуточные итоги.
Запрос 4.20. Для сотрудников, работающих в отделах 30,
50 и имеющих rating_e >2, вывести общее количество таких
сотрудников в каждом отделе и количество сотрудников,
которые имеют одинаковые пары значений (job_id, rating_e)
SELECT department_id, job_id,rating_e, count(*)
FROM Employees
WHERE department_id IN (30,50)
AND rating_e >2
GROUP BY GROUPING SETS(department_id, (job_id, rating_e))
ORDER BY department_id;
department_idIj ob_id
|rating_eI count|
--------------- +--------- +--------- +----- +
301
1
50|
|
|SH_CLERK|
IST_CLERK|
1ST_CLERK|
|PU_CLERK|
1
1
|SH_CLERK|
|SH_CLERK|
|ST_CLERK|
|ST_MAN
|
142
31
31
51
31
4|
51
41
41
4|
4 1
26|
1 |
6|
4 1
4 1
1 |
5 1
61
2 1
1 |
Глава 4. Агрегатные функции и группировка данных
Задачи для самостоятельного решения:
і
і
J Задача 4.1. Определить средний размер комиссионных. |
і
L_____--------------------------
і
____________________□
! Задача 4.2. Найти количество товаров, в названии которых есть
• слово CORE.
L______________________________________________________________
! Задача 4.3. Вывести номера менеджеров и суммарную зарплату
; их подчиненных, имеющих нечетный рейтинг.
Задача 4.4. Вывести количество заказов, которые клиент 46
оформил в течение каждого года.
Задача 4.5. Вывести количество заказов, оформленных за каж
дый месяц 2019 года.
Задача 4.6. Определить номера товаров, по которым было со
вершено меньше 10 продаж. Продажа - это строка в таблице
Огбег_іІетэ.
Задача 4.7. Вывести номера отделов, в которых более 5 ме
неджеров. Менеджером является сотрудник, который руководит
другими сотрудниками. Его етрІоуее_ісІ содержится в столбце
manager_id других сотрудников.
Задача 4.8. Для заказов вывести номера товаров в заказе, их
количество, общую стоимость каждого товара и всего заказа.
Вывести эти данные только для заказов, у которых order_id <10.
PostgreSQL: 8РЬ + PL/pgSQL
Задача 4.9. Вывести количество заказов, оформленных в тече
ние каждого года, и количество заказов, которые оформил каж
дый клиент. Вывести только те строки, в которых количество за
казов >4.
Задача 4.10. Для каждого отдела вывести суммарную зарплату
сотрудников за весь период их работы.
144
Глава 5.
МНОГОТАБЛИЧНЫЕ
ЗАПРОСЫ
PostgreSQL: SQL + PL/pgSQL
РозІдгѳЗОС
Реляционная база данных представляет собой
совокупность взаимосвязанных таблиц. При решении
большинства задач обработки данных необходимо
извлекать информацию из нескольких таблиц. Запросы,
в которых используется несколько таблиц, называют
многотабличными.
В многотабличных запросах нужно обязательно указывать условия соеди
нения таблиц. При отсутствии условий соединения ошибки не возникает, а
происходит декартово произведение таблиц: каждая строка одной таблицы
соединяется с каждой строкой другой таблицы.
Столбцы, по которым осуществляется соединение, должны иметь одинако
вый тип и совпадающие или сравнимые значения. Чаще всего в соединениях
используется ключ одной таблицы и столбец другой таблицы, который явля
ется внешним ключом.
Если в многотабличном запросе участвует N таблиц, то число условий
соединения должно быть N - 1. Обычно в качестве операции соединения
используется =, но можно использовать и другие операции, например > <.
Можно установить соединение как между таблицами, которые связаны в
схеме базы данных, так и между таблицами, у которых такой связи нет.
Кроме таблиц базы данных, в соединениях могут участвовать подзапросы,
представления, хранимые функции. В этом случае сначала выполняется
запрос, который содержится в одном из этих объектов, а потом результат
этого запроса используется как виртуальная таблица.
Глава 5. Многотабличные запросы
Полное имя столбца состоит из имени таблицы и имени столбца, между ко
торыми стоит точка, например: Employees.employee_id.
Если столбцы разных таблиц, используемых в многотабличных запросах,
имеют одинаковые имена, то необходимо указывать полные имена
Условия соединения могут быть заданы или в предложении WHERE, или в
предложении FROM.
5.1. Условия соединения таблиц в предложении WHERE
Условия соединения в предложении WHERE в общем виде могут быть за
писаны следующим образом?;
SELECT {список столбцов}
FROM Таблица1, Таблица2, [ТаблицаЗ ...]
WHERE
Таблицаі.столбец<операция соединения> Таблица2.столбец
[AND ТаблицаЗ.столбец <операция соединения> ...]
Например, нужно вывести номера и названия отделов, расположенных в
определенном городе. Для решения данной задачи необходимы данные, со
держащиеся в таблицах Departments и Locations.
Запрос 5.1. Вывести номера и названия отделов,
расположенных в городе London. ЗАПРОС СОДЕРЖИТ ОШИБКУ —
отсутствует условие соединения
SELECT department_id, department_name
FROM Departments, Locations
WHERE city = 'London';
В этом запросе отсутствует условие соединения таблиц, поэтому будет
выполнено декартово произведение таблиц. Каждая строка таблицы
Departments соединится с каждой строкой таблицы Locations. Формально
это означает, что каждый отдел расположен во всех городах, поэтому при
выполнении этого запроса будет выведен список всех отделов, и этот спи
сок не будет меняться при изменении названия города. Правильный запрос
должен содержать условие соединения. Соединение этих таблиц осущест
вляется через столбец location id.
[ 147 '
PostgreSQL: SQL + PL/pgSQL
Следует иметь в виду то, что при создании базы данных между этими табли
цами была определена связь. Связь на уровне определения таблиц представ
ляет собой правило, которым должны удовлетворять данные, содержащиеся
в этих таблицах. Например, при вводе данных об отделе (Departments) нель
зя ввести значение locationid, которого нет в таблице Locations.
Запрос 5.2. Вывести номера и названия отделов,
расположенных в городе London
SELECT department_id, department_name, loc.location_id
FROM Departments dep, Locations loc
WHERE dep.location_id=loc.location_id
AND city = 'London';
department_idIdepartment_nameIlocation_idI
--------------- +----------------- +------------- +
40|Human Resources|
24001
В этом запросе, кроме добавления условия соединения, использованы псев
донимы таблиц. Так как столбец location id есть в обеих таблицах, то необ
ходимо использовать полное имя loc.locationid, loc — псевдоним таблицы
Locations.
Запрос 5.3. Вывести данные о товарах,
приобретал покупатель 45
которые
SELECT ord.order_id, ord.order_date, pr.product_name, oi.quantity,
oi.unit_price
FROM Orders ord, Order_Iterns oi, Products pr
WHERE ord.order_id = oi.order_id
AND oi.product_id = pr.product_id
AND ord.customer_id = 45
ORDER BY ord.order_id;
order_idIorder_dateIproduct_name
I quantity Iunit_priceI
---------------+------------------- +----------------------------------------------------------------- +--------------- +------------------- +
148
11128-11-2018|LG V30+ Black (H930DS)
|
30|12-08-2018|Lenovo IdeaPad 510-15 (80SV0047RK)I
30|12-08-2018|ASUS 27" VG279QM TUF Gaming
|
70|21-02-2017|Gigabyte B450M S2H mATX AM4
|
70|21-02-2017|HP ProBook 430 G4 (Y7Z47EA)
|
95 119-09-20191 Gigabyte 27" Aorus AD27QD-EK
|
95Ц9-09-20191Gigabyte B460M DS3H mATX LGA1200
I
1131
70 1
64 1
146|
32|
138 1
144 1
102121-12-20191 Gigabyte Z490 AORUS PRO AX LGA1200I
69|
560.001
2020.001
1160.001
1900.001
2800.001
1260.001
1900.001
■
1850.001
Глава 5. Многотабличные запросы
В запросе участвуют 3 таблицы, поэтому предложение WHERE содержит
два условия соединения и условие выбора.
В рассмотренных примерах строки первой таблицы соединялись с теми
строками второй таблицы, которые имели совпадающие значения столбца,
по которому осуществлялось соединение. Такой способ соединения таблиц
называется соединением по эквивалентности.
Рассмотрим другой способ соединения, который называют соединением по
неэквивалентности.
В соединениях по неэквивалентности вместо операции = используются дру
гие операции: > ,< , BETWEEN и др.
Данный тип соединения можно определять для таблиц, между которыми нет
связи на уровне определения данных. В качестве примеров использования
такого типа соединения рассмотрим следующие задачи.
Запрос 5.4. Для каждого сотрудника определить номера и
названия товаров, которые он имеет право продавать. Это
право определяется следующим правилом: рейтинг сотрудника
должен быть больше рейтинга товара или равен ему
SELECT employee_id, product_id, product_name, rating_e, rating_p
FROM Products, Employees
WHERE rating_e >= rating_p
ORDER BY employee_id;
Запрос 5.5. Для сотрудника employee_id =108 определить
должности, которые он имеет право занимать, получая
текущую зарплату. Право занимать должность определяется
следующим правилом: min_salary <= salary <= max_salary.
Значения min_salary, max_salary содержатся в таблице Jobs
SELECT employee_id, e.job_id, salary, j.job_id, min_salary,max_salary
FROM Employees e, Jobs j
WHERE salary BETWEEN min_salary AND max_salary
AND employee_id =108;
employee_idIj ob id|salary
Ij ob_idImin_salaryImax_salary|
----------- +------ +-------- +------ +----------- +----------- +
108 IFI_MGRI 12000.00 IFI_MGRI
108 IFI_MGR|12000.00|AC_MGR|
8200.001
8200.001
16000.001
16000.001
[ 149
PostgreSQL: SQL + PL/pgSQL
108
108
108
108
IFI_MGR112000.00
IFI_MGR112000.00
IFI_MGR|12000.00
IFI_MGR112000.00
.................................................. f PostgreSQL
ISA_MANI
ISA_REPI
IPU_MAN|
IMK_MANI
10000.001
6000.001
8000.001
9000.001
20000.001
12000.001
15000.001
15000.001
5.2. Условия соединения таблиц в предложении
FROM
Для соединения таблиц в предложении FROM используется
оператор JOIN, который имеет следующий синтаксис:-^
{таблица 1}{тип соединения} JOIN {таблица 2}
{условие соединения}
Таблицу, расположенную слева от оператора JOIN ({таблица 1}), будем на
зывать левой таблицей, а таблицу, расположенную справа от оператора JOIN
({таблица 2}), будем называть правой таблицей. Можно создавать два типа
соединений: внутренние и внешние.
5.2.1. Внутренние соединения
При использовании внутреннего соединения запрос будет выводить только
те строки левой таблицы, которые имеют связанные строки в правой табли
це. Условия соединения, указанные в предложении WHERE, создают вну
тренние соединения. Есть несколько вариантов определения внутреннего
соединения в предложении JOIN.
Оператор NATURAL JOIN (Естественное соединение) имеет следующий
синтаксис:
SELECT {список столбцов}
FROM {таблица 1} NATURAL JOIN {таблица 2}
Этот оператор соответствует операции соединения реляционной алгебры.
При использовании этого оператора необходимо, чтобы соединяемые табли
цы имели один или несколько одноименных столбцов. Строки левой табли
цы соединяются с теми строками правой таблицы, которые имеют совпада
ющие значения всех одноименных столбцов.
' 150 ]
Глава 5. Многотабличные запросы
Запрос 5.6а. Вывести названия населенных пунктов,
номера и названия отделов, которые в них расположены
SELECT location_id, city, department_id, department_name
FROM Locations NATURAL JOIN Departments;
Другой способ определения внутреннего соединения реализует оператор
INNER JOIN, который имеет следующий синтаксис:-;
{таблица 1}[INNER] JOIN {таблица 2}
{условие соединения}
Этот оператор является более общим способом реализации внутреннего со
единения и требует указаний условий соединения. Служебное слово INNER
можно не указывать.
Для определения условий соединения можно использовать следующие
конструкции:
USING ({имя столбца});
ON ({таблица 1.имя столбца}{оператор соединения}{таблица
2.имя столбца}
При использовании USING таблицы должны иметь одноименный столбец,
по которому будет осуществляться соединение.
Конструкция USING позволяет осуществлять соединение
по нескольким столбцам, в этом случае в качестве
параметра задается список столбцов. Строки левой таблицы
соединяются с теми строками правой таблицы, которые
имеют совпадающие значения всех столбцов из этого списка.
Рассмотрим пример использования конструкции USING.
Запрос 5.66. Вывести названия населенных пунктов,
номера и названия отделов, которые в них расположены
SELECT location_id, city, department_id, department_name
FROM
Locations
JOIN Departments USING (location_id);
PostgreSQL: SQL + PL/pgSQL
Конструкция ON предоставляет намного больше возможностей. Она
позволяет:
•
осуществлять соединение по столбцам, имеющим разные имена в левой
и правой таблице;
• осуществлять соединение по неэквивалентности.
Запрос 5.7. Вывести данные о заказах, которые оформил
сотрудник 165
SELECT employee_id, order_id, customer_id, order_date
FROM Employees JOIN Orders ON (employee_id=salesman_id)
WHERE employee_id =165;
employee_id|order_idIcustomer_id|order_dateI
—- ------- +------- +---------- +--------- +
1651
1651
66|
67|
36123-01-20201
13122-10-20181
В запросе 5.8 приведено решение задачи из запроса 5.4, которое требует со
единения по неэквивалентности, с использованием конструкции JOIN ON.
Запрос 5.8. Для каждого сотрудника определить номера и
названия товаров, которые он имеет право продавать
SELECT employee_id, product_id, product_name, rating_e, rating_p
FROM Products JOIN Employees ON (rating_e >= rating_p)
ORDER BY employee_id;
Строки, полученные в результате выполнения запроса, можно группировать
по значениям вычисляемых столбцов.
Запрос 5.9. Вывести общую сумму продаж за каждый месяц
2017 года
SELECT TO_CHAR(order_date, 'ММ') As Mon,
SUM(quantity*unit_price) As Sales
FROM Orders
JOIN Order_Items USING (order_id)
WHERE order_date BETWEEN '01/01/2017' AND '31/12/2017'
GROUP BY Mon
ORDER BY Mon;
J52J
Глава 5. Многотабличные запросы
mon I sales
I
---+------------ +
02
Ц635170.00І
670860.001
03
I
05
11132070.001
07
I
08
I
637800.001
09
I
582480.001
374120.001
Используя JOIN, можно установить соединение 3 и более таблиц. В общем
виде такое соединение можно представить следующим образом:
{таблица 1}
JOIN {таблица 2}{условие соединения 1}
JOIN {таблица 3}{условие соединения 2}
JOIN {таблица N}{условие соединения N-1}
Запрос 5.10. Для сотрудников из отдела 80 определить
общую сумму продаж
SELECT employee_id, first_name, last_name, job_id,
SUM (quantity*unit_jprice) As Sales
FROM Employees JOIN Orders ord ON (employee_id=salesman_id)
JOIN Order_Iterns oit ON (ord.order_id = oit.order_id)
WHERE department_id =80
GROUP BY employee_id, first_name, last_name, job_id;
employee_id | first_name
| last_name
179|Charles
161|Sarath
152|Peter
153|Christopher
157|Patrick
I
I
I
I
I
Johnson
Sewall
Hall
Olsen
Sully
|job_id|sales
|
ISA REP|1199100.00 I
|SA REP| 504730.00 |
ISA REP|1440050.001
ISA REP| 973650.001
|SA_REP| 191100.001
147|.........
Запрос 5.11. Определить общую сумму покупок клиентов,
вывести результаты в порядке убывания общей суммы покупок
SELECT customer_id, c_name, ROUND(SUM(quantity*unit_price)) As Sales
FROM Customers JOIN Orders USING(customer_id)
JOIN Order_Iterns USING(order_id)
PostgreSQL: SQL + PL/pgSQL
PostgreSQL.
GROUP BY customer_id, c_name
ORDER BY Sales DESC;
customer_idIc_name
------------+-------------------
I sales
I
+------- +
49|Vaillant Group
48|Electrolux Group
45|Nikon Corporation
2|Boeing
4 6|Liebherr
11880350 I
11512100 I
11221050 I
I 9604201
I 9501801
61...
В одном запросе можно использовать разные способы соединения таблиц.
Запрос 5.12 содержит другой вариант решения задачи из запроса 5.10, в ко
тором одновременно используются и ON, и USING.
Запрос 5.12. Для сотрудников из отдела 80 определить
общую сумму продаж
SELECT employee_id, first_name, last_name, job_id,
SUM(quantity*unit_price) As Sales
FROM Employees JOIN Orders ON (employee_id=salesman_id)
JOIN Order_Iterns USING(order_id)
WHERE department_id =80
GROUP BY employee_id, first_name, last_name, job_id;
5.2.2. Внешние соединения
При использовании внутренних соединений запрос выводит только те стро
ки левой таблицы, которые связаны со строками правой таблицы. При реше
нии некоторых задач необходимо выводить все строки таблиц, участвующих
в запросе. Для этого следует использовать внешние соединения.
Существуют три вида внешнего соединения:
1. Левое внешнее соединение;
2. Правое внешнее соединение;
3. Полное внешнее соединение.
Левое внешнее соединение
Синтаксис:
{таблица 1}LEFT [OUTER] JOIN {таблица 2}
{условие соединения}
Глава 5. Многотабличные запросы
Запрос будет выводить все строки левой таблицы и те строки правой табли
цы, которые связаны со строками левой таблицы. Если строка левой табли
цы не связана со строками правой таблицы, то столбцы правой таблицы для
этой строки будут иметь значение NULL.
Запрос 5.13. Вывести названия населенных пунктов,
находящихся в стране country_id = 'UK’, и названия
отделов, которые в них расположены
SELECT 1.location_id, city, department_name
FROM Locations 1 LEFT OUTER JOIN Departments d
ON (1.location_id = d.location_id)
WHERE country_id = ' UK' ;
location_id|city
Idepartment_name|
---------- +-------- +--------------+
2400|London
|Human
2500|0xford
| Sales
2600|Stretford|
Resources I
I
I
При использовании внутреннего соединения результат запроса не содержал
бы данных о населенном пункте Stretford, в котором нет отделов.
Запрос 5.14. Вывести данные обо всех населенных
пунктах, в которых нет отделов
SELECT 1.location_id, d.department_id
FROM Locations 1 LEFT OUTER JOIN Departments d
ON (1.location_id = d.location_id)
WHERE d.department_id IS NULL;
location_id|department_idI
------------ +--------------- +
2900|
1600|
1100 1..
|
|
В этом запросе использовано следующее свойство внешнего соединения:
если строка левой таблицы не связана со строками правой таблицы, то все
столбцы правой таблицы будут иметь значение NULL.
[ 155
PostgreSQL: SQL + PL/pgSQL
Ф
PostgreSQL
Запрос 5.15. Вывести названия населенных пунктов, имеющих
почтовые индексы 00989, 3095, M5V 2L7, 80925, и названия
отделов, расположенных в этих городах, если они есть
SELECT city, department_name
FROM Locations LEFT OUTER JOIN Departments USING (location_id)
WHERE postal_code IN ('00989', '3O95','M5V 2L7','80925');
city
|department_name |
-------- +------------------- +
Toronto|Marketing
|
Munich |Public Relations|
Roma
|
I
Bern
|
I
Правое внешнее соединение
Синтаксис:
{таблица 1} RIGHT [OUTER] JOIN {таблица 2}
{условие соединения]
Запрос будет обрабатывать все строки правой таблицы и те строки левой
таблицы, которые связаны со строками правой таблицы. Если строка правой
таблицы не связана со строками левой таблицы, то столбцы левой таблицы
для этой строки будут иметь значение NULL.
Может возникнуть вопрос: зачем нужны и левое, и правое внешние соедине
ния? Ведь можно просто поменять таблицы местами. Оба вида соединений
могут понадобиться в запросах, где участвуют 3 и более таблицы.
Рассмотрим следующую задачу: необходимо вывести данные о заказах. Эти
данные должны содержать информацию о сотруднике, который оформил за
каз, и о содержимом заказа. Правила предметной области допускают воз
можность отсутствия данных как о сотруднике, так и о содержимом заказа.
В используемой базе данных таблицы Employees и Order items находятся
по обе стороны таблицы Orders. Для решения этой задачи необходимо ис
пользовать левое и правое соединения.
Запрос 5.16. Необходимо вывести данные о заказах,
которые были оформлены в период с 10.05.17 по 31.05.17.
Данные должны содержать информацию о сотруднике, который
оформил заказ, его рейтинге и о содержимом заказа
Глава 5. Многотабличные запросы
SELECT employee_id,rating_e,order_id, order_date,product_id, quantity
FROM Employees RIGHT JOIN Orders ON (employee_id=salesman_id)
LEFT JOIN Order_Iterns USING(order_id)
WHERE order_date BETWEEN '10.05.17' AND '31.05.17'
ORDER BY order_date;
employee_idIrating_eIorder_idIorder_date|product_idI quantity I
-- +--1591
1531
1551
1451
1511
1
1
---- +--------- +--- ----- +-- ----- +
-----------+-------
31
51
51
31
31
1
1
41112-05-20171
34112-05-20171
44 121-05-2017 |
3126-05-20171
2Ц27-05-2017 |
20127-05-20171
19127-05-20171
1
15|
1
1
1
26|
38 1
1411
1
1
1051
53|
Существует еще одна разновидность оператора внешнего соединения, кото
рая выводит все строки обеих таблиц.
Полное внешнее соединение
Синтаксис:
{таблица 1} FULL [OUTER] JOIN {таблица 2}
{условие соединения}
Запрос будет анализировать все строки как правой, так и левой таблицы.
Запрос 5.17. Необходимо вывести данные о заказах,
которые были оформлены в период с 10.05.17 по 31.05.17.
Данные должны содержать информацию о сотруднике,
который оформил заказ, и о содержимом заказа
SELECT salesman_id,о.order_id,order_date ,item_id,product_id, quantity
FROM Orders о FULL join order_iterns oi on о.order_id=oi.order_id
WHERE order_date BETWEEN '10.05.17' AND '31.05.17';
salesman_id|order_idIorder_dateIitem_idIproduct_idI quantity I
------------+-------- +----------- +------- +----------- +-------- +
1
1
153 1
151 1
19127-05-20171
20127-05-20171
34 112-05-20171
21 1 27-05-2017|
11
11
11
1
38|
26|
151
1
53|
1051
141 1
1
PostgreSQL: SQL + PL/pgSQL
1591
1551
1451
.................................................. f PostgreSQL
41112-05-20171
44121-05-20171
3126-05-20171
|
|
|
|
|
|
|
|
|
В этом запросе не выводится значение столбца rating e, поэтому можно ис
пользовать только две таблицы: Orders и Order_Items, между которыми
устанавливается полное внешнее соединение.
5.3. Декартово произведение таблиц
Синтаксис:
{таблица 1} CROSS JOIN {таблица 2}
При выполнении этой операции каждая строка левой таблицы соединяется с
каждой строкой правой таблицы.
Напомним, что при отсутствии условий соединения
автоматически осуществляется декартово произведение
таблиц, и, как правило, это является ошибкой.
CROSS JOIN следует применять в тех случаях, когда вы сознательно исполь
зуете эту операцию.
Запрос 5.18. Для каждого сотрудника определить товары,
которые он не продавал
SELECT employee_id, product_id
FROM
Employees e CROSS JOIN Products p
WHERE p.product_id NOT IN
( SELECT DISTINCT product_id
FROM ORDERS JOIN ORDER_ITEMS USING (ORDER_ID)
WHERE employee_id = e.employee_id)
ORDER BY employee_id;
В этом запросе сначала с помощью декартова произведения генерируются
все возможные пары значений employee_id, productid, а потом исключа
ются строки, содержащие значения productid товаров, которые продавал
сотрудник.
Глава 5. Многотабличные запросы
5.4. Самосоединение таблицы
Самосоединением называется операция, при которой
строка таблицы соединяется с другими строками этой же
таблицы. Синтаксис и правила соединения остаются такими
же, как при соединении нескольких таблиц.
Рассмотрим следующую задачу: в таблице Employees необходимо найти
однофамильцев. Предположим сначала, что у нас есть две копии таблицы
Employees: Empl и Emp2.
Условие соединения этих таблиц при поиске однофамильцев может быть за
писано следующим образом
Empl.last_name = Emp2.last_name
AND
Empl.employee_id <> Emp2.employee_id
Эту задачу можно решить без использования копий таблицы. Для этого в
предложении FROM нужно определить два псевдонима таблицы и исполь
зовать эти псевдонимы при определении условий соединения.
Запрос 5.19. Вывести данные об однофамильцах
SELECT empl.emplоуee_id, empl.first_name, empl.last_name, empl.job_id
FROM Employees empl JOIN Employees emp2 ON
(empl.Iast_name=emp2.last_name
AND empl.employee_id<>emp2.employee_id)
ORDER BY empl.last_name;
employee_id I first_name | last_name | job_id
|
-------------------- +------------------ - +-------------- --+-------------- +
154|Nanette
148 1 Gerald
199|Douglas
178|Kimberely
100|Steven
156|Janette
171|William
1591 Lindsey
176 I Jonathon
180 1 Winston
1CambraultI SA REP
|
1Cambrault1 SA MAN
|
|SH CLERK 1
1 Grant
1SA_REP
1
1 Grant
|AD PRES 1
IKing
ISA REP
1
1 King'
1 Smith
ISA REP
1
1 Smith
1SA_REP
1
1 Taylor
1SA_REP
1
1 Taylor
1SH_CLERK1
159
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
B этом примере следует обратить внимание на то, что необходимо указывать
полные имена столбцов, так как формально мы используем две разные
таблицы.
В таблице Employees столбец managerid содержит значение employeeid
сотрудника, являющегося начальником данного сотрудника.
Запрос 5.20. Вывести имена сотрудников, являющихся
начальниками в отделе 80, и имена сотрудников, которые
находятся в их непосредственном подчинении
SELECT empl.employee_id As Boss,empl.firs t_name,empl.last_name,
emp2.employee_id As Subject,emp2.first_name, emp2.last__name
FROM Employees empl JOIN Employees emp2 ON (emp2.manager_id =
empl.employee_id)
WHERE empl.department_id=80
ORDER BY empl.employee_id;
boss |first_name I last_name I subject |first_name
---- +----------- +---------- +—
145|John
145|John
1451 John
1451 John
1451 John
145|John
1461 Karen
146 I Karen
I Russell
I Russell
I Russell
I Russell
I Russell
I Russell
I Partners
I Partners
I
|
|
|
I
|
|
I..
---- +-----------
I
1531 Christopher
1541 Nanette
1551 Oliver
1501 Peter
1521 Peter
1511 David
1571 Patrick
1 Olsen
ICambrault
ITuvault
1 Tucker
IHall
1 Bernstein
1 Sully
|last_name
1
|
1
1
1
1
1
Глава 5. Многотабличные запросы
Задачи для самостоятельного решения:
Задача 5.1. Вывести название отдела, которым руководит ме
неджер 108, и название города, в котором расположен отдел.
Задача 5.2. Вывести названия городов, имеющих почтовые ин
дексы 00989, 3095, М5Ѵ 217, 98199, и названия отделов, распо
ложенных в этих городах.
Вариант а. вывести только те города, где есть отделы.
Вариант б: вывести все города с заданными почтовыми индек
сами.
Задача 5.3. Вывести названия отделов и названия товаров, ко
торые продавали сотрудники этих отделов.
Вариант а: вывести только те отделы, сотрудники которых про
давали товары.
Вариант б. вывести все отделы.
Задача 5.4. Вывести даты продаж и общую сумму продаж за
каждую дату.
Задача 5.5. Вывести количество сотрудников и суммарную зар
плату сотрудников, работающих в каждом городе. Должны быть
выведены данные обо всех городах из таблицы Locations.
Задача 5.6. Вывести етрІоуее_ісІ менеджеров 80-го отдела и
суммарную зарплату сотрудников, находящихся в их непосред
ственном подчинении, за весь период работы.
Ро8І§ге8ОЕ: 8РЬ + PL/pgSQL
РозІдгеЮЬ
Задача 5.7. Вывести данные о сотрудниках, у которых сумма
продаж более чем в 50 раз больше зарплаты, которую они полу
чают.
Задача 5.8. Для каждого отдела определите отношение суммы
всех продаж, выполненных сотрудниками этого отдела, к сум
марной заработной плате этого отдела.
Задача 5.9. Выведите данные о зарплате сотрудников с итого
выми строками, которые содержат суммарную зарплату по каж
дой должности, отделу и городу. Исключить данные о сотрудни
ках, которые работают в США (соипІгу_ісІ ='118').
Глава 6.
ПОДЗАПРОСЫ
(ВЛОЖЕННЫЕ
ЗАПРОСЫ)
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Операторы DML могут содержать в себе вложенные операторы SELECT.
Такие запросы называются подзапросами. Чаще всего подзапросы исполь
зуются в предложениях WHERE и HAVING, для формирования условий
выбора, и в предложении FROM, в качестве источника данных.
В зависимости от того, как подзапрос взаимодействует с основным запро
сом, различают простые и коррелированные подзапросы.
6.1. Простые подзапросы
Простые подзапросы выполняются один раз, и результат
их выполнения используется основным запросом. Для
анализа результатов, которые вернул простой подзапрос,
можно использовать операции сравнения =, >, <, а также
операторы IN, ANY, ALL.
Рассмотрим примеры использования простых подзапросов.
Запрос 6.1. Найти сотрудников, зарплата которых больше
средней зарплаты по всей фирме
SELECT einployee_id, depar tment_id, first_name, last_name, salary
FROM Employees
WHERE salary > (SELECT AVG( salary) FROM Employees);
В этом примере подзапрос возвращает среднее значение зарплаты, а основ
ной запрос выводит данные о сотрудниках, зарплата которых больше среднего значения.
Глава 6. Подзапросы (вложенные запросы)
Запрос 6.2. Найти сотрудников, которые были приняты на
работу после сотрудника с номером 110
SELECT employee_id, depar tment_id, first_name, last_name, hire_date
FROM Employees
WHERE hire_date > (SELECT hire_date
FROM Employees WHERE employee_id=110);
В этом примере подзапрос возвращает дату приема на работу сотрудника
ПО, а основной запрос выводит данные о сотрудниках, которые были при
няты на работу позже этой даты.
Запрос 6.3. Найти сотрудника, который был принят на
работу раньше всех
SELECT employee_id,department_id, job_id, first_name, last_name, hire_date
FROM Employees
WHERE hire_date = (SELECT MIN(hire_date) FROM Employees);
employee_idIdepartment_id|job_id Ifirst_name|last_nameIhire_date |
----------- +-------------- +------- +----------- +----------+----------- +
100|
90|AD_PRES|Steven
|King
|17-06-1987|
В этом примере подзапрос возвращает минимальное значение даты приема
на работу (hire date), а основной запрос выводит данные о сотрудниках, ко
торые были приняты на работу в эту дату. Запрос 6.3 вывел данные только
об одном сотруднике, но в общем случае результат этого запроса мог бы со
держать несколько строк.
Запрос 6.4. Найти отделы, в которых средняя зарплата
сотрудников выше средней зарплаты по всей фирме
SELECT department_id, ROUND(AVG( salary)) FROM Employees
GROUP BY department_id
HAVING AVG( salary) >=(SELECT AVG( salary) FROM Employees);
department_idI round|
-------------- +----- +
70Ц0000 I
80| 89551
20| 95001
I 70001
90 I 19333 I
PostgreSQL: SQL + PL/pgSQL
1001 86001
110 110150 I
40| 65001
В этом примере подзапрос возвращает среднее значение зарплаты по всей
фирме, а основной запрос, который является запросом с группировкой, вы
водит данные об отделах, где средняя зарплата сотрудников больше средней
зарплаты по всей фирме.
6.1.1. Подзапросы и значение Null
Если подзапрос возвращает значение NULL или не возвращает ни одной
строки, то любая операция сравнения в основном запросе будет иметь зна
чение NULL (Не определено), и результат выполнения основного запроса
будет пуст. Рассмотрим запрос, который в дальнейшем будет использован
как подзапрос.
Запрос 6.5. Вывести значение столбца rating_e сотрудника,
у которого last_name = ’Nayer'
SELECT rating_e
FROM Employees
WHERE last_name = ’Nayer';
rating_eI
--------- +
Этот запрос вернул значение NULL, так как у этого сотрудника значение
столбца ratinge равно NULL. Если в условии ввести значение столбца
last name, которого нет в таблице Employees, например 'Noyer', то результат
этого запроса будет пуст.
Запрос 6.6. Вывести данные о сотрудниках, работающих в
отделе 50 и имеющих такой же рейтинг, как у сотрудника
last_name = 'Bissot'
SELECT employee_id,depar tment_id, job_id, first_name, last_name, rating_e
FROM Employees
WHERE department_id = 50
AND rating_e = (SELECT rating_e FROM Employees
WHERE last name = 'Bissot');
I 166 J
Глава 6. Подзапросы (вложенные запросы)
employee_idIdepartment_id|j ob_id
| first_name|last_name|rating_e|
----------- +-------------- +-------- 4.----------- +----------+-------- +
128 1
1291
140 1
1411
1851
187 1
192 1
195 1
501ST_CLERK|Steven
501ST_CLERK1 Laura
501ST_CLERK1 Joshua
50|ST_CLERK|Trenna
501SH_CLERK|Alexis
5 01SH_CLERK1 Anthony
501SH_CLERK|Sarah
501SH_CLERK|Vance
IMarkle
IBissot
1 Patel
IRajs
IBull
1 Cabrio
IBell
1 Jones
1
1
1
1
1
1
1
1
51
51
51
51
51
51
5|
51
1961
501SH_CLERK|Alana
IWalsh
1
51
Если вместо условия last_name = 'Bissot' поставить last_name = 'Nayer' или
last_name = 'Noyer', то результат запроса будет пуст, даже в том случае, если
в отделе 50 будут сотрудники, у которых столбец ratinge имеет значение
NULL.
Запрос 6.7. Вывести данные о сотрудниках, работающих в
отделе 50 и имеющих рейтинг NULL
SELECT employee_id, department_id, job_id, first_name, last_name,
rating_e
FROM Employees
WHERE department_id = 50
AND rating_e IS NULL;
employee_idIdepartment_idIj ob_id| first_nameIlast_name
Irating_eI
----------- +-------------- +------ +----------- +------------ +-------- +
126|
501
I Irene
IMikkilineniI
|
Если подзапрос возвращает несколько строк, то для формирования условий
в предложении WHERE нужно использовать операторы IN, ANY, ALL.
6.1.2. Использование выражения IN
Рассмотрим следующую задачу:
Запрос 6.8. Вывести значения столбцов department_id,
department_name отделов, расположенных не в Соединенных
Штатах Америки (соибгу_1б=’УЗ')
167
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
SELECT department_id, department_name
FROM Departments WHERE location_id NOT IN
(SELECT location_id FROM Locations
WHERE country_id ='US');
department_id|department_name I
--------------- +-------------------+
20(Marketing
40 I Human Resources
70 I Public Relations
80 I Sales
|
I
I
I
В этом примере подзапрос возвращает коды городов (locationid), которые
расположены в США. Основной запрос выводит данные об отделах,
location id которых нет в результатах выполнения подзапроса.
Запрос 6.9. Определить номера клиентов, которые
заказывали товары, имеющие рейтинг 3
SELECT DISTINCT customer_id
FROM Orders JOIN Order_Iterns USING (order_id)
WHERE product_id IN
(SELECT product_id FROM Products
WHERE rating_p = 3) ;
customer__id |
69|
45|
60 |
49|
48 |
47 |
39 |
44 |
Запрос 6.10. Вывести имена и фамилии всех однофамильцев
SELECT first_name, last_name
FROM Employees
WHERE last_name IN
(SELECT last_name
FROM Employees
GROUP BY last_name
168
Глава 6. Подзапросы (вложенные запросы)
HAVING COUNT(
)>1)
*
ORDER BY last name;
first_name I last_name I
+----------------- +
Nanette
Gerald
Douglas
Kimberely
Steven
Janette
William
Lindsey
Jonathon
Winston
ICambrault|
ICambraultI
I Grant
|
I Grant
|
(King
|
IKing
|
(Smith
|
I Smith
|
(Taylor
|
I Taylor
|
В этом примере подзапрос является запросом с группировкой. Группиров
ка осуществляется по столбцу lastname. Условие HAVING COUNT(
)>1
*
оставляет в результате выполнения подзапроса только те значения last
name, которые встречаются более одного раза.
6.1.3. Использование выражений ALL и ANY
Выражения ALL и ANY используются в комбинации с операторами сравне
ния. Выражение ALL возвращает значение TRUE, если оператор сравнения
выполняется для всех элементов, возвращаемых подзапросом, а выражение
ANY возвращает значение TRUE, если оператор сравнения выполняется
хотя бы для одного элемента.
Запрос 6.11. Найти сотрудников, чья зарплата выше всех
зарплат сотрудников из отдела 50
SELECT employee_id, department_id, job_id, first_name, last_name,
salary
FROM Employees WHERE salary > ALL
(SELECT salary FROM Employees
WHERE department_id = 50);
•
Если написать < ALL, то запрос вернет данные о сотрудниках, зарплата
которых меньше зарплаты всех сотрудников из отдела 50.
[ 169 ^
PostgreSQL: SQL + PL/pgSQL
........................................... PostgreSQL
• Если написать = ALL, то результат запроса будет пуст, так как одно значе
ние не может быть одновременно равно нескольким значениям. Исклю
чение составляет ситуация, когда все значения, возвращаемые подзапро
сом, одинаковы.
•
Если написать < ANY, то запрос вернет данные о сотрудниках, зарплата
которых меньше зарплаты хотя бы одного сотрудника из отдела 50.
•
Если написать > ANY, то запрос вернет данные о сотрудниках, зарплата
которых больше зарплаты хотя бы одного сотрудника из отдела 50.
• Если написать =ANY, то запрос вернет данные о сотрудниках, зарплата
которых совпадает с одним из значений зарплаты сотрудников, работаю
щих в отделе 50.
В качестве упражнения рекомендуется написать запросы без использования
операторов ALL и ANY, результат которых совпадает с результатом запросов
из этого списка.
Рассмотрим неочевидные особенности операторов ALL и ANY, которые мо
гут стать причиной трудно обнаруживаемых ошибок.
6.1.4. Выражения ALL, ANY и значение NULL
Запрос 6.12. Вывести значения столбца rating_e
сотрудников, которые работают в отделе 100
SELECT rating_e
FROM Employees
WHERE department_id = 100;
rating_eI
21
31
41
41
II
4 I
Используя этот запрос в качестве подзапроса, решим следующую задачу:
170
Глава 6. Подзапросы (вложенные запросы)
Запрос 6.13. Вывести значения столбцов етр1оуее_ісі,
гаЪіпд_е сотрудников, которые работают в отделе 60 и
рейтинг которых больше рейтинга любого сотрудника из
отдела 100
SELECT employee_id, rating_e
FROM Employees
WHERE department_id = 60
AND rating_e > ALL
(SELECT rating_e
FROM Employees
WHERE department_id = 100) ;
employee_idIrating_e|
------------ +
+
105|
5|
Заменим в этом запросе выражение ALL на ANY.
Запрос 6.14. Вывести значения столбцов етр1оуее_ісі,
гаЪіпд_е сотрудников, которые работают в отделе 60
и рейтинг которых больше рейтинга хотя бы одного
сотрудника из отдела 100
SELECT employee_id, rating_e
FROM Employees
WHERE department_id = 60
AND rating_e > ANY
(SELECT rating_e
FROM Employees
WHERE department_id = 100);
employee_idIrating_e|
+-------- +
103 I
104 1
106|
107 1
105 1
3
3
4
3
5
Присвоим столбцу rating_e значение NULL у сотрудника НО, который ра
ботает в отделе 100.
171
PostgreSQL: SQL + PL/pgSQL
UPDATE Employees
SET rating_e = NULL
WHERE employee_id = 110;
Если теперь выполнить запрос 6.13, то он не вернет ни одной строки, а ре
зультат выполнения запроса 6.14 не изменится, и это остается справедливым
для любой операции сравнения.
Причиной этого является то, что если в списке значений, анализируемых
оператором ALL, будет присутствовать значение NULL, то этот оператор,
совместно с любой операцией сравнения, вернет значение FALSE, а на ре
зультат оператора ANY значения NULL не влияют.
6.1.5. Выражения ALL, ANY и пустые подзапросы
Рассмотрим работу выражений ALL и ANY в том случае, если подзапрос,
который они обрабатывают, будет пуст. Для этого в операторе из примера
6.17 заменим номер отдела 100 на 120.
Запрос 6.15. Вывести значения столбцов ешр1оуее_ісІ,
гаЪіпд_е сотрудников, которые работают в отделе 60
и рейтинг которых больше рейтинга хотя бы одного
сотрудника из отдела 120
SELECT employee_id, rating_e
FROM Employees
WHERE department_id = 60
AND rating_e > ANY
(SELECT rating_e
FROM Employees
WHERE department_id = 120);
employee_id|rating_e|
------------ +--------- +
В таблице Employees нет сотрудников, работающих в отделе 120, следова
тельно, подзапрос не вернет ни одной строки, поэтому результат выполнения
запроса 6.15 будет пуст, и это справедливо для любой операции сравнения.
172
Глава 6. Подзапросы (вложенные запросы)
Если в запросе из примера 6.15 вместо оператора ANY использовать опера
тор ALL, то запрос вернет данные обо всех сотрудниках, которые работают
в отделе 60.
Запрос 6.16. Вывести значения столбцов employee_id,
rating_e сотрудников, которые работают в отделе 60 и
рейтинг которых больше рейтинга любого сотрудника из
отдела 120
SELECT employee_id, rating_e
FROM Employees
WHERE department_id = 60
AND rating_e > ALL
(SELECT rating_e
FROM Employees
WHERE department_id = 120);
employee_idIrating_eI
103 I
104 I
1061
107 I
105|
31
31
4|
31
51
Причиной этого является то, что если подзапрос, который обрабатыва
ют операторы ANY и ALL, будет пуст, то оператор ANY вернет значение
FALSE, а оператор ALL вернет значение TRUE совместно с любой операци
ей сравнения.
6.1.6. Многостолбцовые подзапросы
Многостолбцовые подзапросы возвращают значения
нескольких столбцов. Они могут быть однострочными и
многострочными.
Этот вид подзапросов можно использовать в предложении WHERE. Для
сравнения значений нескольких столбцов со списком значений, возвраща
емых многостолбцовым подзапросом, используется оператор IN, который в
этом случае должен иметь следующий синтаксис:
»
PostgreSQL: SQL + PL/pgSQL
WHERE
({список столбцов})
IN
(SELECT {список значений} FROM .. )
Список столбцов и список значений должны содержать одинаковое количе
ство элементов, а тип столбца также должен совпадать с типом соответству
ющего ему значения.
Запрос 6.17. Вывести данные о сотрудниках, у которых
должность и зарплата совпадают с должностью и
зарплатой сотрудника 106
SELECT employee_id, job_id, salary
FROM Employees
WHERE (job_id, salary) IN
(SELECT DISTINCT job_id, salary
FROM Employees
WHERE employee_id = 106)
AND employee_id <> 106;
employee_idIjob_id I salary |
------------ +-------- +-------- +
105|IT_PROG|4800.001
Запрос 6.18. Вывести данные о сотрудниках, у которых
значение зарплаты и рейтинг совпадают с одним из
значений зарплаты и рейтинга сотрудников job_id = ’ІТ_
PROG’, но занимающих другую должность
SELECT employee_id, job_id, salary, rating_e
FROM Employees
WHERE (salary, rating_e) IN
(SELECT DISTINCT salary, rating_e
FROM Employees
WHERE job_id ='IT_PROG'
AND rating_e Is Not NULL)
AND job_id <>'IT_PROG';
employee_idIjob_id
(salary |rating_e|
------------ +----------- +-------- +--------- +
109|FI_ACCOUNT|9000.001
3|
Подзапросы можно использовать в качестве источника данных. В одних
случаях это позволяет существенно сократить время выполнения запроса, в
других обойти ограничения в использовании элементов языка SQL.
Глава 6. Подзапросы (вложенные запросы)
Рассмотрим следующую задачу: требуется вывести данные об отделах, в
которых работает более 10 сотрудников. Решение этой задачи без использо
вания подзапросов содержится в запросе 6.19, а решение с использованием
подзапроса содержится в запросе 6.20. Оба запроса формируют один и тот
же результат.
Запрос 6.19. Требуется вывести данные об отделах, в
которых работает более 10 сотрудников. Без использования
подзапроса
SELECT d. depar tment_id, d.department_name, d.manager_id, COUNT(*)
AS number_emp
FROM Departments d JOIN Employees e ON (d.department_id=e.
department_id)
GROUP BY d.department_id, d.department_name, d.manager_id
HAVING COUNT(*) >10;
Запрос 6.20. Требуется вывести данные об отделах,
которых работают более 10 сотрудников
в
SELECT d.department_id, d.department_name, d.manager_id, e.count_emp
FROM Departments d JOIN
(SELECT department_id, COUNT(*) AS count_emp
FROM Employees
GROUP BY department_id
HAVING COUNT(*) > 10) e
ON (d.department_id=e.department_id);
department_id I department_name | manager_id I count_emp I
-------------- +----------------- +---------- +--------- +
501 Shipping
80|Sales
I
I
121|
1451
451
34|
Проанализируем действия, которые должна выполнить СУБД, в процессе
выполнения этих запросов. Обозначим п - число отделов, m - число
сотрудников.
Запрос 6.19.
1. Соединение таблиц Departments и Employees потребует п*ш операций
сравнения. (Так как каждый сотрудник может работать только в одном
отделе, в результате соединения может быть получено не более m строк.)
2. Группировка m*n строк.
^~.<й,е^
Ѵ*ХІ^й^
PostgreSQL: SQL + PL/pgSQL
Ф PostgreSQL
3. Проверка условия HAVING COUNT(*) > 10.
Запрос 6.20.
1. Группировка в подзапросе п строк.
2. Проверка условия COUNT(*) > 10 (в результате будет получено к
строк, равное количеству отделов, для которых выполняется условие
COUNT(*)> 10).
3. Соединение таблицы Departments с результатом выполнения подзапроса,
n*k строк.
Так как к меньше т, то время выполнения запроса 6.20 будет
меньше.
Очень часто подзапросы позволяют обойти ограничения языка SQL. Одним
из таких ограничений является ограничение на вложенность агрегатных
функций. С таким ограничением мы сталкиваемся при решении следующей
задачи: найти отдел с максимальной суммарной заработной платой.
Рассмотрим основные этапы решения этой задачи.
Запрос 6.21. Вывести данные о 5 отделах с наибольшими
значениями суммарной заработной платы
SELECT department_id, SUM(salary)As s_salary
FROM Employees
GROUP BY department_id
ORDER BY s_salary desc
LIMIT 5;
department_id|s_salary I
--------------- +---------- +
80 I 304500.00 I
50Ц56400.00|
90| 58000.001
1001 51600.001
60| 28800.001
На первый взгляд может показаться, что для решения рассматриваемой за
дачи можно использовать выражение LIMIT 1. Но такое решение следует
считать неправильным (неточным), так как максимальный размер суммар
ной заработной платы может быть у нескольких отделов.
^ 176 ]
Глава 6. Подзапросы (вложенные запросы)
Для определения максимальной суммарной заработной платы отдела следу
ет использовать запрос 6.22. В подзапросе используется агрегатная функция
SUM() для вычисления суммарной заработной платы каждого отдела, а в ос
новном запросе агрегатная функция МАХ() находит максимальное значение
этой суммы.
Запрос 6.22. Вывести максимальное значение суммарной
заработной платы отдела
SELECT MAX(s_salary)
FROM (SELECT department_id, SUM(salary)As s_salary
FROM Employees
GROUP BY department_id) d_sum_sal;
max
I
---------- +
304500.00 I
Используя запрос 6.22 как подзапрос, получим решение рассматриваемой
задачи следующим образом.
Запрос 6.23. Найти отдел с максимальной суммарной
заработной платой
SELECT department_id, SUM(salary)As s_salary
FROM Employees
GROUP BY department_id
HAVING SUM(salary)=
(SELECT MAX(s_salary)
FROM (SELECT SUM(salary)As s_salary
FROM Employees
GROUP BY department_id) sml);
department_idIs_salary |
------------ — —-f---- —------ h
80 I 304500.00 I
6.2. Коррелированные подзапросы
Коррелированный подзапрос использует один или
несколько столбцов основного запроса и выполняется для
каждой строки основного запроса.
Г 177
PostgreSQL: SQL + PL/pgSQL
Запросы, содержащие коррелированный подзапрос, выполняются следую
щим образом. Выбирается первая строка таблицы, сформированная основ
ным запросом. Значения определенных столбцов этой таблицы передаются
в подзапрос. Если эти значения удовлетворяют условиям подзапроса, то эта
строка помещается в результат выполнения основного запроса. После этого
выбирается вторая строка и т.д.
Запрос 6.24. Найти сотрудников, у которых зарплата
выше средней зарплаты отдела, в котором они работают
SELECT first_name, last_name, department_id, salary
FROM Employees о
WHERE o.salary > (SELECT AVG(i.salary)
FROM Employees i
WHERE i.department_id = о.department_id);
Этот запрос выполняется следующим образом:
1. Выбирается первая строка таблицы Employees о.
2. Значение столбца o.departmentid передается в коррелированный подза
прос, который возвращает среднее значение зарплаты отдела, в котором
работает рассматриваемый сотрудник.
3. Если зарплата рассматриваемого сотрудника больше средней зарплаты
отдела, в котором он работает, то данные о таком сотруднике помещаются
в результат выполнения основного запроса.
Запрос 6.25. Найти сотрудников, которые получают
зарплату больше чем 90% от максимально допустимой
зарплаты по должности, которую они занимают
SELECT employee_id, first_name, last_name, job_id, salary
FROM Employees e WHERE salary >
0.9*( SELECT max_salary FROM Jobs j WHERE j.job_id=e.job_id);
employee_id 1 first_name 1last_name 1 job_id
------------ +---------- -+-------109|Daniel
1101 John
1201 Matthew
121|Adam
1221Payam
1681 Lisa
1 salary
1
----- +--------------- +
1Faviet
|FI_ACCOUNT| 9000.001
1 Chen
1 FI ACCOUNT 1 8200.00 1
1ST_MAN
1 Weiss
1 8000.001
1 Fripp
1ST_MAN
1 8200.001
1 Kaufling 1ST_MAN
1 7900.001
1 Ozer
1SA_REP
111500.001
Глава 6. Подзапросы (вложенные запросы)
174|Е11еп
204|Hermann
206|William
|АЬе1
IBaer
IGietz
|SA_REP
111000.001
IPR_REP
I 10000.00 I
|AC_ACCOUNT| 8300.001
В этом запросе в подзапрос передается код должности (e.job_id), которую
занимает сотрудник. Подзапрос возвращает максимальное значение зарпла
ты по этой должности (max_salary). После этого проверяется условие salary
>0.9*max_salary. Если это условие выполняется, то данные о сотруднике
помещаются в результат выполнения основного запроса.
Рассмотрим пример использования коррелированного подзапроса в предло
жении SELECT.
Запрос 6.26. Вывести название отдела, которым
руководит менеджер 108, название города, в котором
расположен 'отдел, имя и фамилию менеджера
SELECT d.manager_id,(SELECT first_name||' '||last_name
FROM Employees
WHERE employee_id=d.manager_id)AS name,
department_name, city
FROM Departments d JOIN Locations USING(location_id)
WHERE d.manager_id=l08;
manage r_idI name
----------- +----
Idepartment_nameI city
I
+----------------- +-------- +
108 I Nancy Greenberg I Finance
I Seattle I
В этом запросе коррелированный подзапрос содержится в предложении
SELECT. В подзапрос передается значение столбца d.manager, и он возвра
щает значения столбцов first name и last name, которые объединяются в
одну строку операцией конкатенации. Без использования конкатенации бу
дет возникать ошибка.
6. 2.1. Выражение EXISTS
При работе с коррелированными подзапросами часто используется выраже
ние EXISTS, которое возвращает значение TRUE в том случае, если резуль
тат выполнения подзапроса не пуст.
PostgreSQL: SQL + PL/pgSQL
Запрос 6.27. Вывести данные о сотрудниках, которые
работают в отделе 80 и руководят другими служащими
SELECT department_id, first_name, last_name, salary
FROM Employees o
WHERE department_id = 80 and EXISTS
(SELECT employee_id
FROM Employees i
WHERE i.manager_id = o.employee_id);
department_idI first_name|last_nameI salary
|
--------------- +----------- +---------- +--------- +
80|John
80 I Karen
80|Alberto
80|Gerald
80|Eleni
IRussell
Ц4000.00|
I Partners 113500.00 1
IErrazuriz112000.00 I
|Cambrault|11000.00|
IZlotkey
|10500.00|
В этом запросе в подзапрос передается значение столбца employeeid. Если
это значение встречается в столбце manager_id, то это означает, что рассма
триваемый сотрудник руководит другими служащими. Результат выполне
ния подзапроса будет не пуст, EXISTS вернет значение TRUE, и основной
запрос выведет данные об этом сотруднике.
Запрос 6.28. Определить товары, которые не продавал
сотрудник с номером 109
SELECT * FROM Products р
WHERE NOT EXISTS
(SELECT * FROM Orders JOIN Order_Items oi USING (order_id)
WHERE oi.product_id = p.product_id
AND salesman_id = 109) ;
В подзапрос передается значение столбца product_id рассматриваемого то
вара. Если сотрудник 109 не продавал этот товар, то результат выполнения
подзапроса будет пуст, NOT EXISTS вернет TRUE и основной запрос вы
ведет данные об этом товаре.
Очевидно, что если результат этого запроса будет пуст, то это будет означать,
что сотрудник продавал все товары. Используя это, решим следующую за
дачу:
Глава 6. Подзапросы (вложенные запросы)
Запрос 6.29. Найти сотрудников, которые продавали все
товары
SELECT * FROM Employees
WHERE NOT EXISTS
(SELECT * FROM Products p
WHERE NOT EXISTS
(SELECT * FROM Orders JOIN Order_Iterns oi USING (order_id)
WHERE oi.product_id = p.product_id
AND salesman_id = employee_id));
В этом примере запрос 6.28 используется в качестве подзапроса. Из основ
ного запроса в подзапрос передается значение столбца employee id. Если
рассматриваемый сотрудник продавал все товары, то результат подзапроса
будет пуст и данные об этом сотруднике попадут в результат выполнения
основного запроса.
6.3. Использование оператора WITH
При создании сложных запросов, содержащих большое число подзапросов,
рекомендуется использовать оператор WITH. В этом операторе подзапросам
присваиваются имена. Эти имена используются в основном запросе как име
на таблиц. Использование WITH улучшает производительность и облегчает
чтение запроса. Синтаксис:
WITH {имя подзапроса!} AS (подзапрос!),
{имя подзапроса2} AS (подзапрос2),
SELECT список столбцов
FROM {таблица} | {имя подзапроса} | {view}
WHERE {условия};
Для того чтобы проиллюстрировать возможности, которые предоставляет
оператор WITH, рассмотрим задачу из запроса 6.23.
Запрос 6.30. Найти отдел с максимальной суммарной
заработной платой, используя оператор WITH
WITH
D_Sum_Sal As
(SELECT department_id, SUM(salary)As s_salary
FROM Employees
PostgreSQL. SQL + PL/pgSQL
..................................................
w
PostgreSQL
GROUP BY department_id),
D_Max_Sal As
(SELECT MAX(s_salary)as max_sum
FROM D_SUM_SAL)
SELECT department_id
FROM D_Sum_Sal
WHERE s_salary=(SELECT max_sum FROM D_Max_Sal);
department_id|
------------- +
80 I
Этот запрос легче анализировать, что упрощает процесс отладки и сопрово
ждения.
Решим следующую задачу: вывести данные о клиентах, у которых средняя
сумма заказа превышает общую среднюю сумму заказа. Рассмотрим сначала
запросы, которые нам понадобятся для решения этой задачи.
Запрос 6.31. Для каждого клиента вычислить среднюю
сумму заказа
SELECT customer_id, AVG(quantity*unit_price) As avg_sum
FROM Orders
JOIN Order_Items USING (order_id)
GROUP BY customer_id
ORDER BY avg_sum DESC;
Запрос 6.32. Определить среднюю сумму одного заказа
SELECT AVG(quantity *unit_price)
FROM
Order_Items;
As avg_total
Используя эти запросы и оператор WITH, можем представить решение рас
сматриваемой задачи в следующем виде:
Запрос 6.33. Вывести данные о клиентах, у которых средняя
сумма заказа превышает общую среднюю сумму одного заказа
WITH
Avg_C AS
(SELECT customer_id, AVG(quantity*unit_price) As avg_cust
FROM Orders JOIN Order_Items USING (order_id)
Глава 6. Подзапросы (вложенные запросы)
GROUP BY customer_id),
Avg_T AS
(SELECT AVG(quantity*unit_price) As avg_total
FROM Order_Iterns)
SELECT customer_id, c_name
FROM Customers est
WHERE (SELECT avg_cust FROM Avg_C
WHERE customer_id = est.customer_id)
> (SELECT avg_total FROM Avg_T );
6.4. Составные запросы
Результат выполнения оператора SELECT, который возвращает несколько
строк, можно рассматривать как множество. Используя операции над мно
жествами, можно комбинировать результаты, возвращаемые двумя или бо
лее операторами SELECT, и формировать из них единый результат. Такие
запросы называют составными. Для создания составных запросов можно
использовать следующие операции:
• UNION [ALL] — объединяет результаты выполнения двух операторов
SELECT. Результат может содержать повторяющиеся строки.
• INTERSECT [ALL] — осуществляет пересечение результатов выполне
ния двух операторов SELECT и возвращает только те строки, которые
содержатся в результатах обоих запросов.
• EXCEPT [ALL] — из результатов выполнения левого оператора SELECT
удаляет строки, которые возвращает правый оператор SELECT.
При использовании ALL результат может содержать одинаковые строки.
Запросы, к которым применяются эти операции, должны удовлетворять сле
дующим условиям:
1. Они должны возвращать одинаковое количество столбцов.
2. Типы соответствующих столбцов должны совпадать или быть совмести
мыми.
3. Имена соответствующих столбцов могут быть различными.
Составной запрос может быть представлен в следующем виде:
[ 183 ^
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
SELECT А {Операция 1} SELECT В
[{Операция 2} SELECT C .. ]
Операции имеют одинаковый приоритет и выполняются слева направо. Из
менить порядок выполнения операций можно, используя скобки.
Рассмотрим сначала простые запросы, которые потом будут использованы в
составных запросах.
Запрос 6.34. Вывести номера клиентов,
оформлял сотрудник 153
заказы которым
SELECT DISTINCT customer_id
FROM Orders
WHERE salesman_id = 153
ORDER BY customer_id;
customer_id|
44 I
45 I
46|
48 I
49|
Запрос 6.35. Вывести номера клиентов,
оформлял сотрудник 149
заказы которым
SELECT DISTINCT customer_id
FROM Orders
WHERE salesman_id = 149
ORDER BY customer_id;
customer__id |
------------ +
47|
48 |
Используя эти запросы в качестве компонентов составного запроса, можем
получить решения следующих задач.
184
Глава 6. Подзапросы (вложенные запросы)
Запрос 6.36. Вывести номера клиентов,
оформляли сотрудники 149 и 153
заказы которым
SELECT customer_id
FROM Orders WHERE salesman_id = 153
UNION
SELECT customer_id
FROM Orders WHERE salesman_id =149
ORDER BY customer_id;
customer_idI
44 |
45 |
46|
47|
48 |
49|
Запрос 6.37. Вывести номера клиентов, которым
оформляли заказы и сотрудник 153, и сотрудник 149
SELECT customer_id
FROM Orders WHERE salesman_id = 153
INTERSECT
SELECT customer_id
FROM Orders WHERE salesman_id =149
ORDER BY customer_id;
customer_idI
48 I
Запрос 6.38. Вывести номера клиентов, которым оформлял
заказы сотрудник 153, исключив тех клиентов, которым
оформлял заказы сотрудник 149
SELECT customer_id
FROM Orders WHERE salesman_id = 153
EXCEPT
SELECT customer_id
FROM Orders WHERE salesman_id = 149
ORDER BY customer id;
PostgreSQL: SQL + PL/pgSQL
customer_idI
44 I
45 I
46|
49|
Составные операторы могут быть использованы в
качестве подзапросов.
Запрос 6.39. Вывести данные о клиентах,
оформляли сотрудники 149 и 153
SELECT customer_id, c_name
FROM customers HERE customer_id IN
(SELECT customer_id
FROM Orders WHERE salesman_id = 153
UNION
SELECT customer_id
FROM Orders WHERE salesman_id = 149);
customer_idIc_name
44|Canon Inc.
45|Nikon Corporation
4 6|Liebherr
47IDAIKIN INDUSTRIES
48|Electrolux Group
4 9|Vaillant Group
I
заказы которым
Глава 6. Подзапросы (вложенные запросы)
Задачи для самостоятельного решения:
Задача 6.1. Найти заказы, сумма которых больше максималь
ной суммы заказа, оформленного сотрудником 109.
Задача 6.2. Найти сотрудников, зарплата которых с учетом ко
миссионных больше зарплаты их начальников.
Задача 6.3. Найти отделы, которые являются единственными в
той стране, где они находятся.
Задача 6.4. Вывести данные о сотрудниках, получающих 7-ю по
величине зарплату.
Задача 6.5. Вывести значения столбцов departments_id,
employeejd, salary сотрудников, у которых оба значения salary
и commission_pct совпадают со значениями salary и commission_
pct хотя бы одного сотрудника из отдела 30.
Задача 6.6. Для каждого сотрудника вывести число месяцев,
прошедших между датой приема на работу этого сотрудника и
датой приема на работу первого сотрудника в отдел, в котором
работает сотрудник.
Задача 6.7. Определить год и месяц, когда у сотрудника 152
была максимальная сумма продаж.
Задача 6.8. Для каждого дня продаж, осуществленных в мае
2017, вывести данные о заказе, который имеет максимальную
сумму из всех заказов, которые были оформлены в этот день.
PostgreSQL: SQL + PL/pgSQL
Задача 6.9. В таблице Orders найти продавцов (salesmanjd), у
которых список клиентов, совпадает со списком клиентов про
давца 179. Клиентом является покупатель (customerjd), кото
рым продавец оформлял заказы.
Задача 6.10. Вывести данные: количество лет, которые прора
ботал сотрудник и среднюю зарплату сотрудников, для каждо
го значения количества лет. Исключить данные о менеджерах
(jobjd содержит подстроку 'MAN'), и администраторах (jobjd
содержит подстроку 'AD').
188
Глава 7.
ОПЕРАТОРЫ
МОДИФИКАЦИИ ДАННЫХ
PostgreSQL: SQL + PL/pgSQL
В PostgreSQL
При работе с базой данных необходимо добавлять, изменять и удалять дан
ные. Для выполнения этих операций используются следующие операторы
модификации данных:
• INSERT (вставка новых строк);
• UPDATE (изменение значения столбцов);
• MERGE (слияние строк);
• DELETE (удаление строк).
Если при выполнении операторов модификации данных будет нарушено
ограничение ссылочной целостности, то операторы не выполняются, а вы
водится сообщение об ошибке. Ограничение ссылочной целостности будет
рассмотрено в разделе 7.5.
Эти операторы не осуществляют вывод измененных данных, отображается
только число модифицированных строк. Для вывода данных, которые были
изменены, следует добавить предложение^
RETURNING *|[список столбцов]
После выполнения операторов INSERT, UPDATE, MERGE будут выведены
данные, получившиеся в результате модификации, а при выполнении опера
тора DELETE будут выведены строки, которые были удалены.
Глава 7. Операторы модификации данных
7.1. Оператор INSERT
Оператор INSERT используется для добавления (вставки)
новых строк в таблицу. Можно вставить одну строку или
несколько строк, полученных в результате выполнения
оператора SELECT.
Оператор INSERT для добавления одной строки имеет следующий формат:
INSERT имя таблицы [список столбцов]
VALUES (список значений)
[список столбцов] нужно указывать в том случае, если список значений не
совпадает со списком столбцов таблицы.
Добавление данных о новом товаре:
INSERT INTO Products
VALUES (88, 'ASUS X540LB',4,1800);
При выполнении оператора^
INSERT INTO Products
VALUES (89, 'ASUS X555LB', 1800) ;
возникнет ошибка^
SQL Error [23514] : ОШИБКА: новая строка в отношении "products"
нарушает ограничение-проверку "product_r"
Подробности: Ошибочная строка содержит (89, ASUS X555LB, 1800, null).
Причиной ошибки является попытка присвоить столбцу rating p значение
1800, а для этого столбца установлено ограничение 1<= rating_p <=5. Эта
ошибка не возникнет, если указать столбцы, которым присваиваются значе
ния.
*
В PostgreSQL
PostgreSQL: SQL + PL/pgSQL
INSERT INTO Products (product_id, product_name, price)
VALUES (89, 'ASUS X555LB',1800);
Столбцу ratingp, который отсутствует в списке, будет присвоено значение
NULL.
7.1.1. Вставка значений, заданных по умолчанию
При создании таблицы для каждого столбца можно задать значение по умол
чанию (DEFAULT), например:
• для столбца orderdate в таблице Orders это текущая дата, возвращаемая
функцией CURRENT DATE;
• для столбца status в таблице Orders задано значение по умолчанию
Pending (в ожидании).
Для того чтобы столбцу при вставке новых строк было присвоено значение
по умолчанию, нужно в списке значений указать служебное слово DEFAULT.
Запрос 7.1. Ввести данные о новом заказе, присвоив
столбцам order_date и status значения по умолчанию
INSERT INTO Orders (order_id, customer_id, salesman__id, order_date,
status)
VALUES (105, 18, 175, DEFAULT, DEFAULT)
RETURNING *;
order_id|customer_idI status Isalesman_id|order_date|
-------- +------------ +------- +------------ +----------- +
105|
18|Pending|
17512023-06-10 I
Значение по умолчанию будет присвоено и в том случае, если DEFAULT
будет отсутствовать в предложении VALUES. Например:
INSERT INTO Orders (order_id, customer_id, salesman_id)
VALUES (105, 18, 175);
Если указать значение DEFAULT для столбца, у которого не задано значение
по умолчанию, то ему будет присвоено значение NULL.
Глава 7. Операторы модификации данных
Запрос 7.2. Ввести данные о новом заказе, присвоив
столбцам salesman_id, order_date, status значения по
умолчанию
INSERT INTO Orders (order_id, customer_id, salesman_id, order_date,
status)
VALUES (106, 18, DEFAULT, DEFAULT, DEFAULT)
RETURNING *;
order_id|customer_id|status |salesman_id|order_date|
-------- +------------ +------- +------------ +-----------+
1061
18|Pending|
12023-06-101
7.1.2. Вставка нескольких строк
Можно вставить в таблицу несколько строк, сформированных в результате
выполнения оператора SELECT. В этом случае оператор INSERT должен
иметь следующий формат:
INSERT имя таблицы [список столбцов]
SELECT [список столбцов] текст запроса;
Списки столбцов после имени таблицы и после SELECT должны совпадать.
Если список столбцов, которые возвращает запрос, точно соответствует
списку столбцов таблицы, то список столбцов после элемента имя таблицы
можно не указывать.
Создадим таблицу РгобпсІвЮЩІ, которая должна содержать данные о това
рах и общее количество товара, которое было реализовано:^
CREATE TABLE Products_Total
( product_id INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL,
rating_p INTEGER,
quantity INTEGER);
Запрос 7.3. Заполнить данными таблицу Products_Total
INSERT INTO Products_Total
SELECT pr.product_id,pr.product_name,pr.rating_p, SUM(quantity)
As quantity
PostgreSQL: SQL + PL/pgSQL
FROM Products pr JOIN Order_Iterns oi ON(pr.product_id=oi.
product_id)
GROUP BY pr.product_id,pr.product_name,pr.rating_p;
Фрагмент содержимого таблицы ProductsTotal после выполнения этого
запроса:
product_idI name
Irating_p|quantity I
63 I Asus X99-E-10G WS|
69 I Xiaomi Mi5 32GB
|
43|AMD 100-50606
|
62 I
67 I
31
31
51
19|
Можно создать новую таблицу и заполнить ее данными, используя один оператор, который имеет следующий синтаксис:
CREATE TABLE имя таблицы As
SELECT ...
J<
Запрос 7.4. Создать копию таблицы Products и заполнить
ее данными о товарах, которые ни разу не продавались
CREATE TABLE Products_Ns As
SELECT * FROM Products
WHERE product_id NOT IN
(SELECT DISTINCT product_id
FROM Order_Iterns);
Фрагмент содержимого таблицы Products_Ns после выполнения этого
запроса:
product_idIproduct_name
Irating_p|price
70 I Xiaomi Mi6 64Gb|
44|ASUS X540LA
|
92 I ASUS X540LC
|
|
31480.001
51950.001
4|
1
Используя этот вид оператора вставки строк, можно реализовать операцию
денормализации таблиц путем объединения данных, содержащихся в
нескольких таблицах, и записью полученного результата в одну таблицу.
Глава 7. Операторы модификации данных
Запрос 7.5. Создать таблицу Product_Sales, которая
должна содержать данные о сумме продаж каждого товара
за каждый месяц каждого года
CREATE TABLE Product_Sales As
SELECT product_id,TO_CHAR(order_date, 'YYYY MM') As MON ,
SUM(quantity*unit_price) As Sales
FROM Orders
JOIN Order_Items USING (order_id)
GROUP BY product_id, TO_CHAR(order_date, 'YYYY MM');
Фрагмент содержимого таблицы ProductSales после выполнения этого
запроса:
product_idI mon
----------- +---
112018
112020
612017
712017
712018
1012018
1012020
I sales
I
+---------------- +
10 1 36480.00
05 1194370.00
02|1 311040.00
02 1 90440.00
03 і1283220.00
08 Ц49600.00
09!1286000.00
7.2. Оператор UPDATE
Оператор UPDATE используется для изменения существующих строк в
таблице и имеет следующий синтаксис:
UPDATE {имя таблицы)
SET столбец = {значение{|{выражение}|{запрос}
WHERE {условия};
Этот оператор изменяет значения столбцов тех строк, которые удовлетворя
ют заданным условиям. Следует обратить внимание на то, что новое значе
ние столбца может быть результатом запроса, который возвращает скаляр
ное значение.
Запрос 7.6. Установить новую зарплату, равную 8500,
для сотрудника 110
UPDATE Employees
SET salary = 8500
WHERE employee_id = 110;
195
PostgreSQL: SQL + PL/pgSQL
..................................................f PostgreSQL
Запрос 7.7. Увеличить на 10% зарплату сотрудников,
работающих в отделе 70
UPDATE Employees
SET salary = salary*1.1
WHERE department_id = 70;
7.2.1. Присвоение значений, заданных по умолчанию
В рассматриваемой предметной области может быть определено следую
щее правило: если в отделе нет назначенного начальника, то начальником
этого отдела является руководитель предприятия Steven King, employee_id
которого равен 100. Для обеспечения этого правила значение по умолчанию
столбца manager id в таблице Departments равно 100.
Запрос 7.8. Присвоить значение по умолчанию столбцу
manager_id в таблице Departments для отдела 10
UPDATE departments
SET manager_id = DEFAULT
WHERE department_id = 10;
Можно изменить значения нескольких столбцов в одном операторе UPDATE.
Запрос 7.9. Установить сотруднику 122 новую должность,
оклад и рейтинг
UPDATE Employees
SET Job_id = 'SA_MAN',
salary = 10000,
rating_e = 4
WHERE employee_id = 122;
Присваиваемое значение может быть результатом выполнения запроса, ко
торый возвращает одну строку. Этот запрос может возвращать значения од
ного или нескольких столбцов.
Запрос 7.10. Сотруднику 122 изменить значение столбца
department_id на значение, которое имеет этот столбец
у сотрудника 147
Глава 7. Операторы модификации данных
UPDATE Employees
SET department_id =
(SELECT department_id FROM Employees
WHERE employee_id =147)
WHERE employee_id = 122;
Запрос 7.11. Сотруднику 122 изменить значение столбца
department_id и job_id на значение, которые имеют эти
столбцы у сотрудника 147
UPDATE Employees
SET (department_id, job_id) =
(SELECT department_id, job_ID FROM Employees
WHERE employee_id = 147)
WHERE employee_id = 122;
7.2.2. Обновление строк с использованием коррелированного
подзапроса
Создадим копию таблицы Order items, назовем новую таблицу Order items
Сору:
CREATE TABLE Order_Items_Copy As
SELECT * FROM Order Items;
и добавим в эту таблицу новый столбец rating_p:
ALTER TABLE Order_Items_Copy
ADD Column rating_p integer;
Запрос 7.12. Заполнить столбец rating_p в таблице Order
Items_Copy данными, извлекая их из таблицы Products
UPDATE Order_Items_Copy as oic
set rating__p = (SELECT p.rating_j> FROM Products p
WHERE p.product_id=oic.product_id)
RETURNING *;
Фрагмент таблицы Order Items Copy после выполнения этого оператора:
197
....................................................................................... Ф PostgreSQL
PostgreSQL: SQL + PL/pgSQL
о rde г id 1 item id 1 product _id| quantity 1 unit_price1 rating_p1
78|
81
32 1
35|
60|
611
67 |
87 1
51
11
11
11
11
11
11
11
79|
34|
14|
76|
15|
16|
52|
11
101
144 1
86|
99|
36|
67|
85|
57|
2000.001
150.001
700.001
1160.001
280.001
730.001
500.00 1
640.001
11
51
41
11
4|
41
21
41
В этом запросе основной запрос последовательно рассматривает все строки
таблицы Order_Items_Copy. Для каждой строки основного запроса в корре
лированный подзапрос передается значение столбца product_id. Подзапрос
возвращает значение столбца rating_p рассматриваемого товара, которое
присваивается одноименному столбцу в таблице OrderltemsCopy.
Рассмотрим пример запроса, когда присваиваемое значение является вычис
ляемым и использует данные из нескольких таблиц.
Ранее мы создали таблицу Product_Sales, которая содержит данные о реали
зации каждого товара за каждый месяц.
Запрос 7.13. Обновить таблицу Product_Sales
UPDATE Product_Sales ps
set sales =
(select sales
from (SELECT product_id,TO_CHAR(order_date, 'YYYY MM') As MON,
SUM (quantity*unit_j3rice) As Sales
FROM Orders
JOIN Order_Items USING (order_id)
GROUP BY product_id, TO_CHAR(order_date, 'YYYY MM')) sg
WHERE ps.product_id = sg.product_id
AND ps.mon= sg.mon);
Этот запрос обновит данные о продаже товаров и месяцах, которые уже со
держатся в таблице Product Sales. Данные о продаже других товаров и за
другие месяцы добавлены не будут. Для решения такой задачи следует ис
пользовать оператор MERGE.
7.3. Оператор MERGE
Данный оператор позволяет сливать строки из одной таблицы в другую
таблицу. Если в таблице-приемнике, куда осуществляется слияние, суще-
Глава 7. Операторы модификации данных
ствуют строки, для которых выполняется условие слияния, то выполняются
операции обновления (UPDATE), в противном случае выполняется опера
ция вставки новых строк (INSERT).
Синтаксис оператора MERGE:
MERGE INTO [ONLY] {Таблица приемник}
USING {Таблица или запрос источник}
ON {условие слияния}
WHEN MATCHED THEN DELETE|UPDATE
SET {столбец 1} = {значение 1/выражение 1}
[{Столбец n} = {значение n/выражение n}]
WHEN NOT MATCHED THEN INSERT
VALUES ({список столбцов});
Если перед именем таблицы содержится служебное слово ONLY, то соответ
ствующие строки обновляются или удаляются только в таблице-приемнике.
Если only не указано, то совпадающие строки также обновляются или уда
ляются во всех таблицах, наследуемых от таблицы-приемника.
Если этот оператор будет содержать предложение WHEN MATCHED THEN
DELETE, то в таблице-приемнике будут удалены строки, для которых вы
полняется условие слияния, и будут вставлены строки из таблицы-источни
ка, для которых это условие не выполняется.
Рассмотрим примеры использования оператора MERGE. На рисунке 7.1
показано содержимое таблицы Orders l, которая содержит данные о 4 за
казах, имеющих статус Pending. На рисунке 7.2 показано содержимое
таблицы Orders_2, которая содержит новые данные о заказах. Эта табли
ца может содержать данные о заказах из таблицы Orders l, с измененным
значением столбца status, и данные о новых заказах. В результате слияния
этих таблиц нужно изменить значение столбца status и добавить данные о
новых заказах.
Рис. 7.1. Содержимое таблицы Orders l
PostgreSQL: SQL + PL/pgSQL
orders^ o
’4*
' /me SQ
вЫр&*€>we чтЫз ы отфильтровать pe зульпюты
1^5 customerjd
1
4'
№ PostgreSQL
^ I «»c status
^
123 salesman_id
^ | ^ order_date
4
Shipped
145
^
2019-09-26
2
3
5
Shipped
146
2019-09-26
3
6
6
Pending
145
2019-10-15
4
7
7
Pending
147
2019-10-15
5___
9
8
Shipped
147
2019-10-05
Puc. 7.2. Содержимое таблицы Orders_2
Далее будет рассмотрено несколько вариантов оператора слияния этих
таблиц, для этого нужно создать копии таблицы Orders 1, которые будут
иметь имена Orders 11 и Orders 1 2.
Запрос 7.14. Выполнить слияние таблиц Orders_l и 0rders_2
MERGE INTO Orders_l ordl USING Orders_2 ord2
ON ordl.order_id = ord2.order_id
WHEN MATCHED THEN
UPDATE SET status = ord2.status
WHEN NOT MATCHED THEN
INSERT (order_id, customer_id, status,
salesman_id, order_date)
VALUES (ord2.order_id, ord2.customer_id, ord2.status,
ord2.salesman_id, ord2.order_date);
B orders_ 1
rc
hr
z C ^^ k^ €
1Щ crder_id
SQL вЬ'рОЖ£-<ие ч^'-ot Ь/ ОТ £v^fr,po&ai 1'6 реЗ'/РЬ'ТіОІГЫ
▼ | 123 customerjd
▼
«t status
t
| 123 salesmanjd
IT
*-* r.date
1
5
5
Pending
156
2019-09-09
2
8
28
Pending
156
2019-09-09
3
2
4
Shipped
145
2019-09-26
4
3
5
Shipped
146
2019-09-26
5
6
6
Pending
145
2019-10-15
6
7
7
Pending
147
2019-10-15
7
9
8
Shipped
147
Рис. 7.3. Содержимое таблицы OrdersI после выполнения запроса 7.14
Анализируя эти данные, можно установить, что в процессе выполнения
запроса 7.14:
•
данные о заказах 5 и 8 не изменились;
•
изменен статус заказов 2 и 3;
•
добавлены данные о заказах 6, 7, 9.
.........................................................................................................................................................................................................................................................................................................
Глава 7. Операторы модификации данных
Запрос 7.15. Выполнить слияние таблиц Orders_l_l и
0rders_2, используя предложение WHEN MATCHED THEN DELETE
MERGE INTO Orders_l_l ordl USING Orders_2 ord2
ON ordl.order_id = ord2.order_id
WHEN MATCHED THEN DELETE
WHEN NOT MATCHED THEN
INSERT (order_id, customer_id, status,
salesman_id, order_date)
VALUES (ord2.order_id, ord2.customer_id, ord2.status,
ord2.salesman_id, ord2.order_date);
5 row(s) modified.
: PR OrdetS 1 1
• те i
2^Ж' Si
Щ order.id
5 1
2
Eh
t4
11___
•
Ц С'^'У’ 7’^&&ЯъГГр&$^
customerjd
* 1 йВС status
P5
* | 123 salesman_id
sr
£| order_date
sr
5
Pending
156
2019-09-09
8
28
Pending
156
2019-09-09
6
7
6
7
Pending
145
2019-10-15
Pending
147
2019-10-15
9
8
Shipped
147
2019-10-05
Puc. 7.4. Содержимое таблицы Orders ! после выполнения запроса 7.15
Анализируя эти данные, можно установить, что в процессе выполнения
запроса 7.15:
• удалены данные о заказах 2 и 3, которые удовлетворяют условиям
слияния;
•
данные о заказах 5 и 8 остались без изменений;
•
вставлены данные о заказах 6, 7, 9.
В качестве источника данных при слиянии можно использовать оператор
SELECT.
Запрос 7.16. Слияние таблицы 0rders_l_2 только с теми
строками таблицы 0rders_2, которые удовлетворяют
условию salesman_id = 145
MERGE INTO Orders_l_2 ordl USING
(SELECT * FROM orders_2 WHERE salesman_id = 145) ord2
ON (ordl.order_id=ord2.order_id)
[ 201 2
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
WHEN MATCHED THEN
UPDATE SET status = ord2.status
WHEN NOT MATCHED THEN
INSERT (order_id, customer_id, status,salesman_id, order_date)
VALUES (ord2.order_id, ord2.customer_id,
ord2.status, ord2.salesman_id, ord2.order_date);
2 row(s) modified.
Рис. 7.5. Содержимое таблицы Orders_l_2 после выполнения запроса 7.16
Анализируя содержимое таблицы Orders 12 после выполнения запроса
7.16, можно установить, что изменилось состояние заказа 2 и добавлены
данные о заказе 6. Эти заказы оформлял сотрудник 145.
Вернемся к задаче обновления таблицы Product Sales, которая содержит
данные о сумме продаж каждого товара за каждый месяц каждого года.
Для того чтобы результат обновления был более наглядным, выведем дан
ные о продажах товара 78.
SELECT * FROM Product_Sales
where product_id=78
ORDER BY mon;
product_idI mon
|sales
|
----------- +-------- +--------- +
7812017
7812017
7812018
7812019
07144440.001
09163800.001
10132120.001
1Ц 61160.00|
Выведены данные только об одном товаре, так как таблица РгогіисІ_8а1е$
содержит много строк. Добавим данные о двух новых заказах, которые со
держат товар 78.
Глава 7. Операторы модификации данных
INSERT INTO Orders (order_id,customer_id,status ,salesman_
id,order_date)
VALUES (117, 10, 'Pending', 145, TO_DATE('27-09-2019','DD-MMYYYY'));
INSERT INTO
Order_Iterns
(order_id,item_id,product_
id,quantity,unit_price)
VALUES (117, 1, 78, 10, 1360);
INSERT INTO Orders (order_id,customer_id,status,salesman_
id,order_date)
VALUES (127,10, 'Pending', 145, TO_DATE('20-11-2019','DD-MMYYYY')) ;
INSERT INTO Order_Iterns
(order_id, item_id,product_id,quantity,unit_price)
VALUES (127, 1, 78, 20, 1380);
После добавления новых данных о новом заказе таблицу Products Total нуж
но обновить.
Запрос 7.17. Обновление таблицы Product_Sales с
использованием оператора MERGE
MERGE INTO Product_Sales ps USING
(SELECT product_id, TO_CHAR(order_date, 'YYYY MM') As MON,
SUM(quantity*unit_price) As Sales
FROM Orders
JOIN Order_Items USING (order_id)
GROUP BY product_id, TO_CHAR(order_date, 'YYYY MM')) sg
ON ((ps.product_id = sg.product_id) AND (ps.mon= sg.mon))
WHEN MATCHED THEN
UPDATE SET sales = sg.sales
WHEN NOT MATCHED THEN
INSERT (product_id, mon, sales)
VALUES (sg.product_id, sg.mon, sg.sales);
Выведем данные о продажах товара 78 после обновления^
select * FROM product_sales
where product_id=78
ORDER BY mon;
PostgreSQL: SQL + PL/pgSQL
........................................... Ф PostgreSQL
product_idI mon
|sales
I
----------- +-------- +--------- +
7812017
7812017
7812018
7812019
7812019
07144440.001
09163800.001
10132120.001
09113600.001
1Ц 88760.00|
Анализируя эти данные, можно увидеть, что в таблицу ProductSales добав
лена новая строка о продажах товара 78 за 2019-09 и обновлено значение
столбца sales за 2019-11. Это означает, что обновление таблицы Product_
Sales успешно выполнено, и она содержит все актуальные данные на момент
выполнения запроса.
7.4. Оператор DELETE
Оператор DELETE используется для удаления существующих строк в
таблице и имеет следующий синтаксис:
DELETE FROM
таблица
[WHERE условия];
При наличии предложения WHERE удаляются только те строки, которые
удовлетворяют заданным условиям. Если предложение WHERE отсутству
ет, то будут удалены все строки.
В запросах, рассматриваемых в этом разделе, будет изменяться содержимое
таблиц: Customers, Products, Orders, Orders Items. Для того чтобы сохранить
содержимое этих таблиц, которое мы будем использовать далее, были созда
ны копии этих таблиц.
Запрос 7.18. Удалить данные о товаре 77
DELETE FROM Products_Copy
WHERE product_id =77
RETURNING *;
product_idIproduct_name
---------- +-------------
Irating_pI price|
+-------- +----- +
77|Logitech G810 Orion Spectrum (920-007750)1
1140.001
Глава 7. Операторы модификации данных
Запрос успешно выполнен, это означает, что товар 77 не продавался.
Запрос 7.19. Удалить данные о товаре 75
DELETE FROM Products_Copy
WHERE product_id =75
returning *;
SQL Error [23503]: ОШИБКА: UPDATE или DELETE в таблице "products_
copy" нарушает ограничение внешнего ключа "ord_it_pr_fк" таблицы
"order_items_copy".
Подробности: На ключ (product_id)=(75) всё ещё есть ссылки в
таблице "order_items_copy".
При выполнении этого запроса возникла ошибка, так как товар 75 продавал
ся и удаление данных о нем привело бы к нарушению ограничения ссылоч
ной целостности.
Запрос 7.20. Удалить данные о заказах клиента 5,
которые были отменены
DELETE FROM Orders_Copy
WHERE customer_id = 5 and status ='Canceled’
RETURNING *;
order_idIcustomer_idI status
Isalesman_idIorder_dateI
---------+------------ +--------- +------------ +----------- +
5|
3|
5|Canceled|
5|Canceled|
156|2018-03-15|
145|2017-05-26|
Подзапросы могут быть использованы в операторе DELETE для формирова
ния условий удаления.
Запрос 7.21. Удалить данные о товарах, которые ни разу
не продавались
DELETE FROM Products_Copy
WHERE product_id NOT IN
(SELECT DISTINCT product_id
FROM order_items_copy) ;
В этом запросе простой подзапрос извлекает из таблицы Order Items Copy
список товаров, которые продавались. Основной запрос последовательно
просматривает товары и удаляет те товары, которых нет в этом списке.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Рассмотрим пример использования коррелированных подзапросов в опера
торе DELETE.
Запрос 7.22. Удалить данные о заказах, если общая сумма
всех заказов клиента превышает его кредитный лимит
DELETE FROM Orders_Copy ос
WHERE
((SELECT credit_limit from customers_copy
where customer_id = oc.customer_id)
(SELECT
SUM(quantity*unit_price)
FROM Orders_Copy JOIN Order_items_Copy USING (order_id)
WHERE customer_id = oc.customer id));
В этом примере используется два коррелированных подзапроса. Первый
подзапрос возвращает кредитный лимит клиента, который оформил заказ, а
второй подзапрос возвращает общую сумму его заказов.
Следует обратить внимание на то, что в этом запросе последовательно по од
ному просматриваются заказы. Заказ удаляется, если выполняется заданное
условие, соответственно уменьшается сумма заказов клиента. После удале
ния определенного числа заказов каждого клиента общая сумма заказов кли
ента уменьшится, и заказы удаляться не будут.
7.5. Ошибки нарушения ограничения ссылочной
целостности при выполнении операторов
изменения данных
Ограничение ссылочной целостности состоит в том, что столбец, являю
щийся внешним ключом подчиненной таблицы, может принимать значения,
совпадающие с одним из значений столбца, являющегося первичным клю
чом главной таблицы, или иметь значение NULL. Это ограничение проверя
ется при выполнении операторов изменения данных. Если выполнение опе
ратора изменения данных приведет к нарушению ограничения ссылочной
целостности, то этот оператор не будет выполнен и будет выдано сообщение
об ошибке. Рассмотрим примеры операторов, которые вызывают нарушение
ограничения ссылочной целостности.
Глава 7. Операторы модификации данных
Нарушение ограничения ссылочной целостности при вставке
новых строк
Между таблицами Employees и Departments определена связь и установлено
ограничение внешнего ключа: значение столбца department_id в таблице
Employees должно соответствовать одному из значений столбца department_
id в таблице Departments, поэтому при попытке добавить нового сотрудника,
указав номер несуществующего отдела, возникнет ошибка, и оператор не
будет выполнен.
INSERT INTO Employees(employee_id, first_name, last_name, email,
department_id, hire_date, job_id, salary)
VALUES(302,'Gregory','Polyakov', 'gpolyakov', 300,CURRENT_DATE,
'IT_PROG', 4200);
SQL Error [23503]: ОШИБКА: INSERT или UPDATE в таблице "employees"
нарушает ограничение внешнего ключа "emp_dept_fк".
Подробности: Ключ (department_id)=(300) отсутствует в таблице
"departments".
Нарушение ограничения ссылочной целостности при обновлении
данных
Изменить значение столбца department id в таблице Employees на значе
ние, которого нет в столбце department_id таблицы DEPARTMENTS.
UPDATE Employees SET department_id = 300
WHERE employee_id = 155;
SQL Error [23503]: ОШИБКА: INSERT или UPDATE в таблице "employees"
нарушает ограничение внешнего ключа "emp_dept_fк".
Подробности: Ключ (department_id)=(300) отсутствует в таблице
"departments".
При удалении данных нужно учитывать ограничения целостности. Если для
внешнего ключа не установлены правила поддержания ссылочной целост
ности ON DELETE SET NULL или DELETE CASCADE, то из главной
таблицы нельзя удалять строки, которые связаны со строками подчиненной
таблицы. Такая ошибка возникнет при выполнении следующего запроса:
PostgreSQL: SQL + PL/pgSQL
DELETE FROM Employees
WHERE employee_id = 201;
SQL Error [23503]: ОШИБКА: UPDATE или DELETE в таблице "employees"
нарушает ограничение внешнего ключа "emp_manager_fк" таблицы
"employees".
Подробности: На ключ (employee_id)=(201) всё ещё есть ссылки в
таблице "employees".
Эта ошибка возникла из-за того, что для столбца managerid в таблице
Employees определено ограничение внешнего ключа, а правил поддержания
ссылочной целостности ON DELETE SET NULL или DELETE CASCADE
для этого ограничения нет. Это приводит к тому, что нельзя удалять данные
о сотрудниках, которые являются руководителями других сотрудников.
208
Глава 7. Операторы модификации данных
Задачи для самостоятельного решения:
Задача 7.1. Создать копию таблицы Employees, с дополнитель
ным столбцом total_salary, и заполнить ее данными. Значение
столбца total_salary должно быть равно полной зарплате со
трудника с учетом комиссионных.
Задача 7.2. Создать таблицу ЕМР (employee id, first_name, last_
name, hire_date, rating_e, working, layer) и заполнить данными о
сотрудниках, работающих в отделе 80. Столбцу working присво
ить значение, равное количеству полных лет, которые проработал
сотрудник. А значение столбца layer будет зависеть от значения
столбца rating_e. Если rating_e равен 5, то Іауег= 'А', если rating_e
равен 4 или 3, то Іауег= 'В', у остальных сотрудников Іауег= 'С.
Задача 7.3. Увеличить на 1 гайпд_е сотрудников, которые осу
ществили продажи на сумму более 1000000 и имеют гайпд_е < 5.
Задача 7.4. В таблице Огиег_Кетз_Сору у заказа 78 для то
вара 23 установите цену продажи (ипИ_ргісе), равную текущей
цене этого товара (ргісе) в таблице РгобисІ5_Сору.
Задача 7.5. Добавить в таблицу ЕтрІоуеез_Сору столбец
етр_заІѲ5 и присвоить ему значение общей стоимости продаж,
осуществленных каждым сотрудником.
Задача 7.6. Создайте таблицу Order_ltems_New, которая со
держит данные о новых продажах, и заполните ее данными.
Выполните слияние таблицы Order_ltems_Copy с таблицей
Order_ltems_New. Алгоритм слияния: если в таблице Order_
ltems_Copy существует строка, у которой значения столбцов
orderjd, productjd совпадают со значениями этих столбцов в
добавляемой строке из таблицы Order_ltems_New, то нужно об
новить значение столбца quantity, в противном случае вставить
новую строку.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Задача 7.7. Выполните слияние таблицы Ordersl только с теми
строками таблицы Orders2, в которых заказы находятся в
состоянии 'Shipped'.
Задача 7.8. Удалить данные об отмененных заказах (status =
'Canceled'), с даты оформления которых прошло более 5 лет.
Задача 7.9. Удалить из таблицы Оггіег_Иет8_Сору данные о
продаже товаров, которые нарушают правило: рейтинг продав
ца должен быть больше или равен рейтингу товара.
Глава 8.
ОПЕРАТОРЫ
ОПРЕДЕЛЕНИЯ
ДАННЫХ
PostgreSQL: 8РЬ + PL/pgSQL
РозідгеЗОЬ
В этой главе будут рассмотрены операторы для создания и редактирования
объектов базы данных. Наиболее важным видом объектов базы данных явля
ются таблицы, поэтому большая часть этой главы посвящена рассмотрению
операторов, предназначенных для создания, редактирования и управления
таблицами. После этого будут рассмотрены правила выполнения этих опе
раций для других видов объектов: представлений, последовательностей, ин
дексов.
База данных — это совокупность взаимосвязанных
таблиц, предназначенных для хранения данных о
некоторой предметной области. Кроме таблиц, база данных
может содержать и другие объекты: представления,
последовательности, индексы, хранимые процедуры и
функции, триггеры.
База данных может содержать одну или несколько схем, каждая из которых
содержит собственный набор таблиц. Обычно в отдельную схему объединя
ют таблицы, предназначенные для решения определенной прикладной задачи.
Таблица представляет собой именованный набор строк.
Столбцы таблицы имеют имя и должны содержать данные
одного типа. Ячейки таблицы должны содержать только
простые, неделимые данные.
В современных СУБД, к которым относится PostgreSQL, последнее требова
ние не выполняется, поэтому такие СУБД называют псевдореляционными.
^ 212 ]
Глава 8. Операторы определения данных
Для создания и редактирования объектов базы данных
используются операторы определения данных — DDL (Data
Definition Language).
Операторами определения данных являются:
•
CREATE — используется для создания объектов базы данных;
•
ALTER — используется для редактирования объектов базы данных;
•
DROP — используется для удаления объектов базы данных.
Изучение правил использования этих операторов начнем с создания новой
базы данных POST1 и пользователя user_postl, который будет обладать
правами разработчика:
CREATE DATABASE POST1;
CREATE ROLE user_postl SUPERUSER CREATEDB LOGIN PASSWORD 'user_postl';
GRANT ALL PRIVILEGES ON DATABASE POST1 TO user_postl;
Эти команды должен ввести пользователь, обладающий правами админи-
PostgreSQL: SQL + PL/pgSQL
f
PostgreSQL
После подключения создадим схему РОС
CREATE SCHEMA РОС;
В этой схеме создадим некоторые таблицы из схемы HRPOC.
8.1. Создание таблиц базы данных
Для того чтобы создать таблицу, нужно задать ее имя и определить столбцы.
Каждому столбцу нужно присвоить имя, определить тип и размер. Типом
столбца могут быть встроенные типы данных и большинство типов, создан
ных пользователями. Для столбца можно задать значение по умолчанию и
ограничения, которым он должен удовлетворять.
Оператор создания таблицы может быть представлен следующим образом:
CREATE TABLE [{имя схемы}].{Имя таблицы}
({Имя столбца 1} {Тип столбца_1}
[CONSTRAINT {имя_ограничения_с1}
код_ограничения_с1]
[DEFAULT значение_1],
({Имя столбца и} {Тип столбца_п}
[CONSTRAINT имя_ограничения_сп
код_ограничения_сп]
[DEFAULT значение_п],
[CONSTRAINT {имя_ограничения_Ъ1}
код_ограничения_Ъ1,]
[CONSTRAINT {имя_ограничения_1п}
код_ограничения_ѣп]);
Это краткое описание синтаксиса оператора CREATE TABLE, с полным
описанием можно ознакомиться в официальной документации.
Оператор CREATE TABLE создаст новую таблицу, не
содержащую строк. Пользователь, который ввел этот
оператор, будет обладать всеми привилегиями для
выполнения операций с этой таблицей.
^ 214 ]
Глава 8. Операторы определения данных
Если задано имя схемы, то таблица создается в указанной схеме. В против
ном случае она создается в текущей схеме.
Запрос 8.1. Оператор создания таблицы Customers
CREATE TABLE Customers
( customer_id int4 NOT NULL,
c_name varchar(255) NOT NULL,
address varchar(255),
credit_limit numeric(10,2),
CONSTRAINT customer s_id_j>k PRIMARY KEY (customer_id)
);
8.1.1. Значения по умолчанию
Для столбца можно задать значение по умолчанию. Если оно не указано, то
этому столбцу в добавляемой строке будет присвоено значение NULL.
Присваиваемое значение может быть константой или выражением. В каче
стве примера приведем присвоение столбцу order_date, имеющему тип
DATE, значения текущей даты.
order_date DATE DEFAULT CURRENT_DATE,
8.2. Ограничения
Важным элементом при создании таблиц является задание ограничений,
которые позволяют отслеживать правильность модификации имеющихся
данных или вставляемых в таблицу новых данных.
Ограничения могут быть определены на уровне столбца
или на уровне таблицы.
В запросе 8.1 строка:
CONSTRAINT customers_idj)k PRIMARY KEY (customer_id)
содержит ограничение PRIMARY KEY, определенное на уровне таблицы.
[ 215
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
В общем виде, определение ограничения выглядит следующим образом:
CONSTRAINT {имя__ограничения}
{определение_ограничения}
Имя ограничения рекомендуется задавать следующим образом:
{Имя таблицы}__{имя_столбца}_{тип ограничения}
Рекомендуется использовать сокращения, которые однозначно идентифици
руют соответствующий элемент, например, в имени ограничения cust_id_pk
•
cust — сокращенное имя таблицы Customers;
•
id — сокращенное имя столбца customer id;
•
pk — сокращенное имя ограничения PRIMARY KEY.
В ограничениях, определенных на уровне столбца, служебное слово
CONSTRAINT и имя ограничения могут отсутствовать. Большинство огра
ничений рекомендуется определять на уровне таблицы.
Таблица 8.1 содержит типы ограничений, их краткое определение и реко
мендуемые сокращения.
Таблица 8.1. Типы ограничений
Имя ограничения
Рекомендуемое
сокращение
Описание
NOT NULL
nn
Не допускаются неопределенные зна
чения в столбце
UNIQUE
un
Все значения в столбце должны встре
чаться только по одному разу
EXCLUDE
ex
Позволяет указать правила, которые
определяют, какие значения не могут
существовать вместе в определенном
столбце
CHECK
ck
Определяет диапазон значений, кото
рые могут быть присвоены элементам
столбца
і 216 ]
Глава 8. Операторы определения данных
PRIMARY KEY
рк
Определяет первичный ключ — один
или несколько столбцов, однозначно
идентифицирующих строки таблицы
Определяет внешний ключ — один
или несколько столбцов, используе
мых для установления связи с другой
таблицей
FOREIGN KEY
8.2.1. Ограничение NOT NULL
Данное ограничение не позволяет присваивать
неопределенные значения (NULL) столбцу при
добавлении новых строк в таблицу. Данное ограничение
задается на уровне столбца без использования служебного
слова CONSTRAINT.
Пример:
c_name VARCHAR(255) NOT NULL,
8.2.2. Ограничение UNIQUE
Данное ограничение заключается в том, что все значения
в столбце должны встречаться только по одному разу.
Ограничение UNIQUE допускает значения NULL.
Используются два варианта назначения данного ограничения:
1. В виде ограничения на уровне столбца:
{Имя столбца}
{Тип столбца} UNIQUE
Например:
name__p varchar(20) UNIQUE,
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
2. В виде ограничения на таблицу:
CONSTRAINT <имя ограничения> UNIQUE(<имя столбца>),
Например:
CONSTRAINT products_name_p_un UNIQUE(name^p),
При определении ограничения на уровне таблицы можно задать ограниче
ние UNIQUE для нескольких столбцов.
8.2.3. Ограничение EXCLUDE
Ограничение EXCLUDE позволяет указать правила, которые
определяют, какие значения не могут существовать вместе в
определенном столбце или наборе столбцов.
Стандартным примером использования ограничения EXCLUDE может слу
жить ограничение на пересечение временных интервалов.
Создадим таблицу Events 1, которая должна содержать данные о проведении
учебных занятий
CREATE TABLE Events1(
id_event int4 PRIMARY KEY,
name_event varchar(lOO) NOT NULL,
event_time TSTZRANGE,
CONSTRAINT no_event_time overlap EXCLUDE
USING GIST (event_time WITH &&)
Столбец еѵепНіте содержит диапазон: дата и время начала занятия -^ дата
и время окончания занятия. Предполагается, что эти занятия проводит один
преподаватель. Естественным ограничением предметной области является
то, что эти диапазоны для разных занятий не должны пересекаться.
Введем данные о двух занятиях, у которых время занятий не пересекается.
Глава 8. Операторы определения данных
INSERT INTO Events1(id_event, name_event, event_time)
VALUES (1,'Database_labl', '["2023-04-22 13:00:00", "2023-04-22
16:30:00"]');
INSERT INTO Events1(id_event, name_event, event_time)
VALUES (2,'Database_lab2','["2023-04-29 13:00:00", "2023-04-29
16:30:00"]');
На рис. 8.2 показано содержимое таблицы Events 1 после выполнения этих
операторов.
НВ eventsl iJJ Введите SQ1 вь^раженые члх^ы отфильтрово >?ь ревулытюп?»
abc
name_event
▼
S event_time
^
Database_lab1
[“2023-04-22 13:00:00+ 03", “2023-04-22 16:30:00+03"]
Database_lab2
[“2023-04-29 13:00:00+03", "2023-04-29 16:30:00+03“]
Рис. 8.2. Содержимое таблицы Eventsl
Попробуем ввести данные о занятии, у которого время проведения пересека
ется со временем проведения занятия 2.
INSERT INTO eventsl(id_event, name_event, event_time)
VALUES (3,'Database_lk2',1["2023-04-29 13:00:00", "2023-04-29 14:30:00"]');
SQL Error [23P01]: ОШИБКА: конфликтующее значение ключа нарушает
ограничение-исключение "no_event_time_overlap"
Подробности: Ключ (event_time)=(["2023-04-29 13:00:00+03","202304-29 14:30:00+03"]) конфликтует с существующим ключом (event_
time) = (["2023-04-29 13:00:00 + 03" , "2023-04-2 9 16:30:00 + 03"] ) .
Этот оператор не будет выполнен, и причиной этого является нарушение
ограничения EXCLUDE, которое было задано для столбца event time.
8.2.4. Ограничение CHECK
Данное ограничение определяет диапазон значений, которые могут быть
присвоены элементам столбца. Ограничение задается путем определения
логического выражения, которое должно иметь значение true для всех эле
ментов столбца. Если при добавлении новой строки логическое выражение
будет иметь значение false, то СУБД выдаст сообщение об ошибке, и
строка добавлена не будет.
PostgreSQL: SQL + PL/pgSQL
В PostgreSQL
B общем виде:
CONSTRAINT <имя ограничения>
CHECK (<логическое выражение>),
Создание ограничения CHECK:
CONSTRAINT products_rating_p_ch
CHECK rating_p BETWEEN 1 AND 5;
8.2.5. Ограничение первичного ключа (PRIMARY KEY)
При создании таблицы, как правило, определяется первичный ключ —
один или несколько столбцов, однозначно идентифицирующих строки этой
таблицы. Первичный ключ надо обязательно определять в том случае, если
данная таблица является главной при установлении связи с подчиненной
таблицей. Одна строка главной таблицы может быть связана с несколькими
строками подчиненной таблицы.
Описание ограничения первичного ключа в общем виде может быть пред
ставлено следующим образом??
CONSTRAINT <имя ограничения> PRIMARY KEY (<Список столбцов>)
Ограничение первичного ключа гарантирует, что все значения ключа не
пустые (NOT NULL) и уникальны (UNIQUE).
Ограничение PRIMARY KEY может быть определено как на уровне столбца,
так и на уровне таблицы. Но если первичный ключ состоит из нескольких
столбцов, то он должен быть определен на уровне таблицы.
Создание ограничения PRIMARY KEY на уровне столбца:-)
customer_id
integer PRIMARY KEY
Создание ограничения PRIMARY KEY на уровне таблицы??
CONSTRAINT cust_id_pk PRIMARY KEY (customer_id)
Глава 8. Операторы определения данных
Для определения первичного ключа, состоящего из нескольких столбцов,
необходимо определить ограничение на уровне таблицы, например:
CONSTRAINT Ordit_ Id_It_Pk PRIMARY KEY (order_id, item_id)
8.2.6. Ограничение внешнего ключа (FOREIGN KEY)
Связь между таблицами реализуется путем объявления первичного ключа в
главной таблице и объявления внешнего ключа в подчиненной таблице, Зна
чения внешнего ключа могут повторяться, но обязательно должны совпадать
с одним из значений первичного ключа в главной таблице или иметь значе
ние NULL. Это свойство называется свойством ссылочной целостности.
За его выполнением будет следить СУБД.
Сначала нужно определить ограничение PRIMARY KEY в главной таблице,
потом определить ограничение FOREIGN KEY в подчиненной таблице.
Данное ограничение, как правило, определяется на уровне таблицы и может
быть представлено следующим образом:
CONSTRAINT <имя ограничения> FOREIGN KEY (<Список столбцов>)
REFERENCES СИмя родительской таблицы>
(<Список столбцов первичного ключа родительской таблицы>)
[<Правила поддержания целостности связи>];
В отличие от ограничения первичного ключа, таблица может иметь несколь
ко ограничений внешнего ключа.
Столбцы, составляющие внешний ключ, должны иметь типы, совпадающие
с типами первичного ключа в главной таблице. При определении внешнего
ключа можно указать, какие правила поддержания целостности необходимо
использовать при удалении строк главной таблицы:
• ON DELETE CASCADE — каскадное удаление строк подчиненной
таблицы. При удалении строки главной таблицы удаляются связанные с
ней строки подчиненной таблицы;
• ON DELETE SET NULL — присвоение значения NULL столбцам внеш
него ключа. При удалении строки главной таблицы в связанных с ней
строках подчиненной таблицы столбцам внешнего ключа присваивается
значение NULL.
PostgreSQL: SQL + PL/pgSQL
^^PostgreSQL
Для того чтобы создаваемая база данных соответствовала правилам пред
метной области, нужно при определении ограничения FOREIGN KEY пра
вильно определять правила поддержания целостности. Эти правила вы
бираются исходя из ограничений, существующих в предметной области, и
определять их должен клиент, а не программист.
Схема Рос будет содержать следующие таблицы: Customers, Products,
Orders, Order items. Таблицу Customers мы уже создали, рассмотрим опера
торы создания остальных таблиц схемы. Таблицы нужно создавать в опреде
ленной последовательности. Таблица, содержащая внешний ключ для связи
с некоторой таблицей, должна создаваться после этой таблицы.
Запрос 8.2. Оператор создания таблицы Products
CREATE .TABLE Products
(
product_id
INTEGER PRIMARY KEY,
product_name VARCHAR( 255 ) NOT NULL,
rating_p
INTEGER,
price
NUMERIC(10,2),
CONSTRAINT product_r CHECK((rating_p>0) AND (rating_p<=5))
);
В этом запросе первичный ключ определяется на уровне столбца, а на
уровне таблицы определяется ограничение CHECK для столбца ratingp
(0<=rating_p<=5).
Запрос 8.3. Оператор создания таблицы Orders
CREATE TABLE Orders
(
order_id
INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
status
VARCHAR(20) NOT NULL,
salesman_id INTEGER,
order_date DATE NOT NULL,
CONSTRAINT fk_orders_customers FOREIGN KEY (customer_id)
REFERENCES Customers (customer_id) ON DELETE CASCADE
В этом запросе на уровне таблицы определяется внешний ключ (customer_
id) для создания связи с таблицей Customers. Свойства этой связи будут рас
смотрены позже.
Глава 8. Операторы определения данных
Запрос 8.4. Оператор создания таблицы Order_Items
CREATE TABLE Order_Iterns
(
order_id
integer, — fk, pk
item_id
integer,-- pk
product_id integer NOT NULL, — fk
quantity
integer NOT NULL,
unit_price numeric(10,2)NOT NULL,
PRIMARY KEY(order_id,item_id) ,
FOREIGN KEY(product_id) REFERENCES Products(product_id),
FOREIGN KEY(order_id) REFERENCES Orders(order_id)
ON DELETE CASCADE
);
Таблица Order_Items имеет составной первичный ключ (order_id, item_id),
поэтому он определен на уровне таблицы. При этом столбец order_id одно
временно является внешним ключом для установления связи с таблицей
Orders. Такие связи называются идентифицирующими. Строка таблицы
Order_Items не может существовать, если она не связана со строкой табли
цы Orders. Также для этой таблицы определен внешний ключ (product_id)
для связи с таблицей Products.
Рассмотрим свойства связей между созданными таблицами и проанализиру
ем правила поддержания ссылочной целостности, заданные для этих связей.
1. Связь между таблицами Products и Order_Items:
CONSTRAINT Ord_It_Pr_fk FOREIGN KEY (product_id)
REFERENCES Products(product_id);
2. Связь между таблицами Customers и Orders:
CONSTRAINT Ord_fk FOREIGN KEY (customer_id)
REFERENCES Customers(customer_id)
ON DELETE SET NULL;
3. Связь между таблицами Orders и Order_Items:
CONSTRAINT ord_it_or_fk FOREIGN KEY (order_id)
REFERENCES Orders(order_id)
ON DELETE CASCADE;
В этих таблицах использованы разные правила поддержания ссылочной
целостности, определяющие действия, которые должны быть выполнены
[ 223
PostgreSQL: SQL + PL/pgSQL
..................................................
w
PostgreSQL
co строками подчиненной таблицы при удалении связанной с ними строки
главной таблицы.
• Для первой связи это правило не задано. Это означает, что нельзя уда
лить товар, который был продан. Обосновать это можно следующим об
разом: данные о продаже теряют смысл, если не будет известно, какой
товар был продан.
• Для второй связи задано правило ON DELETE SET NULL. Это озна
чает, что при удалении данных о клиенте у всех заказов, которые сделал
этот клиент, значение столбца customer_id в таблице Orders будет иметь
значение NULL. Обосновать это можно следующим образом: данные о
заказе не теряют смысла даже в том случае, если неизвестно, какой клиент
сделал этот заказ.
• Для третьей связи задано правило ON DELETE CASCADE. Это оз
начает, что при удалении данных о заказе будут удалены данные о со
держимом этого заказа. Целесообразность использования этого правила
очевидна.
На рис. 8.3 представлена E-R диаграмма схемы РОС.
Я customers
1^ customerid
«вс c_name
«вс address
123 creditjimit
Рис. 8.3. E-R диаграмма схемы РОС
8.3. Экспорт данных
Заполним таблицы схемы РОС данными, экспортируя их из таблиц схемы
НК РОС. Для этого нужно выбрать таблицу, из которой нужно выгрузить
224
Глава 8. Операторы определения данных
данные, щелкнуть на ее имени правой кнопкой и в появившемся контекст
ном меню выбрать команду Экспорт данных, рис. 8.4.
v
Базы данных
V В postgres
v Й Схемы
v У; hr.pcc
v
Таблицы
> ЯВ сиротел
Создать
> ?Я customers.ccpy
> ® departments
^
Открыть объект "Таблица'
а ^ departments. 1
Т
Фильтр
> ® етр
F4
3
View Diagram
> ^ етр1
> ^ етр.п
-<
> ^ employees
,♦$
® employees.copy *2
> - iobs
> ^ locations
3
/ W order.items
)£
View Data
Сравнить/Мигрировать
‘
Экспортданные
Импорт данных
Инструменты
> ® order.items.ccpy
j ^ orders
; ^ crders.1
Генерация SQL
£J
> $B orders. 1.1
Read data in SQL editor
Ctrl*C
Колировать
* ® orders. 1.2
Ctrl*'»'
> ^ orders.2
Вставить
> ® orders.copy
Копировать полную информацию
> 3? prcduct.sales
> i? products
£
*
? ® products.ccpy
> # products.ns
^
Ctrl*Shift*С
Delete
Удалить
Переименовать
F2
Refresh
F5
> ^ products_n$1
> ffl products.total
Рис. 8.4. Выбор команды "Экспорт данных"
В появившемся окне (рис. 8.5) нужно выбрать формат экспорта. В рассма
триваемом примере выбран формат SQL. В этом случае результат экспорта
будет представлять собой оператор INSERT, содержащий все данные экс
портируемой таблицы.
□
^ Трансфер данных
Цели экспорта
Подтвердите цель и формат трансфера
V Формат экспорта
Настройки извлечение
Настройки формата
Вывод
Подтвердить
S База данных
{a; DbUnit
и»hl HTML
«&JSON
ML Markdown
SSQl
Таблицы базы данных
Экспорт
Экспорт в CSV файл(ы)
® hr_poc.customers
Описе
Экспорт в XML-фэйл (ы) DbUmt
Экспорт в HTML файл(ы}
Экспорт в файл(ы) /SON
Экспорт в mark down-фай л (ы)
Экспорт в SQL INSERT выражения
Экспорт в простой текстовый фермат
S XML
Сох ранить ?адаму
Экспорт в XML файл(ы)
Экспортировать в массивы исходного к
Д,
< Back
Next >
Proceed
Cancel
Рис. 8.5. Выбор формата экспорта
225
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
После выбора формата нужно нажать кнопку Next, в результате на экране
появится окно настройки извлечения данных (рис. 8.6).
Нужно нажать кнопку Next, и на экране появится окно настройки формата
(рис. 8.7).
@ Трансфер данных
□
Настройки формата
Настройки формата файла
ѵ Формат экспорта
Общее
V Настройки извлечена
* Настройки формата
V Вывод
Редактировать,..
vj
Форматирование:
<Настройки соединениях
Двоичные данные
Встраивать
Формат отображения значений:
Стандартный (по умолчанию)
* Кодировка:
Binary
ѵ
Подтвердить
ѵ
Конфигурация колонок...
Настройки экспорта
Значение
Имя
Нативный формат даты/времени
[V]
Опустить имя схемы
[у]
Строк данных в выражении
20С|
< Back
I
Nett >
~]
Proceed
Рис. 8.7. Окно настройки формата
226
Cancel
X
Глава 8. Операторы определения данных
В этом окне (рис. 8.7) нужно обязательно установить опцию Опустить имя
схемы, иначе при загрузке данных в таблицы схемы с другим именем будет
возникать ошибка. В следующем окне (рис. 8.8) нужно выбрать папку и
ввести имя файла, который будет содержать экспортируемые данные.
@ Трансфер данных
X
С
Вывод
Конфигурация экспорта
V Формат экспорта
Общее
ч* Настройки извлечения
О Копировать в клипборд
ѵ Настройки формата
: C:\Postgre\out
Directory:
] М ^ Global Settings
✓ Вывод
Подтвердить
Шаблон имени файла:
insert.custl
Кодировка:
UTF-8
v I Шаблон временной отметки: i уууу □ Вставить ВОМ
Писать в один файл
□ Сжатие
□ Разбить выходной файл Маггцпчльныи ра’п>:'файл
1000000
> On object data file name conflict Autofix name; On blob value file name conflict Autofix name
© Вы можете использовать переменные в настройках вывода.
Сохранить задачу
А
< gack
Proceed
£jext >
Cancel
Рис. 8.8. Выбор папки и ввод имени файла
На рис. 8.9 представлено окно Подтверждение экспорта, после нажатия на
кнопку Proceed данные из выбранной таблицы будут записаны в результирующий файл.
^ Трансфер данных
Подтвердить
Проверка настроек
ѵ Формат же порта
ѵ Настройки извлечение
ѵ Настройки формата
V Вывод
^ Подтвердить
Объекты
Контейнер источника
*Т postgres
Источник
ф hr_poc customers
Настройки источника
Цель
Ш insert.cust1.sql
Настройки цели
; Таблица настройки;
Открыть новое соединение No
Чтение данных: ЯМОіЕ.ОиЕРѴ
Выбирать число строк: No
Только выбранные строки; No
Только выбранные колонки; No
Файлы настройки:
Писать в один файл: No
Папка: C:\Postgre\out
Шаблон имени файла: insert.custl
On object data file name conflict Autofix name
On blob value file name conflict Autofix name
Кодировка; UTF-8
Шаблон временной отметки: yyyyMMddHHmm
Вставить BOM: No
Сжатие: No
Двоичные данные INLINE
Кодировка: BINARY
КГМ нлгтплДгы
Сохранить задачу
< Back
Контейнер цели
М C:\Postgre\out
Next >
Рис. 8.9. Подтверждение экспорта
Proceed
Cancel
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Для того чтобы загрузить экспортированные данные в таблицу другой базы
данных, нужно выбрать эту базу (соединение) и схему на панели инструмен
тов ОВеаѵег (была отображена на рис. 1.2.).
После этого следует открыть файл экспорта, используя команду главного
меню Файл —> Open file, и выполнить оператор INSERT, который содержит
ся в этом файле (рис. 8.10).
T” ’ О
Auto
2 <postgres>...
►
j© ’
W postl
£J <postgres>.«
’ Й P°c@post1
£J <postgres>...
’ © й ’
Ц ’
£J <postgres>...
£J <postgres>...
£J <postgres> ..
£J <postgres>...
- INSERT INTO customers (custoeer_id,c_na«e,address,creditlimit) VALUES)
.
§
яE
(2,’Boeing',’100 North Riverside Chicago, Illinois 60606 USA’,1200000.00),
(3,’Oracle Corporation','500 Oracle Parkway Redwood Shores, CA 04065 USA',1200000.00),
(4,'United Technologies’,'10 Farm Springs Rd. Farmington, CT 06032 USA’,120000.00),
(5,’Rockwell Collins’,‘400 Collins Road N.E. Cedar Rapids, IA 52498 USA’,500000.00),
(6,’Textron’,'40 Westminster Street Providence, RI 02903 USA',360000.00),
(7,’Daimler’,'Daimler AG 70546 Stuttgart Germany',240000.00),
(9,’Triumph Group Inc','Berwyn, Pennsylvania , United States',50000.00),
(10,'Mazda Motor','3-1, Shinchi, Fuchu-Cho Aki-Gun, Hiroshima, 735-0028 Japan',500000.00),
(11,'Toyota Motor’,'Toyota-Cho, Toyota, Aichi, Japan 471-082.6' ,50000.00),
(12,'Zhengzhou Yutong Bus','Zhengzhou,Henan province.China',60000.00),
(13,‘Scania AB’,‘Scania AB, S-151S7, Sodertalje, Sweden',70000.80),
(14,'Volkswagen Group','Berliner Ring 2 38440 Wolfsburg, Germany',70000.00),
(15,‘DEDEMAN S.R.L.',’411 E Wisconsin Ave # 2550',70000.00),
(16,'Samsung Electronics','129, Samsung-no, Yeongtong-gu, Suwon-si, Gyeonggi-do, Korea',90000.00),
(17,'Haier Group’,'! Haier Road, Hi-Tech Zone Qingdao 266101 China’,120000.00),
(18,'Anglo American plc','20 Carlton House Terrace London SU1Y 5AN United Kingdom',120008.00),
(19,'Rio Tinto limited’,'Level 7 360 Collins Street Melbourne Australia 3000',120000.00),
(20,'Caterpillar','501 Southwest Jefferson Avenue, Peoria, IL, 61630',120000.00),
(21,'Volvo Construction Equipment’,’8olinderv?gen 108 (1 321,64 km) Eskilstuna, Sweden’,120000.00),
Рис. 8.10. Содержимое файла экспорта
Эти операции нужно выполнить для остальных таблиц. Для того чтобы не
возникали ошибки ссылочной целостности, заполнять таблицы нужно в сле
дующей последовательности: Products, Orders, Order items.
8.4. Резервное копирование базы данных
DBeaver поддерживает собственные функции резервного копирования и
восстановления баз данных. Он использует собственные форматы дампа
базы данных и специальные утилиты для прямого высокопроизводитель
ного доступа к базе данных.
Для создания резервной копии нужно выбрать базу данных или схему, щел
кнуть на ее имени правой кнопкой и в появившемся контекстном меню
выбрать команду Инструменты —> Резервная копия (рис. 8.11).
228
Глава 8. Операторы определения данных
^ posti - tofhost;5432
v £1 Вазы данных
ѵ * posti
ѵ ^ Схемы
Задать по умолчанию
> ^ Таблицы
> ^ Представления
Ctrl* Shift* А
Редактор SQL
> ^ Мат. представления
> В Индексы
Создать
у В Функции
Открыть объект "Схема*
> ^ Последовательности
V
F4
Фильтр
> В Типы данных
View Diagram
у Ш Агрегатные функции
ж Событийные триггеры
Срзвнить/Мигрировать
> &3 Расширения
Импорт данных
> ВИ Хранилище
> В Системные объекты
> ^ Роли
^
Инструменты
Q
Генерация SQL
> £3 Администрирование
у ИВ Системные объекты
> ЯГ розідгез
Копировать
Ctrl* С
Вставить
CtrkV
9
Удалить
Delete
/*
Переименовать
F2
^>
Refresh
F5
Ьсо&ыі5432
Диспетчер сессий
Диспетчер блокировок
Создать задачу...
Рис. 8.11. Создание резервной копии
В появившемся окне (рис. 8.12) нужно выбрать объекты, которые следует
записать в резервную копию, и нажать кнопку Next. На экране появится
окно Настройка выгрузки (рис. 8.13). В этом окне следует выбрать папку,
в которой будет создана резервная копия, и ввести шаблон или имя файла
резервной копии. После выполнения этих действий нажать кнопку Старт.
Рис. 8.12. Выбор объектов резервного копирования
PostgreSQL: SQL + PL/pgSQL
@ Резервная копия
Настройки резервирования (выгрузки)
Резервировать настройки
ѵ Выберите объекты для в
Настройки
ѵ Настройки резервироваі
Резервная копия в проц< I
Кодировка: |
□ Использовать для данных INSERT вместо СОР¥-массива.
□ Не резервировать права доступа (GRANT/REVOKE)
Q Пропустить указание на владельца
О Add drop database statement
О Add create database statement
Вывод
Папка файла:
Шаблон имени файла:
[ C:\Postgre\dump
|■ 0
~
dump-S{ data base}-S(timestamp}|
Доп. аргументы команды:
Безопасность
< Back
Локальный Клиент..
Старт
Cancel
Рис. 8.13. Настройка выгрузки
Загрузка схемы базы данных из резервной копии была рассмотрена ранее в
1.2.2.
8.5. Редактирование таблиц
В процессе работы с базой данных возникает необходимость вносить изме
нения в существующие таблицы. Такими изменениями могут быть:
•
добавление или удаление столбцов;
•
изменение имени, типа и значения по умолчанию;
•
добавление и удаление ограничений;
•
удаление таблиц.
Для осуществления этих операций служит команда
ALTER TABLE {имя таблицы}
{код редактирования}
Глава 8. Операторы определения данных
8.5.1. Добавление и удаление столбцов
Для добавления нового столбца используется команда-)
ALTER TABLE {имя таблицы} ADD COLUMN
({имя столбца} {тип столбца};
л
Пример:
ALTER TABLE Customers ADD COLUMN
rating int4;
Для удаления существующих столбцов служит командам
ALTER TABLE {имя таблицы} DROP COLUMN {имя столбца};
При применении этого оператора следует соблюдать следующие правила:
•
одним оператором можно удалить только один столбец,
•
нельзя удалить все столбцы в таблице.
8.5.2. Изменение столбцов
Можно изменить имя столбца, используя команду
ALTER TABLE {имя таблицы}
RENAME COLUMN {старое имя} ТО {новое имя};
Пример:
ALTER TABLE Customers
RENAME COLUMN rating TO rating_c;
Для изменения типа столбца служит команда
ALTER TABLE {имя таблицы}
ALTER COLUMN {имя столбца} TYPE {новый тип};
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Пример:
ALTER TABLE Customers
ALTER COLUMN rating_c TYPE smallint;
При использовании этого оператора следует соблюдать следующие правила:
увеличивать число разрядов, числового столбца и ширину строки символов
можно всегда, а уменьшать можно только до наибольшего значения, содер
жащегося в столбце.
Изменить значение по умолчанию можно командой-^
ALTER TABLE {имя таблицы}
ALTER COLUMN {имя столбца} SET DEFAULT {значение};
Пример:
ALTER TABLE Customers
ALTER COLUMN rating_c SET DEFAULT 1;
Изменение значения по умолчанию столбца влияет только на строки, кото
рые будут добавлены позже, имеющиеся значения изменены не будут.
8.5.3. Изменение ограничений
Добавление ограничения:
ALTER TABLE {имя таблицы} ADD CONSTRAINT
{имя ограничения}{текст ограничения}
Пример:
ALTER TABLE Customers ADD CONSTRAINT Customers_rating_ch
CHECK((rating_c>0) AND (rating_c<=5));
Удаление ограничения:^
ALTER TABLE {имя таблицы}
DROP CONSTRAINT {имя ограничения};
Глава 8. Операторы определения данных
8.5.4. Удаление таблицы
Для удаления таблицы используется команда DROP
TABLE. В результате выполнения этой команды из таблицы
удаляются все данные, и описание таблицы удаляется из
словаря данных.
Удалить таблицу может ее создатель и пользователи, имеющие привилегию
DROP ANY TABLE.
Синтаксис:
DROP TABLE {Имя таблицы};
Если удаляемая таблица связана с другими таблицами, в которых опреде
лены внешние ключи, ссылающиеся на столбцы удаляемой таблицы, то эта
команда не будет выполнена.
Пример:
DROP TABLE Products;
SQL Error [2BP01]: ОШИБКА: удалить объект таблица products нельзя,
так как от него зависят другие объекты.
Подробности: ограничение order_items_product_id_fkey в отношении
таблица order_items зависит от объекта таблица products.
Подсказка: Для удаления зависимых объектов используйте DROP ...
CASCADE.
Для удаления таблиц с ограничениями целостности следует использовать
команду:^
DROP TABLE
{Имя таблицы} CASCADE;
Пример:
DROP TABLE Products CASCADE;
Удаление распространяется на объект ограничение order_items
product_id_fkey в отношении таблица order_items
233
PostgreSQL: SQL + PL/pgSQL
При выполнении этой команды в связанных таблицах удаляются ограниче
ния внешнего ключа.
8.5.5. Получение информации о таблицах базы данных
При работе с базой данных и при ее изменениях необходима информация
о том, какие столбцы есть в каждой таблице, типе столбцов, ограничени
ях, значениях по умолчанию. В главе 1 были рассмотрены возможности
DBeaver для получения этой информации в визуальном режиме.
Рассмотрим способы получения такой информации, используя SQL-запросы
к таблицам, содержащим метаинформацию.
База данных PostgreSQL предоставляет два вида набора таблиц и представ
лений, которые содержат метаинформацию об объектах базы данных:
•
Системный каталог (pg_catalog) — представляет собой набор таблиц и
представлений с описанием всех объектов СУБД.
•
Информационная схема (information schema) — предназначена для
представления метаданных в соответствии со стандартами SQL, но не
отражает ряд специфических особенностей PostgreSQL.
Рассмотрим запросы для получения метаинформации о таблицах схемы РОС.
Запрос 8.6. Вывести имена таблиц и их владельца в схеме РОС
SELECT schemaname,tablename,tableowner
FROM Pg_Catalog.Pg_Tables
WHERE schemaname='poc';
schemaname I tablename
I tableowner I
----------- +-------------------------- ++
рос
рос
рос
рос
рос
leventsl
|user_postl|
|customers
|user_postl|
|orders
|user_postl|
Iorder_itemsIuser_postl|
|products
|user_postl|
Запрос 8.7. Вывести данные о столбцах таблицы Orders
SELECT column_name, data_type, column_default, is_nullable
FROM Information_Schema.Columns
WHERE table name = 'orders';
Глава 8. Операторы определения данных
column_name|data_type
|column_default
lis_ nullable|
order_id
1 integer
|NO
1
customer_id|integer
|NO
salesman_id|integer
|YES
order_date |date
|CURRENT_DATE
|NO
status
(character varying!'Pending'::character varying!NO
1
1
1
1
Запрос 8.8. Вывести данные об ограничениях в таблице Orders
SELECT table_name,constraint_name,constraint_type from
Information_Schema.Table_Constraints
WHERE table_name = 'orders';
table_name|constraint_name
Iconstraint_typeI
+
orders
orders
orders
orders
orders
orders
orders
|orders_pkey
|PRIMARY
Ifk_orders_customers
(FOREIGN
Ifk_orders_employees
(FOREIGN
I 33319_33350_l_not__null (CHECK
I33319_33350_2_not_null|CHECK
|33319_33350_3_not_null(CHECK
I33319_33350_5_not_null(CHECK
KEY
KEY
KEY
8.6. Представления
Представлением называют сохраненный запрос, которому
присваивается имя. Это имя может использоваться в
качестве источника данных в других запросах.
Имя представления не должно совпадать с именем таблиц базы данных. Если
в качестве источника данных указано имя представления, то СУБД выполня
ет содержащийся в нем запрос и возвращает результат его выполнения.
По логике использования представления имеют много общего с подпро
граммами. Если какую-либо обработку данных приходится использовать
многократно в различных запросах, то целесообразно реализовать ее в виде
представления, и указывать его имя там, где это необходимо. Имена пред
ставлений можно указывать там, где можно указывать имена таблиц.
ГИС
PostgreSQL: SQL + PL/pgSQL
« PostgreSQL
Для создания представлений используется оператор CREATE
VIEW, который имеет следующий синтаксис:^
CREATE [ OR REPLACE ] [ TEMP ] [ RECURSIVE ]
VIEW {имя представления} [ {список столбцов} ]
AS {текст запроса}
[ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
Рассмотрим параметры этого оператора:
•
OR REPLACE — если этот параметр указан, то при повторном выполне
нии ранее созданное представление будет перезаписано;
• TEMP — при наличии этого параметра будет создано временное пред
ставление. Временные представления автоматически удаляются в конце
текущего сеанса. Существующие постоянные представления с тем же
именем не видны текущему сеансу, пока существует временное пред
ставление;
• RECURSIVE — будет создано рекурсивное представление;
• WITH [ CASCADED | LOCAL ] CHECK OPTION — этот параметр
управляет поведением представлений, используемых для изменения дан
ных. Если этот параметр указан, то при выполнении операторов NSERT и
UPDATE будет осуществляться проверка: новые строки должны удовлет
ворять условиям, заданным в представлении. Если эти условия не будут
выполнены хотя бы для одной строки, то изменение будет отменено для
всех строк.
Параметр CHECK OPTION нельзя использовать в рекурсивных представлениях.
Представления позволяют:
• упростить создание сложных запросов, которые следует разделить на
части, и реализовать каждую часть в виде представления;
• ограничить доступ пользователей к данным, создавая представления, ко
торые содержат только те столбцы, доступ к которым разрешен.
Представления являются эффективным средством реализации подтипов
данных. Например, таблица customers может содержать данные как о юри
дических, так и о физических лицах. Списки атрибутов, характеризующих
Глава 8. Операторы определения данных
экземпляры этих объектов, могут существенно различаться. Создав пред
ставление для каждой категории клиентов, можно существенно упростить
работу с данными клиентов.
Есть два типа представлений: простые и сложные.
1. Простое представление — это представление, которое использует дан
ные только из одной таблицы, не содержит функций или групп данных.
2. Сложное представление — это представление, которое использует дан
ные из нескольких таблиц или содержит функции или группы данных.
В операторах изменения данных INSERT, UPDATE, MERGE, DELETE мож
но использовать только простые представления.
Установим соединение со схемой HR_POC и рассмотрим примеры создания
и использования представлений.
Запрос 8.9. Создание представления, которое содержит
данные о сотрудниках, зарплата которых больше 8000, но
меньше 10000
CREATE OR REPLACE VIEW View_Salary_10000 AS
SELECT department_id, employee_id, first_name, last_name, salary
FROM Employees
WHERE salary between 8000 and 10000
WITH CHECK OPTION;
Запрос 8.10. Увеличить на 10% зарплату сотрудников,
используя представление View_Salary_10000
UPDATE View_Salary_10000
SET salary = salary*l.1
RETURNING *;
SQL Error [44000] : ОШИБКА: новая строка нарушает ограничениепроверку для представления "view_salary_10000"
Подробности: Ошибочная строка содержит (151, David,
Bernstein, DBERNSTE, 011.44.1344.345268, 1997-03-24, SA_REP,
10450.00, 0.250, 145, 80, 3).
Ошибка возникла вследствие того, что при выполнении этого оператора зар
плата одного или нескольких сотрудников превысила значение 10000.
[ 237
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Перезапишем представление View_Salary_ 10000, убрав параметр WITH
CHECK OPTION
CREATE OR REPLACE VIEW View_Salary_10000 AS
SELECT depar tment_id, employee_id, first_name, last_name, salary
FROM Employees
WHERE salary between 8000 and 10000;
Выполним запрос для изменения зарплаты сотрудников с использованием
этого представления:
UPDATE View_Salary_10000
SET salary = salary*l.l
RETURNING *;
Запрос будет успешно выполнен.
Фрагмент результатов выполнения этого запроса:
department_id|employee_id| first_name |last_name |salary
|
--------------- +------------- +------------- +----------- +--------- +
60
100
50
50
80
80
80
80
80
1
1
1
1
1
1
1
1
1
103 I Alexander
109|Daniel
120|Matthew
121|Adam
150|Peter
151|David
153|Christopher
156|Janette
157| Patrick.
IHunold
IFaviet
I Weiss
I Fripp
I Tucker
I Bernstein
I Olsen
| King
I Sully
1 9900.001
1 9900.001
1 8800.001
1 9020.001
111000.001
1 10450.001
1 8800.001
111000.00 1
110450.001
Запрос 8.11. Создание сложного представления Order_
Summa, которое возвращает order_id, order_date и общую
сумму заказа
CREATE OR REPLACE VIEW Order_Summa (order_id,order_date,summa)
AS
SELECT orders.order_id, order_date, SUM(quantity*unit_price)
FROM Orders JOIN Order_Iterns ON(Orders.order_id=Order_Iterns.order_id)
GROUP BY orders.order_id, orders.order_date;
238
Глава 8. Операторы определения данных
Запрос 8.12. Используя представление Order_Summa,
вывести заказы, оформленные 27.05.2017, общая сумма
которых превышает 100000
SELECT order_id,order_date, TO_CHAR (summa,'999G999D99') As SUMMA
FROM Order_Summa
WHERE order_date ='27.05.2017' AND SUMMA > 100000;
order_id I order_date-| summa
I
-------- +----------- +----------- +
2012017-05-271
577 500,00|
Используя представления, можно упростить код сложных запросов. В каче
стве примера рассмотрим задачу из запроса 6.33 - вывести данные о кли
ентах, у которых средняя сумма заказа превышает общую среднюю сумму
одного заказа.
Запрос 8.13. Создать представление, которое выводит
номера клиентов и среднюю сумму их заказов
CREATE OR REPLACE view Avg_C AS
SELECT customer_id, AVG(quantity*unitjprice) As avg_cust
FROM Orders JOIN Order_Items USING (order_id)
GROUP BY customer_id;
Запрос 8.14. Создать представление,
среднюю сумму заказа
которое выводит
CREATE OR REPLACE view Avg_T AS
SELECT AVG(quantity*unit_price) As avg_total
FROM Order_Iterns;
Используя эти представления, рассматриваемую задачу можно решить сле
дующим образом.
Запрос 8.15. Вывести данные о клиентах, у которых средняя
сумма заказа превышает общую среднюю сумму одного заказа
SELECT customer_id, c_name
FROM Customers cst
WHERE
(SELECT avg_cust FROM Avg_C
WHERE customer_id = cst.customer id)
Г 239 "
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
(SELECT avg_total FROM Avg_T);
Этот запрос легче анализировать, изменять и сопровождать. Кроме этого,
созданные представления можно использовать при решении других задач.
Данные о представлениях содержатся в informationschema.views.
Запрос 8.16. Вывести список представлений в схеме HR_POC
SELECT table_name
FROM information_schema.views
WHERE table_schema ='hr_poc';
table_name
|
-------------------- +
avg_c
I
avg_t
I
view_salary_10000|
order_summa
I
Запрос 8.17. Вывести код представления view_salary_10000
SELECT VIEW_DEFINITION
FROM information_schema.views
WHERE table_name = 'view_salary_10000' ;
view_definition
SELECT employees.department_id, employees.employee_id,
employees.first_name, employees.last_name, employees.salary
FROM employees
WHERE ((employees.salary >= (8000)::numeric)
AND (employees.salary <= (10000)::numeric));|
[ ПРИМЕЧАНИЕ: код представления выводится одной строкой. [
і
і
—
L
-------------- ------- --------------------------------------------- — — _ — — -. — -. — ._._.--. — — — — —
_-... — — — — — _ — _-.__□
8.7. Последовательности
Последовательность является объектом базы данных,
который используется для того, чтобы генерировать
последовательные значения целых чисел.
Глава 8. Операторы определения данных
Значения элементов последовательности генерируются независимо от
таблиц, поэтому одна последовательность может использоваться для не
скольких таблиц. Хотя такая возможность имеется, в большинстве случаев
лучше использовать отдельную последовательность для каждого столбца,
значения которому будут присваиваться при помощи последовательности.
Для создания последовательности используется оператор
CREATE SEQUENCE, который имеет следующий синтаксис:
CREATE [ TEMP ] SEQUENCE [ IF NOT EXISTS ]
{name}
[ AS data_type ]
[START WITH] start] [CACHE cache] [ CYCLE ]
[ INCREMENT [ BY ] increment ]
[ MINVALUE minvalue | NO MINVALUE ]
[ MAXVALUE maxvalue | NO MAXVALUE ]
[ OWNED BY { table_name. column_name } ]
Рассмотрим параметры этого оператора:
• TEMP — при наличии этого параметра последовательность создается
только для этого сеанса и автоматически удаляется при выходе из него.
Существующие постоянные последовательности с тем же именем не вид
ны (в этом сеансе), пока существует временная последовательность;
• IF NOT EXISTS — если этот параметр указан, то не будет возникать
ошибка, если последовательность с тем же именем уже существует. Опе
ратор создания последовательности не будет выполнен;
• NAME — имя последовательности;
• DATA_TYPE — определяет тип данных последовательности. Допусти
мыми типами являются smaliint, integer и bigint. По умолчанию исполь
зуется bigint;
• START — начальное значение, генерируемое последовательностью. Зна
чение по умолчанию равно minvalue для восходящих последователь
ностей и maxvalue для нисходящих;
• CACHE — указывает, сколько порядковых номеров должно быть пред
варительно сформировано и сохранено в оперативной памяти для более
быстрого доступа;
PostgreSQL: SQL + PL/pgSQL
•
f^PostgreSQL
CYCLE — при использовании этого параметра последовательность бу
дет продолжать генерацию значений после достижения максимального
или минимального значения. Когда возрастающая последовательность
достигнет своего максимума, то следующее значение будет равно мини
мальному значению последовательности;
»
increment — шаг изменения значений последовательности, может
быть отрицательным числом, значение по умолчанию равно 1;
» minvalue — минимальное значение, генерируемое последователь
ностью;
»
•
maxvalue — максимальное значение, генерируемое последователь
ностью;
OWNED BY {table_name.column_name} — связывает последователь
ность с определенным столбцом таблицы, так что, если этот столбец (или
вся его таблица) будет удален, последовательность также будет автомати
чески удалена.
8.7.1. Функции для работы с последовательностями
Для работы с последовательностями можно использовать следующие функции:
• NEXTVAL(’{hmh последовательности}’) — формирует следующее зна
чение последовательности и возвращает это значение. При первом обра
щении возвращает значение start;
•
CURRVAL(’{hmh последовательности}’) — возвращает текущее значе
ние последовательности;
•
SETVAL(' {имя последовательности}’, newcur) — задает новое
текущее значение последовательности, равное newcur. Функция
NEXTVALQ вернет значение new cur + increment.
Запрос 8.18. Создание последовательности EMPLOYEES_ID_SEQ
CREATE SEQUENCE EMPLOYEES_ID_SEQ
START WITH 210
INCREMENT BY 1
MAXVALUE 5000;
J42J
Глава 8. Операторы определения данных
Эта последовательность будет использована для заполнения значениями
столбца первичного ключа таблицы Employées.
Запрос 8.19. Создание последовательности DEPARTMENTS_ID_SEQ
CREATE SEQUENCE DEPARTMENTS_ID_SEQ
START WITH 300
INCREMENT BY 10
MAXVALUE 1000;
Эта последовательность будет использована для заполнения значениями
столбца первичного ключа таблицы Departments.
8.7.2. Использование последовательностей
Запрос 8.20. Ввод данных о новом отделе
INSERT INTO Departments
(department_id, department_name, manager_id, location_id)
VALUES (NEXTVAL('departments_id_seq'), 'Logistics',121, 1700);
Запрос 8.21. Ввод данных о новом сотруднике нового отдела
INSERT INTO Employees (employee_id, department_id, first_name, last_
name, hire_date)
VALUES (NEXTVAL('employees_id_seq'), CURRVAL('departments_id_seq'),
'John','Connor', DEFAULT)
returning employee_id, department_id, first_name, last_name, hire_date;
employee_id| department_id| first_name|last_name|hire_date I
----------- +-------------- +---------- +--------- +---------- +
2101
300|John
|Connor
|2023-06-271
Запрос 8.22. Перевод сотрудника в новый отдел
UPDATE Employees
SET department_id = CURRVAL('departments_id_seq')
WHERE employee_id = 121;
Узнать текущее значение последовательно можно, выполнив запрос:
SELECT CURRVAL ('{имя последовательности}');
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Запрос 8.23. Вывести текущее значение
последовательности departments_id_seq
SELECT CURRVAL('departments_id_seq');
currvalI
------- +
300 I
Запрос 8.24. Вывод информации о сотрудниках нового отдела
SELECT enployee_id, department_id, first_name, last_name, jdb_id, hire_date
FROM Employees
WHERE department_id = CURRVAL (' departments_id_seq') ;
employee_id| department_id|first_name | last_name | job_idI hire_date |
---------- +------------ +--------- +-------- +----- +--------- +
2101
121|
300|John
300|Adam
|Connor
|Fripp
|PU_MAN|2023-06-27|
|ST_MAN|1997-04-10|
8.7.3. Изменение последовательности
Параметры последовательности можно изменить командой
ALTER SEQUENCE. Параметры последовательности, не
указанные в команде ALTER SEQUENCE, остаются без
изменений.
Синтаксис:
ALTER SEQUENCE {имя последовательности}
{изменения}
Пример:
ALTER SEQUENCE EMPLOYEES_ID_SEQ
INCREMENT BY 2;
Ограничения на изменения последовательностей:
• нельзя изменить начальное значение последовательности;
244
Глава 8. Операторы определения данных
• минимальное значение не может быть больше текущего значения
(СиЯКѴАЬ);
• максимальное значение не может быть меньше текущего значения
(СиИКѴАЬ).
Удаление последовательности осуществляется командой:
DROP SEQUENCE {имя последовательности} ;
8.7.4. Получение информации о последовательностях
Можно получить информацию о последовательностях, сделав запрос к пред
ставлению informationschema.sequences.
Запрос 8.25. Вывести данные о последовательностях в
схеме HR_POC
SELECT sequence_name, start_value, increment, minimum_value, maximum_
value
FROM information_schema.sequences
WHERE sequence_schema ='hr_poc';
sequence_name
Istart_value|increment|minimum_value|maximum_valueI
------------------ +----------- +--------- +------------- +-------------- +
departments_id_seq|300
employees_id_seq
|210
|10
|2
|1
|1
11000
|5000
|
|
8.8. Индексы
Индексы являются объектами базы данных, которые
позволяют существенно сократить время выполнения
запросов на выборку данных.
Индекс представляет собой отсортированные (упорядоченные) значения
столбца, для которого он создан. Это позволяет быстро находить связанные
с ним строки таблицы и сокращать до минимума число операций обращения
к магнитному диску или другому устройству, на котором хранятся таблицы
базы данных.
PostgreSQL: SQL + PL/pgSQL
.............................................PostgreSQL
Индексы автоматически создаются сервером при определении первичного
ключа или создании ограничения на уникальность значений. При выпол
нении операторов изменения данных (INSERT, UPDATE, DELETE) сервер
автоматически обновляет индексы. Обновление индексов требует времени,
поэтому время выполнения операторов изменения данных возрастает.
Индексы следует создавать для таблиц, которые содержат большое количе
ство строк, и для тех столбцов, которые часто используются в предложении
WHERE.
Для создания индекса используется оператор CREATE INDEX>
CREATE [UNIQUE] INDEX [ IF NOT EXISTS ] name
ON table_name [ USING method ] ( { column_name |
[ WHERE { predicate } ]
( expression )}]
Это сокращенное описание синтаксиса оператора CREATE INDEX, с пол
ным описанием можно ознакомиться в официальной документации.
Рассмотрим параметры этого оператора:
• UNIQUE — параметр, который определяет то, что значения индексируе
мого столбца должны быть уникальны;
• IF NOT EXISTS — если этот параметр указан, то не будет возникать
ошибка, если последовательность с тем же именем уже существует. Опе
ратор создания индекса не будет выполнен;
•
name — имя индекса;
• table name — имя таблицы, для которой создается индекс;
•
method — имя метода индексации, который будет использоваться. Воз
можные значения: btree, hash, gist, spgist, gin, brin;
•
column_name | expression — имя столбца таблицы или выражение, кото
рые индексируются;
• predicate — выражение ограничения для частичного индекса.
По умолчанию используется метод индексации btree. Этот метод индекса
ции является оптимальным для большинства задач, он позволяет проиндек
сировать любые данные, которые могут быть отсортированы. В рассматри
ваемых примерах будут использоваться индексы этого типа.
Глава 8. Операторы определения данных
Запрос 8.26. Создание индекса для столбца last_name
таблицы Employees
CREATE INDEX last_name_idx
ON Employees (last_name);
8.8.1. Создание индексов на базе функций
Если для столбца создан обычный индекс, а запрос будет содержать функ
цию, например:
SELECT *
FROM employees
WHERE UPPER(last_name) = 'KING';
то при выполнении этого запроса индекс не будет использован. Для таких
случаев нужно создавать специальный индекс на базе функций.
Запрос 8.27. Создание индекса на базе функции UPPER
для столбца last_name таблицы Employees
CREATE INDEX upper_last_name_idx
ON Employees (UPPER(last_name));
Однако если функция, на базе которой построен индекс, будет возвращать
значения NULL, то этот индекс не будет использоваться или будет работать
неэффективно. Для того чтобы этого избежать, следует добавить условие IS
NOT NULL в предложение WHERE.
Запрос 8.28. Исключение значений NULL при использовании
индекса на базе функции
SELECT employee_id, first_name, last_name
FROM Employees
WHERE UPPER(last_name) Is Not NULL
AND UPPER(last_name) = 'KING';
employee_idI firs t_nameIlast_nameI
---------------------------------------------- +------------------------------------------- +-------------------------------------- +
100|Steven
1561 Janette
I King
I King
|
|
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Можно создать индекс по нескольким столбцам.
Запрос 8.29. Создание индекса по нескольким столбцам
CREATE INDEX FIRST_LAST_NAME_IDX
ON Employees (last_name, first name);
На первом месте следует указать столбец, значения которого повторяются
реже. После создания такого индекса время поиска сократится как при
поиске по столбцу last_name, так и по столбцу first_name. Интуитивно
предполагаю, что ситуации, когда следует использовать такие индексы, до
вольно редки.
Запрос 8.30. Создание индекса по полному имени
CREATE INDEX first_last_name_idx
ON Employees((first_name||' '||last_name))
Запрос 8.31. Поиск сотрудника по полному имени
SELECT employee_id, first_name, last_name
FROM Employees
where (first_name||' '||last_name)='Janette King';
employee id. I first_name | last_name |
------------ +----------- +---------- +
1561 Janette
I King
|
8.8.2. Перестроение индекса
Содержимое индексов изменяется при выполнении операторов изменения
данных, связанных с его таблицей. Размеры индекса могут значительно уве
личиться при удалении большого количества строк, потому что простран
ство, занятое удаленными значениями, индексом не используется.
Можно перестроить индексы и сделать их более компактными, а потому и
более эффективными, периодически применяя команду REINDEX, которая
имеет следующий синтаксис:
REINDEX INDEX {имя индекса}
Пример:
J48J
Глава 8. Операторы определения данных
REINDEX INDEX upper_last_name_idx;
8.8.3. Переименование индекса
Команда для переименования индекса имеет следующий синтаксис:
ALTER NDEX {старое имя индекса}
RENAME ТО {новое имя индекса};
Пример:
ALTER INDEX first_last_name_idx
RENAME TO full_name;
8.8.4. Удаление индекса
Команда для удаления индекса имеет следующий синтаксис:
DROP INDEX {имя индекса};
8.8.5. Получение информации об индексах
Можно получить информацию об индексах, созданных для таблицы, выпол
нив запрос к представлению pr_indexes. В общем виде такой запрос
выглядит следующим образом:
SELECT {список столбцов}
FROM Pr Indexes
WHERE TABLE_NAME = {Имя таблицы}
Запрос 8.32. Вывод данных об индексах таблицы Employees
SELECT tablename, indexname
FROM Pg_Indexes
WHERE tablename='employees';
[ 249 '
PostgreSQL: SQL + PL/pgSQL
W
PostgreSQL
tablename|indexname
I
----------- 1----------------------- p
employees Iemp_email_uk-------- I
employees|emp_emp_id_pk
|
employees|last_name_idx
|
employees|upper_last_name_idx|
employees|full_name
I
Запрос 8.33. Вывод данных об индексе full_name таблицы
Employees
SELECT indexdef
FROM Pg_Indexes
WHERE tablename='employees'AND indexname='full_name';
indexdef
CREATE INDEX full_name ON hr_poc.employees USING btree
name)::text I I ' '::text) I I (last_name)::text))) I
( ( ( ( (first_
Глава 8. Операторы определения данных
Задачи для самостоятельного решения:
Задача 8.1. Создать схему базы данных, E-R диаграмма которой
представлена на рисунке 8.14.
Я course
Я students
Я exams
1^ student_id
1^ student_id
«sc number_test_book
«вс first_name
«SC last_name
1Щ courseid
1Щ course id
«ВС папе
«ВС speciality
123 semester
123 grade
123 lectures
123 lab_works
Рис. 8.14. E-R диаграмма схемы EDU
Задача 8.2. Добавить в схему EDU таблицу Teachers. Устано
вить связь между таблицами Course и Teachers, которая долж
на обеспечивать выполнение следующего правила: каждый пре
подаватель может вести занятия по нескольким дисциплинам,
а занятия по каждой дисциплине ведет только один преподава
тель. После этих изменений создать E-R диаграмму схемы.
Задача 8.3. Схема EDU предполагает, что каждый студент по
каждой дисциплине сдает экзамен один раз. Внести в схему из
менения, которые позволят хранить для каждого студента дан
ные о нескольких экзаменах по каждой дисциплине.
Задача 8.4. Создать представление, которое возвращает дан
ные о студентах и среднем балле каждого студента.
Задача 8.5. Создать представление, которое возвращает значе
ния столбцов Агзіпате, Іаэіпате преподавателя и среднее
значение отметок по каждой дисциплине, которые он вел.
251
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Задача 8.6. Создать последовательность, которую можно ис
пользовать для присвоения значений столбцу student_id при
вводе новых строк в таблицу Students. Привести пример ис
пользования этой последовательности.
Задача 8.7. Создать индекс, который позволит ускорить поиск
данных об отметках, которые получил студент.
252
Глава 9.
СТРУКТУРА
ПРОГРАММ PL/pgSQL
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
PL/pgSQL является процедурным языком СУБД PostgreSQL.
Он позволяет использовать средства процедурных языков
программирования: переменные, условные операторы,
операторы циклов, совместно с операторами SQL. Используя
эти средства, можно создавать хранимые процедуры,
функции и триггеры.
Процедуры используются в тех случаях, когда нужно внести изменения в
данные. Задача функций состоит в том, чтобы выполнить обработку дан
ных и вернуть в вызывающую среду результат этой обработки. Результат
может представлять собой как скалярное значение, так и таблицу. Тригге
ры используются для обеспечения выполнения бизнес-правил, реализации
правил безопасности и контроля изменений, которые пользователи вносят в
таблицы базы данных.
Практически все средства разработки приложений позволяют разместить в
программе текст SQL-запроса. Этот запрос передается на сервер, выполня
ется, и результаты его выполнения передаются в приложение. Однако следу
ет иметь в виду то, что при каждом обращении к серверу происходит анализ,
оптимизация и преобразование запроса во внутренний код, и только после
этого происходит выполнение запроса. Поэтому в большинстве случаев це
лесообразно поместить SQL-запрос в хранимую процедуру или функцию, а
в приложении поместить вызов этой подпрограммы. Синтаксический анализ
подпрограмм осуществляется при их создании, а оптимизация и компиляция —
при первом обращении. При последующих обращениях сразу происходит их вы
полнение, это позволяет существенно сократить время обработки данных.
Выполнение операторов PL/pgSQL осуществляет компонента PL/pgSQL
Engine, которую называют движком PL/pgSQL. Если код PL/pgSQL содер
жит оператор SQL, то он передается на сервер, где выполнение оператора
J54J
Глава 9. Структура программ PL/pgSQL
SQL осуществляет другая компонента SQL Executor, которую называют ис
полнителем запросов SQL. После выполнения оператора SQL его результа
ты могут быть переданы движку PL/pgSQL. Такое взаимодействие
PL/pgSQL Engine с SQL Executor называют переключением контекста.
Движок PL/pgSQL может находиться на стороне клиента, а исполнитель
запросов SQL всегда находится на сервере. Частое переключение контекста
приводит к росту сетевого трафика, а также к существенному увеличению
времени обработки данных. При разработке программ PL/pgSQL следует ис
пользовать средства, которые позволяют сократить количество переключе
ний контекста.
Функции серверного программирования в СУБД PostgreSQL могут быть
реализованы также с использованием других языков программирования:
PL/Perl, PL/Python, PL/Tcl.
Основным назначением PL/pgSQL является разработка хранимых процедур,
функций и триггеров. Рассмотрим основные конструкции этого языка, кото
рые можно использовать при их создании.
9.1. Структура блока PL/pgSQL
Все программы PL/pgSQL состоят из блоков. Блок объединяет операторы,
предназначенные для решения определенной задачи. Одна программа может
содержать несколько блоков, которые могут располагаться последовательно
один за другим или быть вложенными. Блоки могут быть анонимными или
именованными. Анонимные блоки не имеют имени, они не могут быть
сохранены в базе данных, и на них нельзя ссылаться.
Именованные блоки используются при создании подпрограмм. Эти блоки
представляют собой процедуры и функции, которые хранятся в базе данных
и которые можно вызвать по имени.
В общем виде структура блока PL/pgSQL может быть представлена в следу
ющем виде:
[DECLARE]
{Раздел объявлений}
BEGIN
{Выполняемый раздел}
[EXCEPTION]
{Раздел обработки ошибок}
END;
П5Г
PostgreSQL: SQL + PL/pgSQL
............................................ PostgreSQL
• Раздел объявлений может содержать определение типов, переменных,
курсоров, локальных подпрограмм и исключений, определяемых поль
зователем.
•
Выполняемый раздел начинается с ключевого слова BEGIN и заканчи
вается ключевым словом END. Может содержать операторы присваива
ния, управления и операторы SQL.
• Раздел обработки ошибок начинается с ключевого слова Exception и
определяет действия, которые должны выполняться при возникновении
ошибок в выполняемом разделе.
Обязательным является только выполняемый раздел. Он должен содержать
хотя бы один выполняемый оператор.
В PL/pgSQL можно использовать два вида комментариев: комментарий до
конца строки, который начинается с двойного тире (—), и блочный коммен
тарий, который начинается с /* и завершается */.
Для вывода результатов на экран можно использовать оператор RAISE
NOTICE, который имеет следующий формат^
RAISE NOTICE '{текст} % [%]',
{переменная}[{переменная}];
9.2. Анонимные блоки
Анонимный блок является приложением,
взаимодействующим с базой данных.
Анонимные блоки могут создаваться в клиентских программах для вызова
хранимых процедур и функций, содержащихся в базе данных, и для автома
тизации задач администрирования баз данных.
Свойства анонимных блоков:
•
не имеют имени;
•
не хранятся в базе данных;
•
компилируются при каждом выполнении;
Глава 9. Структура программ PL/pgSQL
•
передаются PL/pgSQL Engine для выполнения в реальном времени;
•
не могут быть вызваны.
Примеры анонимных блоков
Листинг 9.1. Определение корней квадратного уравнения
DO $$
DECLARE
a real:= 1;
b real:= -1;
c real:= -б;
d real;
xl real;
x2 real;
BEGIN
d := SQRT(b*b -4*a*c);
xl := (-b - d)/(2*a);
x2 := (-b + d)/(2*a) ;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'Корни уравнения: a*x*x + b*x + c = O';
RAISE NOTICE ' xl= %', xl;
RAISE NOTICE ' x2=%', x2;
END $$;
Результат:
Корни уравнения:
xl= -2
x2 = 3
a*x*x + b*x + c = 0
Если квадратное уравнение не будет иметь действительных корней, то при
выполнении скрипта из листинга 9.1 возникнет ошибка. Эту ошибку можно
обработать в разделе обработки исключений.
Листинг 9.2. Определение корней квадратного уравнения
с разделом обработки исключений
DO $$
DECLARE
a real:= -1;
b real:= -1;
c real:= -б;
d real;
xl real;
257
PostgreSQL: SQL + PL/pgSQL
в PostgreSQL
x2 real;
text_varl text;
text_var2 text;
BEGIN
d := SQRT(b*b -4*a*c);
xl := (-b - d)/(2*a) ;
x2 := (-b + d)/(2*a) ;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'Корни уравнения: a*x*x + b*x + c = O';
RAISE NOTICE ' xl= % ' , xl ;
RAISE NOTICE ' x2=%', x2 ;
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_varl = MESSAGE_TEXT,
text_var2 = RETURNED_SQLSTATE;
RAISE NOTICE ' Причина ошибки = %', text_varl;
RAISE NOTICE ' Код ошибки=%', text_var2;
END $$;
Причина ошибки = извлечь квадратный корень отрицательного числа
нельзя
Код ошибки = 2201F
Эта ошибка возникает из-за неточной реализации алгоритма нахождения
корней квадратного уравнения, который должен включать проверку дискри
минанта. Там, где это возможно, следует использовать процедурные сред
ства предотвращения ошибок, а не обработку исключений.
Листинг 9.3. Определение корней квадратного уравнения
с проверкой дискриминанта
DO $$
DECLARE
a real:= —1;
b real:= -1;
c real:= - 6;
d real;
xl real;
x2 real;
BEGIN
IF (b*b -4*a*c) >= 0
THEN
d := SQRT(b*b -4*a*c);
xl := (-b - d)/(2*a);
x2 := (-b + d)/(2*a);
RAISE NOTICE 'Корни уравнения: a*x*x + b*x + c = O';
3
Глава 9. Структура программ PL/pgSQL
RAISE NOTICE ' xl= %', xl ;
RAISE NOTICE ' x2=%', x2 ;
ELSE
RAISE NOTICE 'Результат :';
RAISE NOTICE ' Действительных корней нет';
END IF;
END $$;
Результат :
Действительных корней нет
9.3. Переменные, константы и типы данных
Переменная - это именованная область памяти, которая
может содержать данные определенного типа. Все
используемые переменные должны быть объявлены.
Тип переменной определяет вид данных, который может храниться в данной
переменной. Кроме переменных, можно объявить и использовать констан
ты. В отличие от переменных, значение констант нельзя изменить.
Объявление переменных и констант
Синтаксис:
{имя} [CONSTANT] {тип данных} [NOT NULL]
[:= {значение} | DEFAULT {значение}];
Имя должно начинаться с буквы, может содержать буквы и цифры, а также
символы $, _. Имена переменных нечувствительны к регистру.
Если при объявлении указано служебное слово CONSTANT, то этот иденти
фикатор является константой, ему нужно при объявлении присвоить значе
ние, которое нельзя будет изменять.
Переменные и константы могут иметь любой тип данных, используемый
при определении таблиц базы данных. В коде PL/pgSQL можно использовать
типы данных, определяемых пользователями, и составные типы данных.
PostgreSQL: SQL + PL/pgSQL
Перечень типов данных, которые можно использовать в коде PL/pgSQL,
весьма обширен, с их описанием и особенностями использования можно оз
накомиться в официальной документации.
При объявлении переменной ей можно присвоить значение.
Это называется инициализацией.
Рассмотрим несколько примеров объявления переменных, их инициализа
ции и использования в исполняемом разделе.
9.3.1. Использование переменных числового типа
Листинг 9.4. Расчет параметров прямоугольного треугольника
DO $$
DECLARE
а real ;
b real ;
с real ;
Р real ;
S real ;
BEGIN
а := 3;
b := 4;
с := sqrt(a*a + b*b)
Р := a+b+c;
S :=a*b/2;
RAISE NOTICE 'Результат :';
RAISE NOTICE 'Параметры прямоугольного треугольника';
RAISE NOTICE
'катет a= %', a;
RAISE NOTICE
'катет b= %',b;
RAISE NOTICE 'Гипотенуза c= %',c;
RAISE NOTICE 'Периметр p= %',p;
RAISE NOTICE 'Площадь c= %',s;
END $$;
Результат:
Параметры прямоугольного треугольника
Катет а = 3
Катет Ь = 4
Гипотенуза с = 5
Периметр р = 12
Площадь с=6
Глава 9. Структура программ PL/pgSQL
Если объявить переменные, но не присвоить им значение, то они будут иметь
значение NULL. Любое арифметическое выражение будет иметь значение
NULL, если хотя бы один элемент этого выражения будет иметь значение
NULL.
Листинг 9.5. Расчет параметров прямоугольного треугольника
с неинициализированными исходными переменными
DO $$
DECLARE
a real;
b real;
c real;
p real;
s real;
BEGIN
c := sqrt(a*a + b*b);
p := a+b+c;
s := a*b/2;
RAISE NOTICE 'Результат:'
RAISE NOTICE 'Параметры прямоугольного треугольника';
RAISE NOTICE
'катет a= %', a;
RAISE NOTICE
'катет b= %',b;
RAISE NOTICE 'Гипотенуза c= %',c;
RAISE NOTICE 'Периметр p= %',p;
RAISE NOTICE 'Площадь c= %',s;
END $$;
Результат:
Параметры прямоугольного треугольника
Катет а = <ыиьь>
Катет Ь = <ЫиЬЬ>
Гипотенуза с = <ыиьь>
Периметр р = <ыиьь>
Площадь с = <ыиьь>
9.3.2. Использование переменных символьного типа
Листинг 9.6. Соединение значений двух символьных переменных
DO $$
DECLARE
v_f_name VARCHAR(IO) ;
v_l_name VARCHAR(10);
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
v_name
VARCHAR(20);
BEGIN
RAISE NOTICE 'Результат: ';
v_f_name:='Ivan';
v_l_name:='Petrov';
v_name: = v_f_name | | ' ' | | v_l_name;
RAISE notice 'Меня зовут %',v_name;
END $$;
Результат:
Меня зовут Ivan Petrov
В этом примере объявляются переменные ѵГпате (имя) и ѵ_1_пате (фа
милия), имеющие тип ѴАИСНАК(ІО). В исполняемом блоке им присваива
ются значения, и они соединяются в одну строку, значение которой присваи
вается переменной ѵ_пате (полное имя).
Листинг 9.7. Изменение значения строковой переменной
DO $$
DECLARE
v_f_name VARCHAR(10);
v_l_name VARCHAR(10);
v_m_name VARCHAR(20):='Ivan Petrov';
v_w_name VARCHAR(20):='Olga Titova';
BEGIN
RAISE NOTICE 'Результат :';
RAISE NOTICE 'Имена молодоженов';
RAISE NOTICE 'Он: %',v_m_name;
RAISE NOTICE 'Она : %',v_w_name;
v_f_name:= SUBSTR(v_w_name,1,(STRPOS(v_w_name,' ' )-1)) ;
v_l_name:= SUBSTR(v_m_name,(STRPOS(v_m_name,' ')+1));
v_w name : =v_f_name | | ' ' | | v_l_name | | ' a ' ;
RAISE NOTICE 'Имена супругов';
RAISE NOTICE 'Муж: %',v_m_name;
RAISE NOTICE 'Жена : %',v_w_name;
END $$;
Результат:
Имена молодоженов
Он: Ivan Petrov
Она: Olga Titova
Имена супругов
Муж: Ivan Petrov
Жена: Olga Petrova
Я
Глава 9. Структура программ PL/pgSQL
В этом примере объявляются и инициализируются значения переменных
ѵтпате и v_w_name, которые содержат полные имена молодоженов.
В исполняемом разделе переменной ѵ Г паше присваивается значение име
ни невесты, а переменной ѵ_1_паше присваивается значение фамилии жени
ха, и после этого формируется новое полное имя новобрачной.
9.3.3. Использование переменных типа Date
Листинг 9.8. Объявление и обработка переменных, имеющих
тип Date
DO $$
DECLARE
v_m_name VARCHAR(20):='Ivan Petrov';
v_m_date_of_birth DATE:='01.09.1990';
v_diff INTEGER;
v_date DATE;
v_day TEXT;
v_time TEXT;
BEGIN
RAISE NOTICE 'Результат : ';
RAISE NOTICE 'Меня зовут: %',v_m_name;
RAISE NOTICE 'Я родился: %', v_m_date_of_birth;
v_date := TO_CHAR(CURRENT_DATE, 'DD-MM-YYYY');
v_day := TO_CHAR(CURRENT_DATE, 'DAY');
v_time := TO_CHAR(CURRENT_TIMESTAMP , 'HH24:MI :SS' ) ;
v_diff := EXTRACT(year FROM AGE(v_m_date_of_birth));
RAISE NOTICE 'Сегодня: %', v_date;
RAISE NOTICE 'День недели: %',v_day;
RAISE NOTICE 'Время: %',
v_time;
RAISE NOTICE 'Мне: %', (v_diff| | ' года');
END $$;
Результат:
Меня зовут: Ivan Petrov
Я родился: 1990-09-01
Сегодня: 2023-07-09
День недели: SUNDAY
Время: 11:24:03
Мне: 32 года
В этом примере объявляется и инициализируется переменная v_m_date_of_
birth, имеющая тип Date и представляющая собой дату рождения человека,
PostgreSQL: SQL + PL/pgSQL
и переменная vdate типа Date, которой в исполняемом разделе присваива
ется значение текущей даты.
Используя функцию TO_CHAR, выделяются и выводятся
значения различных компонент текущей даты, а с помощью
функций EXTRACT и AGE определяется текущий возраст.
9.3.4. Неявное объявление типа переменной
Можно объявить переменную, тип которой совпадает либо с типом ранее
объявленной переменной, либо с типом столбца таблицы.
Синтаксис:
{имя переменной}
{базовая переменная}%TYPE;
{имя переменной}
{таблица.столбец}%TYPE;
Использование атрибута %TYPE позволяет избежать ошибок, вызванных
несоответствием типа или точности данных. Не нужно изменять объявле
ние переменной, если изменится определение переменной или столбца
таблицы, значение которых присваивается переменной.
Если для базовой переменной задано ограничение NOT NULL, то перемен
ной, которой будет присваиваться тип базовой переменной, нужно при объ
явлении обязательно присваивать значение. Но если переменной присваи
вается тип столбца таблицы, для которого задано ограничение NOT NULL,
присваивать значение переменной при объявлении необязательно, так как
это ограничение на переменную не переносится.
В листинге 9.9 приведен пример объявлений переменных с использованием
атрибута %TYPE, при этом предполагается, что для столбца salary в таблице
Employees задано ограничение NOT NULL.
Листинг 9.9. Пример использования переменных с неявным
объявлением типа
DO $$
DECLARE
v_emp_id
Employees.employee_id% type:=10 6;
Глава 9. Структура программ PL/pgSQL
v_old_sal Employees.salary%type;
v_new_sal v_old_sal%type;
BEGIN
SELECT salary INTO v_old_sal
FROM Employees
WHERE employee_id = v_emp_id;
UPDATE Employees
SET salary = salary*1.1
WHERE employee_id = v_emp_id;
SELECT salary INTO v_new_sal
FROM Employees
WHERE employee_id = v_emp_id;
RAISE
RAISE
RAISE
RAISE
END $$;
NOTICE
NOTICE
NOTICE
NOTICE
’Результат : ';
'employee_id= %',
v_emp_id ;
'Старая зарплата =: %',v_old_sal;
'Новая зарплата =: %',v_new_sal;
Результат :
employee_id= 106
Старая зарплата: 5280.00
Новая зарплата: 5808.00
В этом примере используются переменные ѵешріб, ѵоіёваі, v_new_sal,
тип которых задан неявным образом. В выполняемом разделе переменной
ѵ_оШ_ха1 присваивается значение текущей зарплаты сотрудника 106. После
этого зарплата сотрудника увеличивается на 10% и новое значение зарплаты
присваивается переменной v_new_sal.
Можно создать переменную, содержащую несколько полей, структура кото
рой совпадает со структурой определенной таблицы.
Синтаксис:
{имя переменной} {имя таблицы}%ROWTYPE
Обращение к определенному полю такой переменной имеет следующий вид:
{имя переменной}.{имя поля}
PostgreSQL: SQL + PL/pgSQL
^PostgreSOL
Листинг 9.10. Пример объявления и использования
переменных ROWTYPE
DO $$
DECLARE
v_l
Cus tomers %ROWTYPE;
v_2
Customers%ROWTYPE;
v_sum_limit numeric(10,2);
BEGIN
v_l.c_name:='Ivan Petrov';
v_l.credit_limit := 200000;
v_2.c_name:='Sergey Ivanov';
v_2.credit_limit := 300000;
v_sum_limit := v_l.credit_limit + v_2.credit_limit;
RAISE NOTICE 'Результат: ';
RAISE NOTICE 'Имя клиента 1: % % %',v_l.c_name,
'Кредитный лимит: ', v_l.credit_limit;
RAISE NOTICE 'Имя клиента 2: % % %', v_2.c_name,
'Кредитный лимит: ', v_2.credit_limit;
RAISE NOTICE 'Суммарный кредитный лимит: %', v_sum_limit;
END $$;
Результат:
Имя клиента 1: Ivan Petrov Кредитный лимит:
200000.00
Имя клиента 2: Sergey Ivanov Кредитный лимит:
300000.00
Суммарный кредитный лимит: 500000.00
В этом примере объявляются две переменные ѵ_1 и ѵ_2, структура которых
совпадает со структурой таблицы Customers. Полям c_name и credit_limit
этих переменных присваиваются значения. Вычисляется суммарный кре
дитный лимит, и полученный результат выводится.
Это очень востребованный способ неявного объявления типа переменной,
который мы будем довольно часто использовать.
9.3.5. Область действия переменных
Область действия переменной — это часть программы,
в которой можно получить доступ к переменной. Эта
область начинается с момента объявления переменной и
заканчивается в конце блока, в котором она была объявлена.
266
Глава 9. Структура программ PL/pgSQL
Если блок содержит вложенные блоки, то переменные, объявленные во
внешнем блоке, можно использовать во вложенных блоках. Если во внеш
нем и вложенном блоке объявлены переменные, имеющие одинаковое имя,
то это разные переменные. Значение переменной, объявленной во внешнем
блоке, не передается во внутренний блок, а изменение значения переменной
во внутреннем блоке не изменяет значение, которое она имела во внешнем
блоке.
Код, приведенный в листинге, содержит внешний и внутренний блоки. В
обоих блоках объявлены и инициализированы переменные v_product_
name, vjproduct_price.
Листинг 9.11. Пример анонимного блока,
внешний и внутренний блоки
содержащего
DO $$
DECLARE
v_j>roduct_name varchar(20):='HP C2J95AT';
v_product_price numeric(7,2):=2000;
BEGIN
DECLARE
v_product_name varchar(20):='AMD 100-5056062';
v_product_price numeric(7,2):=1500;
BEGIN
RAISE NOTICE'Имя товара: %', v_product_name;
RAISE NOTICE'Цена товара: %', v_product_price ;
END;
RAISE NOTICE'Имя товара: %', v_jproduct_name;
RAISE NOTICE'Цена товара: %', v_product_price ;
END $$;
Результат:
Имя товара: AMD 100-5056062
Цена товара: 1500.00
Имя товара: HP C2J95AT
Цена товара: 2000.00
Во внутреннем блоке можно использовать одноименные переменные из
внешнего блока. Для этого необходимо установить метки блоков, которые
имеют следующий синтаксис «{метка блока}».
Ссылка во внутреннем блоке на переменную из внешнего блока имеет сле
дующий вид: {метка блока}.{имя переменной}.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Рассмотрим следующую задачу:
создать анонимный блок, содержащий два блока - внешний
и внутренний.
Во внешнем блоке объявить и инициализировать переменные:
• v_name - имя и фамилия отца;
• vdateofbirth - дата рождения отца.
Во внутреннем блоке объявить и инициализировать переменные:
• vname - имя и фамилия ребенка;
• v_date_of_birth - дата рождения ребенка.
Объявить переменные:
• v_age_ father - возраст отца;
• v_age_children - возраст ребенка;
• ѵ_аёе_ ftch ~ возраст отца на дату рождения ребенка.
Необходимо присвоить значения этим переменным и вывести на печать.
Листинг 9.12. Пример использования меток внешнего и
внутреннего блоков
DO $$
«А»
DECLARE
v_name varchar(ЗО):= 'Анатолий Иванов';
v_date_of_birth date := TO_DATE('Об.JAN.1968', 'DD.MON.YYYY');
BEGIN
«В»
DECLARE
v_name VARCHAR(30):= 'Надежда Иванова';
v date_of_birth date := TO_DATE('06.AUG.1997', 'DD.MON.YYYY');
v_age_father integer;
v_age_children integer;
v_age_ftch integer;
BEGIN
v_age_father := EXTRACT(year FROM AGE( A.v_date_of_birth));
v_age_children := EXTRACT(year FROM AGE( v_date_of_birth));
v_age_ftch := v_age_father - v_age_children ;
RAISE NOTICE 'Результат:
268J
';
Глава 9. Структура программ PL/pgSQL
RAISE
RAISE
RAISE
RAISE
RAISE
RAISE
RAISE
RAISE
NOTICE'Сегодня: %', CURRENT_DATE;
NOTICE'Имя моего отца: %', A.v_name;
NOTICE'Он родился: %', A. v_date_of_birth;
NOTICE'Сейчас отцу: %', (v_age_father||'лет');
NOTICE'Меня зовут : %', v_name;
NOTICE'Я родилась: %', v_date_of_birth;
NOTICE'Мне : %', (v_age_children||' лет');
NOTICE'Когда я родилась, отцу было : %',
(v_age_ftch||'лет');
END;
END $$;
Результат:
Сегодня: 2023-07-10
Имя моего отца: Анатолий Иванов
Он родился: 1968-01-06
Сейчас отцу: 55 лет
Меня зовут: Надежда Иванова
Я родилась: 1997-08-06
Мне: 25 лет
Когда я родилась, отцу было: 30 лет
В этом примере во внешнем и внутреннем блоках объявлены переменные ѵ_
name и v_date_of_birth. Во внешнем блоке, для которого установлена метка
«А», этим переменным присваиваются значения имени и даты рождения
отца. Во внутреннем блоке, для которого установлена метка «В», этим
переменным присваиваются значения имени и даты рождения ребенка. Во
внутреннем блоке при вычислении возраста отца используется переменная
A.v date of birth из внешнего блока.
9.4. Операторы SQL в PL/SQL
В PL/pgSQL можно использовать следующие виды операторов SQL:
• Операторы манипулирования данными — SELECT, INSERT, UPDATE,
DELETE, MERGE;
• Операторы управления транзакциями — COMMIT, ROLLBACK,
SAVEPOINT.
Если нужно выполнить несколько операторов манипулирования данными,
то каждый оператор является отдельным запросом к базе данных и отправ
ляется на сервер отдельно от других операторов. Результаты выполнения
каждого оператора отправляются обратно клиенту. Выполнение нескольких
PostgreSQL: SQL + PL/pgSQL
w
PostgreSQL
операторов манипулирования данными приводит к множественным переда
чам в обоих направлениях, значительно увеличивая сетевой трафик.
Если эти операторы объединить в блок, то они отправляются на сервер как
единое целое. Сервер выполняет эти операторы и отправляет результаты их
выполнения обратно клиенту как единое целое. Этот процесс более эффек
тивен и занимает существенно меньшее время, чем выполнение каждого
оператора независимо от других.
9.4.1. Использование оператора SELECT
Оператор SELECT можно использовать для присвоения
значений переменным.
Синтаксис:
SELECT {список столбцов или выражений}
INTO [STRICT] {список переменных}
FROM {список источников данных}
WHERE {условное выражение}
Запрос должен возвращать ровно одну строку. Список столбцов и список
переменных должны содержать одинаковое количество элементов с совме
стимыми типами данных.
Если слово STRICT будет отсутствовать, то в случае если запрос не вернет
строк, переменным будет присвоено значение NULL, а если запрос вернет
несколько строк, то переменным будет присвоено значение первой строки.
Ошибки NO DATA FOUND и ТООМ ANY ROWS в этом случае не возни
кают. Использование этого слова будет рассмотрено в главе 13 - Обработка
ошибок.
Листинг 9.13. Извлечение значения одного столбца
DO $$
DECLARE
v_emp_id Employees.employee_id%TYPE := 120;
v_emp_salary Employees.salary%TYPE;
BEGIN
SELECT salary INTO v_emp_salary
^ 270 J
Глава 9. Структура программ PL/pgSQL
FROM Employees
WHERE employee_id = v_emp_id;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'employee_id = %’, v_emp_id;
RAISE NOTICE'salary = %', v_emp_salary;
END $$;
Результат:
employee_id = 120
salary = 8800.00
Значение, присваиваемое переменной, может быть результатом обработки
данных, например значением, возвращаемым агрегатной функцией.
Листинг 9.14. Извлечение значения,
агрегатной функцией
возвращаемого
DO $$
DECLARE
v_dep_id Employees.department_id%TYPE := 80;
v_max_salary Employees.salary%TYPE;
BEGIN
SELECT MAX(salary) INTO v_max_salary
FROM Employees
WHERE department_id = v_dep_id;
RAISE NOTICE ’ Результат: ';
RAISE NOTICE' department_id = %', v_dep_id;
RAISE NOTICE' MAX(salary) = %', v_max_salary;
END $$;
Результат:
department_id =80
MAX(salary) = 14000.00
Можно извлечь и присвоить переменным значения нескольких столбцов.
Листинг 9.15. Извлечение значений нескольких столбцов
DO $$
DECLARE
v_emp_id Employees.employee_id%TYPE := 120;
v_first_n Employees .first_name%TYPE;
27.1
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
v_las t_n Employees.last_name%TYPE;
v_j ob
Employees.j ob_id%TYPE;
v_emp_salary Employees.salary%TYPE;
BEGIN
SELECT employee_id, first_name, last_name,job_id, salary
INTO v_emp__id, v_first_n, v_last_n, v_job, v_emp_salary
FROM Employees
WHERE employee_id = v_emp_id ;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'% % % % %',LPAD('employee_id',11), LPAD('first_name',12),
LPAD('last_name',10), LPAD('job_id',9), LPAD('salary',10);
RAISE NOTICE'% % % % %' , LPAD(v_emp_id:: text,11), LPAD(v_first_n,12),
LPAD(v_last_n,10), LPAD(v_job,9), LPAD(v_emp_salary: : text,10);
END $$;
Результат :
employee_id
120
first_name last_name
Matthew
Weiss
job_id
ST_MAN
salary
8800.00
В подобных случаях удобнее использовать переменную, имеющую тип
ROWTYPE.
Листинг 9.16. Извлечение значений нескольких столбцов
с использованием переменной типа ROWTYPE
DO $$
DECLARE
v_emp_id Employees.employee_id%TYPE := 120;
y_emp Employees%ROWTYPE;
BEGIN
SELECT * INTO v_emp
FROM Employees
WHERE employee_id = v_emp_id;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'% % % % %',LPAD('employee_id',11), LPAD('first_
name',12), LPAD('last_name',10), LPAD('job_id',9), LPAD('salary',10);
RAISE NOTICE'% % % % %' ,LPAD(v_emp.employee_id::text,11), LPAD(v_
emp.first_name,12), LPAD(v_emp.last_name,10), LPAD(v_emp.job_id,9),
LPAD(v_emp.salary::text,10);
END $$;
Результат:
employee_id
120
first_name last_name
Matthew
Weiss
job_id
ST MAN
salary
8800.00
Глава 9. Структура программ PL/pgSQL
9.4.2. Использование оператора INSERT
Правила использования операторов INSERT в блоках PL/pgSQL соответ
ствуют правилам использования DML операторов PostgreSQL, но в услов
ных выражениях и списке значений можно использовать переменные, кото
рые были определены и инициализированы в блоке.
В примерах применения оператора INSERT будет использована таблица
Productsl, которая является копией таблицы Products и не содержит дан
ных. Для присвоения значений ключевому столбцу product_id этой таблицы
создадим последовательность^
CREATE SEQUENCE Prod_l_Id_Seq
START WITH 1
INCREMENT BY 1;
В листинге 9.17 приведен пример добавления новой строки в таблицу
Products l. Для присвоения значения столбцу productid используется
последовательность Prod i Id Seq, значения остальных столбцов заданы
непосредственно в предложении VALUES.
Листинг 9.17. Вставка новой строки в таблицу Productз_1
DO $$
BEGIN
INSERT INTO Products_l(product_id, product_name, rating_p)
VALUES (NEXTVAL('Prod_l_Id_Seq'),'ASUS Z12DE A5 ',2);
END $$;
В листинге 9.18 приведен еще один пример добавления новых строк в
Products l. В качестве источника данных в этом примере используется опе
ратор SELECT, который возвращает данные о товарах из таблицы Products,
рейтинг которых равен 3. На рисунке 9.1 показано содержимое таблицы
Products l после выполнения этого оператора.
Листинг 9.18. Вставка в таблицу Products_l результатов
выполнения оператора SELECT
DO $$
BEGIN
INSERT INTO Products_l(product_id, product_name, rating_p, price)
[ 273 '
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
SELECT NEXTVAL('Prod_l_Id_Seq'), product_name, rating_p, price
FROM Products WHERE rating__p =3 ;
END $$;
і Я products_1 IJ J
Введите SQL выражение чтобы отфильтровать результаты
■^productjd
йОС product_name
1
■»"
123 rating_p
^
ASUSZ12DE А5
2
123 price
’•’■
{NULL]
2
2
Apple iPhone XS 64GB Space Gray (MT9E2RU/A)
3
900
3
3
Apple iPhone XR 64GB Space Gray (MRV42RU/A)
3
750
4
4
AsusX99-E-10GWS
3
620
5
5
Apple iPhone 7 256Gb
3
550
6
6
Apple iPhone 8 Plus 256 GB
3
570
7
7
LG V30+ Black (H930DS)
3
530
8
8
Samsung Galaxy S7 Edge 32Gb Black (SM-G935)
3
520
9
9
Sony Xperia XZ Premium Black (G8142)
3
[NULL]
10
10
Xiaomi МІ5 32GB
3
490
11___
11
Xiaomi МІ6 64Gb
3
480
Рис. 9.1. Содержимое таблицы Productsl
Использование последовательности в этих примерах позволяет обеспечить
уникальность значений ключевого столбца productid. Рассмотрим другой
способ решения этой проблемы. Рассмотрим следующую задачу: необходи
мо добавить данные о новом заказе и о товарах, содержащихся в этом заказе.
Для автоматической нумерации заказов (присвоения значений ключевому
столбцу orderid таблицы Orders) была создана последовательность Orders_
IdSeq.
Листинг 9.19. Вставка данных о новом заказе и его содержимом
DO $$
DECLARE
v_oi_id integer;
BEGIN
INSERT INTO Orders
(order_id,customer_id,status,salesman_id,order_date)
VALUES (NEXTVAL('Orders_Id_Seq') , 11, 'Pending', 147,
CURRENT_DATE);
SELECT COALESCE(MAX(item_id),0) into v_oi_id
FROM Order_Iterns
WHERE order_id = CURRVAL('Orders_Id_Seq');
v_oi_id := v_oi_id+l;
і 274 ]
Глава 9. Структура программ PL/pgSQL
INSERT INTO
Order_Iterns
(order_id,item_id,product_id,quantity,unit_price)
VALUES (CURRVAL('Orders_Id_Seq'), v_oi_id, 78, 10, 1360);
END $$;
В этом примере показано, как можно реализовать автоматическую нуме
рацию товаров в заказе. Для этого используется переменная v_oi_id. Этой
переменной сначала присваивается максимальное значение столбца item_id
для данного заказа, потом ее значение увеличивается на 1, и это значение ис
пользуется в операторе INSERT.
9.4.3. Использование оператора UPDATE
Синтаксис и правила использования операторов UPDATE в блоках
PL/pgSQL соответствуют правилам использования DML операторов
PostgreSQL, но в условных выражениях и операторах присваивания можно
использовать переменные, которые были определены и инициализированы
в блоке.
Листинг 9.20. Изменение значения столбца rating_p в
таблице Products_l
DO $$
DECLARE
v_pr°d_id Products_l.product_id%TYPE := 6 ;
v_add_rating Products_l.rating_p%TYPE :=2;
v_prod Products_l%ROWTYPE;
BEGIN
UPDATE Products_l
SET rating_p = rating_p + v_add_rating
WHERE product_id = v__prod_id;
SELECT * INTO v_prod
FROM Products_l
WHERE product_id = v_prod_id;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'product_id = %',v_prod.product_id;
RAISE NOTICE ' rating__p = %',v_prod.rating_p;
END $$;
Результат:
product_id = 6
rating_p = 5
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
В этом примере рейтинг товара product_id=6 увеличивается на ѵ_аё(1
гаВп§=2, и новое значение рейтинга выводится на экран.
Анализируя содержимое таблицы Productsl, представленное на рис. 9.1,
можно определить, что значения, содержащиеся в столбце product_name,
имеют различные стили используемого регистра. Это может стать причиной
ошибок при обработке этих данных. Листинг 9.21 содержит пример исполь
зования оператора UPDATE для задания единого стиля в столбце product_
name. На рис. 9.2 показано содержимое таблицы Products l после выполне
ния этого оператора.
Листинг 9.21. Изменение регистра столбца product_name
в таблице Products_l
DO $$
BEGIN
UPDATE ProductS_l
SET product_name = INITCAP(product_name);
END $$;
O products_1 15 J Введите SQL выражение чтобы отфильтровать результатъ!
Æ productjd
*
ЛВС product_name
▼
123 rating_p
▼
123 price
▼ J
Asus Z12de AS
2
[NULL]
2
Apple Iphone Xs 64gb Spj
3
900
3
Apple Iphone Xr 64gb Spa
3
750
г
4
4
Asus X99-E-10g Ws
3
620
Л
5
5
Apple Iphone 7 256gb
3
550
Ь
б
7
Lg V30+ Black (H930ds)
3
530
7
8
Samsung Galaxy S7 Edge
3
520
8
9
Sony Xperia Xz Premium I
3
[NULL]
9
10
Xiaomi Mi5 32gb
3
490
10
11
Xiaomi Mi6 64gb
3
480
Apple Iphone 8 Plus 256 G
5
570
11___
б
Рис. 9.2. Содержимое таблицы Products l после изменения регистра
Рассмотрим следующую задачу: необходимо изменить зарплату сотрудни
ков 115, 116, работающих в отделе 30, так чтобы после этого изменения сум
марная зарплата сотрудников отдела 30 была равна 30000, а разница между
зарплатами сотрудников 115 и 116 была равна 290.
Особенностью этой задачи является необходимость предварительного реше
ния системы 2 линейных уравнений.
Введем обозначения:
Глава 9. Структура программ PL/pgSQL
• si — зарплата сотрудника 115 до повышения;
• s2 — зарплата сотрудника 116 до повышения;
• suml — суммарная зарплата сотрудников отдела 30 до повышения;
• sum2 — суммарная зарплата сотрудников отдела 30 после повышения;
• ds — разница между зарплатами сотрудников 115 и 116 после повыше
ния;
• хі — размер повышения зарплаты сотрудника 115;
• х2 — размер повышения зарплаты сотрудника 116.
Значения sum2 и ds заданы, а значения si, s2, suml можно определить с
помощью запросов. Используя условия задачи, составим систему из двух
линейных уравнений:
sum2 - suml = xl + х2 ;
(si + xl) - (s2 +x2) = ds
Решение этой системы уравнений имеет следующий вид:
х2= (зит2 - зит1 + з1 - з2)/2;
х1 = зит2 - зит! - х2
В листинге 9.22 приведена программная реализация рассматриваемой задачи.
Листинг 9.22. Изменение зарплаты сотрудников
DO $$
DECLARE
v_dep_id
v_empl
v_emp2
v_sl
v_s2
v_xl
v_x2
v_ds
v_suml
v_sum2
BEGIN
employees.department_id%TYPE := 30;
employees.employee_id%TYPE:= 115;
employees.employee_id%TYPE:= 116;
employees.salary%TYPE;
employees.salary%TYPE;
employees.salary%TYPE;
employees.salary%TYPE;
employees.salary%TYPE := 290;
numeric(9,2);
numeric(9,2):= 30000;
PostgreSQL: SQL + PL/pgSQL
.................................................. f PostgreSQL
SELECT salary INTO v_sl
FROM Employees
WHERE employee_id= v_empl;
RAISE NOTICE 'Зарплата сотрудника % % % ’,
'до повышения =', v_sl;
SELECT salary INTO v_s2
FROM Employees
WHERE employee_id = v_emp2;
RAISE NOTICE 'Зарплата сотрудника %%% ',
'до повышения =', v_s2;
v_empl,
v_emp2,
SELECT SUM(salary)
INTO v_suml
FROM EMPLOYEES
WHERE department_id = v_dep_id;
RAISE NOTICE 'Зарплата отдела % % % ', v_dep_id,
'до повышения =', v_suml;
v_x2: = ( v_sum2 - v_suml + v_sl - v_s2 -v_ds)/2;
v_ xl: = v_sum2 - v_suml - v_x2;
UPDATE Employees
SET salary = salary +v_xl
WHERE employee_id = v_empl;
UPDATE Employees
SET salary = salary +v_x2
WHERE employee_id = v_emp2;
SELECT salary INTO v_sl
FROM Employees
WHERE employee_id= v_empl;
RAISE NOTICE 'Зарплата сотрудника % % % ',
'повышена на', v_xl;
RAISE NOTICE 'Зарплата сотрудника % % % ',
'после повышения =', v_sl;
v_empl,
SELECT salary INTO v_s2
FROM Employees
WHERE employee_id= v_emp2;
RAISE NOTICE 'Зарплата сотрудника % % % ',
'повышена на', v_x2;
RAISE NOTICE 'Зарплата сотрудника %%% ',
'после повышения = ', v_s2;
v_emp2,
SELECT SUM(salary)
INTO v_sum2
FROM Employees
WHERE department_id = v_dep_id;
v_empl,
v_emp2,
Глава 9. Структура программ PL/pgSQL
RAISE NOTICE 'Зарплата отдела % % % ', v_dep_id,
'после повышения =', v_sum2;
END $$;
Зарплата
Зарплата
Зарплата
Зарплата
Зарплата
Зарплата
Зарплата
Зарплата
сотрудника
115 до повышения = 3100.00
116 до повышения = 2900.00
сотрудника
отдела 30 до повышения = 24900.00
115 повышена на 2595.00
сотрудника
115 после повышения = 5695.00
сотрудника
сотрудника
116 повышена на 2505.00
116 после повышения = 5405.00
сотрудника
отдела
30 после повышения = 30000.00
9.4.4. Использование оператора DELETE
Правила использования этого оператора в блоках PL/pgSQL соответствуют
правилам использования DML операторов PostgreSQL.
Листинг 9.23. Удаление из таблицы Products_l данных о
товарах, имеющих рейтинг 2
DO $$
DECLARE
v_prod_rating Products_l.rating_p%type := 2;
BEGIN
DELETE FROM Products_l
WHERE rating_p = v__prod_rating;
END $$;
Листинг 9.24. Удаление из таблицы Products_l данных о
товарах, цена которых меньше 500 или равна NULL
DO $$
BEGIN
DELETE FROM Products_l
WHERE price <500 OR price IS NULL;
END $$;
Листинг 9.25. Удаление из таблицы Ргобисбз_1 данных о
товарах, имеющих рейтинг, совпадающий с рейтингом товара 6
DO $$
DECLARE
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
v_prod_id Products_l.product_id%type := 6;
BEGIN
DELETE FROM Products_l
WHERE RATING_P = (SELECT rating_p FROM Products_l
WHERE product_id = v_prod_id);
END $$;
В этом примере для формирования условия удаления строк используется
подзапрос. На рис. 9.3 показано содержимое таблицы Productsl после вы
полнения рассмотренных операторов DELETE.
Я products^!
1
^ф^ытф звать результатъ
й8С product_name
ЦІ product_id
t
^
123 rating_p
▼ J 123 price
W І
Apple Iphone Xs 64gb Space Gray (Mt9e2ru/A)
3
3
Apple Iphone Xr 64gb Space Gray (Mry42ru/A)
3
750
4
Asus X99-E-1Og Ws
3
620
4
5
Apple Iphone 7 256gb
3
550
5
7
Lg V30+ Black (H93Ods)
3
530
6
8
Samsung Galaxy S7 Edge 32gb Black (Sm-G935)
3
520
І2
900
Рис. 9.3. Содержимое таблицы Products l после удаления строк
9.4.5. Использование оператора MERGE
Оператор MERGE осуществляет слияние двух таблиц. В листинге 9.26 при
веден пример использования оператора MERGE для слияния содержимого
таблицы Products l с результатом выполнения запроса, который выбирает из
таблицы Products данные о товарах со значением столбца rating_p=3. Слия
ние осуществляется по столбцу product name. У строк таблицы Products l,
для которых выполняется условие слияния, обновляется значение цены то
вара.
Листинг 9.26. Слияние таблицы Products_l с результатом
выполнения запроса
DO $$
DECLARE
v_rating Products_l.rating _p%TYPE := 3;
BEGIN
MERGE INTO Products_l pd USING
(SELECT product_id, product_name, rating_p, price
FROM Products WHERE rating_p = v_rating) pr
ON (pd.product name = INITCAP(pr.product name))
Глава 9. Структура программ PL/pgSQL
WHEN MATCHED THEN UPDATE
SET price = pr.price
WHEN NOT MATCHED THEN
INSERT (product_id, product_name, rating__p, price)
VALUES (pr.product_id, INITCAP(pr.product_name),
pr.rating_p, pr.price);
END $$;
В этом примере запрос возвращает данные о товарах, которые имеют рей
тинг, равный 3. Слияние осуществляется по столбцу ргоёис( паше. Следу
ет обратить внимание на необходимость использования функции ІМТСАР
при формировании условий соединения
ОН (pd. product_name = ІИІТСАР(рг.product_name))
В таблице Productsl данные в столбце были приведены к единому стилю, а
в таблице Products данные в столбце product name имеют различные стили
регистра, и если не использовать функцию INITCAP, то операция соедине
ния будет выполнена некорректно, так как названия одного товара, записан
ного в различных стилях, будут рассматриваться как два разных значения.
; Я produC
ts_1 jJJ Seed
î^ productjd
'
* j »c product_name
- ■
▼ | 123 rating_p
і Apple Iphone Xs 64gb Space Gray (Mt9e2ru/A)
*• | 123 price
3
T||
900
3
Apple Iphone Xr 64gb Space Gray (Mry42ru/A)
3
750
4
AsusX99-E-10gWs
3
620
4
5
Apple Iphone 7 256gb
3
550
1 5
7
Lg V30+ Black (H930ds)
3
530
8
. 2
h
Ь
Samsung Galaxy S7 Edge 32gb Black (Sm-G935)
3
520
68
Sony Xperia Xz Premium Black (G8142)
3
[NULL]
2
70
Xiaomi МІ6 64gb
3
480
9
65
Apple Iphone 8 Plus 256 Gb
3
570
10__
69
Xiaomi МІ5 32gb
3
490
6____
Рис. 9.4. Содержимое таблицы Products l после слияния
281
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Задачи для самостоятельного решения:
І Задача 9.1. Создать анонимный блок. Объявить и инициализиI ровать переменные:
[ v_name — строка, содержащая полное имя человека;
! v_date — текущая дата;
! v_date_of_birth — дата рождения,
і
І Используя эти переменные, вывести на печать следующую ин; формацию:
!
Сегодня: 29-03-2023
' Меня зовут: Ivan Ivanov
і
] Я родился: 20-04-1972
Задача 9.2. В код, полученный после выполнения задания 1, до
бавить переменные:
ѵ_а9е — ваш возраст в годах на текущую дату;
v_fname — ваше имя;
vjname — ваша фамилия.
Присвоить значения этим переменным, используя значения пе
ременных из задания 1, и вывести на печать следующую инфор
мацию:
Сегодня: 29-MAR-2023
Мое имя:
Ivan
Моя фамилия:
Ivanov
Я родился: 20-04-1972
Мне: 51 год
Задача 9.3. Объявить переменные:
v_emp_id employees.employee_id%TYPE := 105;
v_emp Employees%ROWTYPE;
Присвоить значение переменной v_emp для сотрудника
employeejd = 105 и вывести результат в следующем виде:
282
Глава 9. Структура программ PL/pgSQL
Имя: David
Austin
Работает в отделе:
Должность:
60
IT_PROG
Поступил на работу: 25-JUN-97
Зарплата:
5280
Задача 9.4. Объявить и инициализировать переменные:
ѵ_сІер_ісІ — номер отдела (:=30),
ѵ_рсІ — величина увеличения зарплаты, которая задается как
доля от текущей зарплаты (:=0.1).
Используя эти переменные, создать анонимный блок, в котором
выполнить следующие операции:
- вывести суммарную зарплату сотрудников отдела 30 до изме
нения зарплаты и количество сотрудников в отделе;
- увеличить на 10% зарплату всех сотрудников в отделе;
- вывести суммарную зарплату сотрудников отдела после изме
нения зарплаты и общую сумму увеличения зарплаты сотрудни
ков отдела.
Задача 9.5. Создать анонимный блок, в котором выполнить сле
дующие операции:
- найти сотрудника с максимальной суммой продаж за 2017 год;
- вывести employee id сотрудника, его зарплату (salary) и сумму
его продаж за 2017 год (sum2017);
- определить размер увеличения заработной платы сотрудника,
используя формулу add_sal = sum2017/(20*salary);
- увеличить зарплату сотрудника и вывести ее размер после уве
личения.
Задача 9.6. Создать копии таблицы Products: Products_2,
Products_3. Таблица Products_2 пустая, а содержимое таблицы
Products_3 показано на рис. 9.5.
Создать анонимный блок, в котором выполнить следующие опе
рации:
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
- записать в таблицу Products_2 данные о товарах из таблицы
Products, рейтинг которых равен 2;
- увеличить цену товаров в таблице Products_2 на 10%, если те
кущая цена меньше 1000;
- выполнить слияние таблиц Products_2, Products_3 по столбцу
product_id. Для строк таблицы Products_2, которые удовлетво
ряют условию слияния, обновить цену товара.
і
Я products_2_1 ! £ * Seeôume SQL swpaxeiwe w^t
Ж product_id
1
Г......... ................
Х/ЛЫ7 »р Q&3/ЛЬ р£Зу7іЪГГ;й ^'Ь/
Я8С product_name
▼
123 rating_p
., Iiyama 34" ProLite XUB3493WQSU-B1
'•'
123 price
■*
2
1020
2
58
G.Skill Ripjaws 4 Series
2
1000
з
59
Viewsonic 27” VA2719-2K-SMHD
2
820
4
60
Dell 25" UltraSharp UP2516D (516D-2061)
2
770
5
61
Apple iPhone XS 64GB Space Gray (M19E2RU/A)
3
900
6
62
Apple iPhone XR 64GB Space Gray (MRY42RU/A)
3
750
7
63
AsusX99-E-10GWS
3
620
8
68
Sony Xperia XZ Premium Black (G8142)
3
[NULL!
9
69
Xiaomi МІ5 32GB
3
490
10
70
Xiaomi МІ6 54Gb
3
480
Рис. 9.5. Содержимое таблицы Products_3
Задача 9.7. Определить дату приема на работу сотрудника, ко
торая удовлетворяла бы следующим условиям:
- суммарное количество полных месяцев, которые проработал
этот сотрудник и сотрудник етрІоуее_іб = 104 до 01/01/2000,
равно ѵ_зит_топ;
- суммарное количество дней, прошедших с начала года, в
том году, когда сотрудники были приняты на работу, равно
v_sum_day.
284
Глава 10.
ОПЕРАТОРЫ
УПРАВЛЕНИЯ
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
10.1. Условные операторы
Как правило, процесс выполнения команд программы не является последо
вательным, а содержит ветвления, которые должны выполняться в том слу
чае, если заданные условия принимают определенные значения.
Условные операторы позволяют управлять процессом
выполнения программы и содержат условия выполнения
ветвей программы.
Для управления ходом выполнения команд в PL/SQL используется условный
оператор IF, который имеет следующий синтаксис:^
IF {условное выражение A} THEN {Блок операторов};
[ELSIF {условное выражение Bl} THEN{Блок операторов В1};]
[ELSIF {условное выражение BN} THEN {Блок операторов BN};]
[ELSE {Блок операторов С};]
END IF;
Условное выражение может принимать значения: TRUE, FALSE, NULL.
Блоки могут содержать один или несколько операторов PL/pgSQL или SQL.
Условный оператор IF выполняет первый блок операторов, для которого за
данное условное выражение имеет значение TRUE, после этого управление
передается следующему за IF оператору.
2«6
Глава 10. Операторы управления
Условный оператор IF может содержать одну или несколько секций ELSIF.
Условия, заданные в секциях ELSIF, проверяются в том случае, если ни одно
из предыдущих условий не имело значения TRUE.
Условный оператор IF может содержать секцию ELSE. Блок операторов, со
держащихся в этой секции, будет выполнен в том случае, если ни одно из
условных выражений не имело значения TRUE.
В простейшем случае оператор IF имеет следующий вид:^
IF {условное выражение} THEN {Блок операторов};
END IF;
• Если {условное выражение} будет иметь значение TRUE, то будут вы
полнены операторы, входящие в {Блок операторов}, после чего управле
ние будет передано оператору, расположенному после оператора END IF.
• Если {условное выражение} будет иметь значение FALSE или NULL, то
управление будет сразу передано оператору, расположенному после опе
ратора END IF.
В листинге 10.1 приведен пример оператора, имеющего подобную структу
ру. В этом примере переменной ѵвитваі присваивается значение, равное
общей сумме продаж сотрудника, а оператор ІЕ присваивает переменной
ѵ Ьопив значение, которое равно размеру премии.
Листинг 10.1. Пример простого оператора IF
DO $$
DECLARE
v_sum_sal numeric(10,2):=1100000;
v_bonus
numeric(10,2);
BEGIN
IF v_sum_sal > 1000000
THEN
v_bonus := 50000;
END IF;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'y_sum_sal = %',v_sum_sal;
RAISE NOTICE'v_bonus = %',v_bonus;
END $$;
[ 287 "
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Результат:
v_sum_sal = 1100000-. 00
v_bonus = 50000.00
Если переменная v_sum_sal будет иметь значение меньше чем 1000000, то
переменная v_bonus будет иметь значение NULL. Так как значений NULL
рекомендуется избегать, то в операторе IF следует использовать ветвь ELSE.
Листинг 10.2. Пример оператора IF ELSE с условием IF
v_sum_sal >1000000
DO $$
DECLARE
v_sum_sal numeric(10,2):= 900000;
v_bonus
numerі c(10,2);
BEGIN
IF v_sum_sal > 1000000
THEN
v_bonus := 50000;
ELSE
v_bonus := 0;
END IF;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'v_sum_sal = %',v_sum_sal;
RAISE NOTICE'v_bonus = %',v_bonus;
END $$;
Результат:
v_sum_sal = 900000.00
v_bonus = 0.00
Условия определения размера бонуса могут быть сформулированы следую
щим образом: если ѵвишваі < 1000000, то значение переменной ѵЬопив
должно быть равно 0, в противном случае значение переменной ѵ Ьопив
должно быть равно 50000. В листинге 10.3 приведен пример этого варианта
рассматриваемой задачи.
Листинг 10.3. Пример оператора IF ELSE с условием IF
v_sum_sal <1000000
DO $$
DECLARE
Глава 10. Операторы управления
v_sum_sal numeric(10,2);
v_bonus
numeric(10,2);
BEGIN
IF v_sum_sal < 1000000
THEN
v_bonus : = 0;
ELSE
v_bonus :=50000;
END IF;
RAISE NOTICE 'Результат: ';
RAISE NOTICE' v_sum_sal = %',v_sum_sal;
RAISE NOTICE'v_bonus = %',v_bonus;
END $$;
Результат:
v_sum_sal = <NULL>
v_bonus = 50000.00
Оба варианта решения рассматриваемой задачи будут выдавать одинаковые
результаты, если переменной v_sum_sal будут присваиваться определенные
значения. Если переменная vsumsal будет иметь значение NULL, то бу
дут выполнены операторы, содержащиеся в ветви ELSE, и оператор IF из
листинга 10.2 вернет значение 0, а оператор IF из листинга 10.3 вернет зна
чение 50000.
Для того чтобы решить эту проблему, следует использовать функции обра
ботки значений NULL. В листинге 10.4 содержится пример решения рассма
триваемой задачи с использованием функции COALESCE.
Листинг 10.4. Пример оператора IF ELSE с использованием
функции COALESCE
DO $$
DECLARE
v_sum_sal numeric(10,2);
v_bonus
numeric(10,2);
begin
IF COALESCE(v_sum_sal,0) < 1000000
THEN
v_bonus := 0;
ELSE
v_bonus .-=50000;
END IF;
[ 289 ^
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
RAISE NOTICE 'Результат: ' ;
RAISE NOTICE'v_sum_sal = %',v_sum_sal;
RAISE NOTICE'v_bonus = %',v_bonus;
END $$;
Результат :
v_sum_sal = <NULL>
V bonus = 0.00
10.1.1. Использование инструкции ELSIF
Эта форма оператора ІР используется для реализации логики
с несколькими взаимоисключающими альтернативами.
Для рассматриваемой задачи необходимо использовать данную форму опе
ратора ІР в том случае, если необходимо присваивать переменной ѵ Ьопив
несколько различных значений, которые зависят от значений переменной
ѵ_8ит_«а1. Например, алгоритм начисления премии может быть задан сле
дующим образом:
если ѵ_8иш_8а1 > 1000000, то ѵ Ьопив = 50000;
если 500000 < ѵвишваі <= 1000000, то ѵЬопив = 25000;
если 200000 < ѵвишваі <= 500000 , то ѵЬопиз = 10000;
если
ѵвишваі < 200000, то ѵЬопив = 0.
В листинге 10.5 приведен пример оператора ІР с использованием инструк
ций ЕЬ8ІР, который реализует данный алгоритм начисления премии.
Листинг 10.5. Пример оператора IF с использованием
инструкций ELSIF
DO $$
DECLARE
v_sum_sal numeric(10,2):= 900000;
v_bonus
numeric(10,2);
begin
IF COALESCE(v_sum_sal,0) > 1000000 THEN v_bonus := 50000;
ELSIF COALESCE(v_sum_sal,0) > 500000 THEN v_bonus := 25000;
ELSIF COALESCE(v sum sal,0) > 200000 THEN v bonus := 10000;
Глава 10. Операторы управления
ELSE
v_bonus :=0 ;
END IF;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'v_sum_sal = % ' , v_sum_sal ;
RAISE NOTICE'v_bonus = %',v_bonus;
END $$;
Результат :
v_sum_sal = 900000.00
V bonus = 25000.00
Если в операторе IF необходимо использовать значение, которое должно
быть определено в результате выполнения запроса, то необходимо снача
ла получить и присвоить это значение переменной, используя оператор
SELECT ...INTO, и после этого использовать эту переменную в операто
ре IF.
Листинг 10.6. Присвоение значения переменной v_sum_sal
с использованием оператора SELECT ...INTO
DO $$
DECLARE
v_emp_id Employees.employee_id%TYPE:=155;
v__sum_sal numeric(10,2) : =900000;
v_bonus
numeric(10,2);
begin
SELECT sumsal INTO v_sum_sal
FROM Sum_Sal
WHERE employee_id=v_emp_id;
IF COALESCE(v_sum_sal,0) > 1000000 THEN v_bonus := 50000;
ELSIF COALESCE(v_sum_sal,0) > 500000
THEN v_bonus := 25000;
ELSIF COALESCE(v_sum_sal,0) > 200000
THEN v_bonus := 10000;
ELSE
v_bonus :=0;
END IF;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'v_sum_sal = %',v_sum_sal;
RAISE NOTICE'v_bonus = %',v_bonus;
END $$;
Результат:
v_sum_sal = 526380.00
v_bonus = 25000.00
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
В этом примере используется реальная сумма продаж заданного сотрудника.
Для того чтобы упростить код, предварительно было создано представление
8ит 8а1.
CREATE OR REPLACE VIEW Sum__Sal AS
select employee_id,SUM(unit_price*quantity) as sumsal
FROM Employees JOIN Orders ON (employee_id=salesman_id)
JOIN Order_items USING (order_id)
GROUP BY employee_id;
10.1.2. Вложенные операторы IF
Любой из блоков в операторе IF может содержать оператор IF. Такие опера
торы IF называются вложенными. В общем виде оператор IF, содержащий
вложенные операторы IF, может быть представлен следующим образом^
IF {условное выражение Al} THEN
IF {условное выражение All} THEN {Блок операторов АН};
[ELSIF {условное выражение А12} THEN {Блок операторов А12};]
[ELSE {Блок операторов A1N}]
END IF;
[ELSIF {условное выражение Bl} THEN
IF {условное выражение Bll} THEN {Блок операторов Bll};
[ELSIF {условное выражение В12} THEN {Блок операторов В12};]]
[ELSE {Блок операторов BIN}]
END IF;
[ELSE
[IF {условное выражение Cl} THEN] {Блок операторов Cl};
[ELSIF {условное выражение С12} THEN {Блок операторов С12};]]
[ELSE {Блок операторов C1N}]
END IF;
END IF;
Рассмотрим пример использования вложенных операторов для определения
размера премии, который зависит от суммы продаж сотрудника и его рейтинга.
Листинг 10.7. Вычисление размера премии, которая
зависит от суммы продаж сотрудника и его рейтинга
DO $$
DECLARE
v_emp_id Employees.employee_id%TYPE:=155 ;
Глава 10. Операторы управления
v_rating_e Employees.rating_e%TYPE;
v_sum_sal numeric(10,2);
v_bonus
numeric(10,2);
BEGIN
SELECT rating_e INTO v_rating_e
FROM Employees
WHERE employee_id=v_emp_id;
SELECT sumsal INTO v_sum_sal
FROM Sum_Sal
WHERE employee_id=v_emp_id;
IF COALESCE(v_sum_sal,0) > 1000000 THEN
IF v_rating_e= 5 THEN v_bonus := 5000;
ELSIF v_rating_e= 4 THEN v_bonus := 4000;
ELSIF v_rating_e= 3 THEN v_bonus := 2000;
ELSE v_bonus :=1000;
END IF;
ELSIF v_sum_sal,0) > 500000 THEN
IF v_rating_e= 5 THEN v_bonus := 3000;
ELSIF v_rating_e= 4 THEN v_bonus := 2000;
ELSE v_bonus :=1000;
END IF;
ELSIF COALESCE(v_sum_sal,0) > 200000 THEN
IF v_rating_e= 5 THEN v_bonus := 2000;
ELSE v_bonus :=1000;v_bonus := 10000;
END IF;
ELSE v_bonus :=0;
END IF;
RAISE NOTICE 'Результат: ';
RAISE NOTICE' employees_id = % ' , v_einp_id;
RAISE NOTICE' rating_e= %',v_rating_e;
RAISE NOTICE' v_sum_sal = %',v_sum_sal;
RAISE NOTICE' v_bonus = %',v_bonus;
END $$;
Результат:
employees_id = 155
rating_e= 5
v_sum_sal = 526380.00
v_bonus = 3000.00
10.2. Использование команд и выражений CASE
Для реализации ветвлений можно использовать команды и выражения CASE.
[ 293 '
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Команда CASE позволяет выбрать для выполнения одну из
последовательностей команд, а выражение CASE выбирает
для выполнения одно из выражений, и результат выполнения
этого выражения присваивается переменной.
10.2.1. Команда CASE
Существует две разновидности команды CASE:
1. Простая команда CASE (CASE с селектором), выполняет последова
тельность команд, для которой значение селектора совпадает с заданным
значением.
2. Поисковая команда CASE (CASE с условием), выполняет последова
тельность команд, для которой значение заданного условного выражения
имеет значение TRUE.
Синтаксис команды CASE с селектором:
CASE <селектор>
WHEN {значение 1} THEN {последовательность команд 1};
WHEN {значение 2} THEN {последовательность команд 2};
WHEN {значение N} THEN {последовательность команд N};
[ELSE {последовательность команд N+1};]
END CASE;
В качестве селектора можно использовать переменную или выражения, тип
которых должен быть совместим с типом значений, заданных в предложени
ях WHEN. Выполняется та последовательность команд, у которой заданное
значение совпадает со значением селектора.
Рассмотрим пример использования этой команды для решения следующей
задачи: необходимо осуществить поощрение сотрудников в зависимости от
их рейтинга, используя следующее правило:^
• если рейтинг сотрудника v rating = 5, то ему следует повысить зарплату
на 10% и выписать премию в размере 5000;
Глава 10. Операторы управления
• если рейтинг сотрудника v_rating = 4, то ему следует повысить зарплату
на 5%;
• если рейтинг сотрудника v_rating = 3, то ему следует выписать премию в
размере 2000;
• сотрудникам, рейтинг которых меньше 3, поощрения не полагается.
Листинг 10.8. Изменение зарплаты и начисление премии в
зависимости от рейтинга
DO $$
DECLARE
v_emp_id Employees.employee_id%type:=155;
v_rating Employees.rating_e%TYPE;
v_emp_old_salary Employees.salary%TYPE;
v_emp_salary Employees.salary%TYPE;
v_bonus numeric(10,2);
BEGIN
v_emp_id := 155;
SELECT rating_e INTO v_rating
FROM Employees
WHERE employee_id=v_emp_id;
SELECT salary INTO
v_emp_salary
FROM Employees
WHERE employee_id=v_emp_id;
v_emp_old_salary := v_emp_salary;
CASE v_rating
WHEN 5 THEN
WHEN
WHEN
ELSE
END CASE;
v_emp_salary := 1.l*v_emp_salary;
v_bonus := 5000;
4 THEN
v_emp_salary := 1.05*v_emp_salary;
3 THEN
v_bonus := 2000;
v_bonus := 0;
UPDATE Employees
SET salary = v_emp_salary
WHERE employee_id=v_emp_id;
RAISE
RAISE
RAISE
RAISE
RAISE
RAISE
NOTICE
NOTICE'
NOTICE'
NOTICE'
NOTICE'
NOTICE'
'Результат: ';
employees_id = %',v_emp_id;
rating_e= %',v_rating;
old salary= %',v_emp_old_salary;
new salary= %',v_emp_salary;
v_bonus = %',v_bonus;
PostgreSQL: SQL + PL/pgSQL
END $$;
Результат:
employees_id = 155
rating_e = 5
old salary = 7000.00
new salary = 7700.00
v bonus = 5000.00
Синтаксис команды CASE с условием:
CASE
WHEN {условное выражение 1}
THEN {последовательность команд 1};
WHEN {условное выражение 2}
THEN {последовательность команд 2};
WHEN {условное выражение N}
THEN {последовательность команд N};
[ELSE {последовательность команд N+1};]
END CASE;
Выполняется первая последовательность команд, для которой заданное ус
ловное выражение будет иметь значение TRUE, остальные условные выра
жения не рассматриваются. Если ни одно из условных выражений не будет
иметь значение TRUE, то выполняется последовательность операторов
после ELSE.
Рассмотрим пример использования этой команды для решения следующей
задачи: необходимо осуществить поощрение сотрудников в зависимости от
суммы продаж vsumsal по следующему правилу:
• если vsumsal > 1000000, то ему следует повысить зарплату на 10% и
выписать премию в размере 5000;
• если 500000 <= vsum sal < 1000000, то ему следует повысить зарплату
на 5%, премию не выписывать;
• если 100000 <= vsum sal < 300000, то зарплату не менять, премию не
выписывать;
• если vsum sal < 100000, то зарплату уменьшить на 10%, премию не вы
писывать.
Глава 10. Операторы управления
В листинге 10.9 приведен пример использования команды CASE с условием
для решения этой задачи.
Листинг 10.9. Изменение зарплаты и начисление премии в
зависимости от суммы продаж
DO $$
DECLARE
v_emp_id Employees.employee_id%type:= 153;
v_rating Employees.rating_e%TYPE;
v_emp_old_salary Employees.salary%TYPE;
v_emp_salary Employees.salary%TYPE;
v_sum_sal numeric(10,2);
v_bonus numeric(10,2);
BEGIN
SELECT sumsal INTO v_sum_sal
FROM Sum_Sal
WHERE employee_id=v_emp_id;
SELECT salary INTO
v_emp_salary
FROM Employees
WHERE emp1oyee_id=v_emp_id;
v_emp_old_salary := v_emp_salary;
CASE
WHEN
WHEN
WHEN
ELSE
END CASE;
v_sum_sal > 1000000 THEN
v_emp_salary :=1.l*v_emp_salary; v_bonus := 5000;
v_sum_sal > 500000 THEN
v_emp_salary :=1.05*v_emp_salary; v_bonus := 0;
v_sum_sal < 100000 THEN
v_emp_salary :=0.9*v_emp_salary; v_bonus := 0;
v_emp_salary :=v_emp_salary; v_bonus := 0;
UPDATE Employees
SET salary = v_emp_salary
WHERE employeeid = v_emp_id;
RAISE
RAISE
RAISE
RAISE
RAISE
RAISE
END $$;
NOTICE
NOTICE'
NOTICE'
NOTICE'
NOTICE'
NOTICE'
'Результат : ';
employees_id = %',v_emp_id;
old salary= %',v_emp_old_salary;
v_sum_sal = %',v_sum_sal;
new salary= %',v_emp_salary;
v_bonus = %',v_bonus;
Результат :
297
PostgreSQL; SQL + PL/pgSQL
employees_id = 153
old salary = 8800.00
v_sum_sal = 973650.00
new salary = 9240.00
v bonus = 0.00
10.2.2. Выражение CASE
Выражение CASE возвращает одно значение, которое
является результатом вычисления выбранного выражения.
Это значение присваивается заданной переменной, которая
используется в дальнейших вычислениях.
В отличие от команды CASE, после выражений не ставится точка с запятой
и скрипт завершается ключевым словом END. Существуют две разновидно
сти выражения CASE: простая (выражение CASE с селектором) и поисковая
(выражение CASE с условием).
{переменная}:=
CASE CASE {селектор}
WHEN {значение 1} THEN {выражение 1}
WHEN {значение 2} THEN {выражение 2}
WHEN {значение N} THEN {выражение N}
[ELSE {выражение N+1}]
END;
Вычисляется значение выражения, для которого заданное значение совпада
ет со значением селектора, и присваивается заданной переменной.
Рассмотрим пример использования этого выражения для решения следую
щей задачи: необходимо присвоить переменной ѵЬопив значение премии,
которое зависит от рейтинга по следующему правилу:
• если рейтинг сотрудника v_rating = 5, то ѵ Ьопиз = 5000;
•
если рейтинг сотрудника v_rating = 4, то ѵ Ьопиз = 3000;
Глава 10. Операторы управления
• если рейтинг сотрудника v_rating = 3, то ѵЬопиз = 1000;
• сотрудникам, рейтинг которых меньше 3, премия не полагается.
В листинге 10.10 приведен пример использования выражения CASE с селек
тором для решения этой задачи.
Листинг 10.10. Начисление премии в зависимости от рейтинга
DO $$
DECLARE
v_emp_id Employees.employee_id%TYPE:= 111;
v_rating Employees.rating_e%TYPE;
v_bonus numeric(10,2);
BEGIN
SELECT rating_e INTO v_rating
FROM Employees
WHERE employee_id=v_emp_id;
v_bonus :=
CASE v_rating
WHEN 5 THEN
5000
WHEN 4 THEN
3000
WHEN 3 THEN
1000
ELSE 0
END;
RAISE NOTICE 'Результат: ';
RAISE NOTICE' employees_id = %',v_emp_id;
RAISE NOTICE' rating_e= %',v_rating;
RAISE NOTICE' v_bonus = %',v_bonus;
END $$;
Результат :
employees_id = 111
rating_e = 4
v_bonus = 3000.00
Синтаксис выражения CASE с условием:
{переменная}:=
CASE CASE
WHEN {условное выражение 1} THEN {выражение 1}
WHEN {условное выражение 2} THEN {выражение 2}
PostgreSQL: SQL + PL/pgSQL
.................................................. f PostgreSQL
WHEN {условное выражение N} THEN {выражение N}
[ELSE {условное выражение N+l}]
END;
Выполняется первое выражение, для которого заданное условное выраже
ние будет иметь значение TRUE, и его результат присваивается переменной.
Остальные условные выражения не рассматриваются. Если ни одно из ус
ловных выражений не будет иметь значение TRUE, то будет выполнено вы
ражение, расположенное после ELSE.
Рассмотрим пример использования выражения CASE с условием для реше
ния следующей задачи: необходимо вычислить новый размер заработной
платы сотрудника в зависимости от суммы продаж v_sum_sal по следующе
му правилу:
•
если vsumsal > 1000000, то следует повысить зарплату на 10%;
•
если 500000 <= v sum sal < 1000000, то следует повысить зарплату на
5%;
• если 100000 <= vsum sal < 300000, то зарплату не изменять;
• если vsum sal < 100000, то зарплату уменьшить на 10%.
В листинге 10.11 приведен пример использования выражения CASE с селек
тором для решения этой задачи.
Листинг 10.11. Изменение зарплаты в зависимости от
суммы продаж
DO $$
DECLARE
v_emp_id Employees.employee_id%type:=153;
v_emp_old_salary Employees.salary%TYPE;
v_emp_salary Employees.salary%TY₽E;
y_sum_sal numeric(10,2);
v_bonus numeric(10,2);
BEGIN
SELECT sumsal INTO v_sum_sal
FROM Sum_Sal
WHERE employee_id=v_emp_id;
SELECT salary INTO
v_emp_salary
FROM Employees
WHERE employee_id = v_emp_id;
Глава 10. Операторы управления
v_emp_old_salary :=
v_emp_salary;
v_emp_salary :=
CASE
WHEN v_sum_sal > 1000000 THEN
WHEN V sum sal > 500000 THEN
WHEN v_sum_sal < 200000 THEN
ELSE v_emp_salary
END;
1.l*v_emp_salary
1.05*v_emp_salary
0.9*v_emp_salary
UPDATE Employees
set salary = v_emp_salary
WHERE employee_id=v_emp_id;
RAISE
RAISE
RAISE
RAISE
RAISE
END $$;
NOTICE
NOTICE'
NOTICE'
NOTICE'
NOTICE'
'Результат: ';
employees_id = %',v_emp_id;
old salary= %',v_emp_old_salary;
v_sum_sal = % ' , v_sum_sal ;
new salary= %',v_emp_salary;
Результат :
employees_id = 153
old salary= 9240.00
v_sum_sal = 973650.00
new salary= 9702.00
10.3. Операторы цикла
Большинство алгоритмов решения реальных задач требует многократного
выполнения некоторой последовательности действий. Для реализации по
добных процессов используются операторы цикла.
В PL/pdSQL можно использовать три типа операторов цикла:
1. Простые циклы LOOP.
2. Циклы WHILE.
3. Циклы FOR.
10.3Л. Простые циклы LOOP
Оператор цикла LOOP имеет следующий синтаксис:
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
LOOP
{оператор 1};
{оператор N};
EXIT WHEN {условие завершения};
END LOOP;
{условие завершения} - логическая переменная или выражение, которые
могут принимать значения: TRUE, FALSE, NULL. Цикл завершается, если
{условие завершения} будет иметь значение TRUE.
Особенностью циклов LOOP является то, что операторы, входящие в тело
цикла, выполняются хотя бы один раз.
Оператор цикла может содержать команду CONTINUE, которая завершает
текущую итерацию и передает управление следующей итерации цикла, если
заданное условие перехода будет иметь значение TRUE. Синтаксис операто
ра цикла LOOP с командой CONTINUE имеет следующий ви^
LOOP
{оператор 1};
CONTINUE WHEN {условие перехода}
{оператор К};
{оператор N};
EXIT WHEN {условие завершения};
END LOOP;
Рассмотрим примеры использования операторов циклов LOOPS при реше
нии конкретных задач.
ПРИМЕР 1. Необходимо вычислить сумму членов ряда.
х2 х3
р = 1 + х + — + — +....
2! 3!
а0=1>
ам = а*-
і
Вычисления завершить, если а< 0.0001.
^ 302 ]
Глава 10. Операторы управления
Листинг 10.12. Вычисление суммы членов ряда
DO $$
DECLARE
v_i integer:=0;
v_x real:=2;
v_a real:=l;
v_s real:=0;
BEGIN
LOOP
v_s := v_s + v_a;
v_i := v_i +1;
v_a := v_a*v_x/v_i;
EXIT WHEN v_a < 0.0001;
END LOOP;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'x = % % % % %',v_x,', і = ',v_i,
', summa = ',vs;
END $$;
Результат:
x = 2, і = 11,
summa = 7.388995
ПРИМЕР 2. Вывести данные о сотрудниках (employeeid, jobid,
salary), начиная с сотрудника employee id = 101, суммарная
зарплата которых не превышает 60000.
Листинг 10.13. Вывод данных о сотрудниках, суммарная
зарплата которых не превышает заданного значения
DO $$
DECLARE
v_emp Employees%ROWTYPE;
v_emp_id Employees.employee_id%TYPE := 101;
v_rating Employees.job_id%TYPE;
v_add_salary Employees.salary%TYPE;
v_sum_sal Employees.salary%type := 0;
v_sum_sal_max Employees.salary%TYPE := 60000;
BEGIN
RAISE NOTICE 'Результат: ';
RAISE NOTICE'% % %', LPAD('employee_id',11), LPAD('job_id',9),
LPAD('salary',10);
LOOP
SELECT * INTO v_emp
FROM Employees
WHERE employee_id = v_emp_id;
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
v_sum_sal := v_sum_sal+v_emp.salary;
EXIT WHEN v_sum_sal > v_sum_sal_max;
RAISE NOTICE'% % % ' , LPAD(v_emp.employee_id::text,11),
LPAD(v_emp.j ob_id, 9) , LPAD(v_emp.salary::text,10);
v_emp_id:=v_emp_id+l;
END LOOP;
END $$;
Результат:
employee_ id
101
102
103
104
105
job_id
AD_VP
AD_VP
IT_PROG
IT_PROG
IT_PROG
salary
17000.00
17000.00
9900.00
6000.00
4800.00
В этом примере следует обратить внимание на необходимость инициализа
ции переменной vsumsal. Если этого не сделать, она будет иметь значение
NULL, и произойдет зацикливание программы.
ПРИМЕР 3. Необходимо увеличить на 10% зарплату сотрудни
ков, используя заданное значение фонда повышения заработ
ной платы add sum sal = 30000.
Увеличение зарплаты начать с сотрудника employee id = 101. Выйти из цик
ла, если employee id станет больше 170 или будет исчерпан фонд повыше
ния заработной платы. Вывести суммарную зарплату до и после повышения
и значение employee_id последнего сотрудника, которому была увеличена
зарплата.
Листинг 10.14. Увеличение зарплаты сотрудников
(Employees_copy - копия таблицы Employees)
DO $$
DECLARE
v_emp id Employees_copy.employee_id%TYPE:= 101;
v_emp_id_max Employees_copy.employee_id%TYPE:= 170;
v_emp_salary Employees_copy.salary%TYPE;
v_add salary Employее s_copy. salary%TYPE ;
v_sum_sal Employees_copy.salary%type;
v_add_sum_sal Employees_copy.salary%TYPE:= 30000;
v_count INTEGER;
BEGIN
Глава 10. Операторы управления
SELECT SUM(salary) INTO v_sum_sal
FROM Employees_copy;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'Суммарная зарплата до повышения = % ',v_sum_sal;
LOOP
v_emp_id :=v_emp_id+l;
SELECT COUNT(employee_id) INTO v_count
FROM Employееs_copy
WHERE employee_id = v_emp_id-l;
CONTINUE WHEN v_count = 0;
SELECT salary INTO v_emp_salary
FROM Employees_copy
WHERE employee_id = v_emp_id-l;
v_add_salary:= 0.1* COALESCE(v_emp_salary,0);
UPDATE Employees_copy
SET salary = salary + v_add_salary
WHERE employee_id = v_emp_id-l;
v_add_sum_sal:= v_add_sum_sal - v_add_salary;
EXIT WHEN (v_emp_id>v_emp_id_max) OR (v_add_sum_sal <0);
END LOOP;
IF v_add_sum_sal < 0 THEN
UPDATE Employееs_copy
SET salary = salary + v_add_sum_sal
WHERE employee_id = v_emp_id-l;
v_add_sum_sal:= v_add_sum_sal - v_add_sum_sal;
END IF;
v_emp_id:=v_emp_id-l;
SELECT SUM(salary) INTO v_sum_sal
FROM Employees_copy;
RAISE NOTICE'Суммарная зарплата после повышения = % ',v_sum_sal;
RAISE NOTICE'employee_id = %',v_emp_id;
END $$;
Результат:
Суммарная зарплата до повышения = 710600.00
Суммарная зарплата после повышения = 740600.00
employee_id = 149
Анализируя результаты выполнения программы, можно увидеть, что сум
марная зарплата увеличилась ровно на 30000.
В этом примере следует обратить внимание на следующие важные моменты:
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Запрос
SELECT COUNT(employee_id) INTO v_count
FROM Employееs_copy
WHERE employee_id = v_emp_id-l;
вернет значение 0 в том случае, если сотрудник с номером ѵ_ешр_ісі-1 бу
дет отсутствовать в таблице Етріоуеезсору. Если это произойдет, то нужно
перейти к следующему номеру сотрудника. Если этого не сделать, то при
выполнении запроса
SELECT salary INTO v_emp_salary
FROM Employees_copy
WHERE employee_id = v_emp_id-l;
возникнет ошибка.
При вычислении размера увеличения зарплаты v_add_salary необходимо
использовать функцию COALESCE, так как у некоторых сотрудников зар
плата может иметь значение NULL. В этом случае выражение CASE также
вернет значение NULL, и это приведет к тому, что в результате выполнения
оператора
v_add_sum_sal := v_add_sum_sal - v_add_salary;
переменная v_add_sum_sal будет иметь значение NULL.
На последней итерации размер повышения зарплаты сотрудника может быть
больше остаточного значения vaddsumsal. После завершения этой ите
рации v_add_sum sal будет иметь отрицательное значение. А это означает,
что общий размер повышения заработной платы превысил заданное значе
ние фонда повышения заработной платы. В этом случае необходимо выпол
нить коррекцию результата:
IF
v_add_sum_sal < О THEN
UPDATE Employees_copy
SET salary = salary + v__add_sum_sal
WHERE employee_id = v_emp_id-l;
v_add_sum_sal := v_add_sum_sal - v_add_sum_sal;
END IF;
^ 306 J
Глава 10. Операторы управления
10.3.2. Циклы WHILE
Оператор цикла WHILE имеет следующий синтаксис:
WHILE (условие продолжения} LOOP
{оператор 1};
{оператор 2};
[CONTINUE WHEN {условие перехода};]
(оператор К};
[EXIT WHEN {условие завершения};]
{оператор N};
END LOOP;
Цикл начинается с ключевого слова WHILE и завершается ключевыми сло
вами END LOOP. Цикл будет выполняться, если {условие продолжения}
будет иметь значение TRUE. В теле цикла можно использовать команды
CONTINUE и EXIT.
В листинге 10.15 приведен пример использования цикла WHILE для вычис
ления суммы ряда из Примера 1.
Листинг 10.15. Вычисление суммы ряда с использованием
оператора цикла WHILE
DO $$
DECLARE
v_i integer:=0;
v_x real:=2;
v_a real:=1;
v_s real:=0;
BEGIN
WHILE v_a > 0.0001 LOOP
v_s := v_s + v_a;
v_i := v_i +1;
v_a := v_a*v_x/v_i;
END LOOP;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'x = % % % % %',v_x,', і = ',v_i,
', summa = ',v_s;
END $$;
Результат:
x = 2, і = 11,
summa - 7.388995
307
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
ПРИМЕР 4. Необходимо на каждой итерации увеличивать на 1
количество всех товаров в заказе 78 до тех пор, пока общая сумма
заказа меньше 550000.
Выведем содержимое заказа 78 и его общую сумму после выполнения кода
из листинга 10.15.
SELECT * FROM order_items_copy
WHERE order_id = 78;
order_idIitem_idIproduct_id|quantity|unit_priceIrating_p|
--------- +-------- +----------- +--------- + — -------- +--------- +
78
78
78
78
1
1
1
1
51
21
11
31
79|
23 1
19|
52 1
10|
65|
92 1
147 1
2000.001
1660.001
1850.001
1260.001
11
4 1
41
21
SELECT SUM (quantity*unit_jprice) FROM order_items_copy
WHERE order_id = 78;
sum
I
--------- +
483320.00 I
Листинг 10.16. Увеличение количества товаров в заказе
78 до тех пор, пока сумма заказа меньше 550000
DO $$
DECLARE
v_sum numeric(10,2);
v_sum_max numeric(10,2):= 550000;
v_num integer := 78;
BEGIN
SELECT SUM(quantity*unit_price)
FROM Order_Items_Copy
WHERE order_id = v_num;
INTO v_sum
WHILE v sum < v sum max
LOOP
UPDATE Order_Items_Copy
SET quantity = quantity +1
WHERE order id = v num;
SELECT SUM(quantity*unitjprice)
308
INTO v_sum
Глава 10. Операторы управления
FROM Order_Items_Copy
WHERE order_id = v_num;
END LOOP;
IF v_sum > v_sum_max THEN
UPDATE Order_Items_Copy
SET quantity = quantity - 1
WHERE order_id = v_num;
END IF;
END $$;
После оператора цикла добавлен оператор IF, который отменяет увеличение
количества на последней итерации, если сумма заказа превышает значение
550000.
Выведем содержимое заказа 78 и его общую сумму после выполнения кода
из листинга 10.16.
order_idIitem_idIproduct_idI quantity Iunit_priceIrating_p|
--------- +-------- +----------- +--------- +----------- +--------- +
78
78
78
78
1
1
1
1
51
21
11
31
79|
23|
19|
52 1
19|
74|
101 1
1561
2000.001
1660.001
1850.001
1260.001
1
4
4
2
sum
---------- +
544250.00 I
ПРИМЕР 5. Рассмотрим другой алгоритм увеличения стоимости
заказа. Сначала на каждой итерации увеличивается на 1% цена
товаров, если цена повышена на 10%, то на каждой итерации на
1 увеличивается количество всех товаров в заказе. Если количе
ство товаров увеличено более чем на 50, то осуществляется
досрочный выход из цикла.
В листинге 10.17 содержится программа увеличения стоимости заказа в со
ответствии с данным алгоритмом.
[ 309 ^
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Листинг 10.17. Изменение количества и цены товаров
в заказе 78 до тех пор, пока сумма заказа меньше
заданной величины
DO $$
DECLARE
v_sum numeric(10,2);
v_sum_max numeric(10,2):= 950000;
v_num integer := 78;
v_i integer:= 0;
v_k integer:= 0;
v_f integer:= 0;
BEGIN
SELECT SUM(quantity*unit_jorice) INTO v_sum
FROM Order_Items_Copy
WHERE order_id = v_num;
WHILE v_sum < v_sum_max LOOP
IF v_i < 5 THEN
UPDATE Order_Items_Copy
SET unit_price = 1.01*unit_price
WHERE order_id = v_num;
v_i := v_i + 1;
CONTINUE;
END IF;
v_f :=1;
UPDATE Order_Items_Copy
SET quantity = quantity +1
WHERE order_id = v_num;
v_k := v_k + 1;
EXIT WHEN v_k >50;
SELECT SUM(quantity*unit_price) INTO v_sum
FROM Order_Items_Copy
WHERE order_id = v_num;
END LOOP;
IF v_sum > v_sum_max THEN
IF v_f = 1 THEN
UPDATE Orde r_Items_Copy
SET quantity = quantity - 1
WHERE order_id = v_num;
ELSE
UPDATE Orde r_Items_Copy
SET unit_price = unit_price/l.01
" 310 J
Глава 10. Операторы управления
WHERE order_id = v_num;
END IF;
END IF;
END $$;
Выведем содержимое заказа 78 и его общую сумму после выполнения кода
из листинга 10.17.
order_idIitem_id|product_idI quantity Iunit_priceIrating_p|
--------------- +-------------- +-------------------- +----------------+-------------------- +---------------- +
78
78
78
78
1
1
1
1
51
11
ЗІ
21
79|
19|
52 1
23 1
65
147
202
120
1
1
1
1
2209.241
2043.54 1
1391.821 '
1833-. 68 1
1
4
2
4
sum
I
---------- +
945190.22 I
Следует обратить внимание на более сложную коррекцию заказа в случае
превышения максимальной суммы заказа, так как надо учитывать, что вы
ход из цикла может произойти как на этапе повышения цены, так и на этапе
увеличения количества товаров в заказе.
10.3.3. Циклы FOR
Оператор цикла FOR используется в тех случаях, когда
известно число повторений цикла, и имеет следующий
синтаксис:
FOR {счетчик цикла}
IN [REVERSE] {нижняя граница}..{верхняя граница}LOOP
{оператор 1};
{оператор N};
END LOOP;
Цикл начинается с ключевого слова FOR и завершается ключевыми словами
END LOOP.
311
PostgreSQL. SQL + PL/pgSQL
....................................... Л PostgreSQL
{счетчик цикла} - неявно объявляемая переменная, имеющая тип
INTEGER, которая изменяется, начиная со значения {нижняя граница} до
значения {верхняя граница} с шагом, равным 1.
Если используется ключевое слово REVERSE, то начальное значение счет
чика будет равно верхней границе, и будет последовательно уменьшаться с
шагом, равным -1, до значения нижней границы.
Внутри цикла нельзя изменять значение счетчика цикла и границы его из
менения. За пределами цикла переменная {счетчик цикла} будет неопреде
ленна.
Примеры использования цикла FOR.
Листинг 10.18. Вычисление значений факториала числа
DO $$
DECLARE
v_f integer:= 1;
v_n integer:= 5;
BEGIN
FOR v_i IN 1..v_n LOOP
v_f := v_f * v_i;
END LOOP;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'n = % % % ',v_n,', factorial(n) = ',v_f;
END $$;
Результат:
n = 5, factorial (n)
= 120
ПРИМЕР 6: Увеличить зарплату сотрудникам, используя следу
ющий алгоритм:
•
сотрудникам, имеющим рейтинг 5, зарплату увеличить на 500;
•
сотрудникам, имеющим рейтинг 4, зарплату увеличить на 300;
•
сотрудникам, имеющим рейтинг 2, зарплату увеличить на 200;
•
остальным сотрудникам зарплату увеличить на 100.
У величение зарплаты начать с сотрудника employee id = 101 и завершить
сотрудником employee id = 200. Вывести общую сумму повышения
зарплаты.
^ 312 ]
Глава 10. Операторы управления
В листинге 10.19 содержится программа увеличения зарплаты в соответ
ствии с данным алгоритмом.
Листинг 10.19. Повысить заработную плату сотрудникам и
вычислить общую сумму повышения заработной платы
DO $$
DECLARE
v_rating Employees_copy.rating_e%TYPE;
v_add_salary Employees_copy.salary%TYPE;
v_sum_sal numeric(10,2):= 0;
v_count integer;
BEGIN
FOR i IN 101..200 LOOP
SELECT COUNT(employee_id) INTO v_count
FROM Employees_copy
WHERE employee_id = i;
CONTINUE WHEN v_count = 0;
SELECT rating_e INTO v_rating
FROM Employees_copy
WHERE employee_id=i;
v_add_salary :=
CASE v_rating
WHEN
5 THEN
WHEN
4 THEN
WHEN
3 THEN
ELSE 100
END;
500
300
200
UPDATE Employees_copy
SET salary = salary + v_add_salary
WHERE employee_id = і;
v_sum_sal
END LOOP;
:= v_sum_sal +
v_add_salary;
RAISE NOTICE 'Результат: ';
RAISE notice 'sum add salary = %',v_sum_sal;
END $$;
Результат:
sum add salary = 23100.00
313
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
10.3.4. Вложенные циклы
В теле любого цикла могут находиться другие операторы цикла.
Циклы, расположенные внутри другого цикла, называются
вложенными.
Любой цикл может иметь метку «{имя метки}», которая должна распо
лагаться перед первым оператором цикла. Если оператор цикла имеет мет
ку, то в операторе завершения цикла можно (но необязательно) указывать
имя метки. Это упрощает анализ сложных программ, содержащих несколько
операторов цикла.
Вложенный оператор цикла может содержать оператор EXIT...WHEN для
досрочного завершения цикла. Если в этом операторе указать метку внеш
него цикла (EXIT «{метка внешнего цикла}» WHEN), то это приведет к
досрочному завершению как вложенного, так и внешнего цикла.
ПРИМЕР 7. Вычисление биноминальных коэффициентов.
Значения биноминальных коэффициентов, которые вычисляются по формуле:
,„
т\
п\(т - п)
равны числу сочетаний п элементов из т.
Листинг 10.20. Вычисление биноминальных коэффициентов
DO $$
DECLARE
v_cnm integer;
v_m integer:=6;
BEGIN
FOR v_i IN 1..v_m loop
v_cnm := 1;
FOR v_j in 1..v_i loop
v_cnm:= v_cnm*(v_m -v_j+l)/v_j;
END LOOP;
RAISE NOTICE 'Результат: ';
RAISE NOTICE'm = % % % % %', v m,' n = ',v i,
Глава 10. Операторы управления
'cnm = ',v_cnm;
END LOOP;
END $$;
Результат:
m
=б
n
m =6
n
m
=б
n
m
=б
n
m
=б
n
m
=6
n
=
=
=
=
=
=
1
2
3
4
5
б
cnm
cnm
cnm
cnm
cnm
cnm
=
=
=
=
=
=
б
15
20
15
б
1
Листинг 10.21. Вывести суммы продаж за каждый месяц
2017-2018 годов
DO $$
DECLARE
v_jear integer := 2017;
v_sum_sal numeric(10,2):= 0;
BEGIN
RAISE NOTICE 'Результат: ';
« jear_loop>>
WHILE v_jear <= 2018 LOOP
RAISE NOTICE ' jear= %',v_jear;
<<month_loop>>
FOR і IN 1..12 LOOP
SELECT SUM(quantity*unit__price) INTO v_sum_sal
FROM Order_Iterns
WHERE order_id IN
(SELECT order_id FROM Orders
WHERE EXTRACT(YEAR FROM order_date)= v_jear
AND EXTRACT(MONTH FROM order_date)= i) ;
CONTINUE WHEN v_sum_sal is NULL;
RAISE notice ' month= % % %',LPAD(i::text,3),
sum_sales = ',LPAD(ROUND(v_sum_sal) ::text,9);
END LOOP month_loop;
v_jear := v_jear +1;
END LOOP jear_loop;
END $$;
Результат:
year = 2017
month= 2
sum_sales =
month=
3
sum_sales =
1635170
331820
PostgreSQL: SQL + PL/pgSQL
month=
month=
month=
month=
5
7
8
9
sum_ sales
sum__sales
sum_ sales
sum sales
PostgreSQL
1132070
374120
637800
582480
=
=
=
=
year = 2018
month
month
month
month
month
month
month
month
month
=
=
=
=
=
=
=
=
=
2
'3
6
7
8
9
10
11
12
sum_sales
sum_sales
sum_sales
sum_sales
sum_sales
sum_sales
sum_sales
sum_sales
sum sales
=
=
=
=
=
=
=
=
=
838630
496470
396050
514720
496630
284820
348860
150200
1558180
В этом примере используются два вложенных цикла. Внешний цикл, имею
щий тип WHILE, перебирает года из заданного диапазона. Во внутреннем
цикле, который имеет тип FOR IN, вычисляется сумма продаж за каждый
месяц года. Команда CONTINUE, которая содержится во внутреннем цикле,
осуществляет переход на начало внутреннего цикла, в том случае если в рас
сматриваемом месяце продаж не было.
Для работы с курсорами и массивами можно использовать особые формы
циклов, которые будут рассмотрены при изучении этих элементов языка.
Глава 10. Операторы управления
Задачи для самостоятельного решения:
Для решения задач нужно создать и использовать копии таблиц: Employees,
Orders, Order item.
Задача 10.1. Написать программу для повышения зарплаты
сотрудников, занимающих определенные должности. Размер
повышения зарплаты зависит от должности, которую занимает
сотрудник, и указан в таблице.
JOB ID
Размер повышения зарплаты
IT PROG
300
MK MAN
200
MK REP
220
PR REP
180
PU_CLERK
150
Зарплата сотрудников, должности которых отсутствуют в табли
це, не изменяется. Выбор сотрудника осуществляется по его
етріоуее_ід, значение которого следует задать, используя пе
ременную.
Задача 10.2. Если у сотрудника общая сумма продаж больше
500 000, то изменить значение следующих столбцов: увеличить
rating_e на 1, если rating_e < 5, и увеличить salary на 5%, если
rating_e = 5. Выбор сотрудника осуществляется по его employee_
id, значение которого следует задать, используя переменную.
Задача 10.3. Для отдела 60 задать максимальный суммарный
объем заработной платы ѵ тах эит заі. Сравнить текущую
суммарную зарплату отдела со значением ѵ_тах_зит_8аі,
если она меньше этого значения, то увеличить зарплату пропор
ционально текущей зарплате, так чтобы после увеличения она
была равна ѵтахзитэаі. Вывести суммарную зарплату от
дела до и после повышения. Решить эту задачу для следующих
значений ѵ тах зит эаі: 30000, 40000.
□El
PostgreSQL. 8рЕ + PL/pgSQL
Рс^геЗак
Задача 10.4. В имеющийся заказ 78 добавить данные о новом
товаре с проверкой правила: рейтинг сотрудника должен быть
больше рейтинга товара или равен ему.
Если это правило выполнено, то добавить в таблицу Огбег_
Иетз_Сору новую строку, если нет, то вывести сообщение о том,
что операция не выполнена.
Значения столбцов в добавляемой строке задавать, используя
переменные.
Задача 10.5. Изменить рейтинг сотрудников с номерами с 101 по
200, в зависимости от суммы продаж ѵзитзаіагу, используя
следующий алгоритм:
• если ѵ_зит_заіагу > 1000000 и га1іпд_е < 5, то увеличить
гайпд_е на 1;
• если 500000 < ѵ_зигп_заіагу <= 1000000, то гайпд_е не изме
нять;
• если ѵ_зигп_заіагу
гаИпд_е на 1.
< 200000 и гайпд_е > 1, то уменьшить
Задача 10.6. Используя оператор цикла, увеличивать на каждой
итерации зарплату сотрудников отдела номер 50 на 2% до тех
пор, пока средняя зарплата сотрудников этого отдела не станет
больше или равна средней зарплате по всей фирме. Вывести
количество итераций повышения зарплаты.
Задача 10.7. Вывести количество сотрудников, которые были
приняты на работу в каждом месяце года в 1995-1997 гг.
Глава 11.
КУРСОРЫ
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Достоинством языка PL/pgSQL является его тесная интеграция с запросами
SQL как для выборки, так и для изменения данных. При выполнении опера
торов SQL в среде PL/pgSQL СУБД выделяет рабочую область памяти, кото
рая содержит информацию, необходимую для выполнения операторов SQL,
и набор данных, возвращаемых или обрабатываемых этими операторами.
Курсор — это указатель на рабочую область оператора SQL,
и с его помощью программа PL/pgSQL может управлять этой
областью.
11.1. Использование курсоров
Процесс использования явных курсоров состоит из следующих шагов:
• Объявление курсора.
—-—
• Открытие курсора.
• Получение данных из курсора.
• Закрытие курсора.
11.1.1. Объявление курсора
Объявление курсора определяет имя курсора и связывает его
с оператором SELECT.
Глава 11. Курсоры
Синтаксис объявления курсора:
{имя курсора} [[NO] SCROLL] CURSOR [({параметры})]
FOR {оператор SELECT};
Курсоры используются для переходов между строками набора данных, на
ходящегося в рабочей области.
Если при объявлении курсора будет указано служебное слово SCROLL, то
переход будет возможен как в прямом, так и в обратном направлении. Если
будет указано NO SCROLL, то переход будет возможен только в прямом
направлении (возможно только увеличение номера строки).
Примеры объявления курсора
В листинге 11.1 приведен пример объявления курсора cur_orders_date, ко
торый содержит данные о заказах, сделанных в определенную дату.
Листинг 11.1. Объявление курсора, который содержит
данные о заказах, сделанных в определенную дату
DECLARE
Cur_Orders_Date CURSOR FOR
SELECT *
FROM Orders
WHERE order_date ='2019-11-02';
Курсор может быть объявлен с параметрами. Это позволяет курсору гене
рировать различные наборы данных для разных значений параметров. При
определении параметра нужно указать его имя и тип.
В листинге 11.2 приведен пример объявления курсора curordersdate, ко
торый содержит параметр p_ord_date, которому можно присвоить значение
даты оформления заказа.
Листинг 11.2. Объявление курсора с параметром
DECLARE
Cur_Orders_Date_P CURSOR (p_date_ord Orders.order_date%TYPE) FOR
SELECT *
FROM Orders
WHERE order_date = p_date_ord;
П2Г
PoStgreSQL: SQL + PL/pgSQL
ф PostgreSQL
Курсор может содержать данные, полученные в результате выполнения мно
готабличного запроса. Оператор SELECT, используемый в курсоре, может
содержать группировку, агрегатные функции и подзапросы.
Листинг 11.3. Объявление курсора, который содержит
данные о заказах и общую сумму каждого заказа
DECLARE
Cur_Orders_Sum CURSOR (p_date_ord Orders.order_date%TYPE} FOR
SELECT order_id, order_date,
SUM(quantity*unit_price) AS order_sum
FROM Orders JOIN Order_Items USING (order_id)
WHERE order_date = p_date_ord;
GROUP BY order_id, order_date;
11.1.2. Открытие курсора и получение данных
Открытие курсора осуществляется в исполняемом разделе блока PL/pgSQL
и имеет следующий синтаксис:
OPEN {имя курсора};
При выполнении оператора OPEN осуществляются следующие действия:
1. Выделяется память для рабочей области.
2. Выполняется оператор SELECT, содержащийся в объявлении курсора, и
результаты его выполнения записываются в активный набор данных.
3. Указатель курсора устанавливается на первую строку активного набора.
После того как курсор был объявлен и открыт, можно извлекать и использо
вать данные из него. Извлечение данных из курсора осуществляется коман
дой FETCH, которая имеет следующий синтаксис:^
FETCH [FROM]{имя курсора} INTO
{список переменных}I{запись};
При выполнении команды FETCH происходит следующее.
322
Глава 11. Курсоры
Из активного набора данных извлекается строка, на которую установлен
указатель, и значения отдельных элементов этой строки присваиваются пе
ременным, указанным после служебного слова INTO. После этого указатель
активного набора перемещается вперед на следующую строку.
Количество переменных после служебного слова INTO должно совпадать
с количеством столбцов в курсоре, и тип переменных должен совпадать с
типом соответствующих им столбцов.
Команда FETCH может содержать направление перемещения.
FETCH {направление} FROM {имя курсора} INTO
{список переменных}I{запись};
{направление} может иметь следующие значения: NEXT, PRIOR, FIRST,
LAST, ABSOLUTE N, RELATIVE N, FORWARD, BACKWARD.
Если направление не указано, то будет использовано NEXT.
Как правило, извлечение данных из курсора осуществляется внутри цикла,
до тех пор, пока не будет обнаружен конец набора данных. Для обнаруже
ния конца активного набора используется специальная переменная FOUND,
которая имеет значение TRUE, если строка успешно извлечена, и FALSE,
если достигнут конец активного набора данных.
При работе с курсорами можно использовать команду MOVE, которая пере
мещает курсор, но не извлекает строку.
MOVE {направление}
{имя курсора},
11.1.3. Закрытие курсора
После извлечения всех строк в курсоре он должен быть закрыт. Команда
закрытия курсора имеет следующий синтаксису
CLOSE {имя курсора}
Команда CLOSE освобождает рабочую область и отключает активный на
бор. При необходимости курсор может быть повторно открыт. В этом случае
будет снова выполнен оператор SELECT, содержащийся в объявлении кур
сора, и результаты его выполнения записаны в активный набор данных.
ГЕ
PostgreSQL: SQL + PL/pgSQL
^PostgreSQL
Рассмотрим примеры открытия и извлечения данных из курсора.
Листинг 11.4. Извлечение данных из курсора Cur_Orders_
Date_P
DO $$
DECLARE
Cur_Orders_Date_P CURSOR (p_date_ord Orders.order_date%TYPE)
FOR SELECT * FROM Orders
WHERE order_date = p_date_ord;
v cur orders Orders%ROWTYPE;
BEGIN
OPEN Cur_Orders_Date_P('2019-11-02');
RAISE NOTICE 'Результат: ' ;
RAISE notice '%%%%% ',
LPAD('order_id',8),LPAD('cus tomer_id',13),
LPAD('status',10),LPAD('salesman_id',12),
LPAD('order_date',12) ;
LOOP
FETCH cur_orders_date_p INTO v_cur_orders ;
EXIT WHEN NOT FOUND;
RAISE notice '%%%%% ',
LPAD(v_cur_orders.order_id::text,8),
LPAD(v_cur_orders.customer_id::text,13),
LPAD(v_cur_orders.status,10),
LPAD(v_cur_orders.salesman_id::text,12),
LPAD(v_cur_orders.order_date::text,12);
END LOOP;
close Cur_Orders_Date_P;
END $$;
Результат:
customer_id
order_id
61
49
62
50
51
63
64
52
status
Shipped
Pending
Shipped
Shipped
salesman_id
155
155
159
160
order_date
2019-11-02
2019-11-02
2019-11-02
2019-11-02
Листинг 11.5. Извлечение данных из курсора Cur_Orders_
Sum, который использует данные из нескольких таблиц и
группировку данных
DO $$
DECLARE
Cur_Orders_Sum CURSOR (p_date_ord Orders.order_date%TYPE) FOR
SELECT order_id, order_date, SUM (quantity*unit__price) AS
order_sum
FROM Orders JOIN Order Items USING (order id)
Глава 11. Курсоры
WHERE order_date = p_date_ord
GROUP BY order_id, customer_id, status, order_date;
v_order_id integer;
v_order_date date;
v_order_sum numeric (10,2);
BEGIN
OPEN Cur_Orders_Sum('2019-11-02');
RAISE NOTICE 'Результат: ';
RAISE NOTICE '% % % ',
LPAD('order_id',8),LPAD('order_date',12),
LPAD('order_sum',12) ;
LOOP
FETCH Cur_Orders_Sum INTO v_order_id,
v_order_date, v_order_sum;
EXIT WHEN not FOUND;
RAISE NOTICE '% % % ',
LPAD(v_order_id::text,8),LPAD(v_order_date::text,12),
LPAD(v_order_sum::text,12);
END LOOP;
close Cur_Orders_Sum;
END $$;
Результат:
order_id
49
51
52
order_date
2019-11-02
2019-11-02
2019-11-02
order_sum
15600.00
61540.00
248460.00
Сравнивая результаты выполнения программ из листингов 11.4 и 11.5, кото
рые выводят данные о заказах, оформленных в один и тот же день (2019-1102), можно увидеть, что в результатах работы программы из листинга 11.5
отсутствуют данные о заказе 50. Это произошло, потому что заказ 50 пустой,
он не содержит данных о товарах. При выполнении внутреннего соедине
ния таблиц Orders и Order Items его результат не будет содержать данных об
этом заказе.
В листинге 11.6 приведен пример использования курсора, который содержит
данные о сотрудниках, имеющих заданное значение рейтинга, упорядочен
ные по убыванию заработной платы.
Листинг 11.6. Вывод данных о 10 наиболее
высокооплачиваемых сотрудниках, имеющих рейтинг 5
DO $$
DECLARE
Cur_Emp_R CURSOR (p_rating Employees.rating_e%TYPE) FOR
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
SELECT *
FROM Employees
WHERE rating_e = p_rating
ORDER BY salary DESC;
v_cur_emp Employees%ROWTYPE;
v_n integer:=10;
BEGIN
OPEN Cur_Emp_R(5);
RAISE NOTICE 'Результат: ';
RAISE NOTICE '%%%%%%',
LPAD(' N' ,3) ,LPAD('employee_id' ,10) ,RPAD ('first_name ' ,12) ,
RPAD('last_name',10),LPAD('salary',10),LPAD('rating_e',9);
FOR v_i IN 1..v_n LOOP
FETCH Cur_Emp_R INTO v_cur_emp;
RAISE notice '%%%%%%', LPAD(v_i::text,3),
LPAD( v_cur_emp.employee_id::text,10),
RPAD( v_cur_emp.first_name,12),
RPAD( v_cur_emp.last_name,10),
LPAD( v_cur_emp.salary::text,10),
LPAD( v_cur_emp.rating_e::text, 9) ;
END LOOP;
CLOSE Cur_Emp_R;
END $$;
Результат:
N employee_i
1
101
2
164
3
148
4
204
163
5
153
6
7
206
8
155
9
160
165
10
first_name
last_name
Kochhar
Neena
Marvins
Mattea
Cambrault
Gerald
Hermann
Baer
Danielle
Greene
Christopher Olsen
William
Gietz
Oliver
Tuvault
Doran
Louise
David
Lee
salary rating_e
17000.00
5
11700.00
5
11000.00
5
11000.00
5
10450.00
5
9702.00
5
9130.00
5
7700.00
5
7500.00
5
6800.00
5
Рассмотрим пример использования оператора FETCH, в котором использу
ется параметр {направление}.
Листинг 11.7. Необходимо вывести employee_id и salary
сотрудников, которые в списке сотрудников, имеющих
рейтинг 5, упорядоченном по убыванию заработной платы,
занимают 1, 5, 3, 2 и последнее место
" 326 J
Глава 11. Курсоры
DO $$
DECLARE
Cur_Emp_R SCROLL CURSOR (p_rating Employees.rating_e%TYPE) FOR
SELECT *
FROM Employees
WHERE rating_e = p_rating
ORDER BY salary DESC;
v_cur_emp Employees%ROWTYPE;
v_n integer:=10;
BEGIN
OPEN Cur_Emp_R(5) ;
RAISE NOTICE 'Результат: ';
RAISE NOTICE ' % % ' ,
LPAD('employee_id',10),LPAD('salary',10);
- - Извлечение первой строки
FETCH Cur_Emp_R INTO v_cur_emp;
RAISE NOTICE '% %',
LPAD( v_cur_emp.employee_id::text,10),
LPAD( v_cur_emp.salary::text,10);
- - Извлечение пятой строки
FETCH ABSOLUTE 5 FROM Cur_Emp_R INTO v_cur_emp;
RAISE NOTICE '% % ',
LPAD( v_cur_emp.employee_id::text,10),
LPAD( v_cur_emp.salary::text,10);
- - Перемещение курсора на две позиции вверх
FETCH RELATIVE -2 FROM Cur_Emp_R INTO v_cur_emp;
RAISE NOTICE '% % ',
LPAD( v_cur_emp.employee_id::text,10),
LPAD( v_cur_emp.salary::text,10);
- - Извлечение предыдущей строки
FETCH PRIOR FROM Cur_Emp_R INTO v_cur_emp;
RAISE notice '% % ', '
LPAD( v_cur_emp.employee_id::text,10),
LPAD( v_cur_emp.salary::text,10) ;
- - Извлечение последней строки
FETCH LAST FROM Cur_Emp_R INTO v_cur_emp;
RAISE NOTICE '% % ',
LPAD( v_cur_emp.employee_id::text,10),
LPAD( v_cur_emp.salary::text,10);
CLOSE Cur_Emp_R;
END $$;
Результат:
employee_i
101
salary
17000.00
327
PostgreSQL: SQL + PL/pgSQL
163
148
164
128
10450.00
11000.00
11700.00
2200.00
11.2. Циклы для курсоров
При работе с курсорами можно использовать особую форму циклов — цикл
для курсора, или курсорный цикл, который имеет следующий синтаксис X
FOR {переменная цикла} IN
{имя курсора}|{оператор SELECT}
LOOP
{тело цикла}
END LOOP;
Здесь {переменная цикла} — неявно объявляемая переменная, имеющая
тип записи, структура которой совпадает со структурой курсора, указанного
после служебного слова IN.
При выполнении такого цикла последовательно извлекаются строки курсора
и присваиваются переменной цикла, значения которой могут быть обрабо
таны в теле цикла.
Операции открытия, извлечения и закрытия курсора в таких циклах вы
полняются неявно. Это уменьшает объем кода и упрощает его понимание.
Однако следует иметь в виду, что не все задачи обработки данных с исполь
зованием курсоров могут быть реализованы с использованием циклов для
курсоров.
В листинге 11.8 приведен пример решения задачи из листинга 11.4 с исполь
зованием цикла для курсора.
Листинг 11.8. Извлечение данных из курсора Cur_Orders_
Date_P с использованием цикла для курсора
DO $$
DECLARE
Cur_Orders_Date_P CURSOR (p_date_ord Orders.order_date%TYPE)
FOR SELECT * FROM Orders
WHERE order_date = p_date_ord;
Глава 11. Курсоры
BEGIN
RAISE NOTICE 'Результат: ';
RAISE NOTICE '%%%%% ' ,
LPAD('order_id',8),LPAD('customer_id',13),
LPAD('status' , 10),LPAD('salesman_id’, 12) ,
LPAD('order_date’,12);
FOR v_cur_orders IN cur_orders_date_P('2019-11-02')
LOOP
RAISE notice '%%%%% ',
LPAD(v_cur_orders.order_id::text,8),
LPAD(v_cur_orders.customer_id::text,13),
LPAD(v_cur_orders.status,10),
LPAD(v_cur_orders.salesman_id::text,12),
LPAD(v_cur_orders.order_date::text,12);
END LOOP;
END $$;
Результат:
order_id
49
50
51
52
customer_id
61
62
63
64
status
Shipped
Pending
Shipped
Shipped
salesman_id
155
155
159
160
order_date
2019-11-02
2019-11-02
2019-11-02
2019-11-02
Цикл для курсора может содержать условие досрочного выхода. В листинге
11.9 приведен пример решения задачи из листинга 11.6.
Листинг 11.9. Используя цикл для курсора, вывести данные о
сотрудниках, имеющих рейтинг 5, упорядоченные по убыванию
заработной платы. Выйти из цикла, если суммарная зарплата
сотрудников, данные о которых уже выведены, станет больше 60000
DO $$
DECLARE
Cur_Emp_R CURSOR (p_rating Employees.rating_e%TYPE) FOR
SELECT *
FROM Employees
WHERE rating_e = p_rating
ORDER BY salary DESC;
v_sum_sal numeric(10,2):=0;
BEGIN
RAISE NOTICE 'Результат: ';
RAISE NOTICE '%%%%% ',
LPAD (' employee_id' , 10) , RPAD (' first_name ' , 12) ,
RPAD('last_name',10),LPAD('salary',10),LPAD('rating_e',9);
[ 329 '
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
FOR v_cur_emp IN cur_emp_r (5) loop
EXIT WHEN v_sum_sal > 60000;
RAISE notice '%%%%% ' ,
LPAD( v_cur_emp.employee_id::text,10),
RPAD ( v_cur_emp. firs t_name, 12) ,
RPAD( v_cur_emp.las t_name,10),
LPAD( v_cur_emp.salary::text,10),
LPAD( v_cur_emp.rating_e::text, 9) ;
v_sum_sal: =v_sum_sal + v_cur_emp.salary;
END LOOP;
RAISE NOTICE 'v_sum_sal= %’, v_sum_sal;
END $$;
Результат:
employee_i
101
164
148
204
163
v_sum_sal=
first_name
Neena
Mattea
Gerald
Hermann
Danielle
61150.00
last_name
Kochhar
Marvins
Cambrault
Baer
Greene
salary
rating_e
17000.00
5
11700.00
5
11000.00
5
11000.00
5
10450.00
5
Курсоры и циклы для курсоров могут быть вложенными. В этом случае
внутренний курсор выполняется для каждой строки внешнего курсора. В
листинге 11.10 приведен пример использования вложенных курсорных
циклов. Внешний цикл последовательно выбирает данные о клиентах, а во
внутреннем цикле для каждого клиента формируются данные об общей сум
ме заказов этого клиента, находящихся в состоянии ожидания ('Pending').
Листинг 11.10. Пример использования вложенных циклов
для курсора
DO $$
DECLARE
Cur_Customers CURSOR
FOR SELECT * FROM Customers;
cur_orders CURSOR (p_id_customer orders.customer_id%type,
p_status Orders.status%type) FOR
SELECT customer_id, status,
SUM(quantity*unit_price) AS order_sum
FROM Orders JOIN Order_items USING (order_id)
WHERE customer_id = p_id_customer
AND status = p_status
Глава 11. Курсоры
GROUP BY customer_id, status;
BEGIN
RAISE NOTICE 'Результат: ';
RAISE notice '% % %', LPAD('customer_id',10),
LPAD('status' , 10), LPAD('order_sum',12);
FOR v cur cuct IN cur customers
LOOP
FOR v_cur_order IN cur_orders(v_cur_cuct.customer_id,'Pending')
LOOP
RAISE notice '% % %' ,
LPAD(v_cur_order.customer_id::text,10),
LPAD(v_cur_order.status,10),
LPAD(v_cur_order.order_sum::text,12);
END LOOP;
END LOOP;
END $$;
Результат :
customer_i
2
3
10
11
66
status
Pending
Pending
Pending
Pending
Pending
order_sum
489170.00
89300.00
41200.00
13600.00
261620.00
Во внутреннем курсорном цикле можно использовать значение полей кур
сорной переменной (переменной цикла) внешнего цикла. В листинге 11.11
приведен пример использования значений полей курсорной переменной
внешнего цикла. Во внешнем цикле из таблицы Customers извлекаются дан
ные о клиентах, а во внутреннем цикле вычисляется общая сумма заказов
клиента, находящихся в состоянии ожидания ('Pending'), и выводятся дан
ные о клиентах, у которых общая сумма заказов, находящихся в состоянии
Pending, превышает их кредитный лимит.
Листинг 11.11. Вывод данных о клиентах, у которых
общая сумма заказов, находящихся в состоянии Pending,
превышает их кредитный лимит
DO $$
DECLARE
Cur_Customers CURSOR
FOR SELECT * FROM Customers;
Cur_Orders CURSOR (p_id_customer orders.eustomer_id%type,
p_status Orders.status%type)
PostgreSQL. SQL + PL/pgSQL
------------------------------------- PostgreSQL
FOR SELECT customer_id, status,
SUM(quantity*unit_price) AS order_sum
FROM Orders JOIN Order_items USING (order_id)
WHERE customer_id = p_id_customer
AND status = p_status
GROUP BY customer_id, status;
BEGIN
RAISE NOTICE 'Результат: ';
RAISE NOTICE '% % %', LPAD('customer_id',10),
LPAD('credit_limit',12),LPAD('order_sum',12);
FOR v cur cuct IN Cur Customers
LOOP
FOR v_cur_order IN
Cur_Orders(v_cur_cuct.customer_id,'Pending')
LOOP
IF v_cur_order.order_sum > v_cur_cuct.credit_limit
THEN
RAISE NOTICE '% % %',
LPAD(v_cur_order.customer_id::text,10),
LPAD(v_cur_cuct.credit_limit::text,12),
LPAD(v_cur_order.order_sum::text,12);
END IF;
END LOOP;
END LOOP;
END $$;
Результат:
customer_i credit_limit
2
450000.00
66
250000.00
order_sum
489170.00
261620.00
11.3. Использование курсоров для изменения данных
Курсоры для изменения данных не должны содержать операций группиров
ки и сортировки и должны завершаться фразой FOR UPDATE.
Для обновления строки, на которую установлен указатель курсора, исполь
зуется оператор UPDATE, который имеет следующий синтаксис:^
UPDATE {имя таблицы)
SET {оператор обновления}
WHERE CURRENT OF {имя курсора)
332
Глава 11. Курсоры
Для удаления строки, на которую установлен указатель курсора, использует
ся оператор DELETE, который имеет следующий синтаксиса
DELETE FROM {имя таблицы}
WHERE CURRENT OF {имя курсора}
В листинге 11.12 приведен пример использования курсора cur_emp для из
менения зарплаты сотрудникам по следующему правилу:
• если зарплата > 10000, то следует повысить зарплату на 1 000;
• если s зарплата > 5000, то следует повысить зарплату на 500;
• остальным сотрудникам зарплата повышается на 200.
Все надбавки суммируются, и вычисляется размер повышения фонда зара
ботной платы.
Листинг 11.12. Использование курсора для изменения
зарплаты сотрудников
DO $$
DECLARE
Cur_Emp
cursor
IS SELECT * FROM employees_copy
FOR UPDATE;
v_add_salary employees.salary%TYPE;
v_sum_salary numeric(10,2):=0;
BEGIN
FOR v_cur_emp IN Cur_Emp
LOOP ”
v_add_salary :=
CASE
WHEN v_cur_emp.salary > 10000 THEN 1000
WHEN v_cur_emp.salary > 5000 THEN 500
ELSE 200
END;
v_sum_salary := v_sum_salary +v_add_salary;
UPDATE Employees_copy
SET salary = salary + v_add_salary
WHERE CURRENT OF cur_emp;
END LOOP;
RAISE NOTICE 'Результат: ' ;
RAISE notice ' v_sum_salary = %', v_sum_salary;
END $$;
PostgreSQL: SQL + PL/pgSQL
Результат:
v_sum_salary = 51400.00
В листинге 11.13 приведен пример использования курсора cur_orders для
удаления строк. В этом листинге с помощью курсора последовательно про
сматриваются данные о заказах, находящихся в состоянии ожидания, и если
сумма таких заказов превышает кредитный лимит клиента, то заказ удаля
ется.
Листинг 11.13. Использование курсора для удаления заказов
DO $$
DECLARE
Cur_Orders CURSOR (p_status Orders_copy.status%TYPE)
IS SELECT * FROM Orders_copy
WHERE status = p_status;
FOR UPDATE;
v_credit_limit Customers.credit_limit%TYPE;
v_orders_sum numeric(10,2);
BEGIN
RAISE notice 'Удалены заказы';
FOR v_cur IN Cur_Orders('Pending')
LOOP
SELECT credit_limit into v_credit_limit
FROM Customers
WHERE customer_id = v_cur.customer_id;
SELECT SUM(quantity*unit_price) into v_orders_sum
FROM Orders_copy JOIN order_items_copy USING (order_id)
WHERE customer_id = v_cur.customer_id
AND status = 'Pending';
IF
v_orders_sum > v_credit_limit
THEN DELETE FROM Orders_copy
WHERE CURRENT OF cur_orders;
RAISE notice 'customer_id =%%%', v_cur.customer_id,
'order_id = ', v_cur.order_id;
END IF;
END LOOP;
END $$;
Удалены заказы
customer_id = 2
order_id = 44
customer_id = 66 order_id = 55
customer_id = 2
order_id = 78
Глава 11. Курсоры
11.4. Курсорные переменные
В программах PL/pgSQL можно использовать переменные, имеющие тип
курсора (курсорные переменные). Такие переменные объявляются в разделе
объявлений, а значение (набор данных) присваивается в исполняемом разде
ле. После закрытия курсора, связанного с курсорной переменной, ей можно
присвоить новое значение. Используя курсорные переменные, можно пере
давать результаты выполнения запросов из одной программы в другую.
Объявление курсорной переменной:^
{имя курсорной переменной} REFCURSOR;
Присвоение значения курсорной переменной осуществляется в исполняе
мом разделе оператором:
OPEN {имя курсорной переменной} FOR {оператор SELECT};
Закрытие курсорной переменной осуществляется оператором:
CLOSE {имя курсорной переменной};
При выполнении этого оператора освобождаются ресурсы, используемые
оператором SELECT, но активный набор данных, связанный с курсорной
переменной, сохраняется в области видимости переменной.
В листинге 11.14 приведен пример объявления и использования курсорной
переменной v_refcur. В исполняемом разделе ей могут быть присвоены раз
личные значения (результаты выполнения разных операторов SELECT), ко
торые зависят от значения переменной v_t.
Листинг 11.14. Пример использования курсорной переменной
DO $$
DECLARE
v_refcur REFCURSOR;
v_id integer;
v_name varchar(40);
v_wh integer;
v_t integer;
PostgreSQL: SQL + PL/pgSQL
^p PostgreSQL
BEGIN
v_t :=2;
RAISE NOTICE 'Результат: ' ;
RAISE notice ' v_t = %',v_t;
CASE v_t
WHEN 1 THEN OPEN v_refcur FOR
SELECT employee_id, first_name | | ' ' | J last_name,
department_id
FROM Employees WHERE employee_id <110
ORDER BY employee_id;
WHEN 2 THEN OPEN v_refcur FOR
SELECT department_id, department_name,
location_id
FROM Departments WHERE department_id <70
ORDER BY department_id;
END CASE;
LOOP
FETCH v_refcur INTO v_id, v_name, v_wh;
EXIT WHEN NOT FOUND;
RAISE NOTICE 'v_id= %%%%%', LPAD(v_id::text,5),
'v_name= ', RPAD(v_name,20),
'v_wh= ',LPAD(v_wh::text,5);
END LOOP;
CLOSE v_refcur;
END $$;
Результат:
v_t = 2
v_id=
v_id=
v_id=
v_id=
v_id=
v id=
10
20
30
40
50
60
v_name=
v_name=
v_name=
v_name=
v_name=
v name=
Administration
Marketing
Purchasing
Human Resources
Shipping
IT
v_wh=
v_wh=
v_wh=
v_wh=
v_wh=
v wh=
1700
1800
1700
2400
1500
1400
Глава 11. Курсоры
Задачи для самостоятельного решения:
Задача 11.1. Используя курсор с параметрами (p_n, р_т), вы
вести данные (employeejd, firstname, last_name, jobjd, salary)
о p_n наиболее высокооплачиваемых сотрудников, работающих
в отделе р_т.
Задача 11.2. Используя курсор из задания 1, вывести данные о
сотрудниках, которые в списке, упорядоченном по убыванию за
работной платы, занимают второе и последнее место.
Задача 11.3. Используя курсор из задания 1, вывести данные о
сотрудниках с указанием места, которое они занимают в этом
списке. Если несколько сотрудников имеют одинаковую зарпла
ту, то они имеют одно и то же значение этого показателя.
Задача 11.4. Используя курсор с параметрами (pjob_id,
p_salary), вывести данные (employeejd, jobjd, first_name, last_
name, salary) о сотрудниках, которые имеют:
• jobjd = IT_PROG и зарабатывают более 5 000$;
• jobjd = ST_MAN и зарабатывают более 7 000$;
• jobjd = SA_REP и зарабатывают более 10 000$.
Задача 11.5. Используя курсор с параметром (p_departmentjd),
изменить зарплату сотрудников отдела, используя следующее
правило:
• если зарплата сотрудника меньше 3000, то увеличить ее на 1000;
• если зарплата больше 3500, но меньше 4000, увеличить ее на 500;
• если зарплата сотрудника больше 4200, но меньше 4700, то умень
шить ее на 500.
Вывести суммарную зарплату отдела до и после изменения
зарплаты.
PostgreSQL: SQL + PL/pgSQL
.............................................................. PostgreSQL
! Задача 11.6. Вывести данные о заказах, оформленных за опре- !
! деленную дату, и о товарах в каждом заказе. Полученный ре- !
; зультат должен иметь следующий вид:
order_id
customer_id
status
salesman_id
order_date
49
61
Shipped
155
2019-11-02
!
item_id
product_id
quantity
unit_price
;
1
72
104
150.00
I
order_id
customer_id
status
salesman_id
order_date
1
50
62
Pending
155
2019-11-02
item_id
product_id
quantity
unit_price
order_id
customer_id
status
salesman_id
order_date
2019-11-02
J
!
і
I
і
і
[
51
63
Shipped
159
item_id
product_id
quantity
unit_price
1
21
34
1810.00
і
order_id
customer_id
status
salesman_id
order_date
1
52
64
Shipped
160
2019-11-02
item_id
product_id
quantity
unit_price
1
35
123
2020.00
I
!
і
I
і
[
1
Задача 11.7. Для некоторых отделов задан размер повышения
заработной платы, который нужно распределить между сотруд
никами пропорционально их текущей зарплате. Эти данные на
ходятся в таблице Эер_АбсІ_8аІагу (берагІтепМб, абб_заІагу),
которую нужно предварительно создать и заполнить. Осуще
ствить изменение заработной платы, округляя значения до 100.
Для каждого отдела вывести суммарную зарплату до и после по
вышения. Вывести общий размер повышения зарплаты.
Задача 11.8. Изменить цену товаров в заказах, оформленных за
определенную дату (задать, используя переменную) и имеющих
состояние Pending, используя следующие правила:
• если рейтинг товара (rating_p) равен 1 или 2, то цена не изменяется;
• если рейтинг товара равен 3 или 4, то уменьшить цену на 50;
• если рейтинг товара равен 5, то уменьшить цену на 100.
1
Глава 12.
МАССИВЫ
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
В PL/pgSQL переменные и столбцы таблиц могут представлять собой
массив.
Массив — упорядоченный набор данных одного типа.
При объявлении массива необходимо определить его имя, тип данных эле
ментов, [размер(ность)]. Характерным и очень полезным свойством масси
ва является возможность прямого доступа к его элементам. Для обращения к
элементу массива нужно указать имя элемента и его индекс. Элементы мас
сива могут иметь любой из встроенных типов, а также типы, определяемые
пользователями, в том числе и типы массивов.
12.1. Объявление массива
Синтаксис объявления переменной, являющейся массивом:^
{имя переменной}{тип элементов}[{array}][п];
где п — максимальный размер массива (указывать необязательно).
Служебное слово array указывать необязательно, но мы будем его использо
вать, во-первых, потому что это соответствует стандартам SQL, во-вторых,
сразу становится ясно, с каким типом данных мы имеем дело.
Рассмотрим пример объявления и использования переменной, имеющей тип
массив.
3
Глава 12. Массивы
Листинг 12.1. Объявление и использование переменной,
имеющей тип массив
DO $$
DECLARE
v_name varchar(45) array;
BEGIN
v_name[l] := 'Bruce Ernst';
v_name[2] := 'Diana Lorentz';
v_name[3] := 'Alexander Hunold';
RAISE NOTICE 'Результат: ';
RAISE NOTICE '% ', v_name[2];
END $$;
Результат :
Diana Lorentz
Используя курсоры, можно заполнять массивы данными, извлекая их из
таблиц базы данных.
Листинг 12.2. Присваивание значений элементам массива
с использованием цикла для курсора
DO $$
DECLARE
Cur_Emp_Name cursor for
SELECT first_name||' '||last_name As name
FROM Employees WHERE department_id = 60;
v_name VARCHAR(45) array;
v_i integer:=0;
BEGIN
FOR emp_rec IN Cur_Emp_Name
LOOP
v_i := v_i+l;
v_name[v_i] := emp_rec.name;
END LOOP;
RAISE NOTICE 'Результат: ';
RAISE NOTICE '% ', v_name[2];
END $$;
Результат:
Diana Lorentz
341
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
12.2. Функции для работы с массивами
Для работы с массивами можно использовать специальные функции, описа
ние некоторых из них приведено в таблице 12.1.
Таблица 12.1. Основные функции для работы с массивами
Функция
ARRAY_APPEND(a,x)
Описание
Добавляет элемент x в конец массива а.
Пример: ARRAY_APPEND(ARRAY[3,2,4], 3)-*{3,2,4, 3}
ARRAY_CAT(al,a2)
Объединяет два массива.
Пример: ARRAY CAT(ARRAY[4,3,2], ARRAYfl ,5]Н {4,3,2,1,5}
ARRAY LENGTH (a,i)
Возвращает длину указанной (і) размерности массива а.
Пример: ARRAY_LENGTH(ARRAY[[ 1,2,3], [4,5,6]],2) -* 3
Возвращает позицию первого вхождения второго аргумента
в массив либо NULL в случае отсутствия соответствующего
ARRAY_POSITION(a,x) элемента.
Пример: ARRAY_POSITION(ARRAY[4,3,2,1,2],1) ->4
ARRAY_REMOVE(a,x)
ARRAY DIMS(a)
ARRAY_UPPER(a, i)
CARDINALITY(a)
Удаляет из массива все элементы, равные заданному значению.
Пример: ARRAYREMOVE (ARRAY[4,3,2,1,2],1) —► {4,3,2,2}
Возвращает текстовое представление размерностей массива а.
ARRAY_DIMS(ARRAY[[ 1,2,3], [4,5,6]]) -+ [1:2][1:3]
Возвращает верхнюю границу указанной размерности массива.
Пример: ARRAY_UPPER(ARRAY[4,3,2,1,2],1) ^5
Возвращает общее число элементов в массиве и 0, если мас
сив пуст
Разворачивает массив в набор строк.
UNNEST(a)
Пример: UNNEST(ARRAY[1,5]) -►
1
5
342
Глава 12. Массивы
Листинг 12.3. Примеры использования функций для работы
с массивами
DO $$
DECLARE
kl integer;
k2 integer;
k3 integer;
k4 integer;
al integer array:='{{1,2,3},{4,5,6}}';
a2 integer array:='{1,2,3}';
a3 integer array:='{5,6}';
a4 integer array;
a5 integer array;
tl text;
t2 text;
BEGIN
tl:=ARRAY_DIMS(al);
kl:=ARRAY_UPPER(al,1) ;
a2:=ARRAY_APPEND(a2,4) ;
t2:=ARRAY_DIMS(a2) ;
k2:=ARRAY_UPPER(a2,1) ;
a4:=ARRAY_CAT(a2,a3) ;
a5:=ARRAY_REMOVE(a4,3) ;
k3:=ARRAY_POSITION(a5,4);
k4:= CARDINALITY(a5);
RAISE NOTICE 'Результат :';
RAISE NOTICE ' al % ’ ,al;
RAISE NOTICE ' array_dims(al) % * ,tl;
RAISE NOTICE ' ARRAY-UPPER(al,1) %’,kl;
RAISE NOTICE ' a2:=ARRAY_APPEND(a2,4) % ' , a2 ;
RAISE NOTICE ' array_dims(a2) % ',t2;
RAISE NOTICE ' ARRAY_UPPER(a2,1) %',k2;
RAISE NOTICE ' a3 % ’,a3;
RAISE NOTICE ' a4:=ARRAY_CAT(a2,a3) % ',a4;
RAISE NOTICE ’ a5:=ARRAY_REMOVE(a4,3) % ' ,a5;
RAISE NOTICE ' ARRAY-POSITION(a5,4)% ’,k3;
RAISE NOTICE ' CARDINALITY(a5) % ',k4;
END $$;
Результат :
al {{1,2,3},{4,5,6}}
array_dims(al) [1:2][1:3]
ARRAY_UPPER(al, 1) 2
a2:=ARRAY_APPEND(a2,4) {1,2,3,4}
array_dims(a2) [1:4]
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
ARRAY UPPER(a2,1) 4
a3 {5,6}
a4:=ARRAY_CAT(a2,a3) {1,2,3,4,5,6}
a5:=ARRAY_REMOVE(a4,3) {1,2,4,5,6}
ARRAY__POSITION (a5, 4) 3
CARDINALITY(a5) 5
12.3. Циклы для массивов
Для работы с массивами можно использовать специальный вид оператора
цикла, который имеет следующий вид
FOREACH {переменная цикла}
ARRAY {имя массива}
{тело цикла}
END LOOP;
[SLICE n] IN
{переменная цикла} — переменная, которая должна быть объявлена, и ее
тип должен совпадать с типом элементов массива.
Если параметр SLICE не указан или имеет значение 0, то цикл выполняет
ся для каждого элемента массива. Положительные значения SLICE исполь
зуются для многомерных массивов. В этом случае итерации выполняются
по фрагментам массива размерности п. Тип переменной цикла должен со
впадать с типом фрагмента массива. Тело цикла может содержать оператор
EXIT для досрочного выхода из цикла.
В листинге 12.4 содержится пример использования цикла РОИЕАСН для
вывода всех элементов массива ѵ_пате из листинга 12.2.
Листинг 12.4. Вывод содержимого массива с использованием
цикла FOREACH
DO $$
DECLARE
Cur_Emp_Name cursor for
SELECT first_name | | ' ' | I las t_name As name
FROM Employees WHERE department_id = 60;
v_name varchar(45) array;
v_x varchar(45);
344
Глава 12. Массивы
v_i integer:=0;
BEGIN
FOR emp_rec IN Cur_Emp_Name
LOOP
v_i := v_i+l;
V—name[ v_i] := emp_rec.name;
END LOOP;
RAISE notice 'Результат: ';
FOREACH v_x IN ARRAY v_name
LOOP
RAISE NOTICE '% ', v_x;
END LOOP;
END $$;
Результат:
Bruce Ernst
Diana Lorentz
Alexander Hunold
Valli Pataballa
DAVID Austin
Листинг 12.5. Вывод содержимого массива с
использованием цикла FOREACH с параметром SLICE 1
DO $$
DECLARE
Cur_Emp_Name CURSOR FOR
SELECT fi.rst_name||' '||last_name As name
FROM Employees WHERE department_id = 60;
v_name VARCHAR(45) array;
v_x VARCHAR(45) array;
v_i integer:=0;
BEGIN
FOR emp_rec IN Cur_Emp_Name
LOOP
v_i := v_i+l;
v_name[v_i] := emp_rec.name;
END LOOP;
RAISE NOTICE 'Результат: ';
FOREACH v_x SLICE 1 IN ARRAY v_name
LOOP
RAISE NOTICE '% ', v_x;
END LOOP;
END $$;
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Результат:
{"Bruce Ernst","Diana Lorentz ","Alexander Hunold","Valli
Pataballa","DAVID Austin"}
В этом примере содержимое всего массива выведено как один элемент. Сле
дует обратить внимание на то, что тип переменной ѵ_х был изменен.
Элементы массивов могут иметь составной тип. Обращение к определенно
му полю элемента таких коллекций имеет следующий вид:^
{имя массива}[{значение индекса}].{имя поля}
Рассмотрим следующую задачу: необходимо записать в массив данные о
сотрудниках, работающих в заданном отделе, упорядочив их по убыванию
заработной платы. Элементы массива представляют собой составной тип
данных, поэтому необходимо предварительно создать пользовательский тип
данных.
CREATE TYPE t_emp AS
(id_emp integer,
f_name varchar(25),
l_name varchar(25),
dep_id integer,
job_id varchar(lO) ,
rating integer,
salary numeric(10,2)
);
Листинг 12.6. Запись в массив данных о сотрудниках,
работающих в отделе 60, упорядоченных по убыванию
заработной платы
DO $$
DECLARE
Cur_Emp_Dep CURSOR(p_id_dep Employees, department_id%TYPE) FOR
SELECT employee_id, first__name, last_name, department_id,
job_id, rating_e, salary
FROM Employees
WHERE department_id = p_id_dep
ORDER BY salary DESC;
v_emp_dep t_emp array;
v_x t_emp;
Глава 12. Массивы
v_i integer:=0;
BEGIN
FOR emp_rec IN Cur_Emp_Dep(60)
LOOP
v_i := v_i+l;
v_emp_dep [ v_i ] : = emp_rec ;
END LOOP;
RAISE NOTICE 'Результат: ';
FOREACH v_x IN ARRAY v_emp_dep
LOOP
RAISE NOTICE '% ', v_x;
END LOOP;
END $$;
Результат:
(103, Alexander,Hunold,60,IT_PROG, 3, 9900.00)
(104,Bruce,Ernst,60,IT_PROG,3,6000.00)
(106,Valli,Pataballa,60,IT_PROG, 4,5808.00)
(105,DAVID,Austin,60,IT_PROG, 5, 4800.00)
(107,Diana,Lorentz,60,IT_PROG,3,4200.00)
12.4. Многоуровневые массивы
Массив называется многоуровневым, если один или
несколько элементов являются массивами.
Рассмотрим следующую задачу. Необходимо создать и заполнить массив,
который должен сдержать следующие данные: номер, название отдела и све
дения о сотрудниках, работающих в каждом отделе.
Элементы этого массива будут представлять собой составной тип данных,
одно из полей которого будет являться массивом.
Оператор создания типа элементов массива:
CREATE TYPE t_dep AS
(id_dep integer,
dep_name varchar(25),
d_emp t_emp array);
347
PostgreSQL: SQL + PL/pgSQL
.................................................. » PostgreSQL
Листинг 12.7. Заполнение данными многоуровневого массива
DO $$
DECLARE
Cur_Dep cursor FOR
SELECT department_id, department_name
FROM Departments
WHERE department_id in
(SELECT DISTINCT department_id FROM employees);
Cur Emp_Dep cursor(p_id_dep Employees.department_id%TYPE) for
SELECT employee_id, first_name, last_name, department_id,
job_id, rating_e, salary FROM Employees
WHERE department_id = p_id_dep
ORDER BY salary DESC;
v_dep t_dep array;
v_i integer:=0;
v_j integer;
BEGIN
FOR dep_rec IN cur_dep
LOOP
v_i:=v_i+l;
v_dep[ v_i].id_dep:= dep_rec.department_id;
v_dep[ v_i].dep_name:= dep_rec.department_name;
v_j : =0 ;
FOR emp_rec IN Cur_Emp_Dep (dep_rec. department_id)
LOOP
v_j := v_j+l;
v_dep[v_i].d_emp[v_j] := emp_rec;
END LOOP;
END LOOP;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'department_id =%%%', v_dep[2].id_dep,
'department_name = ' , v_dep[2].dep_name;
RAISE notice ’employee_id= % % %',
v_dep[3].d_emp[l].id_emp,
'salary = ', v_dep[2].d_emp[1].salary;
END $$;
Результат:
department_id = 30 department_name = Purchasing
employee_id = 203 salary = 12100.00
Листинг 12.8. Вывод данных об отделе, который в
массиве v_dep занимает вторую позицию
DECLARE
v_y t_emp array;
Глава 12. Массивы
RAISE NOTICE '% %', v_dep[2].id_dep, v_dep[2].dep_name ;
FOREACH v_y IN ARRAY v_dep[2].d_emp
LOOP
RAISE NOTICE '% ',v_y;
END LOOP;
30 Purchasing
(114, Den, Raphaely,30,PU_MAN,1,12100.00)
(115,Alexander,Khoo,30,PU_CLERK,3,6264.50)
(116,Shelli,Baida,30,PU_CLERK,2,5945.50)
(117, Sigal,Tobias,30,PU_CLERK, 3, 3080.00)
(118,Guy,Himuro,30,PU_CLERK,3,2860.00)
(119,Karen,Colmenares,30,PU_CLERK,3,2750.00)
Листинг 12.9. Вывод всех данных, содержащихся в
массиве v_dep
DECLARE
v_x t_dep;
Ѵ_У t_emp array;
FOREACH v_x IN ARRAY v_dep
LOOP
RAISE NOTICE '% %', v_x.id_dep,v_x.dep_name ;
FOREACH v_y IN ARRAY v_x.d_emp
LOOP
RAISE NOTICE '% ',v_y;
END LOOP;
END LOOP;
Фрагмент данных, которые выводит этот оператор:
20 Marketing
(201,Michael,Hartstein,20,MK_MAN,4,13000.00)
(202,Pat,Fay,20,MK REP,5,6000.00)
30 Purchasing
(114,Den,Raphaely,30,PU_MAN, 1, 12100.00)
(115,Alexander,Khoo, 30,PU_CLERK,3,6264.50)
(116,Shelli,Baida,30,PU_CLERK,2,5945.50)
(117, Sigal,Tobias,30,PU_CLERK, 3, 3080.00)
(118,Guy,Himuro,30,PU_CLERK,3,2860.00)
(119,Karen,Colmenares,30,PU_CLERK,3,2750.00)
40 Human Resources
(203,Susan,Mavris,40,HR_REP,2,6500.00)
PostgreSQL: SQL+ PL/pgSQL
PostgreSQL
12.5. Использование массивов в столбцах таблицы
В некоторых случаях ячейка таблицы должна содержать не одно, а несколь
ко значений. Например, в таблице, предназначенной для хранения данных о
сотрудниках, могут быть столбцы, которые должны содержать номера теле
фонов и адреса электронной почты сотрудников. Что делать, если правилами
предметной области допускается наличие нескольких телефонных номеров
и адресов электронной почты у каждого сотрудника?
Если строго следовать правилам реляционной модели, то мы должны хра
нить эти данные в отдельных таблицах, связанных с главной таблицей.
Альтернативным решением является использование столбцов, имеющих
тип массива. Рассмотрим правила использования таких столбцов.
Создадим таблицу Ешр_А, предназначенную для хранения данных о
сотрудниках:
CREATE TABLE Emp_A
(employee_id integer,
varchar(20) ,
first_name
varchar(25) ,
last_name
varchar(40) array,
email
varchar(16)array) ;
phone
Столбцы email и phone являются массивами. Заполним эту таблицу данными.
INSERT INTO emp_a(employee_id, first_name, last_name, email, phone)
VALUES(1,'Alexander','Hunold',ARRAY [ 'alex_hldl@mail.ru','alex_
hld2@gmail.com'],
ARRAY ['+7-123-456-77-88','+7-444-555-33-22'])
INSERT INTO emp_a(employee_id, first_name, last_name, email, phone)
VALUES(2,'Bruce','Ernst',ARRAY['bruc_erl@yandex.ru',
'br_erl@gmail.com','br_er3@gmail.com'],
ARRAY ['+7-333-450-71-22','+7-777-543-22-11']);
INSERT INTO emp_a(employee_id, fi.rst_name, last_name, email, phone)
VALUES(3,'Valli','Pataballa',ARRAY[ 'valli_pat33@yandex.
ru','valli_pat321@gmail.com'],
ARAY['+7-765-455-75-42','+7-777-654-32-21','+7-765-455-75-42'])
Глава 12. Массивы
В предложении VALUES значениям столбцов, которые являются массива
ми, соответствует конструкция:
ARRAY[список значений])
Символьные значения обрамляются одинарными кавычками (’).
Можно также использовать конструкцию:
'{список значений}'
Например:
' {{1,2,3},{4,5,6},{7,8,9}}'
'{{"встреча",
"обед"},
{"тренинг", "презентация"}}'
Символьные значения обрамляются двойными кавычками (”).
Вставим еще одну строку в таблицу Ешр_А.
INSERT INTO emp_a(employee_id, first_name, last_name, email, phone)
VALUES(4,'Sigal','Tobias', '{"sigal_t33@yandex. ru","sigal_tob@
gmail. com"}' ,
'{"+7-789-555-75-44","+7-778-678-12-21"}') ;
Листинг 12.10. Вывод содержимого таблицы Emp_A
SELECT einployee_id as id, first_name as f_name,
last_name as l_name, email
FROM Emp_A;
id|f_name
|l_name
I email
I
—+------ +------------------------ +---------------------- +
1
2
3
4
|Alexander|Hunold I{alex_hldl@mail.ru,alex_hld2@gmail.com}
IBruce
lErnst|{bruc_erl@yandex.ru,br_erl@gmail.com,br_er3@gmail.com}|
IValli
|Pataballa|{valli_pat33@yandex.ru,valli_pat321@gmail.com}
ISigal
ITobias
|{sigal_t33@yandex.ru,sigal_tob@gmail.com}
I
|
|
PostgreSQL: SQL + PL/pgSQL
Для вывода содержимого массивов можно использовать
функцию иЫМЕ8Т, которая преобразует массив в набор
строк.
Листинг 12.11. Вывод содержимого таблицы Етр_А с
использованием функции UNNEST
SELECT employee id as id, first name as f_name, last_name as l_name,
UNNEST(email) as "email", UNNEST(phone)As"phone"
FROM Emp_A;
id|f_name
|l_name
|email
Iphone
I
—+--------- +---------- +------------------------ +----------------- +
1 I Alexander IHunold
|alex_hldl@mail.ru
1+7-123-456-77-88 1
11 Alexander IHunold
|alex_hld2@gmail .com
|+7-444-555-33-22 |
2|Bruce
lErnst
|bruc_erl@yandex.ru
1+7-333-450-71-221
2|Bruce
lErnst
|br_erl@gmail.com
1+7-777-543-22-111
lErnst
|br_er3@gmail.com
|
|
2|Bruce
3|Valli
|PataballaIvalli_pat33@yandex.ru 1+7-765-455-75-421
IPataballa|valli_pat321@gmail.com 1+7-777-654-32-211
3|Valli
3|Valli
IPataballal
1+7-765-455-75-421
ITobias
|sigal_t33@yandex.ru
|+7-789-555-75-44|
4|Sigal
ITobias
|sigal_tob@gmail.com
|+7-778-678-12-21|
4|Sigal
Листинг 12.12. Вывод только первого email и phone
каждого сотрудника
SELECT employee_id as id, fir st_ name as f_name, last_name as l_name,
email[1] as "email", phone[1]As"phone"
FROM Emp_A;
id|f_name
|l_name
I email
Iphone
I
—+--------- -I----------- +----------------------- +------------------ +
11 Alexander IHunold
2 I Bruce
[Ernst
3 I Valli
I Pataballa
4|Sigal
ITobias
|alex_hldl@mail.ru
Ibruc_erl@yandex.ru
I valli__pat33@yandex. ru
|sigal_t33@yandex.ru
1+7-123-456-77-88 1
1+7-333-450-71-22 1
1+7-765-455-75-42 |
1+7-789-555-75-441
Листинг 12.13. Вывод всех данных об одном сотруднике
SELECT employee_id as id, first name as f_name, last_name as l_name,
UNNEST(email) as "email", unnest(phone)As"phone"
FROM Emp_A
WHERE employee id=3;
Глава 12. Массивы
id|f_nameIl_name
|email
Iphone
I
—+------ +--------- +----------------------- +----------------- +
3|Valli IPataballa|valli_pat33@yandex.ru |+7-765-455-75-42|
3 I Valli IPataballa|val1i_pat321@gmail.com 1+7-777-654-32-211
31 Valli IPataballaI
1+7-765-455-75-421
Листинг 12.14. Поиск сотрудника по его телефону
SELECT employee_id as id, first_name as f_name, last_name as Iname,
UNNEST(phone)As"phone"
FROM Emp_A
WHERE '+7-333-450-71-22' = ANY(phone)
id|f_name|l_name|phone
|
—+-----+----- +-------------- +
2|Bruce lErnst 1+7-333-450-71-221
2|Bruce lErnst 1+7-777-543-22-111
Элементы массива, являющегося столбцом таблицы, могут иметь составной
тип данных. Создадим таблицу DepartmentEmp, которая содержит столбцы:
departmentid, department name, empdep. Столбец empdep является
массивом и должен содержать данные о сотрудниках, работающих в каждом
отделе. Элементы этого массива имеют тип TEmpDep.
Создадим тип TEmpDep:
CREATE TYPE T_Emp_Dep AS
(id_emp integer,
f_name varchar(20),
l_name varchar(25),
j ob_id varchar(10),
salary numeric(10,2));
Создадим таблицу Department Emp:
CREATE TABLE Department_Emp
(department_id integer,
department_name varchar(30),
emp_dep t_emp_dep array);
353
PostgreSQL: SQL + PL/pgSQL
..................................................
w
PostgreSQL
Листинг 12.15. Заполнить таблицу Department_Emp
данными об отделах 20, 30, 40, 60, 90, 100 и вывести
данные о сотрудниках, которые работают в отделе 30
DO $$
DECLARE
Cur_Emp CURSOR(p_id_dep INTEGER) FOR
SELECT employee_id, first name, last_name, job_id, salary
FROM Employees
WHERE department_id = p_id_dep;
Cur_Dep CURSOR FOR
SELECT department_id, department_name
FROM Departments
WHERE department_id IN (20,30,40,60,90,100);
v_table t_emp_dep array;
v_tablel t_emp_dep array;
v_emp t_emp_dep array;
v_e t_emp_dep;
v_dep integer:=30;
i integer;
BEGIN
FOR dep_rec IN Cur_Dep
LOOP
INSERT INTO Department_Emp(department_id,department_name)
VALUES (dep_rec.department_id,dep_rec.department_name);
i:=0;
FOR emp_rec IN cur_emp(dep_rec.department_id)
LOOP
i:=i+l;
v_table[i] :=emp_rec;
END LOOP;
UPDATE Department_Emp
SET emp_dep = v_table
WHERE department_id = dep_rec.department_id;
v_table:=v_tablel;
END LOOP;
RAISE notice 'Результат: ';
RAISE notice 'department_id = %',v_dep;
SELECT emp_dep INTO v_emp
FROM Department_Emp
WHERE department_id = v_dep;
FOREACH v_e IN ARRAY v_emp
LOOP
RAISE notice ' % % % % %',
v_e.id_emp, v_e.f_name, v_e.l_name, v_e.j ob_id, v_e.
salary;
END LOOP;
END $$;
' 354 J
Глава 12. Массивы
Результат:
department_id = 30
118 Guy Himuro PU_CLERK 2860.00
119 Karen Colmenares
PU_CLERK 2750.00
114
Den Raphaely PU_MAN 12100.00
117
Sigal Tobias
PU_CLERK 3080.00
115 Alexander Khoo PU_CLERK 6264.50
116
Shell! Baida PU_CLERK 5945.50
Листинг 12.16. Добавить в таблицу Department_Emp
данные о новом отделе и его сотрудниках
INSERT INTO Department_Emp(department_id, department_name, emp_dep)
VALUES(120,'Control And Credit', ARRAY[(210,'John','Partner','PU_
MAN',11000)::t_emp_dep,
(211,'Alberto','Tobias','PU_CLERK',4000)::t_emp_dep])
В этом примере следует обратить внимание на необходимость приведения
типа (::t_emp_dep).
Листинг 12.17. В таблице Department_Emp найти и
вывести данные о сотрудниках, имеющих имя (first_name)
Alexander
DO $$
DECLARE
Cur_Dep cursor for
SELECT department_id, department_name
FROM Department_Emp;
v_y t_emp_dep;
v_emp t_emp_dep array;
v_name varchar(20):='Alexander';
BEGIN
RAISE notice 'Результат:
';
FOR dep_rec IN Cur_Dep LOOP
SELECT emp_dep INTO v_emp
FROM Department_Emp
WHERE department_id = dep_rec.department_id;
FOREACH v_y IN ARRAY v_emp LOOP
IF v_y.f_name = v_name THEN
RAISE notice 'department_id= %%%%%',
dep_rec . department_id, ' first_name= ' , v_y. f_name,
'last_name= ', v_y.l_name;
Г 355 '
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
END IF;
END LOOP;
END LOOP;
END $$;
Результат:
department_id= 30 first_name=
department_id= 60 first_name=
Alexander last_name=
Alexander last_name—
Khoo
Hunold
Рассмотрим еще один пример использования массивов в столбцах таблицы.
Создадим таблицу CustomersProd, которая содержит скалярные столбцы
customer_id, c_name и столбец prodid, имеющий тип массива, элементы
которого имеют тип integer. Столбец prod id предназначен для хранения но
меров товаров (productid), которые приобретал клиент.
CREATE TABLE Customers_Prod
(customer_id integer,
c_name varchar(255),
prod_id integer array);
Листинг 12.18. Заполнение данными таблицы CUST_Prod
DO $$
DECLARE
Cur_Cust CURSOR
FOR SELECT customer_id,c_NAME FROM Customers;
Cur_Prod_Id CURSOR (p_cust_id Customers.customer_id%TYPE)
FOR SELECT DISTINCT product_id
FROM Orders JOIN Order_Iterns USING (order_id)
WHERE customer_id=p_cust_id
ORDER BY product_id;
v_tab_id
integer array;
v_tab_idl integer array;
i integer;
BEGIN
FOR cust_rec IN Cur_Cust
LOOP
INSERT INTO Customers_Prod (customer_id, c_name)
VALUES (cust_rec.customer_id, cust_rec.c_name);
i:=0;
FOR p_id IN Cur_Prod_Id(cust_rec.customer_id)
LOOP "
i:=i+l;
v_tab_id[i]
^ 356 ]
:= p_id.product_id;
Глава 12. Массивы
END LOOP;
UPDATE Customers_Prod
SET prod_id = v_tab_id
WHERE customer_id = cust_rec.customer_id;
v_tab_id: =v_tab_idl ;
END LOOP;
END $$;
Данные, записанные в таблицу CustomersProd, сохраняются, и их можно
использовать в операторах SQL и блоках PL/pgSQL.
Листинг 12.19. Определение номеров товаров,
которые приобретали клиенты с номерами 45 и 48, с
использованием данных из таблицы Customers_Prod
SELECT UNNEST(prod_id) as "product_id"
FROM Customers_Prod
where customer_id =45
INTERSECT
SELECT UNNEST(prod_id) as "product_id"
FROM customers_prod
WHERE customer_id =48;
product idI
------------ P
19|
55|
66|
12.6. Использование массивов и курсоров для
повышения эффективности обработки данных
Как уже отмечалось, выполнение операторов PL/pgSQL осуществляет ком
понента PL/pgSQL Engine, а выполнение операторов SQL осуществляет SQL
Executor.
PL/pgSQL Engine, как правило, находится на стороне клиента, a SQL
Executor всегда находится на сервере. Если в блоке PL/pgSQL содержится
оператор SQL, то выполнение операторов PL/pgSQL приостанавливается и
управление передается SQL Executor. Результаты выполнения запросов SQL
передаются в приложение по сети, после этого выполнение операторов
PostgreSQL. SQL + PL/pgSQL
PL/pgSQL-блока возобновляется. Такое взаимодействие PL/pgSQL Engine с
SQL Executor называют переключением контекста.
Частое переключение контекста приводит к росту сетевого трафика, а также
к существенному увеличению времени обработки данных. Поэтому при раз
работке программ PL/pgSQL следует использовать средства, которые позво
ляют сократить количество переключений контекста.
Рассмотрим возможности повышения эффективности обработки данных за
счет использования массивов и курсоров.
Схема HR РОС содержит мало данных, и получить для нее временные оцен
ки весьма проблематично. Поэтому была создана схема EMPSAL, которая
содержит две таблицы: Ешр и Sales. Таблица Етр содержит данные о
сотрудниках: employee_id - код сотрудника, salary - зарплата сотрудника.
Таблица Sales содержит данные о продажах: employee_id - код сотрудника,
sal - сумма продажи. Каждый сотрудник осуществил множество продаж.
Операторы создания таблиц:
CREATE TABLE Ешр
(employee_id integer,
salary numeric(10,2));
CREATE TABLE Sales
(employee_id integer,
sal
numeric(11,2));
В листингах 12.20 и 12.21 содержатся программы заполнения данными этих
таблиц с использованием датчика случайных чисел. В результате выполне
ния этих программ таблица Ешр будет содержать данные о 200 сотрудниках,
а таблица Sales — данные о 100 000 продаж.
Листинг 12.20. Заполнение данными таблицы Етр
DO $$
DECLARE
numeric (10,2);
BEGIN
FOR ѵ i IN 1..200 LOOP
s
2000 + ROUND((8000*RANDOM())::numeric,-2);
INSERT INTO Emp
VALUES(v_i,v_s);
358
Глава 12. Массивы
END LOOP;
END $$;
Листинг 12.21. Заполнение данными таблицы Sales
DO $$
DECLARE
v_s numeric (10,2);
v_id integer;
BEGIN
FOR v_i IN 1.. 100000 LOOP
v_id := TRUNC((200*random())::numeric)+1;
v_s := 500 + ROUND((5000*RANDOM())::numeric,-2);
INSERT INTO Sales
VALUES(v_id,v_s);
END LOOP;
END $$;
Необходимо изменить зарплату сотрудников в зависимости от суммы про
даж, используя следующий алгоритм:
• если сумма продаж сотрудника >1500000, то ему следует повысить
зарплату на 10%;
• если сумма продаж сотрудника >1000000, то ему следует повысить
зарплату на 5%;
• если сумма продаж сотрудника >500000, то ему следует повысить зарпла
ту на 1%;
• сотрудникам, суммы продаж которых <= 500000, зарплату не изменять.
Для того чтобы иметь возможность восстанавливать исходные данные, была
создана таблица Етр_1, которая является копией таблицы Етр.
Восстановление исходных данных осуществляется операторами:^
DELETE FROM Emp_l;
INSERT INTO Emp_l
SELECT * FROM Emp;
В листинге 12.22 содержится решение этой задачи без использования мас
сивов и курсоров.
359
PostgreSQL: SQL + PL/pgSQL
Листинг 12.22. Увеличение зарплаты сотрудников
DO $$
DECLARE
v_add_salary numeric(10,2): =0;
v_salary numeric(10,2):=0;
v_sum numeric(10,2):=0;
v_sum_sal numeric(10,2):=0;
BEGIN
SELECT SUM(salary) into v_salary
FROM Emp_l;
RAISE NOTICE 'Суммарная ЗП до повышения % ',v_salary;
RAISE notice 'Старт % ',clock_timestamp();
FOR i IN 1..200
LOOP
SELECT SUM(sal) into v_sum
FROM Sales
WHERE employee_id = i;
v_add_salary : =
CASE
WHEN v_sum > 1500000 THEN
WHEN v_sum > 1000000 THEN
WHEN v_sum > 500000 THEN
ELSE 0.0
END;
0.1
0.05
0.01
UPDATE Emp_l
SET salary = salary*(1 + v_add_salary)
WHERE employee_id = i;
END LOOP;
RAISE NOTICE 'Стоп % ',clock_timestamp();
SELECT SUM(salary) into
v_salary
FROM Emp_l;
RAISE NOTICE 'Суммарная ЗП после повышения % ',v_salary;
END $$;
Суммарная ЗП до повышения 1155300.00
Старт 2023-08-30 09:13:33.56406+03
Стоп 2023-08-30 09:13:34.957695+03
Суммарная ЗП после повышения 1243500.00
360
Глава 12. Массивы
Для измерения времени использовалась функция clock timestamp(), которая
возвращает фактическое текущее время. Сравнивая время до и после обра
ботки, можно определить, что время выполнения программы ~ 1.41 сек.
Анализируя этот код, можно установить, что на каждой итерации цикла
дважды происходит переключение контекста: первый раз для определения
суммы продаж сотрудника, второй раз для изменения зарплаты.
Рассмотрим другое решение этой задачи, в котором будут использованы мас
сив и курсоры.
Элементы массива v emp sal имеют составной тип данных T_Emp_Sal AS.
Этот массив содержит данные о сотруднике и общую сумму его продаж.
Заполнение данными этого массива осуществлялось с использованием кур
сора Cur Emp Sal.
CREATE TYPE T_Emp_Sal AS
(id_emp integer,
salary numeric(10,2),
sal
numeric(11,2));
Листинг 12.23. Увеличение зарплаты сотрудников с
использованием массива и курсоров
DO $$
DECLARE
Cur_Emp_Sal cursor FOR
SELECT employee_id, salary, SUM(sal) as sum_sal
FROM Emp_l join Sales USING(employee_id)
group by employee_id, salary;
Cur_Emp cursor FOR
SELECT * FROM Emp_l;
v_emp_sal T_Emp_Sal array;
v_add_salary numeric(3,2): =0;
v_sum_sal numeric(10,2):=0;
v_salary numeric(10,2):=0;
v_i integer:=0;
begin
SELECT SUM(salary) into v_salary
FROM Emp_l;
RAISE notice 'Суммарная ЗП до повышения % ',v_salary;
RAISE notice 'Старт % ',clock_timestamp();
FOR sal_rec IN Cur_Emp_Sal
LOOP
[ 361
PostgreSQL: SQL + PL/pgSQL
w
PostgreSQL
v_i := v_i+l;
v _emp_sal[ v_i] := sal_rec;
END LOOP;
FOR і IN 1..200 LOOP
v_add_salary :=
CASE
WHEN v_emp_sal[i].sal > 1500000 THEN 0.1
WHEN v_emp_sal[i].sal > 1000000 THEN 0.05
WHEN v_emp_sal[i].sal > 500000 THEN
0.01
ELSE 0.0
END;
v_emp_sal[i].salary :=v_emp_sal[i].salary*(l+v_add_salary);
END LOOP;
FOR emp_rec IN Cur_Emp
LOOP
UPDATE Emp_l
SET salary = v_emp_sal[emp_rec.employee_id].salary
WHERE CURRENT OF cur_emp;
END LOOP;
RAISE NOTICE ' Стоп % ',clock_timestamp();
SELECT SUM(salary) into
v_salary
FROM Emp_l;
RAISE NOTICE ' Суммарная ЗП после повышения % ',v_salary;
END $$;
Суммарная ЗП до повышения 1155300.00
Старт 2023-08-30 09:22:09.243086+03
Стоп 2023-08-30 09:22:09.347123+03
Суммарная ЗП после повышения 1243500.00
Анализируя этот код, можно установить, что переключение контекста осу
ществлялось всего два раза при открытии курсоров.
Время выполнения программы ~ 0.1 сек. При этом следует иметь в виду,
что выполнение программы осуществлялось в монопольном режиме, также
сервер и приложение находились на одном компьютере. При многопользо
вательском сетевом взаимодействии относительный выигрыш будет суще
ственно больше.
362
Глава 12. Массивы
Задачи для самостоятельного решения:
Задача 12.1. Создать массив, элементы которого имеют
составной тип, имеющий следующие поля: departmentJd,
department_name, эитзаіагу, где зит_заІагу — суммарная
зарплата сотрудников в отделе. Заполнить этот массив данны
ми, расположив данные об отделах в порядке убывания зара
ботной платы. Используя этот массив, найти и вывести на пе
чать данные об отделах, суммарная зарплата которых является
первой, третьей и предпоследней по величине.
Задача 12.2. Создать массив, элементы которого имеют тип
записи, содержащей следующие столбцы: employeejd,
departmentjd, first_name, last_name, salary, и содержат дан
ные о 10 самых высокооплачиваемых сотрудниках, упорядочен
ные по убыванию зарплаты. Вывести содержимое этого мас
сива. Вычислить и вывести суммарную зарплату сотрудников,
которые в этом списке занимают места с 3 по 7.
Задача 12.3. Используя массив из задачи 2, найти отдел, сотруд
ник которого получает максимальную зарплату.
Задача 12.4. Создать таблицу Dep Job, которая должна содер
жать следующие столбцы: departmentjd, department name,
job. Столбец job является массивом, элементы которого имеют
составной тип: jobjd, job_name, min_salary, max_salary. Запол
нить эту таблицу данными. Столбец job должен содержать непо
вторяющиеся должности сотрудников, которые работают в каж
дом отделе. Значения столбцов job_name, salary_min, salary_max
определить, используя таблицу Jobs. Вывести данные о долж
ностях, которые имеют сотрудники, работающие в отделе 30.
Задача 12.5. Используя данные таблицы Оер ЗоЬ из задачи
12.4, вывести department_id, department_name отдела,
сотрудники которого занимают должность 1Т РИОС, а также
за1агу_т!п, за!агу_тах по этой должности.
[ 363 '
PostgreSQL: SQL + PL/pgSQL
• PostgreSQL
Задача 12.6. Создать таблицу, которая содержит данные о ре
зультатах сдачи экзаменов. Столбцы таблицы: Группа, ФИО
студента, Экзамены. Столбец Экзамены представляет собой
массив, элементы которого имеют составной тип: Предмет, От
метка. Заполнить таблицу данными. Рекомендуется ввести 2
группы, в каждой группе 5 студентов, каждый студент сдавал от
1 до 3 экзаменов.
Определить и вывести следующие данные:
• средний бал каждого студента;
• список студентов, которые сдали экзамены на 4 и 5;
• список студентов, которые получили 2.
Каждое из этих заданий реализовать в виде отдельной программы.
Задача 12.7. Создать таблицу Dishes (Блюда), которая долж
на содержать следующие столбцы: Dish_id, name, price,
compound. Столбец compound представляет собой массив,
элементы которого имеют составной тип: productjd, product_
name, price_prod, weight. Заполнить таблицу Dishes и вывести
данные о любом блюде, указав общий вес всех продуктов в нем.
Задача 12.8. Создать таблицу Products (Продукты на складе):
productjd, product_name, quantity. Quantity - общий вес про
дукта, имеющийся на складе. Используя данные таблицы Dishes
из задачи 12.7, решить следующую задачу: определить блюда,
которые могут быть приготовлены на N персон из имеющихся на
складе продуктов.
Задача 12.9. Используя таблицы Dishes и Products из задач 12.7
и 12.8, определить блюдо, которое дает максимальную прибыль.
Прибыль определяется как разность между ценой блюда и стои
мостью входящих в него продуктов.
364
Глава 13.
ОБРАБОТКА ОШИБОК
PostgreSQL: SQL + PL/pgSQL
В процессе выполнения программы могут возникать ошибки. Если не пред
принимать никаких действий при возникновении ошибок, то выполнение
программы прекращается, и система выдает сообщение о возникшей ошиб
ке. Если блок содержит раздел обработки ошибок (EXCEPTION), то в случае
возникновения ошибки управление передается в раздел EXCEPTION. В этом
случае выполнение программы автоматически не прекращается. Операторы,
которые содержатся в разделе EXCEPTION, могут исправить ошибку или
сообщить пользователю о причинах возникновения ошибки и способах ее
исправления.
Ситуации, в которых возникают ошибки, называют
исключениями, а раздел EXCEPTION называют разделом
обработки исключений.
Далее мы будем считать слова "ошибка" и "исключение" синонимами.
13.1. Раздел обработки ошибок
Синтаксис раздела обработки ошибок:
EXCEPTION
WHEN {имя исключения 1|код ошибки 1}
THEN
{операторы обработки исключения 1}
WHEN {имя исключения N|код ошибки N}
THEN
{операторы обработки исключения N}
[WHEN OTHERS THEN
{операторы обработки OTHERS}]
Глава 13. Обработка ошибок
Раздел обработки ошибок располагается после исполняемого раздела перед
оператором конца блока END. Он может содержать обработку нескольких
исключений. При этом для каждого исключения указываются определенные
действия, которые должны выполняться при возникновении этого исключения.
Каждая ошибка имеет имя и код ошибки. При обработке ошибки нужно
указать ее имя или код. Например, ошибка нарушения уникальности ключа
имеет имя unique_violation и код 23505. Для указания кода ошибки
используется конструкция: sqlstate ’{код ошибки}’.
В простейшем случае операторы обработки исключений могут представлять
собой операторы вывода сообщений, которые в понятной для пользователя
форме сообщают причину возникшей ошибки и содержат рекомендации о
способах ее устранения.
Для инициирования и вывода сообщений об ошибках используется команда
RAISE, которая имеет две основные формы
RAISE [{уровень}] '{формат}' [,{параметры} ]
[USING {параметр} = {значение};
RAISE [{уровень}] {имя исключения}
[USING {список{параметр} = {значение}}];
Первая форма используется для вывода сообщений, и команда будет обяза
тельно выполнена. Параметр {уровень} для этой формы, как правило, имеет
значение NOTICE. Эту форму команды RAISE мы уже использовали.
Вторая форма команды будет выполнена только для заданного исключе
ния. Параметр {уровень} для этой формы, как правило, имеет значение
EXCEPTION. Результатом выполнения команды RAISE с этим значением
уровня является прерывание работы программы.
Рассмотрим более подробно назначение и значения параметров RAISE.
•
{уровень} — задает уровень важности ошибок. Возможные значения:
DEBUG, LOG, INFO, NOTICE, WARNING и EXCEPTION. По умолча
нию используется EXCEPTION.
•
{формат} — определяет текст сообщения. Представляет собой текстовую
константу, содержит текст и символы %. Количество символов % должно
соответствовать количеству параметров. Параметр может быть текстовой
константой или переменной, значение которой будет выведено в сообщении.
[ 367
PostgreSQL: SQL + PL/pgSQL
•
-fPostgreSQL
USING — позволяет добавить дополнительную информацию к сообще
нию об ошибке. Например, USING ERRCODE = 50001 устанавливает код
ошибки.
С более детальным описанием команды RAISE и ее параметров можно озна
комиться в официальной документации.
Невозможно, а в ряде случаев нецелесообразно, учитывать все возможные
ошибки, которые могут возникнуть в процессе выполнения программы,
поэтому раздел обработки исключений может содержать операторы обра
ботки исключений, которые не были обработаны другими обработчиками.
Эти операторы располагаются в конце раздела обработки исключений,
после служебных слов WHEN OTHERS THEN.
В обработчике исключений можно использовать специальные переменные:
•
SQLSTATE — содержит код ошибки;
•
SQLERRM — содержит Сообщение об Ошибке.
Можно получить дополнительную информацию об ошибке, используя
команду:
GET STACKED DIAGNOSTICS {переменная}
:= {элемент};
{переменная} — объявленная переменная, имеющая тип text.
Возможные значения компоненты {элемент} и их описание приведены в
таблице 13.1.
Таблица 13.1. Элементы диагностики ошибок
Имя
Описание
RETURNED_SQLSTATE
Код исключения
COLUMN_NAME
Имя столбца, относящегося к исключению
CONSTRAINT_NAME
Имя ограничения целостности, относящегося к
исключению
PG_DATATYPE_NAME
Имя типа данных, относящегося к исключению
^ 368 ]
Глава 13. Обработка ошибок
Имя
Описание
MESSAGE_TEXT
Текст основного сообщения исключения
TABLE_NAME
Имя таблицы, относящейся к исключению
SCHEMA_NAME
Имя схемы, относящейся к исключению
PG_EXCEPTION_DETAIL
Текст детального сообщения исключения (если есть)
PG_EXCEPTION_HINT
Текст подсказки к исключению (если есть)
Перейдем теперь к рассмотрению конкретных примеров обработки ошибок.
В листинге 13.1 показан блок, который содержит оператор INSERT. При вы
полнении этого оператора возникает ошибка ограничения первичного клю
ча. Эта ошибка возникла из-за того, что в таблице Orders уже есть заказ с
номером (orderid) = 82.
Листинг 13.1. Добавление нового заказа в таблицу Orders
с возникновением ошибки ограничения первичного ключа
DO $$
BEGIN
INSERT INTO Orders (order_id, customer_id, salesman_id,
order_date, status)
VALUES (82, 48, 175, DEFAULT, DEFAULT);
END $$;
SQL Error [23505] : ОШИБКА: повторяющееся значение ключа нарушает
ограничение уникальности "orders_pkey"
Подробности: Ключ "(order_id)=(82)" уже существует.
Где: SQL-оператор: "INSERT INTO Orders (order_id, customer_id,
salesman_id, order_date, status)
VALUES (82, 48, 175, DEFAULT, DEFAULT)"
функция PL/pgSQL in1ine_code_block, строка 3, оператор SQLоператор
Эту ошибку можно перехватить и исправить ее, заменив номер заказа.
Листинг 13.2. Добавление нового заказа в таблицу Orders с
перехватом и исправлением ошибки ограничения первичного
ключа
[ 369
PoStgreSQL: SQL + PL/pgSQL
f^PostgreSQL
DO $$
DECIARE
v_id integer;
BEGIN
RAISE NOTICE 'Результат: ';
INSERT INTO Orders (order_id, customer_id, salesman id,
order_date, status)
VALUES (82, 48, 175, DEFAULT, DEFAULT);
RAISE NOTICE 'Операция успешно выполнена';
EXCEPTION
WHEN UNIQUE_VIOLATION THEN
SELECT MAX(order_id) INTO v_id
FROM Orders;
v_id:=v_id +1;
INSERT INTO Orders (order_id, customer_id, salesman_id,
order_date, status)
VALUES (v_id, 48, 175, DEFAULT, DEFAULT);
RAISE NOTICE 'Операция выполнена, номер заказа изменен
на %', v_id;
END $$;
Результат:
Операция выполнена, номер заказа изменен на 130
В листинге 13.3 приведен пример объявления и обработки исключения для
ошибки 23503. Эта ошибка связана с нарушением целостности данных и
возникает в том случае, если из главной (родительской) таблицы делается
попытка удалить строку, с которой связаны строки подчиненной (дочерней)
таблицы. Эта ошибка не возникнет, если при определении внешнего ключа
указано правило поддержания целостности ON DELETE CASCADE —
каскадное удаление строк подчиненной таблицы. В этом случае при удале
нии строки главной таблицы автоматически удаляются все строки подчи
ненной таблицы, связанные с удаляемой строкой. Следует иметь в виду, что
правило поддержания целостности ON DELETE CASCADE является потен
циально опасным и может привести к потере данных.
Листинг 13.3. Обработка ошибки при удалении отдела, в
котором есть сотрудники
DO $$
DECLARE
v_id integer:=10;
BEGIN
^ 370 J
Глава 13. Обработка ошибок
RAISE NOTICE 'Результат: ';
DELETE FROM Departments
WHERE department_id = v_id;
RAISE NOTICE 'Операция успешно выполнена';
EXCEPTION
WHEN SQLSTATE '23503'THEN
RAISE NOTICE ' Отдел % %',v_id,
' нельзя удалить, так как в этом отделе есть сотрудники.';
END $$;
Результат:
Отдел 10
нельзя удалить,
так как в этом отделе есть сотрудники
Если в операторе CASE задано недопустимое значение параметра, то воз
никает ошибка: 20000. В листинге 13.4 приведен пример обработки этой
ошибки.
Листинг 13.4.Осуществить обработку исключения, которое
возникнет, если при выполнении оператора CASE задано
недопустимое значение параметра
DO $$
DECLARE
v_rating INTEGER := 1;
v_bonus INTEGER;
BEGIN
RAISE NOTICE 'Результат';
RAISE NOTICE 'Значение v_rating =% ', v_rating;
CASE v_rating
WHEN 5 THEN v_bonus := 5000;
WHEN 4 THEN v_bonus := 3000;
WHEN 3 THEN v_bonus := 1000;
END CASE;
RAISE NOTICE 'Значение v_bonus = %',v_bonus;
EXCEPTION
WHEN SQLSTATE '20000' THEN
RAISE NOTICE 'Операция отклонена, указано недопустимое
значение v_rating';
END $$
Результат:
Значение v_rating =1
Операция отклонена, указано недопустимое значение v_rating
PostgreSQL: SQL + PL/pgSQL
w
PostgreSQL
В листинге 13.5 приведен пример добавления новой строки в таблицу
Orders с возникновением ошибки 23503. Эта ошибка также связана с нарушением целостности данных, но возникает в том случае, если в дочернюю
таблицу делается попытка вставить строку, которая не будет связана со стро
кой главной таблицы.
Листинг 13.5. Добавление новой строки в таблицу Orders
с возникновением ошибки 23503
DO $$
BEGIN
INSERT INTO Orders(order_id, customer_id, salesman id)
VALUES (133, 18, 215);
END $$;
SQL Error [23503] : ОШИБКА: INSERT или UPDATE в таблице "orders"
нарушает ограничение внешнего ключа "fk_orders_employees"
Подробности: Ключ (salesman_id)=(215) отсутствует в таблице
"employees".
Где: SQL-оператор: "INSERT INTO Orders (order_id, customer_id,
salesman_id)
VALUES (133, 18, 215)"
функция PL/pgSQL inline_code_block, строка 3, оператор SQL-оператор
Причиной ошибки является недопустимое значение столбца salesman_id,
который является внешним ключом. Предположим, что правила предметной
области допускают возможность не указывать код продавца (salesmanid),
т. е. столбец salesman_id может иметь значение NULL. В этом случае ошиб
ку можно перехватить и исправить, заменив недопустимое значение столбца
salesman id на NULL.
Листинг 13.6. Добавление новой строки в таблицу Orders,
с перехватом и исправлением ошибки 23503
DO $$
DECLARE
text_var1 text;
BEGIN
RAISE NOTICE 'Результат: ';
INSERT INTO Orders (order_id, customer_id, salesman_id)
VALUES (133, 18, 215);
EXCEPTION WHEN SQLSTATE '23503'THEN
GET STACKED DIAGNOSTICS textvarl = MESSAGE_TEXT;
RAISE NOTICE ' Возникла ошибка %', text_varl;
Глава 13. Обработка ошибок
RAISE NOTICE ' Ошибка исправлена, строка добавлена, значение
столбца salesman_id заменено на NULL';
INSERT INTO Orders (order_id, customer_id, salesman_id)
VALUES (133, 18, NULL);
END $$;
Результат:
Возникла ошибка: INSERT или UPDATE в таблице "orders" нарушают
ограничение внешнего ключа "fk_orders_employees".
Ошибка исправлена, строка добавлена, значение столбца salesman_id
заменено на NULL
Как уже упоминалось, невозможно предусмотреть и обработать все возмож
ные ошибки. Однако раздел обработки исключений может содержать обра
ботчик OTHERS, который содержит операторы обработки исключительных
ситуаций, не обработанных другими обработчиками этого раздела.
Листинг 13.7. Добавление новой строки в таблицу Orders,
с разделом обработки исключений, который содержит
обработчик OTHERS
DO $$
DECLARE
text_var1 text;
BEGIN
RAISE NOTICE 'Результат: ';
INSERT INTO Orders (order_id, customer_id, salesman_id)
VALUES (134, 45, 'Pending');
EXCEPTION
WHEN SQLSTATE '23503'THEN
GET STACKED DIAGNOSTICS text_varl = MESSAGE_TEXT;
RAISE NOTICE 'Возникла ошибка %', text_varl;
RAISE NOTICE 'Ошибка исправлена, строка добавлена, значение
столбца salesman_id заменено на NULL';
INSERT INTO Orders (order_id, customer_id, salesman_id)
VALUES (134, 18, NULL);
WHEN OTHERS THEN
RAISE NOTICE 'Код ошибки: %', SQLSTATE;
RAISE NOTICE 'Причина ошибки: %', SQLERRM;
END $$;
Результат:
Код ошибки: 22P02
Причина ошибки: неверный синтаксис для типа integer:
"Pending"
PostgreSQL: SQL + PL/pgSQL
.................................................. • PostgreSQL
В этом примере для определения кода ошибки и причины ее возникнове
ния используются специальные необъявленные переменные SQLSTATE и
SQLERRM.
13.2. Обработка ошибок, определяемых программистом
При реализации операций обработки данных возможны ситуации, которые
не являются ошибочными с точки зрения сервера, но нарушают правила,
определенные для предметной области. Для того чтобы предотвратить вы
полнение таких операций, программист должен инициировать исключения
и обрабатывать их в разделе обработки исключений.
Этот тип исключений называется пользовательскими
исключениями, потому что они определяются
программистом. Для инициирования пользовательского
исключения используется команда RAISE, которая, как
правило, должна находиться внутри условного оператора IF.
IF {условие инициирования} THEN
RAISE EXCEPTION USING ERRCODE = {код ошибки},
MESSAGE = {текст сообщения};
END IF;
После инициирования пользовательского исключения управление передает
ся в раздел обработки исключений.
Рассмотрим пример использования пользовательского исключения для реа
лизации ограничений предметной области. Таблица Orders содержит общие
данные о заказах. Каждый заказ может находиться в 3 возможных состо
яниях:
1. Pending (в ожидании);
2. Canceled (отменен);
3. Shipped (отгружен).
Значение состояния заказа содержится в столбце status. Ограничение,
которое требуется реализовать, можно сформулировать следующим обра
зом: нельзя менять состояние заказа, если он имеет состояние Shipped
(отгружен).
Глава 13. Обработка ошибок
Листинг 13.8. Обработка пользовательского исключения,
обеспечивающего выполнение правила: нельзя менять состояние
заказа, если он имеет состояние Shipped (отгружен)
DO $$
DECLARE
v_status Orders.status%TYPE;
v_new_status Orders.status%TYPE;
v_order_id Orders.order_id%TYPE;
BEGIN
RAISE NOTICE 'Результат: ';
v_new_status :='Pending';
v_order_id: =35;
SELECT status INTO v_status
FROM Orders
WHERE order_id = v_order_id;
UPDATE Orders
SET status = v_new_status
WHERE order_id = v_order_id;
v_status = 'Shipped' THEN
RAISE EXCEPTION USING ERRCODE = 70001;
ELSE
RAISE NOTICE 'Операция выполнена ';
END if;
EXCEPTION
WHEN SQLSTATE '70001' then
ROLLBACK;
RAISE NOTICE 'Операция отклонена, так как нельзя
изменять состояние заказов, имеющих состояние Shipped';
END $$;
IF
Результат:
Операция отклонена, так как нельзя
изменять состояние заказов, имеющих состояние Shipped
Если в разделе EXCEPTION будет отсутствовать обработка пользователь
ского исключения, то работа программы будет прервана и в вызывающую
среду будут переданы код ошибки и сообщение об ошибке.
Листинг 13.9. Инициирование прерывания программы при
возникновении пользовательского исключения
DO $$
DECLARE
ГзтГ
PostgreSQL: SQL + PL/pgSQL
v_status Orders.status%TYPE;
v_new_status Orders.status%TYPE;
v_order_id Orders.order_id%TYPE
BEGIN
RAISE NOTICE 'Результат: ';
v_new_status : ='Pending';
v_order_id:=35;
SELECT status INTO v_status
FROM Orders
WHERE order_id = v_order_id;
IF v_status = 'Shipped' THEN
RAISE EXCEPTION USING ERRCODE = 70001,
MESSAGE = 'Операция отклонена, так как нельзя
изменять состояние заказов, имеющих состояние Shipped';
ELSE
RAISE NOTICE 'Операция выполнена';
END IF;
UPDATE Orders
SET status = v_new_status
WHERE order_id = v_order_id;
END $$;
Результат:
SQL Error [70001] : ОШИБКА: Операция отклонена, так как нельзя
изменять состояние заказов, имеющих состояние Shipped.
Где: функция PL/pgSQL iniinecodeblock, строка 16, оператор
RAISE
Для одной операции может быть задано несколько ограничений, которые
должны быть обеспечены средствами обработки исключений.
Рассмотрим следующую задачу: при добавлении новых строк в таблицу
Order items необходимо обеспечить выполнение следующих ограничений:
•
Нельзя добавлять новые строки в заказ, если он имеет состояние Shipped
(отгружен).
•
Нельзя добавлять новую строку, содержащую данные о товаре, который
уже есть в заказе.
•
Нельзя добавлять новую строку, содержащую номер заказа, которого нет
в таблице Orders, или номер товара, которого нет в таблице Products.
' 376 ]
Глава 13. Обработка ошибок
Для реализации первых 2 ограничений необходимо использовать обработ
ку исключений, определяемых пользователем, а для реализации третьего
ограничения следует использовать обработку ошибки 23503 — нарушение
целостности данных.
Листинг 13.10. Обработка нескольких видов исключений
DO $$
DECLARE
v_order_id
Order_Iterns. order_id%Type : =7 5;
v_item_id
Order_Iterns . item_id%Type :=5;
v_product_id Order_Iterns.product_id%Type :=23;
v_status
Orders.status%Type;
v_count_prod_id integer;
BEGIN
RAISE NOTICE 'Результат: ';
RAISE NOTICE ' order_id= %%%%%', v_order_id,' item_id= ',
v_item_id, ' product_id= ' , v__product_id;
SELECT status INTO v_status
FROM Orders
WHERE order_id = v_order_id;
SELECT COUNT(*) INTO v_count_prod_id
FROM Order_Iterns
WHERE order_id = v_order_id AND product_id = v_product_id ;
v_status = 'Shipped' THEN
RAISE EXCEPTION USING ERRCODE = 70001;
END IF;
IF
v_count_prod_id > 0 THEN
RAISE EXCEPTION USING ERRCODE = 70002;
END if;
IF
INSERT INTO Order_Iterns
VALUES(v_order_id, v_item_id, v_product_id, 10,1800);
RAISE NOTICE 'Операция успешно выполнена';
EXCEPTION
WHEN sqlstate '70001' THEN
RAISE NOTICE 'Операция отклонена, так как нельзя
изменять состояние заказов, имеющих состояние Shipped';
WHEN sqlstate '70002' THEN
RAISE NOTICE
'Операция отклонена, так как нельзя
в заказ добавлять товары, которые уже имеются в заказе';
WHEN SQLSTATE '23503' THEN
RAISE NOTICE 'Операция отклонена, так как произошло нарушение
ограничений целостности данных';
[ 377
PostgreSQL: SQL + PL/pgSQL
WHEN OTHERS THEN
RAISE NOTICE 'Код ошибки: %', SQLSTATE;
RAISE NOTICE 'Причина ошибки: %', SQLERRM;
END $$;
Результат:
order_id = 75
item_id = 5 product_id = 23
Операция отклонена, так как нельзя
изменять состояние заказов, имеющих состояние Shipped
Эта ошибка произошла из-за того, что заказ 75 находится в состоянии
Shipped (Отгружен). Это пользовательское исключение.
Изменим номер заказа на 78:
Результат:
order_id = 78
item_id = 5
product_id = 23
Операция отклонена, так как нельзя
в заказ добавлять товары, которые уже имеются в заказе
Эта ошибка произошла из-за того, что товар 23 уже есть в заказе 78. Это
пользовательское исключение.
Изменим номер товара на 123:^
Результат:
order_id = 78
item_id = 5 product_id = 123
Код ошибки: 23505
Причина ошибки: повторяющееся значение ключа нарушает
ограничение уникальности "order_items_pkey"
Произошло нарушение уникальности ключа. В таблице Order_Items пер
вичный ключ состоит из 2 атрибутов: order_id и item_id. Причиной ошибки
является то, что в таблице Order_Items уже есть строка со значениями
order id = 78, itemid = 5. Ошибка возникла во время выполнения оператора
INSERT и была обработана в разделе OTHERS.
Установим item id = 6
Результат:
order_id = 78
item_id = 6
product_id = 123
Операция отклонена, так как произошло нарушение ограничений
целостности данных
378
Глава 13. Обработка ошибок
Эта ошибка произошла из-за того, что в таблице Products нет товара со зна
чением productid =123.
Установим product_id = 25 р
Результат:
order_id = 78
item_id = 6
Операция успешно выполнена
product_id = 25
13.3. Инициирование ошибок, определяемых
сервером
Команду RAISE можно использовать для инициирования исключений для
ошибок, определяемых сервером. Может возникнуть вопрос: зачем нужно
инициировать исключения для ошибок, определяемых сервером, ведь сер
вер инициирует такие исключения автоматически? Для ответа на этот
вопрос рассмотрим несколько примеров.
Листинг 13.11. Вывести код клиента, который сделал
заказ
DO $$
DECLARE
v_order_id integer = 78;
v_cust_id
integer;
BEGIN
SELECT customer_id INTO STRICT
FROM Orders
WHERE order_id = v_order_id;
v_cust_id
RAISE NOTICE 'Результат:’;
RAISE NOTICE 'order_id= % % %',v_order_id,
v_cust_id;
END $$;
Результат:
order_id = 78
' customer_id= ',
customer_id = 2
Если ввести номер несуществующего заказа, то возникнет исключение
NOD АТА FOUND,код ошибки Р0002.
[ 379 '
PostgreSQL: SQL + PL/pgSQL
Листинг 13.12. Вывести код клиента, который сделал
несуществующий заказ
DO $$
DECLARE
v_order_id integer = 178;
v_cust_id
integer;
BEGIN
SELECT customer_id INTO STRICT
FROM Orders
WHERE order_id = v_order_id;
v_cust_id
RAISE NOTICE 'Результат';
RAISE NOTICE 'order_id= % % %',v_order_id,
v_cust_id;
END $$;
' customer_id= ’,
SQL Error [P0002]: ОШИБКА: запрос не вернул строк.
Где: функция PL/pgSQL inline_code_block, строка 6, оператор SQLоператор
Листинг 13.13. Вывести общую сумму заказа с заданным
номером
DO $$
DECLARE
v_order_id integer = 178;
v_sum numeric(10,2);
BEGIN
SELECT SUM(quantity*unit_jprice) INTO strict v_sum
FROM Order_Iterns
WHERE order_id = v_order_id ;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'order id= % % %',v order id,
END $$;
'v sum= ', v sum;
Результат:
order_id = 178 v_sum = <NULL>
При выполнении этого запроса, если задан номер заказа, которого нет в
таблице Order_items, исключение NOD АТА FOUND инициировано не бу
дет. Приложение, которое использует этот код, будет считать, что операция
успешно выполнена.
і 380 ]
Глава 13. Обработка ошибок
Листинг 13.14. Инициирование исключения NO_DATA_FOUND
DO $$
DECLARE
v_order_id integer = 178;
v_sum
numeric(10,2);
BEGIN
RAISE NOTICE 'Результат:';
SELECT SUM (quantity*unit_jprice) INTO strict v_sum
FROM Orde r_I terns
WHERE order_id = v_order_id;
IF v_sum IS NULL THEN
RAISE EXCEPTION NO_DATA_FOUND;
END IF;
RAISE NOTICE 'order_id= % % %',v_order_id, 'v_sum= ', v_sum;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE NOTICE 'Задан недопустимый номер заказа';
END $$;
Результат:
Задан недопустимый номер заказа
Если будет указан номер заказа, которого нет в таблице Order_items, то
автоматически исключение NO DATA FOUND инициировано не будет. Ко
манда RAISE инициирует это исключение, и приложение получит сообще
ние, что при выполнении операции произошла ошибка.
13.4. Распространение ошибок
Исключительные ситуации могут возникать в разделе объявлений, в испол
няемом разделе и в разделе обработки исключений. В разделе обработки ис
ключений могут быть обработаны только исключения, возникшие в испол
няемом разделе текущего блока.
Если блок PL/pgSQL будет вложенным, то в разделе обработки исключений
внешнего блока могут быть обработаны исключения, возникшие в любом
разделе внутреннего блока, и исключения, возникшие в исполняемом раз
деле внешнего блока.
Структура программы, состоящей из внешнего и внутреннего блоков, в об
щем виде может быть представлена следующим образом:
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
«внешний блок»
DECLARE
«раздел объявлений внешнего блока»
BEGIN
« исполняемый раздел внешнего блока»
«внутренний блок»
DECLARE
«раздел объявлений внутреннего блока»
BEGIN
«исполняемый раздел внутреннего блока»
EXCEPTION
«раздел обработки исключений внутреннего блока»
END «внутреннего блока»
EXCEPTION
«раздел обработки исключений внешнего блока»
END «внешнего блока»
Если исключение возникает в исполняемом разделе внутреннего блока, а об
работчик этого исключения во внутреннем блоке отсутствует, то оно пере
дается в раздел обработки исключений внешнего блока. Если обработчик
этого исключения во внешнем блоке отсутствует, то выполнение программы
прекращается и управление передается в вызывающую среду.
Исключения, возникшие в исполняемом разделе внешнего блока, не обраба
тываются в разделе обработки исключений внутреннего блока.
Листинг 13.15. Анонимный блок,
блока
содержащий два вложенных
DO $$
«А»
DECLARE
v_order_id
integer :=78;
v_item_id
integer:=7;
v_product_id integer :=20;
v_quantity
integer :=10;
v_price
integer :=2000;
v_status
Orders.status%Type;
BEGIN
RAISE NOTICE 'Результат:';
RAISE NOTICE 'order_id= %%%%%', v_order_id,
'item_id= ', v_item_id,'product_id= ', v_product_id;
RAISE NOTICE 'quantity= % % % ', v_quantity,
'price= ', v_price;
Глава 13. Обработка ошибок
SELECT status INTO STRICT v_status
FROM Orders
WHERE order_id =v_order_id;
RAISE NOTICE 'Статус заказа % % %', v_order_id,
' равен ', v_status;
«В»
BEGIN
INSERT INTO Order_Iterns
VALUES(v_order_id, v_item_id, v_product_id,
v_quantity , v__price) ;
RAISE NOTICE 'В таблицу Order_iterns добавлена строка';
END В;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE NOTICE 'Задан недопустимый номер заказа %',
v_order_id;
WHEN OTHERS THEN
RAISE NOTICE 'Код ошибки: %', SQLSTATE;
RAISE NOTICE 'Причина ошибки: %', SQLERRM;
END A $$;
Результат:
order_id = 78
item_id = 7 product_id = 20
quantity = 10 price = 2000
Статус заказа 78 равен Pending.
В таблицу Order_items добавлена строка.
При выполнении программы с этими параметрами ошибок не возникло, и в
таблицу была добавлена новая строка.
Если указать несуществующий номер заказа, то в блоке А возникнет исклю
чение NODATAFOUND, которое будет обработано в разделе обработки
исключений блока А.
Результат:
order_id = 178
item_id = 7 product_id = 20
quantity = 10 price = 2000
Задан недопустимый номер заказа 178
Если задать существующий номер заказа, например 78, но при добавлении
новой строки в блоке В задать номер товара, которого нет в таблице Products,
например 85, то операция определения статуса заказа в блоке А будет успеш
но выполнена, а при добавлении новой строки в блоке В возникнет ошибка.
PostgreSQL: SQL + PL/pgSQL
Так как в блоке В нет раздела EXCEPTION, то эта ошибка будет обработана
в разделе обработки исключений блока А.
Результат:
order_id =78
item_id = 8 product_id = 85
quantity = 5 price = 1000
Статус заказа 78 равен Pending
Код ошибки: 23503
Причина ошибки: INSERT или UPDATE в таблице "order_items" нарушают
ограничение внешнего ключа ’’order items_product_id_fkey"
Если программа содержит несколько ОМЬ-операторов, и мы хотим узнать,
при выполнении какого оператора возникла ошибка, то следует заключить
каждый оператор в отдельный блок, содержащий раздел обработки исклю
чений.
Глава 13. Обработка ошибок
Задачи для самостоятельного решения:
Задача 13.1. В таблице Products для столбца rating_p уста
новлено ограничение
0<=rating_p<=5. Осуществить попытку
вставить новую строку с данными, которые нарушают это огра
ничение. Перехватить ошибку, которая при этом возникнет, и ис
править ее, присвоив столбцу rating_p значение 1.
Задача 13.2. Осуществить обработку исключения, которое будет
возникать, если при вводе данных о новом сотруднике будет ука
зан email, который уже есть в таблице Employees.
Задача 13.3. При вставке новой строки в таблицу Orders мо
жет возникнуть ошибка 23503 — нарушение целостности дан
ных. Причиной этой ошибки может быть или указание значения
столбца customer_id, которого нет в таблице Customers, или
указание значения столбца salesman_id, которого нет в таблице
Employees. Выполнить обработку этой ошибки по следующему
правилу: если указано недопустимое значение столбца customer_id, то вставку строки отменить; если указано недопустимое
значение столбца salesman_id, вставить новую строку, присво
ив столбцу salesman_id значение NULL.
Задача 13.4. В анонимном блоке, который содержит операторы:
UPDATE Employees
SET salary = v_new_salary
WHERE employee_id = v_empl;
DELETE FROM Employees
WHERE employee_id = v_emp2;
осуществить обработку исключения, которое должно возникнуть
в том случае, если указан несуществующий номер сотрудника, с
выводом информации о том, при выполнении какого оператора
возникла ошибка.
PostgreSQL: SQL + PL/pgSQL
w PostgreSQL
Задача 13.5. Между таблицами Orders и Employees существует
ограничение внешнего ключа, заданное для столбца salesman_
id в таблице Orders, которое имеет следующий вид:
CONSTRAINT FK_ORDERS_EMPLOYEES FOREIGN KEY (salesman_id)
REFERENCES Employees(employee_id) ON DELETE SET NULL;
Осуществить инициирование и обработку пользовательского ис
ключения, которое должно возникать при попытке удалить
сотрудника, имеющего оформленные заказы.
Задача 13.6. Обеспечить выполнение следующего ограничения:
нельзя удалять последнего сотрудника в отделе.
Задача 13.7. Для таблицы Employees обеспечить выполнение
следующего ограничения: при выполнении оператора DELETE
можно удалить одну строку, но нельзя удалить несколько строк.
386
Глава 14.
ХРАНИМЫЕ
И
ПРОЦЕДУРЫ
ФУНКЦИИ
Ро§і§ге8ОЬ: 80Ь + PL/pgSQL
^ РозІдгеЗОЬ
Использование хранимых процедур и функций позволяет реализовать ос
новное преимущество архитектуры клиент-сервер: обработка данных может
быть выполнена на сервере базы данных. Это существенно снижает время
обработки и нагрузку на сеть.
Если клиентские приложения осуществляют доступ к данным с помощью
хранимых процедур и функций, то прямой доступ к таблицам базы данных
может быть запрещен. Это существенно повышает безопасность базы данных.
Хранимые процедуры и функции являются средством реализации метода
модульного программирования.
Суть модульного программирования заключается в разбиении программы
на ряд независимых блоков, называемых модулями. Это существенно
сокращает время разработки и отладки программ, а также упрощает их мо
дернизацию и сопровождение.
Процедуры используются в тех случаях, когда нужно внести изменения в
данные. Они имеют входные и выходные параметры. Используя выходные
параметры, можно вернуть в вызывающую среду необходимые данные. За
дача функций состоит в том, чтобы выполнить обработку данных и вернуть
в вызывающую среду результат этой обработки. Результат может представ
лять собой как скалярное значение, так и таблицу.
14.1. Хранимые процедуры
Хранимая процедура — это модуль, предназначенный
для выполнения одного или нескольких действий с базой
данных.
388
Глава 14. Хранимые процедуры и функции
Синтаксис оператора создания хранимой процедуры:
CREATE [OR REPLACE] PROCEDURE {имя процедуры}
[({список параметров})]
AS $$
[DECLARE
{объявление локальных переменных}]
BEGIN
{тело процедуры}
END $$ LANGUAGE {язык программирования} ;
Для создания подпрограмм можно использовать несколько языков програм
мирования: PL/pgSQL, SQL, PL/Perl, PL/Python, PL/Tcl. Оператор создания
процедуры является DDL-оператором.
Оператор CREATE PROCEDURE создаст новую процедуру, a CREATE
OR REPLACE PROCEDURE либо создаст новую процедуру, либо изменит
существующую процедуру.
Как правило, процедура имеет один или несколько параметров, но могут
быть процедуры без параметров. Тип данных параметров задается без ука
зания размера, например:
numeric а не numeric(n,m);
varchar а не varchar(n).
Описание параметра содержит следующие элементы: имя; режим передачи
- IN, OUT, INOUT; тип, значение по умолчанию.
Созданные процедуры являются объектами базы данных. Для вызова про
цедур используется оператор CALL.
CALL {имя процедуры}({значения параметров});
Процедуру можно вызвать из:
• анонимного блока;
• другой процедуры;
• внешнего приложения.
PostgreSQL: SQL + PL/pgSQL
........................................... PostgreSQL.
Параметры используются для передачи данных в процедуру, а также для
возвращения некоторых результатов обработки из процедуры в программу,
которая вызвала процедуру.
Параметры, которые объявляются в заголовке
процедуры, называются формальными параметрами,
а соответствующие им параметры в вызывающей среде
называются фактическими параметрами.
Фактические параметры могут быть константами, переменными или выра
жениями, значения которых передаются в процедуру. Тип фактических пара
метров должен совпадать или быть совместимым с типом соответствующих
им формальных параметров.
При определении формальных параметров процедуры указываются допу
стимые способы их использования, которые называются режимами исполь
зования.
Существует три режима использования формальных параметров: Ш, ОПТ,
ШОиТ:
1. Параметры в режиме № используются для получения данных из вызы
вающей программы, соответствующие им фактические параметры могут
представлять собой константы, переменные или выражения. Режим пере
дачи ГМ используется по умолчанию, и его необязательно указывать.
2. Параметры в режиме ОІІТ используются для передачи данных из про
цедуры в вызывающую программу, соответствующие им фактические
параметры должны обязательно быть переменными совместимого типа.
3. Параметры в режиме ІМОиТ можно использовать как для получения
данных из вызывающей программы, так и для передачи результатов об
работки из процедуры в вызывающую программу.
Фактические параметры, соответствующие формальным параметрам с ре
жимом передачи ГМ OUT, должны быть переменными совместимого типа.
В листинге 14.1 приведен пример создания процедуры Emp_Add_Sal, кото
рая предназначена для увеличения зарплаты сотрудника. Номер сотрудника
и размер увеличения зарплаты являются параметрами процедуры. В вызыва
ющую среду передается новое значение заработной платы.
Глава 14. Хранимые процедуры и функции
Листинг 14.1. Создание процедуры Emp_Add_Sal для
изменения зарплаты сотрудника
CREATE OR REPLACE PROCEDURE Emp_Add_Sal(p_id integer, p_add
numeric, p_salary OUT numeric)
AS $$
BEGIN
UPDATE Employees
SET salary = salary+p_add
WHERE employee_id = p_id;
SELECT salary INTO p_salary
FROM Employees
WHERE employee_id = p_id;
END $$ LANGUAGE plpgsql;
Листинг 14.2. Вызов процедуры Emp_Add_Sal
DO $$
DECLARE
v_emp_id integer :=110;
v_add_salary numeric(8,2):=500;
v_emp_salary numeric(8,2);
BEGIN
RAISE NOTICE 'Результат:';
CALL Emp_Add_Sal(v_emp_id,v_add_salary,v_emp_salary);
RAISE NOTICE 'employee_id =%%%', v_emp_id,
'new_salary = ', v_emp_salary;
END $$;
Результат:
employee_id = 110 new__salary = 9850.00
Рассмотрим еще одну задачу. Нужно изменить статус заказа по его номеру,
с выполнением правила: нельзя изменять статус заказа, если его текущий
статус равен Shipped.
Листинг 14.3. Создание процедуры Ord_Upd_Status для
изменения статуса заказа по его номеру
CREATE OR REPLACE PROCEDURE Ord_Upd_Status(p_id integer,
p_status varchar)
AS $$
DECLARE
[ 391 '
PostgreSQL: SQL + PL/pgSQL
.............................................w PostgreSQL.
v_status varchar(20);
BEGIN
SELECT status INTO v_status
FROM orders_copy
WHERE order_id = p_id;
IF v_status= 'Shipped'
THEN
RAISE NOTICE 'Статус заказа % %', p_id,
'изменить нельзя';
ELSE
UPDATE orders_copy
SET status = p_status
WHERE order_id = p_id;
RAISE NOTICE 'Статус заказа % % %', p_id,
'изменен на', p_status;
END IF;
END $$ LANGUAGE plpgsql;
Листинг 14.4. Вызов процедуры Ord_Upd_Status для
изменения статуса заказа по его номеру
DO $$
DECLARE
v_order_id integer :=101;
v_status varchar(20):=’Canceled’;
BEGIN
RAISE NOTICE 'Результат:';
CALL Ord_Upd_Status(v_order_id,v_status);
END $$;
Результат:
Статус заказа 101 изменен на Canceled
Изменим условия изменения статуса заказа. Нужно изменить статус заказов,
оформленных за определенную дату, но нельзя изменять статус заказов, име
ющих текущий статус Shipped.
За одну дату может быть оформлено несколько заказов, и они могут иметь
разный статус. Поэтому в процедуре необходимо использовать курсор, кото
рый позволит последовательно анализировать и обрабатывать заказы.
Листинг 14.5. Создание процедуры Ord_Upd_Status для
изменения статуса заказов, оформленных за определенную дату
' 392 ]
Глава 14. Хранимые процедуры и функции
CREATE PROCEDURE Ord_Upd_Status(p_date date,p_status varchar)
AS $$
DECLARE
Cur_Ord_Date cursor (p_dat date) IS
select * from orders_copy
where order_date = p_dat;
v_status varchar(20);
BEGIN
FOR v_cur IN cur_ord_date(p_date)
LOOP "
IF v_cur.status= 'Shipped'
THEN
RAISE NOTICE 'Статус заказа % %', v_cur.order_id,
'изменить нельзя';
ELSE
UPDATE orders_copy
SET status = p_status
WHERE CURRENT OF Cur_Ord_Date;
RAISE NOTICE 'Статус заказа % % %', v_cur.order_id,
'изменен на', p_status;
END IF;
END LOOP;
END $$ LANGUAGE plpgsql;
Листинг 14.6. Вызов процедуры Ord_Upd_Status для изменения
статуса заказов, оформленных за определенную дату
DO $$
DECLARE
v_date date :='2019-11-02';
v_status varchar(20):='Canceled';
BEGIN
RAISE NOTICE 'Результат:';
call Ord_Upd_Status(v_date,v_status);
END $$;
Результат:
Статус заказа
Статус заказа
Статус заказа
Статус заказа
52 изменен на Canceled
49
изменить нельзя
50 изменен на Canceled
51
изменить нельзя
Сравнивая листинги 14.3 и 14.5, можно увидеть, что были созданы две раз
ные процедуры, имеющие одинаковое имя Ord Upd Status.
ГЕЕ
PostgreSQL: SQL + PL/pgSQL
41 PostgreSQL
Такая возможность называется перегрузкой подпрограмм. Перегружаемые
подпрограммы должны отличаться по количеству параметров и/или их типу.
Способы связывания формальных и фактических параметров
Можно использовать два метода связывания фактических параметров с фор
мальными параметрами:
1. Позиционное связывание;
2. Связывание по имени.
При использовании позиционного связывания соответствие между факти
ческими и формальными параметрами устанавливается по их позиции в
списке параметров.
В листинге 14.7 приведен пример оператора создания процедуры с пара
метрами, которая предназначена для добавления данных о новом заказе с
проверкой следующего правила: новый заказ можно добавлять, если сумма
заказов, находящихся в состоянии ожидания ('Pending') для данного клиента,
не превышает его кредитного лимита.
Листинг 14.7. Создание процедуры с параметрами Add_Order,
которая предназначена для добавления данных о новом заказе
CREATE OR REPLACE PROCEDURE Add_Order(
p_odder_id integer, p_customer_id integer,
p_status varchar DEFAULT'Pending',
p_salesman_id integer DEFAULT NULL,
p_order_date date default CURRENT_DATE)
AS $$
DECLARE
v_credit_limit numeric(10,2);
v_orders_sum numeric(10,2);
BEGIN
SELECT credit_limit into v_credit_limit
FROM Customers
WHERE customer_id = p_customer_id;
SELECT SUM(quantity*unit_price) into v_orders_sum
FROM Orders JOIN Order_Iterns USING (order_id)
WHERE customer_id = p_customer_id
AND status = 'Pending';
i 394 J
Глава 14. Хранимые процедуры и функции
IF v_orders_sum > v_credit_limit
THEN
RAISE NOTICE 'Операция отклонена, так как у клиента % %',
p_customer_id,' превышен кредитный лимит';
ELSE
RAISE NOTICE 'Данные о заказе успешно добавлены';
INSERT INTO Orders
VALUES (p_odder_id, p_customer_id, p_status,
p_salesman_id, p_order_date);
END IF;
END $$ LANGUAGE plpgsql;
Листинг 14.8. Вызов процедуры Add_Order co значениями
параметров, при которых нарушается сформулированное
правило
DO $$
BEGIN
RAISE NOTICE 'Результат:';
CALL Add_Order(120,2,'Pending',165,'2019-08-08S');
END $$;
Результат:
Операция отклонена, так как у клиента 2 превышен кредитный лимит
Для параметров p_status, p_order_date заданы значения по умолчанию, и
мы хотим, чтобы новый заказ имел эти значения. Однако вызов функции
Add_Order с позиционным связыванием фактических и формальных пара
метров в этом случае приведет к ошибке. Пример такого вызова содержится
в листинге 14.9.
Листинг 14.9. Вызов процедуры Add_Order с использованием
значений по умолчанию и позиционным связыванием параметров
DO $$
BEGIN
RAISE NOTICE 'Результат:';
CALL Add_Order(121,5,165);
END $$;
SQL Error [42883]: ОШИБКА: процедура add_order(integer, integer,
integer) не существует.
Подсказка: Процедура с данными именем и типами аргументов не
найдена. Возможно, вам следует добавить явные приведения типов.
Где: функция PL/pgSQL inline_code_block, строка 4, оператор CALL
[ 395 '
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Ситуацию можно исправить использованием связывания параметров по
имени. При этом методе связывании параметров фактические параметры за
писываются следующим образом:^
{имя формального параметра} => {значение параметра}
Так как имена формальных параметров указываются явно, то необязатель
но указывать фактические параметры в порядке их следования в заголовке
вызываемой процедуры. В листинге 14.10 содержится пример вызова про
цедуры со значениями по умолчанию и связыванием параметров по имени.
Листинг 14.10. Вызов процедуры Add_Order с использованием
значений по умолчанию и связыванием параметров по имени
DO $$
BEGIN
RAISE NOTICE 'Результат:';
CALL Add_Order (p_odder_id=>122, p_salesman_id=>165,
p_customer__id=>5) ;
END $$;
Результат:
Данные о заказе успешно добавлены
14.2. Хранимые функции
Хранимая функция — это подпрограмма, который
принимает значения одного или нескольких параметров,
выполняет обработку данных и возвращает полученный
результат в вызывающую программу.
Возможны функции без параметров, но реально функции без параметров
используются довольно редко. Синтаксис оператора для создания хранимой
функции выглядит следующим образом:
CREATE [OR REPLACE] FUNCTION {имя функции}
[{список параметров}]
RETURNS {возвращаемый тип данных}
і 396 ]
Глава 14. Хранимые процедуры и функции
AS $$
[DECLARE
{объявление локальных переменных}]
BEGIN
{Тело функции
RETURN {возвращаемый результат}
}
END $$ LANGUAGE plpgsql;
Это сокращенный синтаксис оператора создания хранимых функций. Пол
ное описание приведено в официальной документации.
Имя новой функции не должно совпадать с именем существующей функции
или процедуры с одинаковым списком и типом параметров. Однако функции
и процедуры с одинаковыми именами, но с разными списками и типами па
раметров можно использовать, это называется перегрузкой.
Описание параметра содержит следующие элементы: имя; режим передачи,
тип, значение по умолчанию.
После служебного слова RETURNS указывается тип данных, возвращаемых
функцией. Функции могут возвращать данные практически любого типа,
как скалярные типы данных, так и данные, имеющие сложную структуру:
коллекции, курсорные переменные, объектные типы.
В теле функции должен содержаться оператор RETURN, после этого опе
ратора указывается значение, возвращаемое в программу, которая вызвала
функцию. Функция может содержать несколько операторов RETURN.
Переедем к рассмотрению конкретных примеров создания и использования
функций.
Листинг 14.11. Создание функции SUM_SAL, которая
возвращает общую сумму покупок клиента
CREATE OR REPLACE FUNCTION SUM_SAL(p_cust_id integer)
RETURNS numeric
AS $$
DECLARE
v_sum_sal numeric(10,2);
BEGIN
SELECT SUM(quantity*unit_price) INTO v_sum_sal
FROM Orders JOIN Order_Iterns USING (order_id)
WHERE customer_id = p_cust_id;
[ 397
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
RETURN v_sum_sal;
END $$ LANGUAGE plpgsql;
В отличие от вызова процедуры, который представляет собой отдельный
оператор, вызов функции является частью исполняемого оператора
PL/pgSQL. Для вызова функции в исполняемом операторе нужно указать
имя функции и значение ее параметров вместо переменной, имеющей тип
данных, возвращаемых функцией.
Листинг 14.12. Пример использования функции SUM_SAL
DO $$
DECLARE
v_sum_sal numeric(10,2);
v_cust_id integer:=3;
BEGIN
RAISE NOTICE 'Результат:';
v_sum_sal:= SUM_SAL(v_cust_id);
RAISE NOTICE 'Сумма покупок клиента % % %', v_cust_id , '
равна ',
v_sum_sal ;
END $$;
Результат:
Сумма покупок клиента 3
равна
819220.00
Если тип данных, которые возвращаются функцией, поддерживается SQL,
то такие функции можно использовать в операторах SQL. В листинге 14.13
содержится пример запроса SQL, который использует функцию SUM_SAL.
Листинг 14.13. Вывести номера и суммы продаж клиентов,
у которых сумма покупок >1000000
SELECT customer_id, SUM_SAL(customer_id) AS "SUM_SALES"
FROM Customers
WHERE SUM_SAL(customer_id) > 1000000
customer_id|SUM_SALES |
------------ +----------- +
45Ц221050.00|
48 I 1512100.00 I
4911880350.001
Глава 14. Хранимые процедуры и функции
Создадим функцию Sum Sal Cpd, аналогичную Sum_Sal, которая содер
жит три параметра: код клиента (p_cust_id), код товара (p_prod_id) и дату
оформления заказа (p date). Функция должна возвращать результат для
любой комбинации фактических параметров. Например, должно быть воз
можно: указать только дату заказа, номер клиента и номер товара, номер то
вара и дату заказа и т.д.
Листинг 14.14. Создание функции Sum_Sal_Cpd, которая
возвращает общую сумму покупок для любой комбинации
фактических параметров
CREATE OR REPLACE FUNCTION Sum_Sal_Cpd
(p_cust_id integer default null,
p_prod_id integer default null,
p_date date default null)
RETURNS numeric AS $$
DECLARE
v_sum_sal numeric(10,2);
BEGIN
SELECT SUM(quantity*unit_price) INTO v_sum_sal
FROM Orders JOIN Order_Iterns USING (order_id)
WHERE ((customer_id = p_cust_id) or (p_cust_id is null))
AND((product_id = p_prod_id) or (p_prod_id is null))
AND((order_date = p_date) or (p_date is null));
RETURN v_sum_sal;
END $$ LANGUAGE plpgsql;
Листинг 14.15. Пример использования функции Sum_Sal_Cpd
DO $$
DECLARE
v_sum_sal numeric(10,2);
v_cust_id integer := 64;
v_prod_id integer :=35;
v_date
date:='2019-11-02';
BEGIN
RAISE NOTICE 'Результат:';
v_sum_sal:= SUM_SAL_CPD(v_cust_id, v_prod_id, v_date);
RAISE NOTICE 'Сумма покупок равна %', v_sum_sal ;
END $$;
Результат:
Сумма покупок равна 248460.00
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Результаты при других комбинациях фактических параметров:-.
v_sum_sal:= SUM_SAL_CPD(p_date=> v_date);
Результат:
Сумма покупок равна 325600.00
v_sum_sal:= SUM_SAL_CPD(p_date=> v_date, p_prod_id=> v_prod_id);
Результат:
Сумма покупок равна 310000.00
14.3. Использование массивов в качестве
параметров процедур и функций
В качестве параметров процедур и функций можно использовать массивы.
Рассмотрим несколько примеров, демонстрирующих эту весьма полезную
возможность.
Листинг 14.16. Создание функции F_Sum_Ar, которая
возвращает сумму элементов одномерного целочисленного
массива
CREATE OR REPLACE FUNCTION F_Sum_Ar( p_ar integer!])
RETURNS integer
AS $$
DECLARE
v_sum integer:=0;
v_x integer;
BEGIN
FOREACH v_x IN ARRAY p_ar
LOOP
v_sum := v_sum + v_x;
END LOOP;
return v_sum;
END $$ LANGUAGE plpgsql;
Листинг 14.17. Пример использования функции F_Sum_Ar
DO $$
DECLARE
Глава 14. Хранимые процедуры и функции
al integer array:='{!,2,3,4,5,6}’;
v_sum_ar integer;
BEGIN
RAISE NOTICE 'Результат:';
RAISE NOTICE 'Массив % ',al;
v_sum_ar:=f_Sum_ar(al);
RAISE NOTICE 'Сумма элементов массива % ', v_sum_ar;
END $$;
Результат:
Массив
{1,2,3,4,5,6}
Сумма элементов массива 21
В листинге 14.18 приведен пример создания перегружаемой функции
EmpAddSal, предназначенной для увеличения зарплаты сотрудников. В
отличие от исходной процедуры из листинга 14.1, параметром этой проце
дуры является массив, содержащий номера сотрудников, которым нужно по
высить зарплату.
Листинг 14.18. Создание процедуры Emp_Add_Sal,
увеличивает зарплату сотрудников
которая
CREATE PROCEDURE Emp_Add_Sal(p_id integer[], p_add numeric)
AS $$
DECLARE
v_id integer;
BEGIN
FOREACH v_id IN ARRAY p_id
LOOP
UPDATE employees_copy
SET salary = salary+p_add
WHERE employee_id = v_id;
END LOOP;
END $$ LANGUAGE plpgsql;
Листинг 14.19. Пример использования функции Emp_Add_Sal
DO $$
DECLARE
al integer array:='{105, 107, 111}';
v_sum numeric(8,2);
BEGIN
RAISE NOTICE 'Результат:';
SELECt SUM(salary) into v_sum
[ 401
PostgreSQL. SQL + PL/pgSQL
FROM Employees_Copy
WHERE employee_id IN (105,107,111);
RAISE NOTICE 'Суммарная зарплата до повьппения
^PostgreSQL
%', v_sum;
CALL Emp_Add_Sal(al,2 00) ;
RAISE NOTICE 'Зарплата сотрудникам % %', al, 'повышена';
SELECt SUM(salary) INTO v_sum
FROM Employees_Copy
WHERE employee_id IN (105,107,111);
RAISE NOTICE 'Суммарная зарплата после повышения %', v_
sum;
END $$;
Результат:
Суммарная зарплата до повышения
16700.00
Зарплата сотрудникам {105,107,111} повышена
Суммарная зарплата после повышения
17300.00
Элементы массива, являющегося параметром процедуры или функции,
могут иметь составной тип. В листинге 14.20 приведен пример создания
функции F_SUM_SAL_EMP, которая вычисляет суммарную зарплату
сотрудников. Параметром этой процедуры является массив, элементы кото
рого имеют составной тип t_emp. Этот тип был создан при решении задачи
из листинга 12.6.
Листинг 14.20. Создание функции F_SUM_SAL_EMP , которая
вычисляет суммарную зарплату сотрудников, данные о
которых содержатся в массиве составного типа
CREATE FUNCTION F_Sum_Sal_Emp(p_emp t_emp[])
RETURNS numeric
AS $$
DECLARE
v_sum numeric(8,2): =0 ;
v_x t_emp;
BEGIN
FOREACH v_x IN ARRAY p_emp
LOOP
v_sum := v_sum + v_x.salary;
END LOOP;
RETURN v_sum;
END $$ LANGUAGE plpgsql;
Глава 14. Хранимые процедуры и функции
В листинге 14.21 приведен пример использования функции F_SUM_SAL_EMP.
Листинг 14.21. Пример использования функции Emp_Add_Sal
DO $$
DECLARE
Cur_Emp_Dep cursor(p_id_dep Employees, department_id%TYPE) FOR
SELECT employee_id, first_name, last_name, department_id,
job_id, rating_e, salary
FROM Employees
WHERE department_id = p_id_dep
ORDER BY salary DESC;
v_emp_dep t_emp array;
v_x t_emp;
v_sum_sal numeric(8,2);
v_i integer:=0;
v_dep integer:=60;
BEGIN
FOR emp_rec IN Cur_Emp_Dep(v_dep)
LOOP
v_i := v_i+l;
v_emp_dep[v_i] := emp_rec;
END LOOP;
RAISE NOTICE 'Результат: ';
RAISE NOTICE 'Сотрудники отдела % ',v_dep;
FOREACH v_x IN ARRAY v_emp_dep
LOOP
RAISE NOTICE '% ', v_x;
END LOOP;
v_sum_sal: = f_Sum_Sal_Emp (v_emp_dep) ;
RAISE NOTICE 'Суммарная зарплата %', v_sum_sal;
END $$;
Результат:
Сотрудники отдела 60
(103,Alexander,Hunold,60,IT_PROG, 3, 9900.00)
(104,Bruce,Ernst,60,IT_PROG,3,6000.00)
(106,Valli,Pataballa,60,IT_PROG,4,5808.00)
(105,DAVID,Austin,60,IT_PROG,5,4800.00)
(107,Diana,Lorentz,60,IT_PROG,3,4200.00)
Суммарная зарплата 30708.00
PostgreSQL: SQL + PL/pgSQL
w
PostgreSQL
14.4. Хранимые функции, возвращающие таблицу
Этот вид хранимых функций позволяет осуществлять сложную обработку
данных, требующую использования операторов PL/pgSQL, с возможностью
обращаться к этим данным в операторах SQL.
Можно также использовать эти функции для замены представлений. Преи
мущество табличных функций заключается в том, что они имеют параметры
и могут возвращать разные данные, которые будут зависеть от значений па
раметров. Заголовок оператора создания функции, возвращающей таблицу,
должен быть представлен в следующем виде:^
CREATE [OR REPLACE] FUNCTION {имя функции}
[{список параметров}]
RETURNS TABLE {список: имя столбца тип}
В теле функции для каждой строки возвращаемой таблицы столбцам нужно
присвоить значение и выполнить оператор RETURN NEXT.
Если табличная функция возвращает результат одного SQL-запроса и в ее
теле нет операторов PL/pgSQL, то для создания такой функции следует ис
пользовать язык SQL. В этом случае оператор создания функции, возвраща
ющей таблицу, выглядит следующим образом:^
CREATE [OR REPLACE] FUNCTION {имя функции}
[{список параметров}]
RETURNS TABLE {список: имя столбца тип}
AS $$
SELECT {список столбцов}
$$ LANGUAGE 'sql';
Список столбцов оператора SELECT должен соответствовать списку столб
цов, возвращаемых функцией.
Изучение этих функций начнем с рассмотрения функции TabEmp, кото
рая должна возвращать данные о сотрудниках, имеющих заданное значение
JOB ID.
Глава 14. Хранимые процедуры и функции
Листинг 14.22. Создание функции Tab_Emp, которая
возвращает данные о сотрудниках, имеющих заданное
значение job_id
CREATE OR REPLACE FUNCTION Tab_Emp(p_job_id IN varchar)
RETURNS TABLE
(id_emp integer,
f_name varchar(25),
l_name varchar(25),
dep_id integer,
job varchar(10))
AS $$
DECLARE
Cur_Emp cursor(p_id varchar) FOR
SELECT employee_id, first_name, last_name,
department_id,job_id
FROM Employees
WHERE job_id =p_id;
BEGIN
FOR emp_rec IN Cur_Emp (p_job_id)
LOOP
id_emp := emp_rec.employee_id;
f_name : = emp_rec. fir s t_name;
l_name := emp_rec. last_name;
dep_id := emp_rec.department_id;
job
:= emp_rec.j ob_id;
RETURN NEXT;
END LOOP;
END $$ LANGUAGE plpgsql;
Создадим еще одну функцию с именем Tab_Emp, в которой в условии вы
бора используется столбец department id.
Листинг 14.23. Создание функции Tab_Emp, которая
возвращает данные о сотрудниках, имеющих заданное
значение department_id
CREATE OR REPLACE FUNCTION Tab_Emp(p_dep_id integer)
RETURNS TABLE (id_emp integer, f_name varchar(25),
l_name varchar(25), dep_id integer, job varchar(10))
AS $$
DECLARE
Cur_Emp cursor(p_id integer) FOR
SELECT employee_id, first_name, last_name,
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
department_id,job_id
FROM Employees
WHERE department_id =p_id;
BEGIN
FOR emp_rec IN Cur_Emp (p_dep_id)
LOOP
id_emp := emp_rec.employee_id;
f_name : = emp_rec. firs t_name ;
l_name : = emp_rec. last_name ;
dep_id : = emp_rec. department_id;
job
:= emp_rec.j ob_id;
RETURN next;
END LOOP;
END $$ LANGUAGE plpgsql;
Проверим работу этих функций:
SELECT * FROM Tab_Emp('ST_MAN')
id_emp|f_name Il_name
|dep_idIj ob
I
------- 1--------- 1---------- 1-------- 1-------- p
122 | Payam
IKauflingl
123|Shanta
IVollman |
124|Kevin
IMourgos |
120|Matthew|Weiss
I
121|Adam
|Fripp |
50|ST_MAN|
50|ST_MAN|
50|ST_MAN|
50|ST_MAN|
300|ST_MAN|
SELECT * FROM Tab_Emp(60)
id_emp|f_name
|l_name
Idep_idIj ob
I
------ +---------- +---------- +------- +-------- +
104|Bruce
|Ernst
|
107|Diana
|Lorentz
|
103|Alexander|Hunold
|
106|Valli
IPataballal
105|DAVID
|Austin
|
60|IT_PROG|
60|IT_PROG|
60|IT_PROG|
60|IT_PROG|
60|IT_PROG|
Создадим функцию OrdersView для выборки данных из таблицы Orders,
которая может быть использована как универсальное представление (View).
Универсальность означает, что при ее вызове можно использовать любую
406
Глава 14. Хранимые процедуры и функции
комбинацию фактических параметров. Для создания этой функции был ис
пользован язык SQL (LANGUAGE ’sql’).
Листинг 14.24. Создание функции Orders_View
CREATE OR REPLACE FUNCTION Orders_View
(p_ord_id integer default null,
p_cust_id integer default null,
p_status varchar(20) default null,
p_date date default null,
p_sal_man integer default null)
RETURNS TABLE(ord_id integer, cust_id integer,
status_ord varchar(20), date_ord date,
sal_man integer)
AS $$
SELECT order_id,customer_id,status,order_date,salesman_id
FROM Orders
WHERE((order_id=p_ord_id) or (p_ord_id is null))
AND ((cus tome r_id = p_cust_id) or (p_cust_id is null))
AND((status=p_status)or(p_status is null))
AND((order_date = p_date) or (p_date is null))
and((salesman_id =p_sal_man)or (p_sal_man is null));
$$ LANGUAGE SQL;
Приведем несколько примеров использования этой функции:^
SELECT * FROM Orders_Viewl(p_date =>'02-11-2019');
ord_id|cust_id|status_ordIdate_ord
Isal_man|
------ +-------- +----------- +----------- +-------- +
49|
50|
51|
52|
61|Shipped
621Pending
63|Shipped
64|Shipped
12019-11-021
12019-11-02 1
12019-11-021
12019-11-021
155|
155|
159|
160|
SELECT * FROM Orders_View(p_status=>'Shipped',
p_date =>'02-11-2019');
ord_id|cust_id|status_ord|date_ord
Isal_manI
------ +-------- +----------- +----------- +-------- +
49|
51|
52|
61|Shipped
63|Shipped
64|Shipped
12019-11-021
12019-11-021
12019-11-021
155|
159|
160|
PostgreSQL: SQL + PL/pgSQL
SELECT * FROM Orders_View(p_cust_id=>63,p_status=>'Shipped’,
p_date =>’02-11-2019’);
ord_id|cust_id|status_ordIdate_ord
Isal_manI
------ +-------- +----------- +----------- +-------- +
51|
63|Shipped
12019-11-021
159|
SELECT * FROM Orders_View(p_sal_man=>159);
ord_id|cust_id|status_ordIdate_ord
Isal_manI
------ +-------- +----------- +----------- +-------- +
41|
51|
59|
408
9|Shipped
63|Shipped
70|Shipped
|2017-05-12|
|2019-ll-02|
|2017-07-29|
159|
159|
159|
Глава 14. Хранимые процедуры и функции
Задачи для самостоятельного решения:
Задача 14.1. Создать хранимую процедуру, которая увеличивает
на 1 рейтинг товаров, если их рейтинг меньше 5 и сумма продаж
больше р зит заі. Входной параметр: р_зит_8аі.
Задача 14.2. Создать хранимую процедуру с параметрами
р зитт тіп, р_зитт_тах, которая изменяет значение кре
дитного лимита клиентов в зависимости от суммы оформленных
заказов ѵзитт. По следующему правилу:
• если ѵ_зитт > р_8итт_тах, то повысить кредитный лимит на 10%;
• если р_зитт_тіп <= ѵ_зитт <= р_зитт_тах, то оставить текущее
значение кредитного лимита;
• если ѵ_зитт < р_зитт_тіп, то уменьшить кредитный лимит на 10%.
Задача 14.3. Создать перегружаемые процедуры, которые уда
ляют из заказов товары, которые клиенту запрещено приобре
тать, и имеют следующие параметры:
• номер заказа;
• дата заказа;
• номер клиента, дата заказа.
Данные о товарах, которые запрещено приобретать клиентам,
содержатся в специальной таблице: (номер клиента, номер то
вара). Эту таблицу нужно создать и заполнить данными.
Привести примеры использования созданных процедур. Реко
мендуется использовать копию таблицы ОгбеНІетв.
Задача 14.4. Создать хранимую функцию, которая возвращает
1 в том случае, если рассматриваемый сотрудник осуществлял
продажи товара с заданным номером, и 0 в противном случае.
Входные параметры: номер сотрудника, номер товара.
Задача 14.5. Создать табличную функцию, которая возвращает
данные о N товарах с максимальными суммами продаж. Значе
ние N задать в виде параметра.
409
Ро8І§ге8ОЬ: 8РЬ + PL/pgSQL
РозідгеЗСХ.
Задача 14.6. Создать хранимую функцию, которая возвращает
общую сумму продаж заданного товара за заданный промежуток
времени. Входные параметры: номер товара, начало периода,
конец периода.
Задача 14.7. База данных ресторана имеет следующие таблицы:
• Меню: (код блюда, название, цена, состав (код продукта, коли
чество)),
• Продукты: (код продукта, название, цена продукта, количество
на складе).
Написать табличную функцию, которая выводит данные о блю
дах, которые можно приготовить из имеющихся на складе про
дуктов, на N персон. N - параметр функции.
Глава 15.
ТРИГГЕРЫ
PostgreSQL: SQL + PL/pgSQL
Триггер - это спецификация, согласно которой СУБД
должна автоматически выполнять определенную триггерную
функцию.
Триггерная функция должна быть создана до создания триггера, который ее
использует, она не имеет параметров и возвращает тип TRIGGER.
Типы событий, которые могут запустить триггер:
• Выполнение операторов вставки, обновления и удаления строк в табли
цах базы данных. Такие триггеры называют DML-триггерами.
•
Выполнение операторов создания, удаления и редактирования объектов
базы данных. Такие триггеры называют триггерами событий.
Триггеры можно использовать для:
• реализации сложных бизнес-правил, которые нельзя обеспечить с помо
щью ограничений целостности;
• контроля изменений, которые пользователи вносят в таблицы базы дан
ных, посредством записи в специальные таблицы данных о выполненных
изменениях, времени изменения и имени пользователя, который осуще
ствил эти изменения;
• реализации сложных правил обеспечения безопасности;
• сбора статистической информации.
Следует иметь в виду, что наличие большого числа триггеров может увели
чить время выполнения операций с базой данных. Так же могут возникнуть
Глава 15. Триггеры
сложные проблемы, связанные с взаимным влиянием триггеров друг на дру
га. Поэтому триггеры следует использовать только тогда, когда проблема,
решаемая с помощью триггера, является важной и ее нельзя решить, исполь
зуя другие средства PL/pgSQL.
15.1. DML-триггеры
Этот тип триггеров запускается при выполнении операторов INSERT,
UPDATE, DELETE, для определенной таблицы или представления базы дан
ных. Это наиболее распространенный тип триггеров, который используется
разработчиками, другие типы триггеров используют в основном админи
страторы базы данных.
В таблицах триггеры быть определены для DM L-операторов, либо до
(BEFORE), либо после (AFTER) их выполнения. В представлениях триггеры
могут быть определены для выполнения операций вместо DML-операторов
(INSTEAD OF).
Оператор создания DML-триггера имеет следующий синтаксис
CREATE [OR REPLACE] TRIGGER {имя триггера}
{момент срабатывания} {событие} ON {имя таблицы|представления }
[FOR ЕАСН ROW]
[WHEN {условие}]
EXECUTE {FUNCTION|PROCEDURE}{имя};
В качестве значения {момент срабатывания} можно указать:
•
BEFORE — перед выполнением оператора;
• AFTER — после выполнения оператора;
•
INSTEAD OF — вместо выполнения оператора (можно использовать
только для представлений).
Параметр {событие} - это выполнение операторов DML: INSERT, UPDATE,
DELETE и оператора TRUNCATE.
Если фраза FOR ЕАСН ROW отсутствует, то такой триггер называется опе
раторным, и он будет срабатывать один раз.
[ 413
PostgreSQL. SQL + PL/pgSQL
PostgreSQL
При наличии фразы FOR EACH ROW триггер называется триггером строк,
и он будет выполняться для каждой строки, к которой будет применен опе
ратор DML.
Фраза WHEN {условие} предназначена для уточнения условий, при которых
должна быть выполнена функция триггера.
Значением параметра {событие} не может быть MERGE. Вместо этого ука
зывается INSERT, UPDATE или DELETE.
Порядок активизации DML-триггеров
Триггеры, которые активизируются при выполнении операторов DML, вы
полняются в следующей последовательности:
1. Выполняются операторные триггеры BEFORE (при их наличии).
2. Выполняются строковые триггеры BEFORE (при их наличии).
3. Выполняется собственно оператор.
4. Выполняются строковые триггеры AFTER (при их наличии).
5. Выполняются операторные триггеры AFTER (при их наличии).
Если для одного и того же события определено несколько триггеров одного
и того же типа, они будут запущены в алфавитном порядке по имени.
15.2. Триггерные функции
Триггерные функции определяют действия, которые должны
быть выполнены при срабатывании триггера.
Триггерная функция должна быть создана до того, как будет создан триггер.
Синтаксис оператора создания триггерной функции:^
CREATE FUNCTION {имя()}
RETURNS TRIGGER AS $$
[DECLARE
{объявление переменных}]
BEGIN
{тело функции}
Глава 15. Триггеры
RETURN NEWI OLD I NULL
END/LANGUAGE plpgsql;
Триггерная функция должна вернуть либо NULL, либо строку таблицы,
для которой сработал триггер. Триггерные функции операторных триггеров
всегда должны возвращать NULL.
Триггерные функции, вызываемые триггерами строк, должны возвращать:
•
NEW — для операций INSERT и UPDATE;
•
OLD — для операции DELETE;
•
NULL — если эти операции не выполнялись (строка была пропущена).
В теле триггерных функций можно использовать специальные переменные,
которые содержат данные о сработавшем триггере и связанном с ним объ
екте. В табл. 15.1 содержится описание этих переменных.
Таблица 15.1. Специальные переменные триггерных функций
Тип
данных
Описание
record
В строчных триггерах для команды INSERT
содержит новую строку таблицы, для ко
манды UPDATE новые значения столбцов,
для команды DELETE и в операторных
триггерах имеет значение NULL
OLD
record
В строчных триггерах для команды
DELETE содержит удаляемую строку, для
команды UPDATE старые значения столб
цов, для команды INSERT и в операторных
триггерах имеет значение NULL
TG_NAME
name
Содержит имя сработавшего триггера
TG_WHEN
text
Содержит момент срабатывания триггера:
BEFORE, AFTER или INSTEAD OF
TG LEVEL
text
Содержит тип триггера: ROW - строчный,
STATEMENT - операторный
Имя
NEW
[ 415
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Имя
Тип
данных
Описание
TG ОР
text
Содержит имя оператора, для которого сра
ботал триггер: INSERT, UPDATE, DELETE
или TRUNCATE
TG_TABLE_NAME
name
Содержит имя таблицы, для которой срабо
тал триггер
TG_TABLE_SCHEMA
name
Содержит имя схемы, содержащей таблицу,
для которой сработал триггер
В триггерных функциях можно использовать функции и операторы предо
ставления системной информации. Некоторые из них приведены в таблице
15.2.
Таблица 15.2. Функции и операторы системной информации
Имя
Тип
данных
CURRENT DATABASEO
name
Возвращает имя текущей базы данных
CURRENT SCHEMA
name
Возвращает имя схемы
CURRENT_USER
name
Возвращает имя пользователя
CURRENT QUERY()
text
Описание
Возвращает текст выполняемого в дан
ный момент запроса
15.3. Примеры использования DML-триггеров
Один и тот же триггер может срабатывать при выполнении разных DMLоператоров и содержать для каждого из них отдельный код обработки.
Рассмотрим пример создания триггера, который срабатывает при выполне
нии операторов INSERT, UPDATE, DELETE с таблицей Orders и записывает
Глава 15. Триггеры
в специальную таблицу (лог) имя пользователя, текст оператора DML, дату
и время выполнения оператора.
Листинг 15.1. Создание таблицы Dml_Log_Order
CREATE TABLE Dml_Log_Order
(username text,
dml_command text,
date_time timestamp);
Листинг 15.2. Создание триггерной функции Log_Dml_Ord()
CREATE OR REPLACE FUNCTION Log_Dml_Ord()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO dml_log_Order
(username, dml_command,date_time)
VALUES
(current_user,current_query(),
current_timestamp) ;
RETURN NULL;
END $$ LANGUAGE plpgsql;
Листинг 15.3. Создание триггера Tr_Log_Ord_Dml
CREATE TRIGGER Tr_Log_Ord_Dml
AFTER INSERT OR UPDATE OR DELETE ON Orders
EXECUTE FUNCTION Log_Dml_Ord();
Выполним несколько DML-операторов:
INSERT INTO Orders
VALUES (116,4,'Pending',156);
UPDATE Orders
SET customer_id = 5
WHERE order_id = 116;
DELETE FROM Orders
WHERE order id = 116;
Выведем содержимое таблицы Dml Log Order после выполнения этих опе
раторов.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
SELECT * FROM Dml_Log_Order ;
username Idml_command
Idate_time
I
-------------- +------------------------------------------------------------------------------- +------------------------------------------ -
postgres|INSERT INTO Orders VALUES (116,4,'Pending')|2023-08-11 12:42:32.8881
postgres|UPDATE Orders SET customer_id = 5 WHERE id612023-08-11 12:42:45.3001
postgres|DELETE FROM Orders WHERE order_id = 116
12023-08-11 12:44:35.7671
Рассмотрим еще один пример использования триггеров. Добавим в табли
цу Departments !, которая является копией таблицы Departments, столбец
quanemp. Этот столбец должен содержать количество сотрудников в отделе.
ALTER TABLE
Departments_l ADD column quan_emp
integer
Заполним столбец quan_emp данными:-
UPDATE Departments_l d
SET quan_emp =
(SELECT COUNT(*) FROM Employees e
WHERE e.department_id = d.department_id);
Выведем содержимое таблицы Departments ! после выполнения этого
запроса.
Листинг 15.4. Вывод содержимого таблицы Departments_l
SELECT * FROM Departments_l
WHERE department_id <=80;
department_id|department_name
I manager _id| location_id1quan_emp(
10 I Administration
|
30|Purchasing
|
40(Human Resources |
60|IT
1
70(Public Relations!
80(Sales
I
20(Marketing
|
50(Shipping
|
200 1
114 1
203(
1031
204 1
145 1
1
(
1700 1
17001
2400 1
1400 1
2700 1
25001
1800 1
1500 1
К
61
К
51
К
34 (
21
43|
Условие WHERE department id <= 80 было добавлено для того, чтобы
сократить число выводимых строк.
^ 418 ]
Глава 15. Триггеры
Создадим триггер, который должен корректировать значение quan_emp при
добавлении нового сотрудника, удалении сотрудника и переводе сотрудника
из одного отдела в другой.
Листинг 15.5. Создание триггерной функции F_IDU_Emp()
CREATE OR REPLACE FUNCTION F_IDU_Emp()
RETURNS TRIGGER
AS $$
BEGIN
CASE
WHEN TG_OP = 'INSERT' THEN
UPDATE Departments_l
SET quan_emp = quan_emp +1
WHERE department_id = new.department_id;
RETURN NEW;
WHEN TG_OP = 'DELETE' THEN
UPDATE Departments_l
SET quan_emp = quan_emp -1
WHERE department_id = old.department_id;
RETURN OLD;
WHEN TG_OP = 'UPDATE' THEN
UPDATE Departments_l
SET quan_emp = quan_emp +1
WHERE department_id = new.department_id;
UPDATE Departments_l
SET quan_emp = quan_emp -1
WHERE department_id = old.department_id;
RETURN NEW;
END CASE;
END $$ LANGUAGE plpgsql;
Листинг 15.6. Создание строчного триггера Tr_IDU_Emp
CREATE OR REPLACE TRIGGER Tr_IDU_Emp
AFTER INSERT OR DELETE OR update of department_id, job_id on
Employees
FOR EACH ROW
EXECUTE FUNCTION F_IDU_Emp();
Удалим сотрудника 134, который работает в отделе 50.
[ 419 2
PostgreSQL: SQL + PL/pgSQL
DELETE FROM Employees
WHERE employee_id = 134 ;
Введем данные о новом сотруднике:
INSERT INTO Employees(employee_id, first_name, last_name,
department_id, job_id, salary, email)
VALUES(211,'John','Poop',40,'MK_MAN',10000,'John_Poop');
Переведем сотрудника 115 из отдела 30 в отдел 40:
UPDATE EMPLOYEES
SET DEPARTMENT_ID =40
WHERE EMPLOYEE ID = 115;
Выведем содержимое таблицы Departments 1 после выполнения этих опе
раторов.
SELECT * FROM Departments_l
WHERE department_id <=80;
department_id|department_name |manager_id|location_id|quan_emp|
-------- +-------------- +
-------------- +----------------- +----
------ +---
101 Administration
60|IT
70|Public Relations
80|Sales
20(Marketing
50(Shipping
40(Human Resources
30(Purchasing
|
|
|
I
|
|
|
|
2001
1031
204 1
1451
203 1
114 1
17001
1400 1
27001
25001
1800 1
1500 1
2400 1
1700 1
К
51
К
34|
21
42 1
31
51
Анализируя эти данные, можно установить, что число сотрудников в отделе
50 уменьшилось на 1. Это произошло, потому что 1 сотрудник из этого от
дела был удален. Число сотрудников в отделе 30 также уменьшилось на 1,
это произошло, потому что один сотрудник из этого отдела был переведен
в отдел 40. Число сотрудников в отделе 40 увеличилось на 2, один новый
сотрудник был добавлен в этот отдел, а второй переведен из отдела 30. Это
означает, что триггер Тг_Юи_Етр выполнил свои функции.
Глава 15. Триггеры
Важной функцией триггеров является реализация ограничений и бизнесправил, которые действуют в предметной области. Следует иметь в виду, что
ограничения, которые обеспечиваются использованием триггеров, довольно
сложно обойти.
Создадим триггер для реализации следующего ограничения: нельзя добав
лять новый заказ для клиента, у которого сумма заказов, находящихся в
состоянии ожидания, превышает его кредитный лимит. Для упрощения кода
этого триггера будем использовать функцию Р Сгесій ІлтіЦі), которая воз
вращает кредитный лимит клиента, и функцию Г Огбег8 8ит(і,]), которая
возвращает общую сумму заказов клиента і, находящихся в состоянии]. Код
создания этих функций приведен в листингах 15.7 и 15.8 соответственно.
Листинг 15.7. Создание функции F_Credit_Limit
CREATE OR REPLACE FUNCTION F_Credit_Limit
(p_customer_id integer)
RETURNS numeric(10,2)
AS $$
DECLARE
v_credit_imit numeric(10,2);
BEGIN
SELECT credit_limit into v_credit_imit
FROM Customers
WHERE customer_id = p_customer_id;
RETURN v_credit_imit;
END $$ LANGUAGE plpgsql;
Листинг 15.8. Создание функции F_Orders_Sum
CREATE OR REPLACE FUNCTION F_Orders_Sum
(p_customer_id integer,p_status varchar)
RETURNS numeric(10,2)
AS $$
DECLARE
v_orders_sum numeric(10,2);
BEGIN
SELECT SUM(quantity*unit_price) into v_orders_sum
FROM Orders JOIN Order_items USING (order_id)
WHERE customer_id = p_customer_id
AND status = p_status ;
RETURN v_orders_sum;
END $$ LANGUAGE plpgsql;
421
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Листинг 15.9. Создание триггерной функции F_Ord_Limit(),
которая обеспечивает выполнение рассматриваемого бизнес-правила
CREATE OR REPLACE FUNCTION F_Ord_Limit()
RETURNS TRIGGER AS $$
BEGIN
IF F__Orders_Sum(NEW.customer_id, NEW.status) >
F_Credit_Limit(NEW.customer_id)
THEN
RAISE EXCEPTION 'Операция отклонена, так как превышен
кредитный лимит клиента';
END IF;
RETURN NEW;
END $$ LANGUAGE plpgsq;
Листинг 15.10. Создание строчного триггера, который
активирует функцию F_Ord_Limit()
CREATE OR REPLACE TRIGGER Tr_Ord_Limit
BEFORE INSERT ON Orders
FOR EACH ROW
EXECUTE FUNCTION F_Ord_Limit()
Проверим работу этого триггера, выполнив оператор добавления данных о
новом заказе клиента 2, у которого превышен кредитный лимит.
INSERT INTO Orders (order_id,customer_id,status,salesman_
id,order_date)
VALUES (116,2,'Pending',169,DEFAULT)
SQL Error [P0001]: ОШИБКА: Операция отклонена, так как превышен
кредитный лимит клиента.
Где: функция PL/pgSQL f_ord_limit(), строка 6, оператор RAISE
При выполнении этого оператора триггер Tr_Ord_Limit инициирует пользо
вательское исключение, и операция добавления нового заказа будет отклонена.
Рассмотрим еще один пример.
Листинг 15.11. Создание триггерной функции F_Ord_It_Ins(),
которая обеспечивает выполнение следующего ограничения: в
одном заказе не может быть больше 5 разных товаров
422
Глава 15. Триггеры
CREATE OR REPLACE FUNCTION F_Ord_It_Ins()
RETURNS TRIGGER AS $$
DECLARE
v_count integer;
BEGIN
SELECT COUNT(*) INTO v_count
FROM Order_Iterns
WHERE order_id=new.order_id;
IF v_count>=5 THEN
RAISE NOTICE 'Добавление товара отклонено, так как
превышено максимально допустимое число товаров в заказе';
RETURN NULL;
ELSE RETURN NEW;
END IF;
END $$ LANGUAGE plpgsql;
Листинг 15.12. Создание строчного триггера,
активирует функцию F_Ord_It_Ins()
который
CREATE TRIGGER Tr_Ord_It_Ins
BEFORE INSERT on Order_Iterns
FOR EACH ROW
EXECUTE FUNCTION F_Ord_It_Ins() ;
Проверим работу этого триггера, делая попытку добавить новый товар в за
каз 74, который уже содержит данные о 5 товарах.
INSERT INTO Order_Iterns
VALUES(74,2, 25, 30, 1600);
Добавление товара отклонено, так как превышено максимально
допустимое число товаров в заказе
15.4. Триггеры для оператора MERGE
В качестве события, которое активирует триггер, не может быть указан опе
ратор MERGE. Но при выполнении этого оператора будут выполнены либо
операторы UPDATE и INSERT, либо операторы DELETE и INSERT. Поэто
му, если у триггера указаны события INSERT OR UPDATE OR DELETE, то
он будет активирован и при выполнении оператора MERGE.
423
PostgreSQL: SQL + PL/pgSQL
....................................... Ф PostgreSQL
Создадим триггер, который должен обеспечивать выполнение следующего
бизнес-правила: зарплата сотрудника не может быть больше максимально
допустимой зарплаты по должности, которую он занимает. Значения макси
мально допустимых зарплат для каждой должности содержатся в таблице
Jobs.
Листинг 15.13. Создание триггерной функции F_Emp_Merge(),
которая должна обеспечивать выполнение этого бизнесправила
CREATE OR REPLACE FUNCTION F_ Emp_ Merge ()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'UPDATE' THEN
IF NEW.salary >
(select max_salary from Jobs
WHERE job_id = new.job_id)
THEN RAISE EXCEPTION 'Операция отклонена, так как
новое значение зарплаты % %', new.salary,
' превышает максимально допустимое значение';
RETURN NULL;
ELSE RETURN NEW;
END IF;
END IF;
IF TG_OP = 'INSERT' THEN
IF NEW.salary >
(select max_salary from Jobs
WHERE job_id = new.job_id)
THEN RAISE EXCEPTION 'Операция отклонена, так как
новое значение зарплаты % %', new.salary,
' превышает максимально допустимое значение';
RETURN NULL;
ELSE RETURN NEW;
END IF;
END IF;
END $$ LANGUAGE plpgsql;
Листинг 15.14. Создание строчного триггера, который
активирует функцию F_Emp_Merge()
CREATE OR REPLACE TRIGGER Tr_Emp_Merge
BEFORE INSERT or UPDATE ON Employees
FOR EACH ROW
EXECUTE FUNCTION F_Emp_Merge()
424
Глава 15. Триггеры
В таблице Emp Upd содержатся данные о сотрудниках. Нужно выполнить
слияние таблиц Employees и Emp Upd.
Выведем содержимое таблицы Emp Upd
SELECT employee_id, first_name, last_name,
department_id, job_id, salary
FROM Emp_Upd;
employee_idI first_name | last_name|department_id|job_id |salary|
----------- +----------- +----------+-------------- +------- +------ +
2161 Diana
|Hutton
|
60|IT_PROG|
90001
Выполним слияние таблиц Employees и Emp Upd (листинг 15.15) и выве
дем после этого данные о сотрудниках отдела 60 (листинг 15.16).
Листинг 15.15. Слияние таблиц Employees и Emp_Upd
MERGE into Employees emp USING Emp_Upd emp_m
ON emp.employee_id = emp_m.employee_id
WHEN MATCHED THEN
UPDATE SET department_id =emp_m.department_id,
job_id = emp_m.job_id,
rating_e = emp_m.rating_e,
salary = emp_m.salary
WHEN NOT MATCHED THEN
INSERT (employee_id, first_name, last_name,
department_id, job_id,salary, email)
VALUES (emp_m. employee_id, emp_m.first_name , emp_m. last_name,
emp_m. department_id,emp_m. j ob_id, emp_m. salary,emp_m. email) ;
Листинг 15.16. Вывод данных о сотрудниках отдела 60
SELECT employee_id, first_name, last_name,
department_id, job_id, salary
FROM Employees
WHERE department_id = 60;
employee_id I first_name Ilast_nameI department
+-------- +--------104|Bruce
I Ernst
I
107 I Diana
I Lorentz
I
103 I Alexander IHunold
I
106|Valli
IPataballaI
|Austin
I
105 I DAVID
I Hutton
216|Diana
I
id|job_id I salary |
—+------ +------ +
60|IT_PROG|6000.001
60|IT_PROG|4200.001
60|IT_PRGG|9900.00|
60 IIT_PROGI 5808.00|
60 IIT_PROGI 4800.00|
60 I IT PROGI9000.00|
w
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Анализ результатов этого запроса показывает, что данные о новом сотруд
нике успешно добавлены. Максимальная зарплата по должности ІТ РИОС
равна 10000, следовательно, ограничение выполнено.
Попробуем добавить нового сотрудника с зарплатой более 10000. Данные об
этом сотруднике были записаны в таблицу Етр Грд.
employee_idIfirst_name |last_name|department_id|job_id |salary|
---------- — -j—----- -- — _ _|_ — —-------- 1-------------- 4--------- 1------- p
217|Jack
I Taylor
|
60|IT_PROG|
110001
Выполним слияние таблиц Employees и Emp_Upd (листинг 15.15)
SQL Error [P0001]: ОШИБКА: Операция отклонена, так как
новое значение зарплаты 11000.00 превышает максимально допустимое
значение.
Где: функция PL/pgSQL f_emp_merge(), строка 7, оператор RAISE
Операция отклонена, следовательно, триггер выполнил свою задачу.
Добавим в таблицу Emp_Upd данные о существующем сотруднике, указав
недопустимое значение зарплаты:^
employee_id I first_name |last_nameIdepartment_id |job_id |salary I
------------ +----------- +---------- +-------------- +------- +------ +
1071 Diana
|Lorentz
|
60|IT_PROG|
12000 1
Выполним операцию слияния. В этом случае будет выполняться оператор
UPDATE.
SQL Error [Р0001]: ОШИБКА: Операция отклонена, так как
новое значение зарплаты 12000.00 превышает максимально допустимое
значение.
Где: функция PL/pgSQL f_emp_merge(), строка 7, оператор RAISE
Следовательно, триггер Tr Emp Merge успешно выполнил свою задачу и в
этом случае. Очевидно, что этот триггер будет работать и для операторов
INSERT и UPDATE.
" 426 ]
Глава 15. Триггеры
Однако, если операторы INSERT, UPDATE, DELETE будут обрабатывать
несколько строк, то, если хотя бы для одной строки условие будет нарушено,
возникнет исключение, транзакция будет отменена, и ни одна строка не бу
дет добавлена или обновлена.
Для демонстрации этого добавим в таблицу Етр_ирсі данные о 3 новых
сотрудниках. Для двух из них ограничения по зарплате будут выполнены, а
для одного нет. Содержимое таблицы Emp_Upd:
employee_id( first_name
2191 Den
220 1 David
218 1 Bruce
1last_name|department_idIj ob_id I salary 1
1 Weiss
1 Faviet
(Austin
60|IT PROGI
60 ЦТ PROGI
60 ЦТ PROGI
1
(
1
8000 1
11000 1
7000 1
Попробуем вставить эти строки в таблицу Employees
INSERT INTO Employees(employee_id, first_name, last_name,
department_id, job_id, salary, email)
SELECT employee_id, first_name, last_name, department_id,
job_id, salary, email
FROM Emp_Upd;
SQL Error [P0001]: ОШИБКА: Операция отклонена, так как
новое значение зарплаты 11000.00 превышает максимально допустимое
значение.
Где: функция PL/pgSQL f_emp_merge() , строка 17, оператор RAISE
В результате срабатывания триггера возникла ошибка.
Выведем данные о сотрудниках отдела 60:^
SELECT employee_id, first_name, last_name,
department_id, job_id, salary
FROM Employees
WHERE department_id = 60;
employee_id | first_name | last_name | department_id | job_id (salary |
----------- +----------- +--------- +-------------- +------- +------- +
104 I Bruce
107 I Diana
I Ernst
(Lorentz
I
|
601IT_PROG|6000.001
60|IT_PROG|4200.00|
427
PostgreSQL: SQL + PL/pgSQL
103 I Alexander
106 I Valli
1051 DAVID
2161 Diana
IHunold
|
|Pataballa|
I Austin
|
I Hutton
|.
60 IIT_PROGI 9900.00 1
60 IIT_PROGI 5808.00|
60 IIT_PROGI 4800.00|
60|IT_PRGG|9000.00|
Анализируя эти данные, можно увидеть, что ни одна из строк не была до
бавлена. Актуальной задачей является сделать так, чтобы данные о сотруд
никах, зарплата которых удовлетворяет ограничениям, были добавлены.
Решить эту задачу можно, используя триггеры с моментом срабатывания
INSTEAD OF.
15.5. Триггеры с моментом срабатывания
INSTEAD OF
Фраза INSTEAD OF переводится как "вместо". При наличии
такого триггера оператор, для которой определен триггер,
не выполняется, а выполняются операции, заданные в теле
триггера. Триггеры с моментом срабатывания INSTEAD OF
можно создавать только для представлений.
Создадим представление EmpView, которое можно использовать для про
смотра и изменения таблицы Employees
CREATE OR REPLACE VIEW Emp_View
AS
SELECT employee_id, first_name, last_name,
department_id, job_id, salary, email
FROM Employees;
Листинг 15.17. Создание триггерной функции F_Emp_View(),
которая должна проверять данные при слиянии таблиц
Employees и Emp_Upd
CREATE OR REPLACE FUNCTION F_Emp_View()
RETURNS TRIGGER AS $$
BEGIN
CASE
WHEN TG_OP = ’UPDATE' THEN
IF (new.salary <
Глава 15. Триггеры
(SELECT max_salary from Jobs
WHERE job_id = new.job_id))
THEN
UPDATE Employees
SET department_id = new.department_id,
j ob_id = new.j ob_id,
rating_e = new.rating_e,
salary = new.salary
WHERE employee_id = new.employee_id;
RETURN NEW;
ELSE RETURN NULL;
END IF;
WHEN TG_OP = 'INSERT' THEN
IF (new.salary <
(SELECT max_salary FROM Jobs
WHERE job_id = new.job_id))
THEN
INSERT INTO Employees(employee_id, first_name,
last_name, department_id, job_id, salary, email)
VALUES (new.employee_id, new.first_name, new. last_name,
new.department_id, new.job_id, new.salary, new.email);
RETURN NEW;
ELSE RETURN NULL;
END IF;
END CASE;
END $$ LANGUAGE plpgsql;
Эта функция для каждой строки таблицы EmpUpd проверяет ограничения
на размер зарплаты. Если строка не нарушает ограничений, то она или встав
ляется в строку Employees таблицы, или обновляет строку этой таблицы.
При нарушении ограничений обрабатываемая строка пропускается.
Листинг 15.18. Создание строчного триггера Tr_Emp_View,
который активирует функцию F_Emp_View()
CREATE OR REPLACE TRIGGER Tr_Emp_View
INSTEAD OF INSERT or UPDATE ON Emp_View
FOR EACH ROW
EXECUTE FUNCTION F_Emp_View();
Этот триггер будет срабатывать при выполнении операторов INSERT и
UPDATE с представлением Emp View. Использовать представления в опе
раторе MERGE нельзя.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Используя представление Emp View, выполним оператор вставки строк, со
держащихся в таблице Emp_Upd.
INSERT INTO Emp_View(employee_id, first_name, last_name,
department_id, job_id, salary, email)
SELECT employee_id, first_name, last_name, department_id,
job_id, salary, email
FROM Emp_Upd;
2 row (s) modified.
Выведем данные о сотрудниках отдела 60 после выполнения этого оператора:
SELECT employee_id, first_name, last_name,
department_id, job_id, salary
FROM Employees
WHERE department_id = 60;
employee_id | first_name I last_name | department_id | job_id | salary |
104|Bruce
107|Diana
103|Alexander
106|Valli
105|DAVID
2161 Diana
219|Den
218|Bruce
|Ernst
|
(Lorentz
|
IHunold
|
|Pataballa|
(Austin
|
|Hutton
(
(Weiss
|
(Austin
|
60|IT PROG|6000.00|
60|IT PROG|4200.00|
60|IT PROG|9900.00|
60|IT PROG|5808.00|
60|IT PROG|4800.00|
60|IT PROG|9000.00|
60|IT_PROG|8000.00|
60|IT PROG|7000.00|
Анализируя эти данные, можно установить, что строки, которые не наруша
ют ограничения, вставлены в таблицу, а строка, которая нарушала ограниче
ние, была пропущена.
15.5. Триггеры событий
Этот тип триггеров используется для выполнения определенных действий
при создании, редактировании и удалении объектов базы данных.
Триггер события срабатывает, когда в базе данных
происходит связанное с ним событие.
430
Глава 15. Триггеры
Триггеры событий можно использовать для аудита изменения объектов базы
данных и для запрета применения операторов DDL к определенным
объектам.
События, которые могут активировать этот тип триггеров:
• ddl_command_start — происходит непосредственно перед выполнением
команд CREATE, ALTER, DROP, GRANT, REVOKE;
• ddl_command_end — происходит непосредственно после выполнения
этих команд. Триггер срабатывает после того, как эти действия были вы
полнены, но до фиксации транзакции. Для того чтобы получить информа
цию об операциях DDL, вызвавших возникновение события, использует
ся функция pg_event_trigger_ddl_commands(), которая возвращает набор
строк;
• sqldrop — происходит непосредственно перед событием ddl_
command_end для команд, которые удаляют объекты базы данных. Для
получения списка удалённых объектов используется функция pg_event_
trigger_dropped_objects(), которая возвращает набор строк;
• table rewrite — происходит после того, как таблица будет перезаписана,
в результате выполнения команд ALTER TABLE и ALTER TYPE.
Триггеры событий не могут выполняться в прерванной транзакции. Поэто
му, если команда DDL завершается ошибкой, соответствующие триггеры
ddlcommandend не сработают. И наоборот, если триггер ddlcommandend
завершился с ошибкой, то последующие триггеры событий не сработают,
так же как и сама команда не будет выполнена.
В триггерных функций событий в качестве типа возвращаемого результата
должно быть указано EVENT_TRIGGER.
После создания триггерной функции можно создать триггер, связанный с
конкретным событием, который активирует эту функцию. Синтаксис созда
ния триггера событий:^
CREATE EVENT TRIGGER trigger_name
ON {событие}
[WHEN {условие}]
EXECUTE FUNCTION {имя триггерной функции};
Рассмотрим примеры использования триггеров событий.
par
PostgreSQL: SQL + PL/pgSQL
Создадим триггер, который записывает в таблицу DDL_Alter_Log: имя поль
зователя, создавшего объект, дату изменения, оператор и текст 89Ь-запроса,
которым осуществляется изменение объекта.
Листинг 15.19. Создание таблицы ООЬ_Сгеабе_Ьод, в
которую будут записываться данные
CREATE TABLE DDL_Log
(user_name
text,
date_time
timestamp,
obj_name
text,
ddl_comand text);
Листинг 15.20. Создание триггерной функции F_Log_Ddl(),
которая будет записывать в таблицу DDL_Create_Log
данные о выполненных DDL-операторах
CREATE OR REPLACE FUNCTION F_Log_Ddl()
RETURNS EVENT_TRIGGER AS $$
BEGIN
INSERT INTO ddl_log
(user_name, date_time, obj_tag, ddl_comand)
VALUES(current_user, current_timestamp , tg_tag, current_query());
END; $$ LANGUAGE plpgsql;
Листинг 15.21. Создание триггера событий Tr_Log_Ddl,
который активирует функцию F_Log_Ddl()
CREATE EVENT TRIGGER Tr_Log_Ddl
ON Ddl_Command_End
EXECUTE FUNCTION F_Log_Ddl();
Выполним несколько DDL-операторов и выведем после этого содержимое
таблицы DDLLog:
SELECT date_time::time,obj_tag,SUBSTRING(ddl_comand ,1,35)
FROM DDL Log;
date_timeIobj_tag
|substring
|
--------- +---------------- +--------------------------------------- +
12:02:07|CREATE TABLE
12:02:51|CREATE TABLE
12:04:01|CREATE TABLE
(CREATE TABLE Coursel
(CREATE TABLE Studentsl
(CREATE TABLE Teachersl
(Course_id |
(student_i |
(teacher |
Глава 15. Триггеры
12:04:43|ALTER TABLE
12:05:17|ALTER TABLE
12:06:59|CREATE TABLE
12:08:02|CREATE VIEW
12:09:33 | CREATE SEQUENCE
(ALTER
|ALTER
|CREATE
|CREATE
| CREATE
TABLE Coursel ADD COLUMN teal
TABLE Coursel ADD CONSTRAIN!
TABLE Examsl
(student_id
I
VIEW Std_Avg_Grade As SELEC|
SEQUENCE Styd_ID_SEQ 3ISTART |
Можно вести лог о выполненных ВВГ-операторах только для определенно
го типа объектов базы данных. Создадим триггер, который будет фиксиро
вать данные только об удаленных таблицах.
Листинг 15.22. Создание таблицы Огор_ТаЬ1е_Ьод, в
которую будут записываться данные
CREATE TABLE Drop_Table_Log
(user_name text,
da te_time time s tamp,
obj_type text,
obj_name text);
Листинг 15.23. Создание триггерной функции F_Drop_Table_Log(),
которая будет записывать в таблицу Drop_Table_Log данные об
удалении таблиц
CREATE OR REPLACE FUNCTION F_Drop_Table_Log()
RETURNS EVENT_TRIGGER AS $$
DECLARE
v_name text;
v_type text;
BEGIN
SELECT object_name, object_type into v_name,v_type
FROM pg_event_trigger_dropped_objects ();
IF v_type='table' then
INSERT INTO Drop_Table_Log
(user_name, date_time, obj_type, obj_name)
VALUES(current_user, current_timestamp, v_name, v_type);
END IF;
END $$ LANGUAGE plpgsql;
Для получения данных об удаленном объекте и его имени используется
функция pg_event_trigger_dropped_objects().
[ 433 '
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Листинг 15.24. Создание триггера событий Tr_Drop Table_
Log, который активирует функцию F_Drop_Table_Log()
CREATE EVENT TRIGGER Tr_Drop_Table_Log
ON SQL_DROP
EXECUTE FUNCTION F_Drop_Table_Log();
Удалим несколько объектов разных типов (TABLE, VIEW, SEQUENCE) и
выведем после этого содержимое таблицы Drop Table Log:
SELECT * FROM Drop_Table_Log;
user_nameIdate_time
I obj_name|obj_type|
---------- +--------------------------- +---------- +--------- +
postgres J2023-08-14 14 :11:44.342|coursel
|table
postgres 12023-08-14 14:12:18.078 IteacherslI table
postgres 12023-08-14 14 :14 : 32.997|examsl
|table
I
I
I
Из результатов этого запроса следует, что зафиксированы только операции
удаления таблиц.
Используя триггеры событий, можно запретить удаление только определен
ных объектов базы данных. Создадим триггер, который будет запрещать уда
ление таблиц, имена которых начинаются с символов stu.
Листинг 15.25. Создание триггерной функции F_Table_NO_DROP(),
которая будет инициировать пользовательское исключение
при попытке удаления таблиц, имена которых начинаются с
символов stu
CREATE OR REPLACE FUNCTION F_Table_NO_DROP()
RETURNS EVENT_TRIGGER AS $$
DECLARE
v_name text;
v_type text;
BEGIN
SELECT object_name, object_type into v_name, v_type
FROM pg_event_trigger_dropped_objects();
IF v_type='table' and v_name like 'stu%'
THEN RAISE EXCEPTION 'Удаление таблицы % %', v_name,
'отменено, так как таблицы, имена которых начинаются на
^ 434 ]
Глава 15. Триггеры
Stu, удалять нельзя.';
END IF;
END $$ LANGUAGE plpgsql;
Листинг 15.24. Создание триггера событий Tr_NO_Drop_Table,
который активирует функцию F_Table_NO_DROP ()
CREATE EVENT TRIGGER Tr_NO_Drop_Table
ON Sql_Drop
EXECUTE FUNCTION F_Table_NO_DROP();
Сделаем попытку удалить таблицу Students
DROP TABLE Students;
SQL Error [P0001]: ОШИБКА: Удаление таблицы students отменено, так
как таблицы, имена которых начинаются на stu, удалять нельзя.
Где: функция PL/pgSQL f_table_no_drop(), строка 10, оператор RAISE
При выполнении этого оператора возникло пользовательское исключение, и
удаления таблицы Students не произошло.
Триггеры событий — это мощная функция PostgreSQL, которая позволяет
реализовать сложные бизнес-правила, контролировать изменения, которые
пользователи вносят в базу данных, и помогает автоматизировать операции
с базой данных. Используя триггеры событий, можно создавать более на
дежные и эффективные системы обработки данных.
15.6. Управление триггерами
При необходимости DML-триггеры можно отключить, используя команду:
ALTER TABLE {имя таблицы}
DISABLE TRIGGER {имя триггера}|ALL;
Можно отключить определенный триггер, указав его имя, или все триггеры,
связанные с таблицей, указав ключевое слово ALL.
Для отключения триггеров событий используется команда:
PostgreSQL: SQL + PL/pgSQL
ALTER EVENT TRIGGER {имя триггера} DISABLE;
После отключения триггер остается в базе данных. Однако если происходит
событие, связанное с триггером, отключенный триггер не срабатывает.
Триггеры, которые были отключены, можно включить.
Включение ОМЬ-триггеров осуществляется командой:^
ALTER TABLE {имя таблицы}
ENABLE TRIGGER {имя триггера}|ALL;
Для включения триггеров событий используется команда:
ALTER EVENT TRIGGER {имя триггера} ENABLE;
Для удаления DML-триггеров используется команда:
DROP TRIGGER [IF EXISTS] {имя триггера}
ON {имя таблицы} [CASCADE| RESTRICT]
Для удаления триггеров событий используется команда:
DROP EVENT TRIGGER [IF EXISTS]
[CASCADE! RESTRICT]
{имя триггера}
Если указать IF EXISTS, то не будет возникать ошибка при удалении несу
ществующего триггера. Если указать CASCADE, то будут удалены объекты,
которые зависят от триггера. По умолчанию используется RESTRICT. В
этом случае удаление триггера будет отменено, если от него зависят другие
объекты.
436
Глава 15. Триггеры
Задачи для самостоятельного решения:
Задача 15.1. Создать триггер, который срабатывает при измене
нии столбцов job_id и salary таблицы Employees и записывает в
таблицу Audit_Emp_Values следующие данные:
currentuser,
current_timestamp,
employee_id,
first_name,
last_
name, OLD.job_id ,NEW.job_id , OLD.salary, NEW.salary.
Нужно предварительно создать таблицу Audit_Emp_Values.
Задача 15.2. Создать триггер, который срабатывает при вы
полнении операций INSERT, DELETE, UPDATE с таблицей
Employees и записывает в таблицу AUDIT_EMP_IDU следующие
данные: current user, current timestamp, employee id, first_
name, last_name и операцию, которая была выполнена.
Задача 15.3. Создать триггер, который сохраняет данные о до
бавленных и удаленных строках таблицы Products.
Задача 15.4. Создать триггер, который должен обеспечивать
выполнение следующего правила: если при добавлении новой
строки в таблицу Order_ltems выясняется, что добавляемый то
вар в заказе есть, то строка не добавляется, а обновляется зна
чение столбца quantity в уже имеющейся строке.
Задача 15.5. Создать триггер, который, при добавлении новой
строки в таблицу Огбег Кетз, обеспечивает выполнение
бизнес-правила: рейтинг товара должен быть меньше рейтинга
сотрудника, оформившего заказ, или равен ему.
Задача 15.6. Создать триггер, который обеспечивает выполне
ние следующего ограничения: в одном заказе не может быть
больше 5 разных товаров.
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Задача 15.7. Некоторым клиентам запрещено приобретать
определенные товары. Эти данные содержатся в таблице
Limit_Customers, эту таблицу нужно создать и заполнить данны
ми. Есть таблица Items, которая содержит данные о приобретен
ных товарах, которые надо добавить в существующие заказы.
Создать триггер, который будет проверять эти данные по сле
дующему правилу: если нет запрета на товар для клиента, то
данные добавляются в таблицу, в противном случае нет.
Задача 15.8. Создать триггер, который запрещает удалять
таблицы после 18:00 и фиксирует попытки это сделать.
Задача 15.9. Создать триггер, который будет фиксировать все
случаи выполнения ОМЬ-операторов в субботу и воскресенье.
В лог нужно записывать: имя пользователя, дату и время, текст
запроса.
438
Глава 16.
ВСТРОЕННЫЙ
ДИНАМИЧЕСКИЙ 80Е
PostgreSQL: SQL + PL/pgSQL
f
PostgreSQL
Термин "динамический SQL" используется для обозначения операторов
SQL, код которых формируется в процессе выполнения программы с ис
пользованием заданных параметров.
Использование динамического SQL существенно повышает гибкость разра
батываемых приложений и позволяет обойти некоторые ограничения язы
ка PL/pgSQL. Например, в блоках PL/pgSQL нельзя использовать команды
языка определения данных (DDL), но в код блока PL/pgSQL можно вставить
динамический оператор, содержащий такую команду, и выполнить его. Сле
дует иметь в виду то, что динамические операторы выполняются медленнее
статических и усложняют процессы отладки и сопровождения программ.
16.1. Выполнение динамических операторов
SELECT
Динамические операторы SQL создаются в виде символьных строк с ис
пользованием параметров, указанных в процессе выполнения программы.
Эти строки должны содержать допустимые операторы SQL.
Команда EXECUTE осуществляет анализ и выполнение динамического опе
ратора, содержащегося в символьной строке. Синтаксис команды EXECUTE:EXECUTE {строка запроса}
[INTO [STRICT] {список переменных}]
[USING {значения параметров}];
Глава 16. Встроенный динамический SQL
•
{строка запроса} — строка или переменная типа text, содержащая опера
тор SQL, который должен быть выполнен;
•
INTO [STRICT] {список переменных} — одна или несколько перемен
ных, которым присваивается результат выполнения запроса. Если указа
но STRICT, то выдается сообщение об ошибке, в случае если оператор не
возвращает ровно одну строку;
• USING — содержит значения входных параметров, на которые можно
ссылаться в операторе: $1, $2 и т.д.
Результат, возвращаемый из динамического оператора, может быть скаляр
ным, представлять собой запись или состоять из нескольких строк. Рассмо
трим на конкретных примерах особенности реализации этих вариантов.
Листинг 16.1. Пример формирования и выполнения
динамического оператора SELECT, в который передается
значение столбца employee_id first_name и возвращается
значение столбца first_name
DO $$
DECLARE
v_col_value integer:= 110;
v_ret_value text;
BEGIN
EXECUTE'SELECT first_name FROM Employees
WHERE employee_id = $1'
INTO v_ret_value
USING v_col_value;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'employees_id =%%%', v_col_value e,
'first_name= ' , v_ret_value;
END $$;
Результат:
employees_id = 110
first_name = John
Динамический оператор SELECT может вернуть всю строку таблицы. В
этом случае переменная, которой присваивается результат, должна иметь
составной тип, структура которого совпадает со структурой таблицы, из ко
торой извлекается строка. В листинге 16.2 приведен код для реализации это
го варианта выполнения динамического оператора SELECT.
441
PostgreSQL: SQL + PL/pgSQL
PostgreSQL...
Листинг 16.2. Пример формирования и выполнения
динамического оператора SELECT, который возвращает
одну строку таблицы
DO $$
DECLARE
v_col_value integer:= 110;
v_rv Employees%rowtype;
BEGIN
EXECUTE'SELECT * FROM Employees
WHERE employee_id = $1'
INTO v_rv
USING v_col_value;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'employees_id =%%%%%% %',v_rv.employee_id,
' first_name = ' , v_rv. firs t_name,
'last_name = ',v_rv.last_name, 'job_id=', v_rv.job_id;
END $$;
Результат:
employees_id = 110 first_name = John last_name = Chen
job_id = FI_ACCOUNT
Текст запроса может быть присвоен переменной строкового типа. После
чего можно выполнить этот запрос, указав имя этой переменной в операторе
EXECUTE.
Листинг 16.3. Пример формирования и выполнения
динамического оператора SELECT, который сформирован с
использованием переменной строкового типа
DO $$
DECLARE
sql_stmt text;
v_col_value integer:= 110;
v_rv Employees%rowtype;
BEGIN
sql_stmt:= 'SELECT * FROM Employees
WHERE employee_id = $1';
EXECUTE sql_stmt
INTO v_rv
USING v_col_value;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'employees_id =%%%%%% %',v_rv.employee_id,
'first_name = ',v_rv.first_name,
'last_name = ',v_rv.last_name, 'job_id=', v_rv.job_id;
Глава 16. Встроенный динамический SQL
END $$;
Результат:
employees_id = 110
FI ACCOUNT
first_name = John last_name = Chen job_id =
В рассмотренных примерах оператор SELECT возвращал одну строку. Если
в таблице Employees будет несколько строк, у которых first_name = 'Adam',
то оператор SELECT вернет несколько строк и возникнет ошибка. В этом
случае переменная, которой присваивается результат, должна иметь тип
REFCURSOR.
Листинг 16.4. Пример формирования и выполнения
динамического оператора SELECT, который возвращает
несколько строк
DO $$
DECLARE
sql_stmt text;
v_col_value text: = * John';
v_rv Employees%rowtype;
v_refcur REFCURSOR;
BEGIN
RAISE NOTICE' Результат:';
RAISE NOTICE' % % % % % ',
LPAD('employee_id',12),RPAD('first_name',12),
RPAD('last_name',10),LPAD('job_id',10),LPAD('salary',10) ;
sql_stmt:='SELECT * FROM Employees
WHERE first_name = $1' ;
OPEN v_refcur FOR EXECUTE sql_stmt
USING v_col_value;
LOOP
FETCH v_refcur INTO v_rv;
EXIT WHEN NOT FOUND;
RAISE NOTICE' % % % % % ',
LPAD( v_rv.employee_id::text,12),
RPAD( v_rv.firs t_name,12),
RPAD( v_rv.last_name,10),
RPAD( v_rv.job_id,10),
LPAD( v_rv.salary::text,10);
END LOOP;
CLOSE v_refcur;
END $$;
PostgreSQL: SQL + PL/pgSQL
Результат:
employee_id
139
145
110
211
210
first_name
John
John
John
John
John
last_name
Seo
Russell
Chen
Poop
Connor
job_id
ST_CLERK
SA_MAN
FI_ACCOUNT
MK_MAN
PU_MAN
salary
2700.00
14000.00
9850.00
10000.00
8500.00
В этом примере был использован динамический курсор, который содержит
результат выполнения динамического запроса. Синтаксис использования
курсорных переменных в динамических запросах:-
OPEN {курсорная переменная}
FOR EXECUTE {строка запроса}
[USING {значения параметров}];
CLOSE курсорная переменная;
Содержимое курсорной переменной можно записать в массив или в таблицу.
Рассмотрим следующую задачу: необходимо записать результат динамиче
ского запроса из листинга 16.4 в таблицу Етріоуеез і, которая имеет следующую структуру:
CREATE TABLE Employees_l
( employee_id
integer,
first__name
VARCHAR(20),
las t_name
VARCHAR(25),
job_id
VARCHAR(10),
salary
numeric(10,2));
Листинг 16.5. Запись результата выполнения динамического
оператора, который возвращает несколько строк, в таблицу
DO $$
DECLARE
sql_stmt text;
v_col_value text:='John';
v_rv Employees % rowtype;
v_refcur REFCURSOR;
BEGIN
444
Глава 16. Встроенный динамический SQL
sql_stmt:='SELECT * FROM Employees
WHERE first_name = $1';
OPEN v_refcur FOR EXECUTE sql_stmt
USING v_col_value;
LOOP
FETCH v_refcur INTO v_rv;
EXIT WHEN NOT FOUND;
INSERT INTO Employees_l(employee_id, first_name,
last_name, job_id, salary)
VALUES(v_rv.employee_id, v_rv.firs t_name,
v_rv.last_name, v_rv.job_id, v_rv.salary);
END LOOP;
CLOSE v_refcur;
END $$;
Выведем содержимое таблицы Employees_l:
SELECT * FROM Employees_l;
employee_id| first_name|last_name|job_id
|salary
I
------------ +----------- +---------- +----------- +--------- +
139|John
145|John
110|John
2111 John
210 1 John
I
I
I
I
I
Seo
Russell
Chen
Poop
Connor
IST_CLERK
I 2700.001
|SA_MAN
I 14000.00|
I FI ACCOUNT| 9850.00 1
|MK_MAN
I 10000.00 |
|PU_MAN
I 8500.001
16.2. Использование динамических DML-операторов
Динамический DML-оператор может содержать предложение RETURNING
для возврата данных о строке, к которой он был применен. Оператор, который
содержит это предложение, должен изменять только одну строку, в против
ном случае возникнет ошибка. При отсутствии предложения RETURNING
динамический DML-оператор будет выполнен, но не вернет результат.
В листинге 16.6 приведен пример формирования и выполнения динамиче
ского оператора UPDATE для присвоения нового значения столбца salary у
сотрудника с заданным значением столбца employee_id.
PostgreSQL: SQL + PL/pgSQL
ф PostgreSQL
Листинг 16.6. Пример формирования и выполнения
динамического оператора UPDATE
DO $$
DECLARE
sql_stmt text;
v_emp_id integer := 107;
v_emp_idl integer;
v_add_salary numeric(9,2) := 1000;
v_emp_salary numeric(9,2);
v_f_name
text ;
v_ j ob_id
text ;
BEGIN
sql_stmt:='UPDATE employees SET salary = salary + $1
WHERE employee_id = $2
RETURNING employee_id, first_name, job_id, salary';
EXECUTE sql_stmt
INTO v_emp_idl, v_f_name, v_job_id, v_emp_salary
USING v_add_salary, v_emp_id;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'emp_id= %%%%%%%', v_emp_idl, 'f_name=',
v_f_name, 'job_id=', v_job_id, 'salary=', v_emp_salary;
End $$;
Результат:
emp_id = 107 f_name = Diana
job_id = IT_PROG
salary = 6200.00
Рассмотрим особенности формирования динамических DML-операторов
в том случае, если имена таблиц и столбцов, которые участвуют в DMLоператоре, являются параметрами.
Использовать ссылки на такие параметры нельзя, поэтому нужно опреде
лить в блоке соответствующие переменные, присвоить им значения и ис
пользовать в строке динамического оператора, применяя конкатенацию.
Например, оператор DELETE в этом случае должен быть записан следую
щим образом:
'DELETE FROM '||{имя таблицы)||' WHERE '||{имя столбца)I|' =
'''II{значение}I I'’’';
где: {имя таблицы}, {имя столбца}, {значение} — переменные, которым
присвоены соответствующие значения.
Глава 16. Встроенный динамический SQL
Листинг 16.7. Пример формирования и выполнения
динамического оператора DELETE, который использует
имена таблиц и столбцов в качестве значений переменных
DO $$
DECLARE
sql_stmt text;
v_tab_name text := 'Employees_Copy';
v_col_name text := 'first_name' ;
v_col_value text := 'Clara';
v_ret_name text := 'employee_id';
v_ret_value text;
BEGIN
sql_stmt := 'DELETE FROM ' | | v_tab_name | Г
WHERE 'I Iv_col_name||' = '''|Iv_col_valueI I''''I I
'RETURNING '||v_ret_name;
EXECUTE sql_stmt
INTO v_ret_value;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'Был удален сотрудник';
RAISE NOTICE '% ', v_ret_name ||'='||v_ret_value;
END $$;
Результат:
Был удален сотрудник
employee_id = 162
В этом примере следует обратить внимание на формирование условия
удаления:
' WHERE 'I Iv_col_name||' = '''||v_col_value| | ' ' ' ' | |
На первый взгляд может показаться, что это условие может быть сформиро
вано следующим образом:^
' WHERE ' I Iv_col_name| | ' = ' | |v_col_value
Но после подстановки значений переменных мы получим следующий опе
ратор:
447
PostgreSQL: SQL + PL/pgSQL
DELETE FROM Employees_Copy
WHERE first_name = Clara;
При выполнении этого оператора возникнет ошибка, так как строковые зна
чения должны быть заключены в кавычки.
Если в листинге указать столбец, имеющий числовой тип, например
departmentid, то будет сформирован оператору
DELETE FROM Employees_Copy
WHERE department_id = '10’;
При выполнении этого оператора ошибка не возникает, а будет выполнено
неявное преобразование символьного значения в числовое.
При работе с динамическими командами часто приходится использовать
экранирование одинарных кавычек. Эту задачу можно упростить, используя
специальные функции:
•
QUOTE IDENT(text) — возвращает заданную строку, подходящую для
использования в операторе SQL в качестве идентификатора. Если строка
содержит значение, не являющееся идентификатором, то возвращаемое
значение обрамляется одинарными кавычками.
• QUOTELITERAL(text)— возвращает заданную строку, заключенную
в кавычки, подходящую для использования в операторе SQL в качестве
строкового литерала.
Листинг 16.8. Пример формирования динамического
оператора с использованием функций quote_ident() и
quote_literal()
DO $$
DECLARE
sql_stmt text;
v_tab_name text := 'employees_copy';
v_col_name text := 'first_name' ;
v_col_value text := 'Bruce';
v_ret_name text := 'employee_id';
v_ret_value text;
BEGIN
Глава 16. Встроенный динамический SQL
sql_stmt := 'DELETE FROM ' | | quote_ident (v_tab_name)
I I' WHERE 'I Iquote_ident(v_col_name) ||' = '
I Iquote_literal(v_col_value)
I I' RETURNING '| |v_ret_name;
EXECUTE sql_stmt
INTO v_ret_value;
RAISE NOTICE 'Результат:';
RAISE NOTICE 'Был удален сотрудник';
RAISE NOTICE '% % %', v_ret_name,'=', v_ret_value;
END $$;
Результат
Был удален сотрудник
employee_id = 104
Несмотря на то, что операторы SQL не чувствительны к регистру в именах
таблиц и столбцов, при указании имени таблицы EmployeesCopy было по
лучено сообщение об ошибке: отношения с таким именем не найдено.
Можно создать процедуру, содержащую динамический оператор, кото
рая принимает в качестве параметров имя таблицы, имя столбца, значение
столбца и удаляет строки, удовлетворяющие заданному условию. В листин
ге 16.9 приведен код создания такой процедуры.
Листинг 16.9. Создание процедуры, которая принимает
в качестве параметров имена таблиц и столбцов и
использует их в динамическом операторе
CREATE OR REPLACE PROCEDURE Del_in_Tab(
p_tab_name text,
p_col_name text,
p_col_value text,
p_ret_name text,
p_ret_value OUT text)
AS $$
DECLARE
sql_stmt text;
BEGIN
sql_stmt := 'DELETE FROM '
|| quote_ident(p_tab_name)
I I' WHERE 'I Iquote_ident(p_col_name) | | ' = '
|| quote_literal(p_col_value)
I I ' RETURNING ' | |p ret name;
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
EXECUTE sql_stmt
INTO p_ret_value;
END $$ LANGUAGE plpgsql;
Процедура ОеІіпТаЬ принимает в качестве параметров: рІаЬпаше —
имя таблицы, р_со!_пате — имя столбца, рсоіѵаіие — значение столбца.
В процессе выполнения процедуры из таблицы рІаЬпаше удаляется стро
ка, для которой столбец р соі паше имеет значение р_со!_ѵа1ие, и возвра
щается значение столбца ргеіпаше.
Листинг 16.10. Вызов процедуры Del_in_Tab
do
$$
DECLARE
v_tab_name text:='customers';
v_col_name text:='c_name';
v_col_value text:='Daimler';
v_ret_name text: ='cus tomer_id';
v_ret_value text;
BEGIN
RAISE NOTICE 'Результат:';
CALL Del_in_Tab (v_tab_name, v_col_name,
v_col_value,v_ret_name,v_ret_value);
RAISE NOTICE 'Удален % % %', v_ret_name,' = ', v_ret_value;
END $$;
Результат:
Удален customer_id = 7
16.3. Формирование имени вызываемой
процедуры или функции
Рассмотрим использование динамических операторов для формирования
имени вызываемой процедуры или функции.
Каждый заказ может находиться в одном из 3 возможных состояний:
1. Canceled — заказ отменен;
2. Pending — заказ находится в состоянии ожидания;
3. Shipped — заказ отправлен клиенту.
о............................................................. "
Глава 16. Встроенный динамический SQL
Для вывода информации о заказах, содержащихся в таблице Orders, созданы
следующие функции:
• FUNCTION FOrderCanceled — возвращает сумму заказа;
• FUNCTION FOrderPending — возвращает количество дней, прошед
ших с момента оформления заказа;
• FUNCTION FOrderShipped — возвращает адрес клиента.
В листингах 16.11-16.13 приведен код создания этих функций.
Листинг 16.11. Создание функции F_Order_Canceled
CREATE OR REPLACE FUNCTION F_Order_Canceled
(p_ord_id integer)
RETURNS text AS $$
DECLARE
v_stmt text;
v_sum_prod numeric(9,2) ;
BEGIN
SELECT SUM(guantity*unitjprice) INTO v_sum_prod
FROM Orde r_I terns
WHERE order_id = p_ord_id;
v_stmt:='Сумма заказа '||p_ord_id||'
RETURN v_stmt;
END $$ LANGUAGE plpgsql;
='||v_sum_prod;
Листинг 16.12. Создание функции F_Order_Pending
CREATE OR REPLACE FUNCTION F_Order_Pending
(p_ord_id integer)
RETURNS text AS $$
DECLARE
v_s tint text;
v_nd integer;
BEGIN
SELECT TRUNC(CURRENT_DATE - order_date)
FROM Orders
WHERE order_id = p_ord_id;
v_stmt:= 'В ожидании '||v_nd||'
RETURN v_stmt;
END $$ LANGUAGE plpgsql;
INTO v_nd
дней';
[ 451
PostgreSQL: SQL + PL/pgSQL
................................................ w PostgreSQL
Листинг 16.13. Создание функции F_Order_Shipped
CREATE OR REPLACE FUNCTION F_Order_Shipped
(p_ord_id integer)
RETURNS text AS $$
DECLARE
v_stmt text;
v_adr text;
BEGIN
SELECT address INTO v_adr
FROM Orders JOIN Customers USING (customer_id)
WHERE order_id = p_ord_id;
v_s tmt:=v_adr;
RETURN v_stmt;
END $$ LANGUAGE plpgsql;
Используя эти функции, можно выводить информацию о заказах, при этом
выводимая информация будет зависеть от состояния, в котором находится
заказ.
В листинге 16.14 содержится код анонимного блока, в котором использует
ся цикл для курсора, который содержит данные о заказах, оформленных за
определенную дату. На каждой итерации цикла извлекается состояние за
каза и формируется имя функции, которая должна вернуть информацию об
этом заказе.
Листинг 16.14. Вывод данных о заказах с динамическим
формированием имени вызываемой функции
DO $$
DECLARE
cur_orders CURSOR(p_date date) FOR
SELECT order_id, customer_id, status, order_date
FROM Orders WHERE order_date = p_date;
sql_stmtl text;
sql_stmt2 text;
v_message text;
v_status text;
v_date date:='2019-ll-02 ';
BEGIN
RAISE NOTICE 'Результат';
RAISE NOTICE 'order_date = %', v_date;
RAISE NOTICE '%%%%% ',
LPAD('ord_id',7), LPAD('cust_id',7),
LPAD('status',9), LPAD(1order_date',11),
^ 452 J
Глава 16. Встроенный динамический SQL
RPAD('message',ЗО);
FOR v cur IN cur orders(v date)
LOOP
sql_s tmtl:= 'SELECT status
FROM Orders WHERE order_id = $1';
EXECUTE sql_stmtl
INTO v_status
USING v cur.order id;
sql_stmt2='SELECT F_Order_'||v_status
||'(order_id) FROM Orders where order_id = $1';
EXECUTE sql_stmt2
INTO v_message
USING v cur.order id;
RAISE notice '%%%%% ' ,
LPAD(v_cur.order_id::text, 7) ,
LPAD(v_cur.customer_id::text,7),
LPAD(v_cur.status,9),
LPAD(v_cur.order_date::text,11) ,
RPAD(v_message, 30) ;
END LOOP;
END $$;
Результат :
order date = 2019-11-02
ord_id cust_id
status
49
61
Shipped
62
Pending
50
Shipped
51
63
64
52
Canceled
order_date
2019-11-02
2019-11-02
2019-11-02
2019-11-02
message
1 Becton Drive Franklin Lakes,
В ожидании 1385 дней
Building 3, The Heights, Weybr
Сумма заказа 52 = 248460.00
16.4. Использование динамических DDL-операторов
В программах PL/pgSQL, DDL операторы могут выполняться только с ис
пользованием динамических операторов. Рассмотрим несколько примеров
формирования и выполнения динамических операторов, содержащих опе
раторы DDL.
Листинг 16.15. Пример использования динамического
оператора CREATE TABLE
DO $$
DECLARE
453
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
sql_stmt text;
BEGIN ”
sql_stmt : =
'CREATE TABLE DEP_Count
(dep_id integer,
dep_ct integer)';
EXECUTE sql_stmt;
END $$;
В листинге 16.16 приведен пример динамического оператора, который соз
дает таблицу и записывает в нее данные о сотрудниках, занимающих опре
деленную должность.
Листинг 16.16. Пример использования динамического
оператора для создания и заполнения данными таблицы
DO $$
DECLARE
sql_stmt text;
v_job text := 'IT_PROG';
BEGIN
sql_stmt := 'CREATE TABLE EMP_JOB AS SELECT *
FROM EMPLOYEES
WHERE job_id '||'='||quote_literal(v_job);
EXECUTE sql_stmt;
END $$;
При выполнении кода из листинга 16.16 будет создана таблица ЕМРЮВ.
Выведем ее содержимое.
SELECT employee_id, first_name, last_name, job_id, salary
FROM EMP JOB;
employee_id|first_name|last_nameIjob_id |salary |
------------ +----------- +---------- +-------- +-------- +
104| Br-uce
1031 Alexander
106|Valli
107|Diana
216|Diana
219|Den
218|Bruce
105|DAVID
ЦІП
1 Ernst
1Hunold
1Pataballa
1 Lorentz
1 Hutton
1 Weiss
1 Austin
1 Austin
1IT_PROG|6000.001
1IT_PROG|9900.001
1IT_PROG|5808.00|
1 IT_PROG| 6200.00,1
1IT_PROG|9000.001
1IT_PROG|8000.001
1IT_PROG|7000.001
|IT_PROG18800.001
Глава 16. Встроенный динамический SQL
В динамическом операторе CREATE TABLE можно использовать в качестве
параметров имена таблиц, столбцов и значение столбцов. В листинге 16.17
содержится код создания такого оператора.
Листинг 16.17. Пример использования динамического
оператора CREATE TABLE с параметрами
DO $$
DECLARE
sql_stmt text;
v_name text:='department_id';
v_value integer:= 60;
BEGIN
sql_stmt := 'CREATE TABLE EMP_Dep_'||v_value
I I ' AS SELECT * FROM EMPLOYEES WHERE '
I Iv_nameІ Г = ' I Iv_value;
EXECUTE sql_stmt;
END $$;
В результате выполнения динамического оператора из листинга 16.17 будет
создана таблица Emp_Dep_60, которая будет содержать данные о сотрудни
ках отдела 60. Выведем содержимое этой таблицы:
SELECT department_id, employee_id, first_name, last_name, job_id,
salary
FROM Emp_Dep_60;
department_id|employee'_id | first_name I last_name 1 job_ id |salary |
60
60
60
60
60
60
60
60
1
1
1
1
1
1
1
1
104|Bruce
103|Alexander
106|Valli
107|Diana
216|Diana
219|Den
218|Bruce
105|DAVID
1 Ernst
|IT_ PROGI6000.00 I
IHunold
1IT__PROG| 9900.00 |
1Pataballa|IT_ PROG|5808.00 |
1 Lorentz
1IT_ PROG I 6200.00 I
1 Hutton
|IT_ PROG I9000.001
1 Weiss
|IT_ PROG|8000.00|
1 Austin
1IT_ ’PROG |7000.00|
1 Austin
1 IT PROG|8800.00
PostgreSQL: SQL + PL/pgSQL
............................................. Ф PostgreSQL.
Задачи для самостоятельного решения:
Задача 16.1. Создать динамический оператор SELECT для вы
вода значений столбцов employee_id, first_name, last_name в
таблице Employees, для которых выполняется условие: ѵ_соі_
name = v_col_value. Переменной v_col_name присвоить имя
столбца, а переменная v_col_value должна быть равна суще
ствующему значению этого столбца.
Задача 16.2. Создать динамический оператор UPDATE для уве
личения на 1 рейтинга сотрудника, employee_id которого равен
v_emp_id. Вывести значение столбца employeejd и столбца
rating_e после повышения.
Задача 16.3. Создать динамический оператор DELETE для уда
ления строк из копии таблицы Employees. Условие удаления
формируется с использованием столбцов job_id и rating_e. Уда
ление должно быть выполнено при любой комбинации значений
этих столбцов: job_id, rating e, jobjd и rating_e.
Задача 16.4. Создать процедуру, которая имеет параметры:
р_пате1, р_Іуре1, р_пате2, р_1уре2, р_патеЗ, р_1уреЗ, где
параметр р_патеі определяет имя столбца, а рЗуреі его тип.
Процедура должна создать таблицу с этими столбцами, объявив
столбец, определяемый параметром р_пате1, первичным
ключом.
Задача 16.5. Используя процедуру из задачи 16.4, создать
таблицу со столбцами таблицы Employees, значение параметра
p_name1 должно быть равно employee_id. Заполнить эту табли
цу данными, используя динамический оператор.
Задача 16.6. Создайте табличную функцию с параметрами: имя
столбца (p_name), значение столбца (p_value). Функция должна
возвращать данные о заказах (Orders), которые удовлетворяют
заданным значениям параметров.
456
Глава 17.
УПРАВЛЕНИЕ
ТРАНЗАКЦИЯМИ
PostgreSQL: SQL + PL/pgSQL
Транзакция - последовательность операций с БД, которые
составляют логическую единицу работы. Эти операции
связаны между собой и выполняют взаимосвязанные
действия.
Из этого вытекает основное требование к транзакциям: либо все операции
выполняются успешно, либо не выполняется ни одна из них.
Необходимость подобной организации обработки данных можно пояснить
на примере обслуживания клиента, который оформляет заказ на приобрете
ние товаров. Этот процесс можно реализовать следующей последовательно
стью действий:
1. Оформление заказа.
2. Оплата заказа.
3. Передача товаров со склада в службу доставки.
4. Доставка заказа.
Очевидно, что если окажется невозможным выполнить одно из этих дей
ствий, то все остальные действия должны быть отменены.
17.1. Требования к транзакциям
Транзакция должна удовлетворять следующим требованиям (ACID):
• Атомарности (atomicity) — либо все операции, которые входят в тран
закцию, выполняются успешно, либо не выполняется ни одна из них;
•
Целостности (consistency) — транзакция должна переводить базу дан
ных из одного целостного состояния в другое целостное состояние. Це
лостное состояние означает то, что все строки и значения должны удов
летворять требованиям предметной области;
Глава 17. Управление транзакциями
• Изолированности (isolation) — транзакция должна выполняться без вза
имодействия с другими транзакциями. Для этого транзакция не должна
использовать изменения, которые вносят другие транзакции;
• Постоянства (durability) — после успешного завершения транзакции
все внесенные ею изменения должны быть сохранены.
Важным требованием, предъявляемым к транзакциям, является их изолиро
ванность, которое, в краткой форме, можно сформулировать так: параллель
но выполняемые транзакции не должны оказывать влияния друг на друга.
Это требование легко сформулировать, но довольно сложно реализовать.
Перечислим основные проблемы, которые приходится решать для реализа
ции этого требования:
• Проблема потерянного обновления — если разные транзакции одно
временно изменяют одни и те же данные, то одно из этих изменений бу
дет потеряно.
• Проблема промежуточных данных (’’грязное чтение”) — транзакция
считывает данные, которые были изменены параллельной, незавершен
ной транзакцией. Если транзакция, которая изменила данные, будет от
менена, то первая транзакция будет использовать данные, которых нет и
не было в базе данных.
• Проблема несогласованных данных (’’неповторяющееся чтение") —
транзакция дважды считывает одни и те же данные. Если между этими
считываниями параллельная транзакция изменит эти данные, то резуль
таты считывания данных первой транзакцией будут разными.
• Проблема строк-призраков ("фантомное чтение") — эта проблема по
хожа на предыдущую проблему. Отличие состоит в том, что параллельная
транзакция вставляет или удаляет строки.
• Аномалия сериализации — результат успешной фиксации группы тран
закций несовместим со всеми возможными порядками выполнения этих
транзакций по очереди.
Основным средством решения этих и других проблем, которые нужно ре
шить для корректного выполнения параллельных транзакций, является бло
кировка данных на уровне таблиц, строк и столбцов. Но чем выше уровень
блокировки, тем ниже производительность. Параллельным транзакциям
приходится ждать, пока транзакция, которая началась раньше, освободит
PostgreSQL: SQL + PL/pgSQL
данные. Блокировка данных обеспечивается средствами СУБД, но пользова
тель может установить уровень изолированности транзакций.
В таблице 17.1 приведены уровни изолированности транзакций, которые
может установить пользователь, и ограничения, которые они накладывают.
Таблица 17.1. Уровни изолированности транзакций
Неповторяющееся Фантомное
Аномалия
сериализации
чтение
чтение
Уровень
изоляции
Грязное
чтение
READ
UNCOMMITTED
Невозможно
Возможно
Возможно
Возможно
READ
COMMITTED
Невозможно
Возможно
Возможно
Возможно
REPEATABLE
READ
Невозможно
Невозможно
Невозможно
Возможно
SERIALIZABLE
Невозможно
___________________
Невозможно
Невозможно
Невозможно
Стандарт SQL допускает на уровне изоляции READ UNCOMMITTED чте
ние "грязных" (незафиксированных) данных. Однако в PostgreSQL чтение
незафиксированных данных на этом уровне не допускается. Следовательно,
в PostgreSQL уровень READ UNCOMMITTED совпадает с уровнем READ
COMMITTED.
Для установки уровня изоляции транзакции служит команда
TRANSACTION, которая имеет следующий формат:^
SET TRANSACTION {режим транзакции}
Где {режим транзакции} может быть:^
ISOLATION LEVEL {SERIALIZABLE | REPEATABLE READ |
READ COMMITTED | READ UNCOMMITTED}
READ WRITE | READ ONLY
SET
Глава 17. Управление транзакциями
Доступными характеристиками транзакции являются: уровень изоляции
транзакции и режим доступа к транзакции (чтение/запись или только для
чтения). Посмотреть текущий уровень изоляции транзакций можно, исполь
зуя команду:^
SHOW transaction_isolation;
17.2. Команды управления транзакциями
Изменения в базе данных могут быть осуществлены в результате выполне
ния DML-операторов, содержащихся в блоке, операторов процедур, вызыва
емых в этом блоке, и в триггерах, которые были инициированы в процессе
выполнения блока.
Транзакция начинается с выполнения первого SQL-оператора и завершается
при наступлении следующих событий:
• При выполнении команд COMMIT или ROLLBACK.
• При нормальном завершении блока, при этом автоматически фиксируют
ся все изменения, осуществленные в этой транзакции.
• При возникновении ошибок сервера и аварийном завершении сеанса
пользователя, при этом отменяются все изменения, осуществленные в
последней транзакции.
В PostgreSQL транзакция начинается командой BEGIN; и завершается ко
мандой COMMIT;. В блоках PL/pgSQL (анонимных, процедурах, функ
циях, триггерах) транзакция начинается неявно, в начале блока, и может за
вершиться неявно, при завершении работы блока, либо завершается явно
командами COMMIT или ROLLBACK
Для управления транзакциями в PostgreSQL используются следующие команды:
• COMMIT
• SAVEPOINT
• ROLLBACK ТО SAVEPOINT
• ROLLBACK
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
1 7.2.1. Команда COMMIT
Фиксирует все изменения, осуществленные в текущей
транзакции, и имеет следующий синтаксис:^
COMMIT [AND[NO]CHAIN]
Если указано AND CHAIN, то запускается новая транзакция с теми же харак
теристиками транзакции. Команда COMMIT снимает все блокировки, уста
новленные в текущем сеансе, и удаляет все точки сохранения, установлен
ные командой SAVEPOINT.
1 7.2.2. Команда SAVEPOINT
Устанавливает точку сохранения внутри транзакции. Это
позволяет отменить изменения, которые будут выполнены
после точки сохранения, и сохранить изменения, которые
были выполнены до точки сохранения.
Синтаксис команды SAVEPOINT:
SAVEPOINT {имя точки сохранения]
Точка сохранения — это специальная метка внутри
транзакции, которая позволяет выполнять откат всех команд,
выполняемых после ее создания, восстанавливая состояние
транзакции до того, каким оно было на момент создания
точки сохранения.
Точки сохранения могут быть установлены только внутри блока транзакции.
В транзакции может быть определено несколько точек сохранения.
17.2.3. Команда ROLLBACK ТО SAVEPOINT
Глава 17. Управление транзакциями
Отменяет все изменения, осуществленные поле
определенной точки сохранения, и имеет следующий
синтаксис:
ROLLBACK [TRANSACTION] TO [SAVEPOINT]
{имя точки сохранения}
После выполнения этого оператора точка сохранения остается активной, и
при необходимости откат можно будет повторить. Транзакция не заканчи
вается. Для уничтожения точки сохранения используется команда release
SAVEPOINT. Взаимодействие операторов SAVEPOINT и ROLLBACK ТО
SAVEPOINT можно пояснить на следующем примере:^
BEGIN;
{операторы DML1}
SAVEPOINT Al;
{операторы DML2}
SAVEPOINT А2;
{операторы DML3}
ROLLBACK ТО {X} ;
COMMIT;
Если значение {X} будет равно А1, то после завершения транзакции коман
дой COMMIT сохранятся только те изменения, которые были осуществлены
в результате выполнения последовательности операторов DML1.
Если значение {X} будет равно А2, то после завершения транзакции коман
дой COMMIT сохранятся только те изменения, которые были осуществлены
в результате выполнения последовательностей операторов DML1 и DML2.
Если команда ROLLBACK ТО SAVEPOINT будет отсутствовать, то сохра
нятся все изменения, осуществленные в этой транзакции.
Если будет отсутствовать команда COMMIT, то транзакция останется неза
вершенной.
17.2.4. Команда ROLLBACK
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Команда ROLLBACK отменяет все изменения, которые были
внесены в базу данных в текущей транзакции, и работа блока
PL/pgSQL, который содержит эту транзакцию, завершается.
ROLLBACK [TRANSACTION]
Если указано AND CHAIN,
теристиками транзакции.
[AND[NO]CHAIN]
to
запускается новая транзакция с теми же харак
17.3. Примеры использования команд
управления транзакциями
Рассмотрим примеры транзакций и использования команд управления тран
закциями. В этих примерах будут выполняться операции по вставке данных
о новых заказах и о товарах, входящих в заказы. Были созданы копии таблиц
Orders (Orders_3) и Order items (Order_Items_l). До выполнения транзак
ций эти таблицы не содержали данных.
Листинг 17.1. Транзакция, которая содержит операторы
вставки данных о новых заказах и их содержимом
BEGIN;
INSERT INTO 0rders_3
(order_id, customer_id, status, salesman_id, order_date)
VALUES (135, 11, 'Pending', 147, '2019-09-27');
INSERT INTO Order_Items_l
(order_id, item_id, product_id, quantity, unit_price)
VALUES (135, 1, 78, 10, 1360);
INSERT INTO Orders_3
(order_id, customer_id, status, salesman_id, order_date)
VALUES (137,10, 'Pending', 145, '2019-09-27');
INSERT INTO Order_Iterns_1
(order_id, item_id, product_id, quantity, unit_price)
VALUES (137, 1, 78, 20, 1380);
INSERT INTO Order_Items_l
(order_id, item_id, product_id, quantity, unit_price)
Глава 17. Управление транзакциями
VALUES (137, 2, 38, 10, 1000);
INSERT INTO Order_Items_l
(order_id, item_id, product_id, quantity, unit_price)
VALUES (137, 3, 21, 7, 500);
COMMIT;
Для того чтобы выполнить эту транзакцию, следует на панели инструмен
тов нажать кнопку Выполнить SQL-скрипт, а не кнопку Выполнить SQLзапрос, которой мы пользовались ранее.
Для того чтобы проверить результат выполнения этой транзакции, выведем
основные данные о заказах и их содержимом.
SELECT ос.order_id,order_date ,item_id,product_id,quantity,
unit_price
FROM Orders_3 oc JOIN Order_Items_l oi ON (oc.order_id=oi.
order_id)
WHERE order date = '2019-09-27';
order_id|order_date|item_id|product_idI quantity Iunit_price|
13512019-09-271
137|2019-09-27|
137 12019-09-27|
13712019-09-27|
11
11
21
31
78|
78 |
38 |
21|
10|
20 |
10|
7|
1360.001
1380.001
1000.00 |
500.001
Анализ этих данных показывает, что все операции транзакции были успеш
но выполнены.
Удалим данные из этих таблиц и выполним транзакцию, которая будет со
держать команды SAVEPOINT и ROLLBACK ТО SAVEPOINT.
Листинг 17.2. Транзакция, которая содержит операторы
вставки данных о новых заказах и их содержимом и
команды SAVEPOINT и ROLLBACK ТО SAVEPOINT
BEGIN;
INSERT INTO Orders_3
(order_id, customer_id, status, salesman_id, order_date)
VALUES (135, 11, 'Pending', 147, '2019-09-27');
INSERT INTO
Order_Items_l
PostgreSQL: SQL + PL/pgSQL
.................................................
w
PostgreSQL
(order_id, item_id, product_id, quantity, unit_price)
VALUES (135, 1, 78, 10, 1360);
INSERT INTO 0rders_3
(order_id, customer_id, status, salesman_id, order_date)
VALUES (137,10, 'Pending', 145, '2019-09-27');
SAVEPOINT Al;
INSERT INTO Order_Items_l
(order_id, item_id, product_id, quantity, unit_price)
VALUES (137, 1, 78, 20, 1380);
INSERT INTO Order_Items_l
(order_id, item_id, product_id, quantity, unit__price)
VALUES (137, 2, 38, 10, 1000);
ROLLBACK TO Al;
INSERT INTO Order_Items_l
(order_id, item_id, product_id, quantity, unit__price)
VALUES (137, 3, 21, 7, 500);
COMMIT;
Выведем данные о заказах после выполнения этой транзакции:^
SELECT ос.order_id,order_date,item_id,product_id,quantity,
unit_price
FROM Orders_3 oc JOIN Order_Items_l oi ON
(oc.order_id=oi.order_id)
WHERE order_date = '2019-09-27';
order_id|order_date|item_id|product_id|quantity Iunit_price|
--------- 1------------ 1--------- 1------------ 1---------- 1------------ p
135|2019-09-27|
137|2019-09-27|
1|
3|
78|
21|
10|
7|
1360.001
500.001
Анализ этих данных показывает, что операции вставки данных о товарах 78
и 38 в заказ 137 были отменены.
Команды SAVEPOINT и ROLLBACK ТО SAVEPOINT нельзя исполь
зовать в блоках PL/pgSQL: анонимных блоках, процедурах, функциях,
триггерах.
466
Глава 17. Управление транзакциями
17.4. Использование команд COMMIT и
ROLLBACK в блоках PL/pgSQL
Рассмотрим примеры использования команд COMMIT и ROLLBACK для
управления транзакциями в блоках PL/pgSQL.
Ранее мы рассматривали реализацию ограничения: зарплата сотрудника не
может быть больше максимально допустимой зарплаты по должности, ко
торую он занимает. Это ограничение было реализовано созданием тригге
ра Tr_Emp_Merge (см. листинги 15.13 и 15.14), который связан с таблицей
Employees. Рассмотрим анонимный блок, который содержит операции для
увеличения зарплаты сотрудника.
Листинг 17.3. Увеличение зарплаты сотрудника
DO $$
DECLARE
v_emp_id integer:=107;
v_add numeric(8,2):=7000;
v_job varchar(lO);
v_sal numeric(8,2);
v_max_sal numeric(8,2);
BEGIN
SELECT job_id,salary INTO v_job, v_sal
FROM Employees_Copy
WHERE employee_id = v_emp_id;
SELECT max_salary into v_max_sal
FROM Jobs
WHERE job_id = v_job;
RAISE NOTICE 'Результат: ';
RAISE NOTICE 'employee_id= %%%%%', v_emp_id,
v_sal,'max_salary=', v_max_sal;
RAISE NOTICE 'add_salary = %' , v_add;
'salary=',
UPDATE Employees_Copy
SET salary = salary + v_add
WHERE employee_id = v_emp_id;
SELECT salary into v_sal
FROM employees_copy
WHERE employee_id = v_emp_id;
RAISE NOTICE 'employee_id= %%%%%', v_emp_id,
'new salary=', v_sal,'max_salary=', v_max_sal;
END $$;
[ 467
PostgreSQL: SQL + PL/pgSQL
.................................................. f PostgreSQL
Результат:
employee_id = 107 salary = 5400.00 max salary = 10000.00
add_salary = 7000.00
employee_id = 107 new salary = 12400.00 max_salary = 10000.00
Из этих результатов следует, что новая зарплата сотрудника превышает
предельно допустимое значение. Это произошло, потому что соблюдение
ограничения обеспечивалось триггером Tr_Emp_Merge, который связан с
таблицей Employees. А в листинге 17.3 используется ее копия — Employees_
Сору, и для этой таблицы триггер работать не будет.
Рассмотрим реализацию рассматриваемого ограничения с помощью команд
управления транзакциями: COMMIT и ROLLBACK.
Перед выполнением блока из листинга 17.4 зарплата сотрудника 107 была
возвращена в исходное состояние.
Листинг 17.4. Увеличение зарплаты сотрудника с
использованием команд COMMIT и ROLLBACK, для
реализации рассматриваемого ограничения
DO $$
DECLARE
v_emp_id integer:=107;
v_add numeric(8,2):=2000;
v_job varchar(lO);
v_sal numeric (8,2);
v_max_sal numeric(8,2);
BEGIN
SELECT job_id,salary INTO v_job, v_sal
FROM Employees_Copy
WHERE employee_id = v_emp_id;
SELECT max_salary into v_max_sal
FROM Jobs
WHERE job_id = v_job;
RAISE NOTICE 'employee_id= %%%%%', v_emp_id,
v_sal,'max_salary=', v_max_sal;
RAISE NOTICE 'add_salary = %' , vadd;
UPDATE Employees_Copy
SET salary = salary + v_add
WHERE employee id = v_emp_id;
'salary=',
Глава 17. Управление транзакциями
SELECT salary into v_sal
FROM employees_copy
WHERE employee_id = v_emp_id;
IF v_sal > v_max_sal
THEN ROLLBACK;
RAISE NOTICE 'Увеличение зарплаты отменено';
ELSE COMMIT;
END IF;
SELECT salary INTO v_sal
FROM Employees_Copy
WHERE employee_id = v_emp_id;
RAISE NOTICE 'employee_id= %%%%%', v_emp_id,
'new salary=', v_sal, 'max_salary=', v_max_sal;
END $$;
Результат:
employee_id = 107 salary = 5400.00 max_salary = 10000.00
add_salary = 2000.00
employee_id = 107 new salary = 7400.00 max_salary = 10000.00
В этом примере размер увеличения зарплаты (add salary) был задан таким,
что не произошло превышения предельно допустимого значения, поэтому
увеличение зарплаты было успешно выполнено.
Выполним еще одно увеличение зарплаты сотрудника 107.
Результат:
employee_id = 107 salary = 7400.00 max_salary = 10000.00
add_salary = 5000.00
Увеличение зарплаты отменено.
employee_id = 107 new salary = 7400.00 max_salary = 10000.00
В этом случае произошло превышение предельно допустимого значения,
поэтому увеличение зарплаты было отменено.
Каждая итерация циклического процесса является неявно объявляемой
транзакцией, поэтому она может завершаться командами COMMIT или
ROLLBACK. Если итерация завершилась командой COMMIT, то измене
ния фиксируются. Если итерация завершилась командой ROLLBACK, то
изменения, произошедшие на этой итерации, отменяются, и осуществляется
переход к следующей итерации.
[ 469
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Рассмотрим задачу: для каждого отдела определен лимит заработной платы.
Необходимо увеличить зарплату сотрудников в каждом отделе на заданную
величину, но если после этого будет превышен лимит заработной платы от
дела, то повышение заработной платы у сотрудников этого отдела необходи
мо отменить.
В рассматриваемом решении этой задачи была использована копия таблицы
Employees (Employees Copy). Была создана таблица Dep_Max_Sum_Sal,
которая содержит лимит заработной платы для каждого отдела.
Выведем содержимое таблицы Dep Max Sum Sal
SELECT * FROM Dep_Max_Sum_Sal
ORDER BY department_id;
department_id|max_sum_salI
--------------- +------------- +
101
20 |
30 |
40 |
50 |
60 |
70 |
80 |
14400.00 |
25000.00 |
33000.00 |
6500.00 |
143700.00 |
26108.00 |
30000.00 |
502992.001
В листинге 17.5 приведен код создания хранимой процедуры, которая реа
лизует рассматриваемый алгоритм изменения зарплаты. Эта процедура со
держит цикл по номерам отделов. На каждой итерации этого цикла изменя
ется зарплата сотрудников отдела и проверяется рассматриваемое правило.
Если оно нарушено, то изменения отменяются, если нет, то изменения фик
сируются. После этого происходит переход к следующей итерации цикла.
Листинг 17.5. Создание хранимой процедуры для увеличения
заработной платы сотрудников, с контролем лимита
заработной платы
CREATE OR REPLACE PROCEDURE Dep_Add_Sal(add_sal numeric)
AS $$
DECLARE
v_sum__sal numeric (10,2) ;
v max sal numeric(10,2);
Глава 17. Управление транзакциями
v_dep_id integer;
BEGIN
v_dep_id:=10;
LOOP
UPDATE employees_copy
SET salary=salary + add_sal
WHERE department_id = v_dep_id;
SELECT SUM(salary) INTO v_sum_sal
FROM employees_copy
WHERE department_id = v_dep_id;
SELECT max_sum_sal into v_max_sal
FROM Dep_max_sum_sal
WHERE department_id = v_dep_id;
IF v_sum_sal > v_max_sal
THEN ROLLBACK;
END IF;
COMMIT;
v_dep_id:=v_dep_id +10;
EXIT WHEN v_dep_id > 80;
END LOOP;
END $$ LANGUAGE plpgsql;
Выведем суммарную зарплату каждого отдела до повышения заработной
платы:
SELECT department_id, SUM(salary)
FROM Employees_copy
GROUP BY department_id
ORDER BY department_id
department_id|sum
4400.001
10|
20 I 19000.001
30 I 33000.001
40 I
6500.001
50 I 143700.00 I
60| 26108.001
70| 11000.001
80 I 302992.00 I
471
PostgreSQL: SQL + PL/pgSQL
PostgreSQL
Вызовем процедуру повышения заработной платы Вер Аёс1_8аі:
DO $$
DECLARE
v_add_salary numeric(8,2):=500;
BEGIN
CALL Dep_Add_Sal(v_add_salary) ;
END $$;
и выведем суммарную зарплату каждого отдела после выполнения этой процедуры:^
SELECT depar tment_id, SUM(salary)
FROM Employееs_copy
GROUP BY department_id
ORDER BY department_id;
department_idI sum
I
--------------- +---------- +
10|
5900.001
20| 22000.001
30| 33000.001
40|
6500.001
50 1143700.00 I
60| 26108.001
70| 12500.001
801350992.001
Анализ этих результатов позволяет установить, что рассматриваемая задача
успешно решена. В отделах, для которых заданное правило было выполнено
(10, 20, 70, 80), зарплата сотрудников увеличена, а в отделах, для которых за
данное правило было нарушено (30, 40, 50, 60), зарплата сотрудников оста
лась прежней.
17.5. Управление транзакциями в ВВеаѵег
ВВеаѵег поддерживает два режима для внесения изменений в базу данных:
472
Глава 17. Управление транзакциями
1. Автоматическая фиксация (автокоммит) — все изменения, которые
были осуществлены в результате выполнения DML-оператора или
PL/pgSQL-блока, немедленно переносятся в базу данных.
2. Ручная фиксация (ручной коммит) — требуется подтверждение поль
зователя, который может либо подтвердить изменения (COMMIT), либо
отклонить их (ROLLBACK).
Выбор режима осуществляется в раскрывающемся списке Транзакции,
расположенном на основной панели инструментов DBeaver, рис. 17.1.
Режим автоматической фиксации используется по умолчанию. Выбор ре
жима ручной фиксации активирует две кнопки на панели инструментов:
Commit и Rollback. Нажимая на эти кнопки, пользователь может принять
или отклонить изменения, которые осуществила транзакция.
Рис. 17.1. Выбор режима внесения изменений
17.5.1. Режим интеллектуальной фиксации (Smart Commit
Mode)
Если включена интеллектуальная фиксация, то в режиме автоматической
фиксации при попытке выполнить любой запрос на изменение данных
(INSERT, UPDATE, MERGE, DELETE) DBeaver переключится в режим руч
ной фиксации и потребует подтвердить или отменить изменения путем на
жатия на кнопки Commit и Rollback.
473
PostgreSQL: SQL + PL/pgSQL
РозІдгеЗа...
Как для автоматического режима, так и для ручного режима фиксации тран
закций можно выбрать режим изоляции транзакций (рис. 17.1).
17.5.2. Журнал транзакций
В журнале транзакций отображаются все транзакции,
сделанные во время текущего сеанса ОВеаѵег.
Чтобы открыть журнал транзакций, нажмите кнопку Журнал транзакций
на панели инструментов (рис. 17.2) и в раскрывающемся меню выберите
команду Журнал транзакций.
Рис. 17.2. Выбор команды "Журнал транзакций "
В окне журнала транзакций (рис. 17.3) отображаются транзакции, которые:
•
выполняются или ожидаются — отображаются без какого-либо специ
ального цвета;
• успешно зафиксированы — отображаются зеленым цветом;
•
отменены — отображаются красным цветом.
474
Глава 17. Управление транзакциями
Рис. 17.3. Журнал транзакций
Чтобы просмотреть все предыдущие транзакции во время текущего сеанса,
установите флажок Показывать предыдущие транзакции. Чтобы просмо
треть все запросы, установите флажок Показывать все запросы.
475
PostgreSQL: SQL + PL/pgSQL
Задачи для самостоятельного решения:
Задача 17.1. В анонимном блоке введите данные о новом заказе
и добавьте данные о 3 товарах в этот заказ (orderjd, item id,
productjd, quantity, unit_price). После выполнения этих опера
ций проверьте: если сумма заказа не превышает значения
v_sum_max, то эти операции фиксируются, в противном случае
отменяются. Приведите примеры выполнения этой транзакции с
разными исходными данными, обеспечивающими как успешное
завершение транзакции, так и ее отмену.
Задача 17.2. Выполнить операцию создания нового заказа и доба
вить в него строки из вспомогательной таблицы Items. После этого
проверить, не содержатся ли в заказе товары, которые запреще
но приобретать данному клиенту (таблица Limit_Customer). Если
ограничения не нарушены, то зафиксировать добавление заказа, в
противном случае отменить операцию добавления заказа.
Задача 17.3. Используя операторы управления транзакциями,
обеспечить выполнение следующего ограничения: зарпла
та сотрудника должна укладываться в диапазон min_salary
- max_salary для должности, которую он занимает. Значения
min_salary, max_salary для каждой должности содержатся в
таблице Jobs.
Задача 17.4. Используя цикл, увеличить на 10% зарплату
сотрудников. В конце каждой итерации проверять, не выходит
ли новая зарплата за допустимые пределы, определенные для
должности, которую занимает сотрудник (столбец max_salary
в таблице Jobs). В случае нарушения отменить изменение
зарплаты этому сотруднику.
Задача 17.5. В задачу 17.4 внести следующие изменения: за
дано предельно допустимое суммарное увеличение заработной
платы ѵ_абд_зит_5аіагу. В конце каждой итерации проверять,
не исчерпан ли фонд повышения заработной платы (ѵ_адб_
5ит_5аіагу), и если да, то отменить изменение зарплаты
последнему сотруднику и выйти из цикла.
Список использованных источников:
1. DBeaver. Universal Database Managers and SQL Clients. [Электронный ресурс] :
официальный сайт - https://github.com/dbeaver/dbeaver/wiki.
2. PostgreSQL [Электронный ресурс] : официальный сайт / The PostgreSQL Global
Development Group. - https://www.postgresql.org.
3. Postgres Professional [Электронный ресурс] : российский производитель СУБД
Postgres Pro : официальный сайт / Postgres Professional. - https://postgrespro.ru.
4. Грофф, Дж. SQL. Полное руководство : пер. с англ. / Джеймс Р. Грофф, Пол Н.
Вайнберг, Эндрю Дж. Оппель. - 3-е изд. - М. : Вильямс, 2015. - 960 с.
5. Лузанов П. В. Postgres. Первое знакомство / П. В. Лузанов, Е. В. Рогов, И. В.
Левшин. - 9-е изд., перераб. и доп. - М.: Постгрес Профессиональный, 2023. - 179 с.
6. Моргунов Е. П. PostgreSQL. Основы языка SQL : учеб, пособие / Е. П. Моргунов ;
под ред. Е. В. Рогова, П. В. Лузанова. - СПб. : БХВ-Петербург, 2018. - 336 с.
7. Ткачев О. А. Основы программирования в СУБД Oracle: SQL+PL/SQL. /
О. А. Ткачев - Издательские решения, 2020. - 456с.
8. https://www.freepik.com/free-vector/abstract-background-white-color_2465590.htm#query=abstract%20
bacground%20white%20color%20cube&position=26&from_view=search&track==ais
Издательство «Наука и Техника» выпускает книги более 25 лет!
Уважаемые авторы!
Приглашаем к сотрудничеству по созданию книг
по ІТ-технологиям, электронике, электротехнике, медицине, педагогике.
Наши преимущества:
•
•
•
•
•
•
•
являемся одним из ведущих технических издательств страны;
выпускаем книги большими тиражами, что положительно влияет на гонорар авторов;
регулярно переиздаем тиражи, автоматически выплачивая гонорар за каждый тираж;
применяем индивидуальный подход в работе с каждым автором;
работаем профессионально: от корректуры до авторских дизайн-проектов;
проводим политику доступной цены;
имеем собственные каналы сбыта: от федеральных сетей, крупнейших книжных
магазинов РФ, ведущих маркетплейсов ОЗОН, Wilclberries, Яндекс-Маркет
и др. до ведущих библиотек вузов, ссузов.
Ждем Ваши предложения:
•
тел. (812) 412-70-26
•
эл. почта: nitmail@nit.com.ru
Будем рады сотрудничеству!
Для заказа книг:
>
интернет-магазин: nit.COm.ru
•
>
>
>
более 3000 пунктов выдачи на территории РФ, доставка 3-5 дней
•
более 300 пунктов выдачи в Санкт-Петербурге и Москве, доставка 1-2 дня
•
тел. (812) 412-70-26
•
эл. почта nitmail@nit.com.ru
магазин издательства: г. Санкт-Петербург, пр. Обуховской обороны, д. 107
•
метро Елизаровская, 200 м за ДК им. Крупской
•
ежедневно с 10.00 до 18.00
•
справки и заказ: тел. (812) 412-70-26
книжные сети и магазины
•
«Читай-город» - сеть магазинов
тел. +7 (495) 424-84-44
•
«Буквоед» - сеть магазинов
тел.+7 (812) 601-0-601
•
Московский дом книги - сеть магазинов
тел. +7 (495) 789-35-91
•
ТД «БиблиоГлобус»
тел. +7 (495) 781-19-12
•
«Амиталь» — сеть магазинов
тел. +7 (473) 223-00-02
•
Дом книги, г. Екатеринбург
тел. +7 (343) 289-40-45
•
Дом книги, г. Нижний Новгород
тел. +7 (831) 246-22-92
•
Приморский торговый Дом книги
тел. +7 (423) 263-10-54
маркетплейсы ОЗОН, Wildberries, Яндекс-Маркет, Myshop и др.
Ткачев Олег Алексеевич
PostgreSQL:
SQL
+
PL/pgSQL
ДЛЯ ТЕХ, КТО ХОЧЕТ СТАТЬ ПРОФЕССИОНАЛОМ
Группа подготовки издания:
Зав. редакцией компьютерной литературы: М. В. Финков
Редактор: Е. В. Финков
Корректор: А. В. Громова
ООО "Издательство Наука и Техника"
ОГРН 1217800116247, ИНН 7811763020, КПП 781101001
192029, г. Санкт-Петербург, пр. Обуховской обороны, д. 107, лит. Б, пом. 1-Н
Подписано в печать 07.11.2023. Формат 70x100 1/16.
Бумага офсетная. Печать офсетная. Объем 30 п. л.
Тираж 700. Заказ 8029.
Отпечатано с готового оригинал-макета
ООО «Принт-М», 142300, М.О., г.Чехов, ул. Полиграфистов, д.1