PostgreSQL 9.5 представил новый функционал, связанный с JSONB, значительно усиливающий его уже имеющиеся NoSQL характеристики. С добавлением новых операторов и функций, теперь стало возможно с легкостью изменять данные, хранящиеся в JSONB формате. В этой статье будут представлены эти новые операторы с примерами, как им можно использовать.

С добавлением типа данных 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» базы данных в Вашем случае, я рекомендую ознакомится со следующими статьями, посвященными вышеперечисленным темам (названия статей и авторы оставлены в оригинале):


Новые 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)


  1. ChudakovII
    24.06.2016 15:43
    +1

    Осталось довести до нужной кондиции ORM фреймворки, чтобы работать с этим типом без танцев с бубном.


  1. defuz
    24.06.2016 19:20
    +1

    NoSQL != «не реляционная»


  1. AlexLeonov
    25.06.2016 10:58
    +2

    С чего вы взяли, что тип данных json сразу делает базу «noSQL»?


  1. lazycommit
    25.06.2016 22:54
    +1

    Честно говоря, синтаксис отвратный. Очень странно смотрится рядом с операциями с такими структурами данных в других БД. И еще не добегая к ORM попахивает настоящими танцами с бубном.


  1. FYR
    28.06.2016 10:24
    -2

    Не понимаю одного: зачем?

    Зачем в реляционной БД noSQL заморочки? Она от этого станет eventualy-consistent с multi-master маштабированием хотябы до десятка машин? Нет!

    Смотришь на мир noSQL систем — они все пытаются придумать SQL-like синтаксис, эти наоборот.

    noSQL — не потому что SQL как язык плох, а потому что реляционная модель БД — накладывает существенные ограничения на маштабируемость. Иногда не нужно ACID, но нужен кластер на 100500 серверов.

    А тут… Нет конечно понятно — «у нас уже есть инфраструктура на PostgreSQL, но нам вот тут срочно понадобилось бонусом хранить в виде документов что-то» не переходить же нам на что-то другое.


    1. Shannon
      28.06.2016 11:15

      Ну хотя бы потому что это удобно. Не всем нужен кластер на 100500 серверов или вообще кластер, а вот schema-less явно удобно, еще удобно взять данные из базы в виде json, этот же json тут же отдать шаблонизатору без каких-либо промежуточных действий


      1. FYR
        28.06.2016 14:07

        Ну может если нужна schema-less, то видимо выбор *реляционной* СУБД PostgreSQL, это не совсем правильное решение? Так то и микроскопом гвозди можно забивать, и даже держаться удобно.
        Я не спорю иметь в системе нечто, что позволит городить по сути костыли: «мы выбрали инструмент для этого не предназначенный» но нам так удобнее/ибо было/умеем работать — я понимаю. Но как то «еще более мощные инструменты» — не тянет.
        Уж лучше бы в сторону масштабируемости глядеть и какие нибудь проекты типа пулинга клиентов а-ля pgpool или кластеризации аля Postgres-XL до ума доводить.


        1. Shannon
          28.06.2016 14:19
          +1

          Зачем, если в конкретной реляционной это работает хорошо?

          Не понятно о каких костылях идет речь, формат бинарный, можно строить любые индексы по любому полю в json и т.д.

          Помимо schema-less остаются плюсы реляционной таблицы