Текст
                    
Ткачев О. А. РобтсреЗОЬ ЗОЬ+РирдЭОЬ ДЛЯ ТЕХ, КТО ХОЧЕТ СТАТЬ ПРОФЕССИОНАЛОМ ^вд Х^йздатёльство^Г "Издательство Наука и Техника" Санкт-Петербург
УДК 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