Расширяемость
Слабоструктурированные данные
16
Авторские права
© Postgres Professional, 2017–2024
Авторы: Егор Рогов, Павел Лузанов, Илья Баштанов, Игорь Гнатюк
Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)
Использование материалов курса
Некоммерческое использование материалов курса (презентации,
демонстрации) разрешается без ограничений. Коммерческое
использование возможно только с письменного разрешения компании
Postgres Professional. Запрещается внесение изменений в материалы
курса.
Обратная связь
Отзывы, замечания и предложения направляйте по адресу:
Отказ от ответственности
Компания Postgres Professional не несет никакой ответственности за
любые повреждения и убытки, включая потерю дохода, нанесенные
прямым или непрямым, специальным или случайным использованием
материалов курса. Компания Postgres Professional не предоставляет
каких-либо гарантий на материалы курса. Материалы курса
предоставляются на основе принципа «как есть» и компания Postgres
Professional не обязана предоставлять сопровождение, поддержку,
обновления, расширения и изменения.
2
Темы
Что такое слабоструктурированные данные
Применение в реляционных базах данных
Необходимые операции
Тип данных для XML: xml
Типы данных для JSON: json и jsonb
Индексирование документов JSON
3
Что это такое
Наиболее известные примеры: XML и JSON
Схема данных определяется самим документом
документ содержит разметку для выделения элементов
и определения иерархии
Данные не соответствуют реляционной модели
конкретный документ можно представить в виде таблиц
документы общего вида тоже можно, но практически — неудобно
Слабоструктурированные (semi-structured) данные — это данные,
представленные не в реляционном (табличном) виде. Обычно это
текстовый документ, но в отличие от текста на естественном языке,
документ содержит разметку, определяющую структурные единицы.
Широко известными примерами форматов (языков разметки) являются
XML и JSON, в последнее время также популярен YAML.
Структура, или схема, документа определяется его собственной
разметкой. (Это не исключает возможности определить структуру
отдельно — например, для XML есть языки DTD, XMLSchema.)
Разметка, как правило, позволяет определить вложенные элементы,
образующие иерархию.
Конечно, любую конкретную иерархию можно представить и
в табличном виде. Можно уложить в таблицы и произвольную иерархию
без жесткой схемы, но результат будет лишен типизации данных,
возможности создания ограничений целостности, с ним будет крайне
неудобно работать в терминах запросов SQL. Поэтому с некоторыми
видами данных действительно удобнее и логичнее работать именно
в таком, слабоструктурированном, виде.
4
Применение
Интерфейсный формат
между приложением и внешней системой
между клиентской и серверной частями приложения
Внутри базы данных как атомарное значение
только хранение и извлечение
не нарушает 1NF, достаточно обычных средств SQL
Внутри базы данных как неатомарное значение
операции с отдельными частями документа
SQL не достаточно; требуется специальный язык запросов
и набор дополнительных операций
гибкость, когда данные плохо укладываются в реляционную модель
Документы XML или JSON могут применяться как удобный
интерфейсный формат между разными компонентами, независимый
от конкретной платформы или языка программирования. Например,
для взаимодействия с внешними системами, или для обмена данными
между клиентской и серверной частями системы. В последнем случае
интерфейсный формат позволяет клиенту послать сложный запрос,
а серверу выполнить его эффективным образом (в отличие от
традиционного подхода, принятого в ORM, когда сервер базы данных
нагружается большим количеством мелких запросов, не оставляя
возможностей для оптимизации).
Если слабоструктурированные данные хранятся внутри самой базы
данных, то все зависит от того, как эти данные используются. Если
внутри SQL запросов не делается попыток работать с отдельными
частями документов, то такие значения можно считать атомарными
с точки зрения СУБД. Тут достаточно обычных средств SQL.
Конечно, более интересен случай, когда серверу необходимо выделять
часть документа, фильтровать вывод на основе содержания
документов и т. п. Для этого SQL уже не хватает и нужен
специализированный язык доступа к слабоструктурированным данным.
Если СУБД предоставляет такую возможность, это позволяет
комбинировать строгий реляционный подход для данных, имеющих
четкую структуру, и подход в духе NoSQL для данных, которые сложно
представить в табличном виде.
5
Операции
Преобразование документа к реляционному виду
сохранение транспортного документа в базу данных
использование средств SQL для обработки
Преобразование реляционных данных к виду документа
выгрузка данных в транспортный формат
Выделение части документа
специализированный язык запросов
Индексирование документов
поддержка операций специализированного языка запросов
Чтобы работать со слабоструктурированными документами
в реляционной базе данных, понадобятся возможности для
преобразования данных из одного вида в другой, поскольку в одних
ситуациях удобнее использовать реляционные средства, в других —
документоориентированные. Особенно это важно, если такой язык, как
XML или JSON, используется в качестве транспортного формата.
Кроме этого, нужны средства для выделения части документа —
каких-то отдельных значений, или какого-то подмножества элементов.
Такие средства обычно представлены в виде специализированного
языка запросов, который учитывает иерархическую структуру
документов. Такие языки запросов можно использовать не только для
выделения части документа, но и, например, для выбора тех
документов, в которых присутствует заданный фрагмент.
Если слабоструктурированные документы используются в реляционной
СУБД, встает задача индексирования таких данных. Для этого, как
правило, не годятся обычные B-деревья, поскольку требуется
поддерживать не операции сравнения документов, а операции,
связанные со специализированным языком запросов.
6
Формат XML
eXtensible Markup Language
универсальный язык разметки, первый стандарт 1998 года
форматы на основе XML: XHTML, FB2, RSS и Atom, SVG, SOAP...
Структура
вложенные элементы
отделяются тегами, могут иметь атрибуты, содержат текст
Инструментарий
описание схемы (DTD, XML Schema), языки запросов XPath и XQuery,
язык преобразования XSLT
PostgreSQL: XPath 1.0
XML (eXtensible Markup Language) появился в контексте веба и
разрабатывается консорциумом W3C. XML является текстовым
документом, содержащим специальную разметку. XML определяет
синтаксическую структуру разметки, которая с помощью тегов
выделяет элементы (возможно, вложенные друг в друга), которые
могут также иметь атрибуты. (Язык XML довольно сложен, в нем
имеются также инструкции обработки, комментарии и другие
сущности, но мы не будем на этом останавливаться.)
XML позволяет определить расширения XML, определяя конкретный
набор элементов и их атрибутов, и наделяя их определенной
семантикой. К известным расширениям относятся XHTML (более
строгий HTML, совместимый с XML), FB2 (формат электронных книг),
RSS и Atom (форматы для новостных лент), SVG (векторная графика),
SOAP (формат сообщений веб-сервисов) и другие.
За время существования формата XML сформировался богатый
инструментарий для работы с ним, включающий языки описания схемы
документов (такие, как DTD, XMLSchema), языки запросовазовый
XPath и более богатый XQuery на его основе), языки преобразования
документов XML (XSLT и XSL-FO на его основе).
В реляционных базах работу с XML регламентирует стандарт SQL/XML.
PostgreSQL поддерживает только XPath 1.0. Преобразования XSLT
доступны в расширениях (xml2 или в сторонних). Индексирование
не реализовано (хотя все возможности для этого есть).
7
Тип данных для XML
xml
хранение в виде обычного текста + синтаксическая проверка
сохраняется исходное форматирование
может хранить XML-документ или фрагмент документа
Тип xml хранит документ в виде текстовой строки. Но по сравнению
с типом text он проверяет введенные значения на допустимость
по правилам XML.
Допускается хранение корректно оформленных документов, а также
их фрагментов, в которых может быть несколько элементов верхнего
уровня или текстовых узлов.
Для типа xml не определены никакие операторы сравнения, поэтому
столбец этого типа нельзя проиндексировать с помощью B-tree. Однако
можно, например, использовать индекс по выражению, в котором
вызывается XPath и результат приводится к строке символов.
Существуют и другие ограничения реализации этого типа в PostgreSQL.
9
Формат JSON
JavaScript Object Notation
простой язык разметки, стандарт 1999 года
появился в JavaScript, но распространен повсеместно
Структура
объекты (пары «ключ: значение»), массивы значений
значения текстовые, числовые, даты, логические
Инструментарий
PostgreSQL: язык запросов JSONPath,
неполная пока поддержка стандарта SQL/JSON,
индексирование
Формат JSON — подмножество языка JavaScript, описывающее:
простые значения (текстовые, числовые, даты и логические);
объекты, состоящие из пар «ключ — значение»;
массивы значений.
В двух последних случаях значения могут быть как простыми, так
и объектами или массивами, то есть документ имеет иерархическую
структуру.
Несмотря на то, что JSON — «родной» формат для JavaScript,
он получил огромное распространение благодаря своей простоте
и удобству, и повсеместно используется в современном интернете.
Изначально PostgreSQL предоставлял собственные операции для
работы с JSON; при необходимости более серьезного инструментария
можно было воспользоваться расширением jsQuery. В настоящее
время в PostgreSQL реализована часть стандарта SQL/JSON:2016,
включая язык запросов JSONPath.
Имеется и индексирование документов JSON. Это позволяет
комбинировать реляционные возможности PostgreSQL
с возможностями, предоставляемыми NoSQL-базами.
10
Типы данных для JSON
Json
хранение в виде обычного текста + синтаксическая проверка
сохраняется исходное форматирование
необходимость повторного разбора при каждом запросе
Jsonb
внутренний формат, исключающий повторный разбор
поддержка SQL/JSON (язык запросов JSONPath)
поддержка индексирования
Имеются два типа данных для представления документов JSON: json
и jsonb.
Первый появился раньше и, по сути, просто хранит документ в виде
текстовой строки (при этом, конечно, проверяется, что строка является
корректным документом JSON). Но при любом обращении к части
документа JSON его приходится заново разбирать. Это вызывает
большие накладные расходы. Кроме того, для формата json не
реализован язык JSONPath и не работает индексирование. Поэтому
json применяется редко.
Обычно используется тип jsonb (b — binary). Этот формат сохраняет
однажды разобранную иерархию элементов, что позволяет эффективно
работать с документом. Следует учитывать, что исходный вид
документа при этом не сохраняется: нарушается порядок следования
элементов, пропадают отступы и дублирующиеся ключи.
12
Метод доступа GIN
Инвертированный индекс
метод применим для сложносоставных значений (массив, текст)
API метода доступа определяет, как выделять элементы значения
c cpp gz h html jpg js json md odp odt sh sql svg tgz txt yml
c h js md sh tgz
c
md
log
Идея метода доступа GIN (Generalized Inverted Index) основана
на том, что для сложносоставных значений имеет смысл использовать
в качестве ключей элементы значений, а не значения целиком.
Представьте предметный указатель в конце книги. Страницы книги —
сложносоставные значения (текст), а указатель позволяет ответить
на вопрос «на каких страницах встречается такой-то термин?».
Для хранения элементов в GIN используется обычное B-дерево,
поэтому элементы должны принадлежать к сортируемому типу данных.
Основные отличия от B-дерева состоят в следующем:
Когда нужно проиндексировать новое значение, это значение
разбивается на элементы и индексируются сразу все элементы.
Поэтому в индекс добавляется не один элемент, а сразу несколько
(обычно много).
Каждый элемент индекса ссылается на множество табличных строк.
Хотя элементы и организованы в B-дерево, классы операторов GIN
не поддерживают операции сравнения «больше», «меньше».
Таким образом, GIN оптимизирован для других условий использования,
нежели B-дерево. (Но имеется расширение btree_gin, реализующее
классы операторов GIN для обычных типов данных.)
14
Итоги
PostgreSQL поддерживает слабоструктурированные типы
Частичная поддержка XML
формат теряет популярность
Полноценная поддержка JSON
направление активно развивается
Индексирование на основе метода доступа GIN
15
Практика
1. Часть сведений о книгах (ISBN, аннотация и другие)
выводятся приложением только на отдельной странице.
Сейчас эти данные хранятся в таблице books, но не
передаются приложению.
Измените функцию, реализующую API приложения, так,
чтобы сведения передавались в виде одного объекта JSON.
Проверьте реализацию в приложении.
2. Многочисленные столбцы, хранящие дополнительные
сведения о книгах, никак не используются в серверной части
приложения. Замените их на один столбец, в котором
сведения будут храниться в формате JSON. Сделайте это
прозрачно для приложения.
1. Сведения должны передаваться в объекте JSON следующего
формата:
{ "ISBN": isbn,
"Аннотация": abstract,
"Издательство": publisher,
"Год выпуска": year,
"Гарнитура": typeface,
"Издание": edition,
"Серия": series
}
2. Действуйте в предположении, что во время замены столбцов
приложение работает и должно корректно выполнять запросы
пользователей. Не забудьте, что вместе с заменой столбцов на один
общий необходимо исправить и интерфейсную функцию приложения.
Кроме того, считайте, что одновременно с работой приложения могут
изменяться как данные о существующих книгах, так и добавляться
новые книги.
16
Практика+
1. В базе данных имеются таблицы пользователей и их заказов,
связанные отношением «один ко многим».
Напишите функцию, которая по идентификатору
пользователя возвращает документ JSON, содержащий все
сведения о пользователе вместе с массивом его заказов.
2. В пользовательском интерфейсе необходимо выводить
названия городов, хранящиеся в базе данных, на выбранном
языке. Один из способов — хранить все переводы названий
в объекте JSON в виде пар «код языка — название».
Реализуйте такой способ. Для удобства создайте
представление, показывающее название городов на языке,
заданном в конфигурационном параметре сервера.
1. Таблицы могут быть следующими:
CREATE TABLE users(
id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name text);
CREATE TABLE orders(
id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id integer REFERENCES users(id),
amount numeric);
Результат должен иметь вид:
{ "user_id": ...,
"name": ...,
"orders": [ { "order_id": ..., "amount": ... }, ... ]
}
Для того чтобы сформировать объект JSON из нескольких полей,
воспользуйтесь функцией jsonb_build_object.
2. Название пользовательского конфигурационного параметра должно
содержать точку в своем имени, например, «translation.lang».
Получить текущее значение параметра можно функцией current_setting.