С добавлением типа данных JSON в версии 9.2, PostgreSQL наконец-то начал поддерживать JSON нативно. Несмотря на то что с выходом этой версии стало возможно использовать PostgreSQL как «NoSQL» базу данных, не так много можно было сделать на самом деле в то время из-за нехватки операторов и интересных функций. С момента выхода 9.2 версии, поддержка JSON значительно улучшалась в каждой следующей версии PostgreSQL, выливаясь сегодня в полное преодоление изначальных ограничений.
Вероятно, наиболее запоминающимися изменениями были добавление типа данных JSONB в PostgreSQL 9.4 и, в нынешней версии PostgreSQL 9.5, представление новых операторов и функций, которые позволят Вам изменять и управлять JSONB данными.
В этой статье мы сосредоточим внимание на новых возможностях, принесенных Postgres 9.5. Однако, прежде чем погрузиться в эту тему, если Вы хотите узнать больше о различиях между JSON и JSONB типами данных, или если у Вас есть сомнения по поводу уместности использования «NoSQL» базы данных в Вашем случае, я рекомендую ознакомится со следующими статьями, посвященными вышеперечисленным темам (названия статей и авторы оставлены в оригинале):
- NoSQL with PostgreSQL 9.4 and JSONB by Giuseppe Broccolo
- JSONB type performance in PostgreSQL 9.4 by Marco Nenciarini
- PostgreSQL anti-patterns: Unnecessary json/hstore dynamic columns by Craig Ringer
Новые JSONB операторы
Операторы и функции, присутствовавшие в PostgreSQL до версии 9.4, позволяли только извлекать JSONB данные. Поэтому, чтобы изменить эти данные, приходилось извлекать их, изменять, затем повторно вставлять их в базу. Не слишком практично, некоторые сказали бы.
Новые операторы представленные в PostgreSQL 9.5, которые были основаны на jsonbx расширении для PostgreSQL 9.4, смогли это изменить, значительно улучшая возможности взаимодействия с JSONB данными.
Конкатенация при помощи ||
Теперь Вы можете конкатенировать два JSONB объекта используя оператор ||:
SELECT
'{"name": "Marie",
"age": 45}'::jsonb || '{"city": "Paris"}'::jsonb;
?column?
----------------------------------------------
{"age": 45, "name": "Marie", "city": "Paris"}
(1 row)
В данном примере, ключ city добавлен к первому JSONB объекту.
Кроме того, данный оператор может быть использован для перезаписи уже имеющихся значений:
SELECT
'{"city": "Niceland",
"population": 1000}'::jsonb || '{"population": 9999}'::jsonb;
?column?
-------------------------------------------
{"city": "Niceland", "population": 9999}
(1 row)
В данном случае, значение ключа population было переписано на значение из второго объекта.
Удаление при помощи -
Оператор - может удалить пару ключ/значение из JSONB объекта:
SELECT
'{"name": "Karina",
"email": "karina@localhost"}'::jsonb - 'email';
?column?
-------------------
{"name": "Karina"}
(1 row)
Как Вы можете видеть, ключ email, указанный оператором -, был удален из объекта.
Кроме того, возможно удалить элемент из массива:
SELECT
'["animal","plant","mineral"]'::jsonb - 1;
?column?
-----------------
["animal", "mineral"]
(1 row)
Вышестоящий пример показывает массив, состоящий из 3 элементов. Зная что первый элемент массива соотносится с 0 позицией (animal), оператор - указывает на элемент, расположенный на позиции 1 и удаляет plant из массива.
Удаление при помощи #-
Разница в сравнении с оператором - заключается в том, что #- оператор может удалить вложенную пару ключ/значение, если путь до нее указан:
SELECT
'{"name": "Claudia",
"contact": {
"phone": "555-5555",
"fax": "111-1111"}}'::jsonb #- '{contact,fax}'::text[];
?column?
---------------------------------------------------------
{"name": "Claudia", "contact": {"phone": "555-5555"}}
(1 row)
Здесь, ключ fax вложен в contact. Мы используем оператор #- с указанием пути до ключа fax, чтобы удалить его.
Новые JSONB функции
Для большей мощности при работе с JSONB данными, вместо только их удаления и перезаписи, мы теперь можем использовать новую JSONB функцию:
jsonb_set
Новая функция обработки jsonb_set позволяет изменять значение для специфического ключа:
SELECT
jsonb_set(
'{"name": "Mary",
"contact":
{"phone": "555-5555",
"fax": "111-1111"}}'::jsonb,
'{contact,phone}',
'"000-8888"'::jsonb,
false);
jsonb_replace
--------------------------------------------------------------------------------
{"name": "Mary", "contact": {"fax": "111-1111", "phone": "000-8888"}}
(1 row)
Гораздо проще понять вышестоящий пример зная структуру jsonb_set функции. Она имеет 4 аргумента:
- target jsonb: значение JSONB, которое должно быть изменено
- path text[]: путь до интересующего значения, представленный в виде текстового массива
- new_value jsonb: новая связка ключ/значение, которая должна быть изменена (или добавлена)
- create_missing boolean: Опционное поле, которое позволяет создание новой связки ключ/значение, если она еще не существует
Оглядываясь на предыдущий пример, на этот раз понимая его структуру, мы видим что вложенный в contact ключ phone был изменен функцией jsonb_set.
Вот еще один пример, на этот раз создающий новый ключ посредством использования логического значения true (4й аргумент в структуре функции jsonb_set). Как говорилось выше, этот аргумент имеет значение true по-умолчанию, так что не обязательно его указывать явно в следующем примере:
SELECT
jsonb_set(
'{"name": "Mary",
"contact":
{"phone": "555-5555",
"fax": "111-1111"}}'::jsonb,
'{contact,skype}',
'"maryskype"'::jsonb,
true);
jsonb_set
------------------------------------------------------------------------------------------------------
{"name": "Mary", "contact": {"fax": "111-1111", "phone": "555-5555", "skype": "maryskype"}}
(1 row)
Связка ключ/значение skype, который не присутствует в оригинальном JSONB объекте, был добавлен и находится на том уровне вложенности, который был указан во втором аргументе jsonb_set функции.
Если же вместо true в 4й аргумент функции jsonb_set поставить false, то ключ skype не будет добавлен в исходный JSONB объект.
jsonb_pretty
Чтение JSONB записи не так то уж просто, учитывая что она не хранит пробелы. Функция jsonb_pretty форматирует вывод, делая его более легким для чтения:
SELECT
jsonb_pretty(
jsonb_set(
'{"name": "Joan",
"contact": {
"phone": "555-5555",
"fax": "111-1111"}}'::jsonb,
'{contact,phone}',
'"000-1234"'::jsonb));
jsonb_pretty
---------------------------------
{ +
"name": "Joan", +
"contact": { +
"fax": "111-1111", +
"phone": "000-1234" +
} +
}
(1 row)
Снова, в этом примере, значение вложенного в contact ключа phone изменено на значение, переданное в 3м аргументе функции jsonb_set. Единственная разница заключается в том, что мы использовали теперь ее вместе с функцией jsonb_pretty, вывод теперь показан в более понятном и читаемом виде.
Заключение
Вопреки тому что нам пытаются доказать, что нереляционная база данных не может быть универсальным решением, далеко не все с этим согласятся.
Поэтому, говоря о «NoSQL» базах данных, нужно иметь в голове мысли о том, подойдет ли Вам такая база лучше, чем реляционная. PostgreSQL, с его JSONB особенностями, может дать Вам преимущество: можно использовать оба варианта (и документоориентированную, и реляционную базы данных), предоставленные одним и тем же решением, избегая всех сложностей использования двух различных продуктов.
Комментарии (8)
lazycommit
25.06.2016 22:54+1Честно говоря, синтаксис отвратный. Очень странно смотрится рядом с операциями с такими структурами данных в других БД. И еще не добегая к ORM попахивает настоящими танцами с бубном.
FYR
28.06.2016 10:24-2Не понимаю одного: зачем?
Зачем в реляционной БД noSQL заморочки? Она от этого станет eventualy-consistent с multi-master маштабированием хотябы до десятка машин? Нет!
Смотришь на мир noSQL систем — они все пытаются придумать SQL-like синтаксис, эти наоборот.
noSQL — не потому что SQL как язык плох, а потому что реляционная модель БД — накладывает существенные ограничения на маштабируемость. Иногда не нужно ACID, но нужен кластер на 100500 серверов.
А тут… Нет конечно понятно — «у нас уже есть инфраструктура на PostgreSQL, но нам вот тут срочно понадобилось бонусом хранить в виде документов что-то» не переходить же нам на что-то другое.Shannon
28.06.2016 11:15Ну хотя бы потому что это удобно. Не всем нужен кластер на 100500 серверов или вообще кластер, а вот schema-less явно удобно, еще удобно взять данные из базы в виде json, этот же json тут же отдать шаблонизатору без каких-либо промежуточных действий
FYR
28.06.2016 14:07Ну может если нужна schema-less, то видимо выбор *реляционной* СУБД PostgreSQL, это не совсем правильное решение? Так то и микроскопом гвозди можно забивать, и даже держаться удобно.
Я не спорю иметь в системе нечто, что позволит городить по сути костыли: «мы выбрали инструмент для этого не предназначенный» но нам так удобнее/ибо было/умеем работать — я понимаю. Но как то «еще более мощные инструменты» — не тянет.
Уж лучше бы в сторону масштабируемости глядеть и какие нибудь проекты типа пулинга клиентов а-ля pgpool или кластеризации аля Postgres-XL до ума доводить.Shannon
28.06.2016 14:19+1Зачем, если в конкретной реляционной это работает хорошо?
Не понятно о каких костылях идет речь, формат бинарный, можно строить любые индексы по любому полю в json и т.д.
Помимо schema-less остаются плюсы реляционной таблицы
ChudakovII
Осталось довести до нужной кондиции ORM фреймворки, чтобы работать с этим типом без танцев с бубном.