10 Занятие 10: SQL / Составные типы
Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: dev1_10_sql_row.html Archive: DEV1-handouts-16.zip Страниц PDF: 24
Page 1: SQL / Составные типы
Авторские права
© Postgres Professional, 2017–2024 Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.
Page 2: Темы
- Составные типы и работа с ними
- Параметры функций составных типов
- Функции, возвращающие одну строку
- Функции, возвращающие множество строк
Page 3: Составные типы
Составной тип
- набор именованных атрибутов (полей)
- то же, что табличная строка, но без ограничений целостности
- Создание
- явное объявление нового типа
- неявно при создании таблицы
- неопределенный составной тип record
- Использование
- атрибуты как скалярные значения
- операции со значениями составного типа: сравнение, проверка
- на NULL, использование с подзапросами
Комментарии преподавателя
Составной тип — это набор атрибутов, каждый из которых имеет свое имя и свой тип. Составной тип можно рассматривать как табличную строку. Часто он называется «записью» (а в Си-подобных языках такой тип называется «структурой»).
https://postgrespro.ru/docs/postgresql/16/rowtypes
Составной тип — объект базы данных, его объявление регистрирует новый тип в системном каталоге, после чего он становится полноценным типом SQL. При создании таблицы автоматически создается и одноименный составной тип, представляющий строку этой таблицы. Важное отличие состоит в том, что в составном типе нет ограничений целостности.
https://postgrespro.ru/docs/postgresql/16/sql-createtype
Атрибуты составного типа могут использоваться как отдельные значения (хотя атрибут, в свою очередь, тоже может иметь составной тип).
Составной тип можно использовать как любой другой тип SQL, например, создавать столбцы таблиц этого типа и т. п. Значения составного типа можно сравнивать между собой, проверять на неопределенность (NULL), использовать с подзапросами в таких конструкциях, как IN, ANY/SOME, ALL.
https://postgrespro.ru/docs/postgresql/16/functions-comparisons
https://postgrespro.ru/docs/postgresql/16/functions-subquery
Page 4: Явное объявление составного типа
Первый способ ввести составной тип — явным образом объявить его:
Такой тип можно использовать точно так же, как любой другой тип SQL. Например, мы можем создать таблицу со столбцами такого типа:
Нужно ли так делать — вопрос сложный, универсальных рецептов тут не существует. В каких-то случаях это может быть полезно; в каких-то удобнее действовать в реляционных рамках: выделить сущность, представляемую типом, в отдельную таблицу и ссылаться на нее. Это позволить избежать избыточности данных (нормализация) и упростить индексирование (в случае составного типа скорее всего потребуется индекс по выражению). В целом PostgreSQL обладает достаточно большим количеством встроенных типов данных, так что, вероятно, необходимость в создании собственного типа будет возникать не часто, если не сказать редко.
Конструирование значений составных типов
Значения составного типа можно формировать в виде строки, внутри которой в скобках перечислены значения. Обратите внимание, что строковые значения заключается в двойные кавычки:
Другой способ — конструктор ROW:
Если составной тип содержит более одного поля, то слово ROW можно опустить:
Атрибуты составного типа как отдельные значения
Обращение к отдельному атрибуту составного типа — по сути то же, что и обращению к столбцу таблицы, ведь строка таблицы — это и есть составной тип:
Page 5: Page 5
Как правило, требуется брать составное значение в скобки, например, чтобы отличать атрибут записи от столбца таблицы:
Или в случае, когда используется выражение:
Составное значение не обязательно связано с каким-то конкретным типом, оно может быть неопределенной записью псевдотипа record:
Но получится ли обратиться к атрибуту такой записи?
Нет, поскольку атрибуты такого типа безымянные.
Неявный составной тип для таблиц
Более частое на практике применение составных типов — упрощение работы функций с таблицами. При создании таблицы неявно создается и одноименный составной тип. Например, места в кинотеатре:
Команда \dT «прячет» такие неявные типы, но при желании их можно увидеть непосредственно в таблице pg_type:
c — composite, то есть составной тип.
Page 6: Операции над значениями составных типов
Значения составных типов можно сравнивать между собой. Это происходит поэлементно (примерно так же, как строки сравниваются посимвольно):
Осторожно: существует много тонкостей, связанных с неопределенными значениями внутри записей. Также работает проверка на неопределенность IS [NOT] NULL и сравнение IS [NOT] DISTINCT FROM. Составные типы можно использовать с подзапросами, что бывает очень удобно. Добавим таблицу с билетами:
Теперь, например, можно написать такой запрос для поиска мест в билетах на сегодняшний сеанс:
Без возможности использовать подзапрос пришлось бы явно соединять таблицы.
Page 7: Параметры подпрограмм
Подпрограмма может принимать параметры составного типа
- Способ реализации вычисляемых полей
- взаимозаменяемость table.column и column(table)
- Другие способы
- представления
- столбцы GENERATED ALWAYS
Комментарии преподавателя
Разумеется, подпрограммы могут принимать параметры составных типов.
Интересно, что для доступа к столбцу таблицы можно использовать не только привычную форму «таблица.столбец», но и функциональную: «столбец(таблица)». Это позволяет создавать вычисляемые поля, определяя функцию, принимающую на вход составной тип.
https://postgrespro.ru/docs/postgresql/16/xfunc-sql
Это несколько курьезный способ, поскольку того же результата можно добиться более явно с помощью представления. Стандарт SQL также предусматривает генерируемые (GENERATED ALWAYS) столбцы, хотя в PostgreSQL эта возможность пока реализована не в соответствии со стандартом — столбцы не вычисляются на лету, а сохраняются в таблице.
https://postgrespro.ru/docs/postgresql/16/ddl-generated-columns
Page 8: Параметры составного типа
Определим функцию, принимающую значение составного типа и возвращающую текстовый номер места.
Обратите внимание, что в общем случае конкатенация имеет категорию изменчивости stable, а не immutable, поскольку для некоторых типов данных приведение к строке может давать разные результаты в зависимости от настроек.
Что удобно, такой функции можно передавать непосредственно строку таблицы:
Можно обойтись и без «звездочки»:
Синтаксисом допускается обращение к функции как к столбцу таблицы (и наоборот, к столбцу как к функции):
Таким образом можно использовать функции как вычисляемые «на лету» столбцы таблиц. Что, если и в таблице окажется столбец с тем же именем? Раньше в любом случае предпочтение отдавалось столбцу, а начиная с версии 11 выбор зависит от синтаксической формы. Разумеется, такого же эффекта можно добиться, определив представление.
Page 9: Page 9
А начиная с версии 12, PostgreSQL позволяет при создании таблиц объявить «настоящие» вычисляемые столбцы. Правда, в отличие от стандарта SQL, такие столбцы не вычисляются на лету, а сохраняются в таблице:
Если мы впоследствии решили, что следует явно задавать значение поля, то нам достаточно удалить выражение:
Существующие данные в столбце остались, но столбец теперь не вычисляемый, а «обычный»:
Page 10: «Однострочные» функции
Возвращают значение составного типа
- Обычно вызываются в списке выборки запроса
- При вызове в предложении FROM возвращают однострочную таблицу
Комментарии преподавателя
Функции могут не только принимать параметры составного типа, но и возвращать значение составного типа.
Обычно функции вызываются в списке выборки запроса (предложение SELECT).
Но функцию можно вызвать и в предложении FROM, как будто таблицу из одной строки.
Page 11: Функции, возвращающие одно значение
Напишем функцию, конструирующую и возвращающую табличную строку по отдельным компонентам. Такую функцию можно объявить как RETURNS seats:
Мы получаем результат составного типа. Его можно «развернуть» в однострочную таблицу:
Имена столбцов и их типы получены здесь из описания составного типа seats. Но функцию можно вызывать не только в списке выборки запроса или в условиях, как часть выражения. К функции можно обратиться и в предложении FROM, как к таблице:
При этом мы тоже получаем однострочную таблицу. Кстати, можно ли подобным образом вызвать функцию, возвращающую отдельное (скалярное) значение?
Да, так тоже можно: мы получили одну строку, состоящую из одного столбца. Другой вариант, который мы уже видели в теме «SQL. Функции» — объявить выходные параметры. Заодно отметим, что в запросе не обязательно собирать составной тип из отдельных полей — это будет проделано автоматически:
Page 12: Page 12
Получаем тот же результат — но имена и типы полей в данном случае получены из выходных параметров функции, а сам составной тип остается анонимным. И еще один вариант — объявить функцию как возвращающую псевдотип record, который обозначает составной тип «вообще», без уточнения его структуры.
Но вызвать такую функцию в предложении FROM уже не получится, поскольку возвращаемый составной тип не просто анонимный, но и количество и типы его полей заранее (на этапе разбора запроса) неизвестны:
В этом случае при вызове функции структуру составного типа придется уточнить — мы явно указываем названия и типы столбцов:
При написании функций допустим любой из этих трех вариантов, но лучше сразу подумать об использовании: будет ли удобен анонимный тип и уточнение структуры типа при вызове.
Page 13: Табличные функции
Объявляются как RETURNS SETOF или RETURNS TABLE
- Могут возвращать несколько строк
- Обычно вызываются в предложении FROM
- Можно использовать как представление с параметрами
- особенно удобно в сочетании с подстановкой тела функции в запрос
Комментарии преподавателя
Мы знаем, что к функциям можно обращаться в предложении FROM, однако до сих пор результатом была одна строка. Конечно, интереснее было бы иметь функции, возвращающие множество строк (табличные функции) — и их действительно можно определять.
Табличные функции естественно вызывать в предложении FROM и можно рассматривать их как своеобразное представление. (Формально PostgreSQL позволяет вызвать такую функцию и в списке выборки, но так лучше не делать.)
Как и с обычными функциями, в ряде случаев планировщик может подставить тело функции в основной запрос. Это позволяет создавать «представления с параметрами» без накладных расходов.
https://wiki.postgresql.org/wiki/Inlining\_of\_SQL\_functions
Page 14: Функции, возвращающие множество строк (табличные функции)
Напишем функцию, которая вернет все места в прямоугольном зале заданного размера.
Ключевое отличие — слово SETOF. В таком случае функция возвращает не первую строку последнего запроса, как обычно, а все строки последнего запроса.
Вместо SETOF seats можно использовать и SETOF record:
Но в этом случае, как мы видели, при вызове функции придется уточнять структуру составного типа:
А можно объявить функцию с выходными параметрами. Но SETOF record все равно придется написать, чтобы показать, что функция возвращает не одну строку, а множество:
Page 15: Page 15
Еще один равнозначный (и к тому же описанный в стандарте SQL) способ объявить табличную функцию — указать слово TABLE:
Иногда в запросах бывает полезно пронумеровать строки в том порядке, в котором они получены от функции. Для этого есть специальная конструкция:
При использовании функции в предложении FROM, перед ней неявно подразумевается ключевое слово LATERAL, что позволяет функции обращаться к столбцам таблиц, стоящих в запросе слева от нее. Иногда это позволяет упростить формулировку запросов. Например, напишем функцию, конструирующую зал наподобие амфитеатра, в котором дальние ряды имеют больше мест, чем ближние:
Page 16: Page 16
Интересно, что к функции, возвращающей множество строк, можно обратиться и в списке выборки запроса:
В некоторых случаях это смотрится логично, но иногда результат может удивить. Например, сколько строк вернет такой запрос?
Получается 6 строк, а в версиях до 10 получалось наименьшее общее кратное числа строк, возвращенных каждой функцией (12 в данном случае). Еще хуже, что запрос может вернуть меньше строк, чем ожидалось, если функция не вернет ни одной строки при каком-то значении параметров. Поэтому такой способ вызова не стоит использовать.
Функции как представления с параметрами
Как мы видели, функцию можно использовать во фразе FROM как таблицу или представление. Но при этом мы дополнительно получаем возможность использовать параметры, что в ряде случаев бывает удобно. Единственная сложность с таким подходом состоит в том, что при обращении к функции (Function Scan) запросы из нее сначала выполняются полностью, и только затем к результату применяются дополнительные условия из запроса.
Page 17: Page 17
Если бы функция содержала сложный, долгий запрос, это могло бы стать проблемой. В некоторых случаях тело функции может подставляться в вызывающий запрос. Для табличных функций ограничения более мягкие: функция написана на языке SQL; функция сама не должна быть изменчивой (VOLATILE) и не должна содержать вызовов таких функций; функция не должна быть строгой (STRICT); тело должно содержать единственный оператор SELECT (но он может быть сложным); и ряд других ограничений. В нашем случае дело в том, что последний раз мы объявили функцию как изменчивую, не указав категорию изменчивости явно.
Теперь нет вызова функции как такового, а условие подставлено внутрь запроса, что более эффективно.
Page 18: Итоги
Составной тип объединяет значения других типов
- Упрощает и обогащает работу функций с таблицами
- Позволяет создавать вычислимые поля и представления с параметрами
- Функции могут возвращать множество строк
Page 19: Практика
- Создайте функцию onhand_qty для подсчета имеющихся в наличии книг. Функция принимает параметр составного типа books и возвращает целое число.
- Используйте эту функцию в представлении catalog_v
- в качестве «вычисляемого поля».
- Проверьте, что приложение отображает количество книг. 2. Создайте табличную функцию get_catalog для поиска книг. Функция принимает значения полей формы поиска («имя автора», «название книги», «есть на складе») и возвращает подходящие книги в формате catalog_v.
- Проверьте, что в «Магазине» начал работать поиск и
- просмотр.
Комментарии преподавателя
При решении хотелось бы воспользоваться уже готовым представлением catalog_v, просто наложив ограничения на строки. Но в этом представлении и название книги, и авторы находятся в одном поле, к тому же в сокращенном виде. Очевидно, что поиск автора «Лев» по полю «Л .Н. Толстой» не даст результата.
Можно было бы повторить в функции get_catalog запрос из catalog_v, но это дублирование кода, что плохо. Поэтому расширьте представление catalog_v, добавив в него дополнительные поля: заголовок книги и полный список авторов.
Проверьте, что корректно обрабатываются пустые поля на форме. Когда клиент вызывает функцию get_catalog, передает ли он в этом случае пустые строки или неопределенные значения?
Page 20: 1. Функция onhand_qty
2. Функция get\_catalog
Расширяем catalog_v заголовком книги и полным списком авторов (приложение игнорирует неизвестные ему поля). Функция, возвращающая полный список авторов:
Используем эту функцию в представлении catalog_v. Такое представление уже существует; мы пересоздадим его — изменим порядок столбцов и запрос:
Функция get_catalog теперь использует расширенное представление:
Page 21: Page 21
Page 22: Практика+
- Напишите функцию, переводящую строку, содержащую число в шестнадцатеричной системе, в обычное целое число. Например: convert(‘FF’) → 255 2. Добавьте в функцию второй необязательный параметр — основание системы счисления (по умолчанию — 16). Например: convert(‘0110’,2) → 6 3. Табличная функция generate_series не работает со строковыми типами. Предложите свою функцию для генерации последовательностей строк из заглавных английских букв.
Комментарии преподавателя
- Например:
Для решения пригодятся: табличная функция regexp_split_to_table, функции upper и reverse, конструкция WITH ORDINALITY.
Другое решение возможно с помощью рекурсивного запроса.
Проверить реализацию можно, используя шестнадцатеричные константы: SELECT X’FF’::integer;
- Например:
- Считайте, что на вход подаются строки равной длины. Например:
Page 23: 1. Функция для шестнадцатеричной системы
Сначала для удобства определим функцию для одной цифры:
Теперь основная функция:
2. Функция для любой системы счисления
Предполагаем, что основание системы счисления от 2 до 36, то есть число записывается цифрами от 0 до 9, либо буквами от A до Z. В этом случае изменения минимальные.
Заметим, что в PostgreSQL начиная с версии 16 есть возможность записи целочисленных констант не только в десятичном, но и в двоичном, шестнадцатеричном и восьмеричном виде. Такая возможность закреплена в современном стандарте SQL:
Page 24: 3. Функция generate_series для строк
Сначала напишем вспомогательные функции, переводящие строку в числовое представление и обратно. Первая очень похожа на функцию из предыдущего задания:
Обратную функцию напишем с помощью рекурсивного запроса:
Теперь функцию generate_series для строк можно переписать, используя generate_series для целых чисел.