PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Използване на JSONB в PostgreSQL:Как ефективно да съхранявате и индексирате JSON данни в PostgreSQL

JSON означава JavaScript Object Notation. Това е отворен стандартен формат, който организира данните в двойки ключ/стойност и масиви, описани подробно в RFC 7159. JSON е най-разпространеният формат, използван от уеб услугите за обмен на данни, съхраняване на документи, неструктурирани данни и т.н. В тази публикация ще разгледаме. за да ви покажем съвети и техники как ефективно да съхранявате и индексирате JSON данни в PostgreSQL.

Можете също да разгледате нашия уебинар за работа с JSON данни в PostgreSQL срещу MongoDB в партньорство с PostgresConf, за да научите повече по темата и да разгледате нашата страница за SlideShare за да изтеглите слайдовете.

Защо да съхранявате JSON в PostgreSQL?

Защо една релационна база данни изобщо трябва да се интересува от неструктурирани данни? Оказва се, че има няколко сценария, при които е полезно.

  1. Гъвкавост на схемата

    Една от основните причини да съхранявате данни с помощта на JSON формата е гъвкавостта на схемата. Съхраняването на вашите данни в JSON е полезно, когато вашата схема е течна и се променя често. Ако съхранявате всеки от ключовете като колони, това ще доведе до чести DML операции – това може да е трудно, когато наборът ви от данни е голям – например проследяване на събития, анализи, маркери и т.н. Забележка:Ако конкретен ключ винаги присъства във вашия документ може да има смисъл да го съхранявате като първокласна колона. Обсъждаме повече за този подход в раздел „JSON шаблони и антимодели“ по-долу.

  2. Вложени обекти

    Ако вашият набор от данни има вложени обекти (на едно или много нива), в някои случаи е по-лесно да ги обработвате в JSON, вместо да денормализирате данните в колони или множество таблици.

  3. Синхронизиране с външни източници на данни

    Често външна система предоставя данни като JSON, така че може да е временно съхранение, преди данните да бъдат погълнати в други части на системата. Например транзакции Stripe.

Хронология на поддръжка на JSON в PostgreSQL

Поддръжката на JSON в PostgreSQL беше въведена в 9.2 и непрекъснато се подобряваше във всяка следваща версия.

  • Вълна 1:PostgreSQL 9.2  (2012) добави поддръжка за тип данни JSON

    JSON база от данни в 9.2 беше доста ограничена (и вероятно преувеличена в този момент) – основно възхваляван низ с добавена известна проверка на JSON. Полезно е да се валидира входящият JSON и да се съхранява в базата данни. Повече подробности са предоставени по-долу.

  • Вълна 2:PostgreSQL 9.4 (2014) добави поддръжка за тип данни JSONB

    JSONB означава „JSON Binary“ или „JSON по-добър“ в зависимост от това кого питате. Това е разложен двоичен формат за съхранение на JSON. JSONB поддържа индексиране на JSON данните и е много ефективен при анализиране и запитване на JSON данните. В повечето случаи, когато работите с JSON в PostgreSQL, трябва да използвате JSONB.

  • Вълна 3:PostgreSQL 12 (2019) добави поддръжка за SQL/JSON стандарт и JSONPATH заявки

    JSONPath носи мощна машина за JSON заявки към PostgreSQL.

Кога трябва да използвате JSON срещу JSONB?

В повечето случаи трябва да използвате JSONB. Въпреки това, има някои специфични случаи, в които JSON работи по-добре:

  • JSON запазва оригиналното форматиране (известно още като празно пространство) и подреждането на ключовете.
  • JSON запазва дублирани ключове.
  • JSON е по-бърз за поглъщане в сравнение с JSONB – обаче, ако извършите допълнителна обработка, JSONB ще бъде по-бърз.

Например, ако просто поглъщате JSON регистрационни файлове и не ги заявявате по никакъв начин, тогава JSON може да е по-добра опция за вас. За целите на този блог, когато говорим за поддръжка на JSON в PostgreSQL, ще се позоваваме на JSONB в бъдеще.

Използване на JSONB в PostgreSQL:Как ефективно да съхранявате и индексирате JSON данни в PostgreSQLC Кликнете, за да туитирате

Шаблони и антимодели на JSONB

Ако PostgreSQL има страхотна поддръжка за JSONB, защо вече имаме нужда от колони? Защо просто не създадете таблица с JSONB blob и да се отървете от всички колони като схемата по-долу:

СЪЗДАВАНЕ НА ТАБЛИЦА тест (id int, данни JSONB, ПЪРВИЧЕН КЛЮЧ (id));

В края на деня колоните все още са най-ефективната техника за работа с вашите данни. JSONB хранилището има някои недостатъци в сравнение с традиционните колони:

  • PostreSQL не съхранява статистически данни за колони за JSONB колони

    PostgreSQL поддържа статистика за разпределението на стойностите във всяка колона на таблицата – най-често срещаните стойности (MCV), NULL записи, хистограма на разпределение. Въз основа на тези данни, PostgreSQL планирането на заявки взема интелигентни решения относно плана, който да използва за заявката. В този момент PostgreSQL не съхранява никаква статистика за JSONB колони или ключове. Това понякога може да доведе до лош избор, като например използването на вложени връзки на цикъл срещу свързвания с хеш и т.н. По-подробен пример за това е предоставен в тази публикация в блога – Кога да избягваме JSONB в PostgreSQL схема.

  • JSONB съхранение води до по-голям отпечатък на съхранение

    JSONB съхранението не дедуплира имената на ключовете в JSON. Това може да доведе до значително по-голям отпечатък за съхранение в сравнение с MongoDB BSON на WiredTiger или традиционното съхранение на колони. Проведох прост тест с модела JSONB по-долу, съхраняващ около 10 милиона реда данни, и ето резултатите – В някои отношения това е подобно на модела за съхранение на MongoDB MMAPV1, където ключовете в JSONB се съхраняват както са без никаква компресия. Едно дългосрочно решение е да преместите имената на ключовете в речник на ниво таблица и да препратите този речник, вместо да съхранявате имената на ключовете многократно. Дотогава решението може да бъде използването на по-компактни имена (в стил unix) вместо по-описателни имена. Например, ако съхранявате милиони екземпляри на конкретен ключ, би било по-добре да го наречете „pb“ вместо „име на издателя“.

Най-ефективният начин за използване на JSONB в PostgreSQL е комбинирането на колони и JSONB. Ако ключ се появява много често във вашите JSONB BLOB-ове, вероятно е по-добре да се съхранява като колона. Използвайте JSONB като „обхват на всичко“, за да обработвате променливите части на вашата схема, като същевременно използвате традиционните колони за полета, които са по-стабилни.

структури от данни JSONB

И JSONB и MongoDB BSON са по същество дървовидни структури, използващи възли на няколко нива за съхраняване на анализираните JSONB данни. MongoDB BSON има много подобна структура.

Източник на изображения

JSONB &TOAST

Друго важно съображение за съхранението е как JSONB взаимодейства с TOAST (Техника за съхранение на атрибути за големи размери). Обикновено, когато размерът на вашата колона надвиши TOAST_TUPLE_THRESHOLD (2kb по подразбиране), PostgreSQL ще се опита да компресира данните и да се побере в 2kb. Ако това не работи, данните се преместват в хранилище извън линия. Това наричат ​​„препичане“ на данните. Когато данните бъдат извлечени, трябва да се случи обратният процес „deTOASTting“. Можете също да контролирате стратегията за съхранение на TOAST:

  • Разширено – Позволява съхранение и компресиране извън линия (с помощта на pglz). Това е опцията по подразбиране.
  • Външен – Позволява съхранение извън линия, но не и компресия.

Ако изпитвате закъснения поради TOAST компресия или декомпресия, една от опциите е проактивно да зададете хранилището на колоните на „EXTENDED“. За всички подробности, моля, вижте този документ за PostgreSQL.

JSONB оператори и функции

PostgreSQL предоставя различни оператори за работа с JSONB. От документите:

Оператор Описание
-> Вземете елемент на JSON масив (индексиран от нула, отрицателните цели числа се броят от края)
-> Вземете JSON поле за обект по ключ
->> Вземете елемент на JSON масив като текст
->> Вземете JSON полето за обект като текст
#> Вземете JSON обект на посочения път
#>> Вземете JSON обект на посочения път като текст
@> Лявата стойност на JSON съдържа ли десния път/стойност на JSON записи на най-горното ниво?
<@ Левите записи за път/стойност в JSON съдържат ли се на най-горното ниво в рамките на дясната JSON стойност?
? Дали низът съществуват като ключ от най-високо ниво в стойността на JSON?
?| Направете някой от тези низове на масива съществуват като ключове от най-високо ниво?
?& Направете всички тези низове на масива съществуват като ключове от най-високо ниво?
|| Свързване на две jsonb стойности в нова jsonb стойност
- Изтриване на двойка ключ/стойност или низ елемент от левия операнд. Двойките ключ/стойност се съпоставят въз основа на тяхната ключова стойност.
- Изтриване на множество двойки ключ/стойност или низ елементи от левия операнд. Двойките ключ/стойност се съпоставят въз основа на тяхната ключова стойност.
- Изтрийте елемента на масива с определен индекс (отрицателните цели числа се броят от края). Извежда грешка, ако контейнерът от най-високо ниво не е масив.
#- Изтрийте полето или елемента с определен път (за JSON масиви отрицателните цели числа се броят от края)
@? Пътят на JSON връща ли някакъв елемент за посочената стойност на JSON?
@@ Връща резултата от проверката на предиката на JSON пътя за посочената стойност на JSON. Отчита се само първият елемент от резултата. Ако резултатът не е булев, тогава се връща null.

PostgreSQL също така предоставя разнообразие от функции за създаване и функции за обработка за работа с JSONB данните.

JSONB индекси

JSONB предоставя широк набор от опции за индексиране на вашите JSON данни. На високо ниво ще разгледаме 3 различни типа индекси – GIN, BTREE и HASH. Не всички типове индекси поддържат всички класове на оператори, така че е необходимо планиране за проектиране на вашите индекси въз основа на типа оператори и заявки, които планирате да използвате.

GIN индекси

GIN означава „обобщени обърнати индекси“. От документите:

„GIN е предназначен за обработка на случаи, при които елементите, които трябва да бъдат индексирани, са съставни стойности, а заявките, които трябва да бъдат обработвани от индекса, трябва да търсят елемент стойности, които се появяват в съставните елементи. Например елементите могат да бъдат документи, а заявките могат да бъдат търсения на документи, съдържащи конкретни думи.“

GIN поддържа два операторски класа:

  • jsonb_ops (по подразбиране) – ?, ?|, ?&, @>, @@, @? [Индексирайте всеки ключ и стойност в елемента JSONB]
  • jsonb_pathops – @>, @@, @? [Индексирайте само стойностите в елемента JSONB]
СЪЗДАВАНЕ НА ИНДЕКС datagin ВЪРХУ книги ИЗПОЛЗВАНЕ на gin (данни);

Оператори за съществуване (?, ?|, ?&)

Тези оператори могат да се използват за проверка за наличието на ключове от най-високо ниво в JSONB. Нека създадем GIN индекс в колоната с данни JSONB. Например намерете всички книги, които се предлагат на брайлово писмо. JSON изглежда така:

"{"tags":{"nk594127":{"ik71786":"iv678771"}}, "braille":false, "keywords":["abc", "kef", "keh"], " твърда корица":true, "издател":"EfgdxUdvB0", "критикуващ":1}
demo=# изберете * от книги, където данни ? 'брайлово писмо';id | автор | isbn | рейтинг | данни---------+----------------+------------+-------- +------------------------------------------------ -------------------------------------------------- -------------------------------------------------- -------------------1000005 | XEI7xShT8bPu6H7 | 2kD5XJDZUF | 0 | {"tags":{"nk455671":{"ik937456":"iv506075"}}, "braille":true, "keywords":["abc", "kef", "keh"], "hardcover":false , "издател":"zSfZIAjGGs", "критикуващ":4}.....demo=# обяснете анализирайте изберете * от книги, където данните ? 'брайлово писмо'; ПЛАН ЗА ЗАЯВКА ----------------------------------------------------- -------------------------------------------------- -----------------------Сканиране на растерна карта върху книги (цена=12.75..1005.25 реда=1000 ширина=158) (действително време=0.033..0.039 rows=15 loops=1) Повторна проверка Cond:(данни? 'braille'::text) Heap Blocks:exact=2-> Bitmap Index Scan on datagin (cost=0.00..12.50 rows=1000 width=0) (действително време =0.022..0.022 rows=15 loops=1) Индекс Cond:(данни? 'braille'::text)Време за планиране:0.102 ms Време за изпълнение:0.067 ms (7 реда)

Както можете да видите от изхода за обяснение, GIN индексът, който създадохме, се използва за търсене. Ами ако искаме да намерим книги с брайлов или твърди корици?

demo=# обяснете анализирайте изберете * от книги, където данните ?| array['braille','hardcover'];ПЛАН ЗА ЗАЯВКАТА------------------------------------- -------------------------------------------------- ------------------------------ Сканиране на растерна карта върху книги (цена=16,75..1009,25 реда=1000 ширина=158) ( действително време=0.029..0.035 rows=15 цикъла=1)Повторна проверка на Cond:(данни ?| '{braille,hardcover}'::text[])Heap Blocks:exact=2-> Сканиране на индекс на растерно изображение при datagin (цена=0,00..16,50 реда=1000 ширина=0) (действително време=0,023..0,023 реда=15 цикъла=1) Индекс Конд:(данни ?| '{braille,hardcover}'::text[])Време за планиране:0,138 ms Време за изпълнение:0,057 ms (7 реда)

Индексът GIN поддържа операторите „existence“ само на клавишите от „горно ниво“. Ако ключът не е на най-горното ниво, тогава индексът няма да се използва. Това ще доведе до последователно сканиране:

demo=# изберете * от книги, където data->'tags' ? 'nk455671';id | автор | isbn | рейтинг | данни---------+----------------+------------+-------- +------------------------------------------------ -------------------------------------------------- -------------------------------------------------- -------------------1000005 | XEI7xShT8bPu6H7 | 2kD5XJDZUF | 0 | {"tags":{"nk455671":{"ik937456":"iv506075"}}, "braille":true, "keywords":["abc", "kef", "keh"], "hardcover":false , "издател":"zSfZIAjGGs", "критикуващ":4}685122 | GWfuvKfQ1PCe1IL | jnyhYYcF66 | 3 | {"tags":{"nk455671":{"ik615925":"iv253423"}}, "publisher":"b2NwVg7VY3", "criticrating":0}(2 реда)demo=# обяснение анализирайте изберете * от книги, където данни ->'маркери'? 'nk455671'; ПЛАН ЗА ЗАЯВКА ----------------------------------------------------- -------------------------------------------------- ------------------Последователно сканиране на книги (цена=0,00..38807,29 реда=1000 ширина=158) (действително време=0,018..270,641 реда=2 цикъла=1)Филтър:((данни -> 'tags'::text) ? 'nk455671'::text)Редове, премахнати от филтър:1000017Време за планиране:0,078 ms Време за изпълнение:270,728 ms (5 реда)

Начинът да проверите за съществуване във вложените документи е да използвате „индекси на изрази“. Нека създадем индекс на data->tags:

СЪЗДАВАЙТЕ ИНДЕКС datatagsgin ВЪРХУ книги ИЗПОЛЗВАЙТЕ gin (data->'tags');
demo=# изберете * от книги, където data->'tags' ? 'nk455671';id | автор | isbn | рейтинг | данни---------+----------------+------------+-------- +------------------------------------------------ -------------------------------------------------- -------------------------------------------------- -------------------1000005 | XEI7xShT8bPu6H7 | 2kD5XJDZUF | 0 | {"tags":{"nk455671":{"ik937456":"iv506075"}}, "braille":true, "keywords":["abc", "kef", "keh"], "hardcover":false , "издател":"zSfZIAjGGs", "критикуващ":4}685122 | GWfuvKfQ1PCe1IL | jnyhYYcF66 | 3 | {"tags":{"nk455671":{"ik615925":"iv253423"}}, "publisher":"b2NwVg7VY3", "criticrating":0}(2 реда)demo=# обяснение анализирайте изберете * от книги, където данни ->'маркери'? 'nk455671'; ПЛАН ЗА ЗАЯВКА ----------------------------------------------------- -------------------------------------------------- ------------------------- Сканиране на растерна карта върху книги (цена=12,75..1007,75 реда=1000 ширина=158) (действително време=0,031 ..0.035 rows=2 loops=1)Повторна проверка на Cond:((data ->'tags'::text) ? 'nk455671'::text)Heap Blocks:exact=2-> Сканиране на индекс на растерно изображение при datatagsgin (цена=0,00 ..12.50 реда=1000 ширина=0) (действително време=0,021..0,021 реда=2 цикъла=1) Индекс Конд:((данни ->'tags'::text) ? 'nk455671'::text)Време за планиране :0,098 ms Време за изпълнение:0,061 ms (7 реда)

Забележка:Алтернатива тук е да използвате оператора @>:

изберете * от книги, където данни @> '{"tags":{"nk455671":{}}}'::jsonb;

Това обаче работи само ако стойността е обект. Така че, ако не сте сигурни дали стойността е обект или примитивна стойност, това може да доведе до неправилни резултати.

Оператори на пътя @>, <@

Операторът „път“ може да се използва за многостепенни заявки на вашите JSONB данни. Нека го използваме подобно на ? оператор по-горе:

изберете * от книгите, в които данни @> '{"braille":true}'::jsonb;demo=# обяснете анализирайте изберете * от книги, където данни @> '{"braille":true}'::jsonb; ПЛАН ЗА ЗАПИТВАНЕ------------------------------------------------ -------------------------------------------------- -------------------Растрово сканиране на Heap върху книги (цена=16.75..1009.25 реда=1000 ширина=158) (действително време=0.040..0.048 реда=6 цикъла =1)Повторна проверка Cond:(data @> '{"braille":true}'::jsonb)Редове, премахнати от индекс Повторна проверка:9Heap Blocks:exact=2-> Сканиране на индекс на растерни изображения при datagin (цена=0.00..16.50 реда =1000 ширина=0) (действително време=0,030..0,030 реда=15 цикъла=1) Индекс Cond:(данни @> '{"braille":true}'::jsonb) Време за планиране:0,100 ms Време за изпълнение:0,076 ms (8 реда)

Операторите на пътя поддържат заявка за вложени обекти или обекти от най-високо ниво:

demo=# изберете * от книги, където данни @> '{"publisher":"XlekfkLOtL"}'::jsonb;id | автор | isbn | рейтинг | данни-----+----------------+------------+--------+--- -------------------------------------------------- --------------------------------346 | uD3QOvHfJdxq2ez | KiAaIRu8QE | 1 | {"tags":{"nk88":{"ik37":"iv161"}}, "publisher":"XlekfkLOtL", "criticrating":3}(1 ред)demo=# обяснение анализирайте изберете * от книги, където данни @> '{"издател":"XlekfkLOtL"}'::jsonb;ПЛАН ЗА ЗАЯВКАТА------------------------------ -------------------------------------------------- ----------------------------------------- Сканиране на растерна карта върху книги (цена=16,75..1009,25 реда=1000 ширина=158) (действително време=0.491..0.492 rows=1 цикъла=1) Повторна проверка Cond:(data @> '{"publisher":"XlekfkLOtL"}'::jsonb) Heap Blocks:exact=1-> Bitmap Индексно сканиране на datagin (цена=0.00..16.50 реда=1000 ширина=0) (действително време=0.092..0.092 реда=1 цикъла=1) Индекс Конд:(данни @> '{"издател":"XlekfkLOtL"} '::jsonb)Време за планиране:0,090 ms Време за изпълнение:0,523 ms

Заявките могат да бъдат и на много нива:

demo=# изберете * от книги, където данни @> '{"tags":{"nk455671":{"ik937456":"iv506075"}}}'::jsonb;id | автор | isbn | рейтинг | данни---------+----------------+------------+-------- +------------------------------------------------ -------------------------------------------------- -------------------------------------------------- -------------------1000005 | XEI7xShT8bPu6H7 | 2kD5XJDZUF | 0 | {"tags":{"nk455671":{"ik937456":"iv506075"}}, "braille":true, "keywords":["abc", "kef", "keh"], "hardcover":false , "издател":"zSfZIAjGGs", "критикуващ":4}(1 ред)

GIN индекс „pathops“ клас на оператор

GIN също поддържа опция „pathops“ за намаляване на размера на GIN индекса. Когато използвате опцията pathops, единствената поддръжка на оператора е „@>“, така че трябва да внимавате със своите заявки. От документите:

„Техническата разлика между jsonb_ops и jsonb_path_ops GIN индекс е, че първият създава независими индексни елементи за всеки ключ и стойност в данните, докато вторият създава индексни елементи само за всяка стойност в данните”

Можете да създадете GIN pathops индекс, както следва:

СЪЗДАВАЙТЕ ИНДЕКС dataginpathops ВЪРХУ книги, ИЗПОЛЗВАЙТЕ gin (данни jsonb_path_ops);

В моя малък набор от данни от 1 милион книги можете да видите, че индексът на pathops GIN е по-малък – трябва да тествате с вашия набор от данни, за да разберете спестяванията:

<предварително обществено | dataginpathops | индекс | sgpostgres | книги | 67 MB |обществено | datatagsgin | индекс | sgpostgres | книги | 84 MB |

Нека изпълним отново нашата заявка от преди с индекса pathops:

demo=# изберете * от книги, където данни @> '{"tags":{"nk455671":{"ik937456":"iv506075"}}}'::jsonb;id | автор | isbn | рейтинг | данни---------+----------------+------------+-------- +------------------------------------------------ -------------------------------------------------- -------------------------------------------------- -------------------1000005 | XEI7xShT8bPu6H7 | 2kD5XJDZUF | 0 | {"tags":{"nk455671":{"ik937456":"iv506075"}}, "braille":true, "keywords":["abc", "kef", "keh"], "hardcover":false , "издател":"zSfZIAjGGs", "критикуващ":4}(1 ред)demo=# обяснете изберете * от книги, където данни @> '{"tags":{"nk455671":{"ik937456":"iv506075" }}}'::jsonb; ПЛАН ЗА ЗАЯВКА ------------------------------------ -------------------------------------------------Растрово изображение Сканиране на купчина на книги (цена=12,75..1005,25 реда=1000 ширина=158) Повторна проверка на Cond:(данни @> '{"tags":{"nk455671":{"ik937456":"iv506075"}}}'::jsonb)-> Сканиране на индекс на растерни изображения на dataginpathops (цена=0.00..12.50 редове=1000 ширина=0) Индекс Конд:(данни @> '{"tags":{"nk455671":{"ik937456":"iv506075"} }}'::jsonb)(4 реда)

Въпреки това, както бе споменато по-горе, опцията „pathops“ не поддържа всички сценарии, които поддържа класът на операторите по подразбиране. С GIN индекс „pathops“, всички тези заявки не могат да използват GIN индекса. За да обобщим, имате по-малък индекс, но поддържа по-ограничен случай на употреба.

изберете * от книгите, където данните ? „маркери“; => Последователен избор на сканиране * от книги, където данни @> '{"tags" :{}}'; => Последователно сканиране изберете * от книги, където данни @> '{"tags" :{"k7888":{}}}' => Последователно сканиране

Индекси на B-Tree

Индексите на B-дърво са най-често срещаният тип индекс в релационните бази данни. Ако обаче индексирате цяла JSONB колона с индекс на B-дърво, единствените полезни оператори са „=“, <, <=,>,>=. По същество това може да се използва само за сравнения на цели обекти, което има много ограничен случай на употреба.

По-често срещан сценарий е използването на „индекси на изрази в B-дърво“. За буквар вижте тук – Индекси на изрази. Индексите на изрази на B-дърво могат да поддържат общите оператори за сравнение „=“, „<“, „>“, „>=“, „<=“. Както може би си спомняте, GIN индексите не поддържат тези оператори. Нека разгледаме случая, когато искаме да извлечем всички книги с данни->критикуване> 4. Така че бихте изградили заявка нещо подобно на това:

demo=# изберете * от книги, където данни->'критикуване'> 4;ГРЕШКА:оператор не съществува:jsonb>=integerLINE 1:изберете * от книги, където данни->'критикуване'>=4;^СЪВЕТ :Никой оператор не съответства на даденото име и типове аргументи. Може да се наложи да добавите изрични прехвърляния на типа.

Е, това не работи, тъй като операторът ‘->’ връща тип JSONB. Така че трябва да използваме нещо подобно:

demo=# изберете * от книги, където (data->'criticrating')::int4> 4;

Ако използвате версия преди PostgreSQL 11, тя става по-грозна. Първо трябва да направите заявка като текст и след това да го преведете към цяло число:

demo=# изберете * от книги, където (data->'criticrating')::int4> 4;

За индексите на изрази индексът трябва да съвпада точно с израза на заявката. И така, нашият индекс ще изглежда така:

demo=# СЪЗДАЙТЕ ИНДЕКС за критикуване ВЪРХУ книги, ИЗПОЛЗВАЙТЕ BTREE (((data->'criticrating')::int4));CREATE INDEXdemo=# обяснете анализирайте изберете * от книги, където (data->'criticrating')::int4 =3; ПЛАН ЗА ЗАЯВКА ----------------------------------------------------- -------------------------------------------------- ------------------------------------ Сканиране на индекс с помощта на критикуване на книги (цена=0,42..4626,93 реда =5000 ширина=158) (действително време=0.069..70.221 реда=199883 цикъла=1) Индекс Конд:(((данни -> 'критикуване'::текст))::цяло число =3)Време за планиране:0,103 ms Време за изпълнение :79,019 ms (4 реда)demo=# обяснете анализирайте изберете * от книги, където (данни->'критикуване')::int4 =3; ПЛАН ЗА ЗАПИСВАНЕ----------------- -------------------------------------------------- -------------------------------------------------- -------------Индекс сканиране с използване на критикуване на книги (цена=0,42..4626,93 реда=5000 ширина=158) (действително време=0,069..70,221 реда=199883 цикъла=1) Индекс Конд :(((данни -> 'критикуване'::текст))::цяло число =3)Време за планиране:0,103 msE Време за изпълнение:79,019 ms (4 реда)1 От горе можем да видим, че индексът BTREE се използва както се очаква.

Хеш индекси

Ако се интересувате само от оператора "=", тогава хеш индексите стават интересни. Например, помислете за случая, когато търсим конкретен етикет върху книга. Елементът, който трябва да бъде индексиран, може да бъде елемент от най-високо ниво или дълбоко вложен.

Напр. тагове->издател =XlekfkLOtL

СЪЗЗВАЙТЕ ИНДЕКС издателски хеш ВЪРХУ книги, ИЗПОЛЗВАЙТЕ ХЕШ ((data->'publisher'));

Хеш индексите също обикновено са по-малки по размер от B-tree или GIN индексите. Разбира се, това в крайна сметка зависи от вашия набор от данни.

demo=# изберете * от книги, където data->'publisher' ='XlekfkLOtL'demo-#;id | автор | isbn | рейтинг | данни-----+----------------+------------+--------+--- -------------------------------------------------- --------------------------------346 | uD3QOvHfJdxq2ez | KiAaIRu8QE | 1 | {"tags":{"nk88":{"ik37":"iv161"}}, "publisher":"XlekfkLOtL", "criticrating":3}(1 ред)demo=# обяснение анализирайте изберете * от книги, където данни ->'publisher' ='XlekfkLOtL'; ПЛАН ЗА ЗАЯВКА ---------------------------------------------- -------------------------------------------------- ------------------------------- Индексно сканиране с използване на хеш на издателя на книги (цена=0,00..2.02 реда=1 ширина=158 ) (действително време=0.016..0.017 rows=1 цикъла=1) Индекс Cond:((data -> 'publisher'::text) ='XlekfkLOtL'::text)Време за планиране:0.080 ms Време за изпълнение:0.035 ms(4). редове)

Специално споменаване:GIN триграмни индекси

PostgreSQL поддържа съвпадение на низове с помощта на триграмни индекси. Индексите на триграми работят, като разделят текста на триграми. Триграмите са основно думи, разделени на поредици от 3 букви. Повече информация можете да намерите в документацията. GIN indexes support the “gin_trgm_ops” class that can be used to index the data in JSONB. You can choose to use expression indexes to build the trigram index on a particular column.

CREATE EXTENSION pg_trgm;CREATE INDEX publisher ON books USING GIN ((data->'publisher') gin_trgm_ops);demo=# select * from books where data->'publisher' LIKE '%I0UB%'; ID | author | isbn | rating | data----+-----------------+------------+--------+--------------------------------------------------------------------------------- 4 | KiEk3xjqvTpmZeS | EYqXO9Nwmm | 0 | {"tags":{"nk3":{"ik1":"iv1"}}, "publisher":"MI0UBqZJDt", "criticrating":1}(1 row)

As you can see in the query above, we can search for any arbitrary string occurring at any potion. Unlike the B-tree indexes, we are not restricted to left anchored expressions.

demo=# explain analyze select * from books where data->'publisher' LIKE '%I0UB%'; QUERY PLAN-------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on books (cost=9.78..111.28 rows=100 width=158) (actual time=0.033..0.033 rows=1 loops=1) Recheck Cond:((data -> 'publisher'::text) ~~ '%I0UB%'::text) Heap Blocks:exact=1 -> Bitmap Index Scan on publisher (cost=0.00..9.75 rows=100 width=0) (actual time=0.025..0.025 rows=1 loops=1) Index Cond:((data -> 'publisher'::text) ~~ '%I0UB%'::text) Planning Time:0.213 ms Execution Time:0.058 ms(7 rows)

Special Mention:GIN Array Indexes

JSONB has great built-in support for indexing arrays. Let's consider an example of indexing an array of strings using a GIN index in the case when our JSONB data contains a "keyword" element and we would like to find rows with particular keywords:

{"tags":{"nk780341":{"ik397357":"iv632731"}}, "keywords":["abc", "kef", "keh"], "publisher":"fqaJuAdjP5", "criticrating":2}CREATE INDEX keywords ON books USING GIN ((data->'keywords') jsonb_path_ops);demo=# select * from books where data->'keywords' @> '["abc", "keh"]'::jsonb; id | author | isbn | rating | data---------+-----------------+------------+--------+----------------------------------------------------------------------------------------------------------------------------------- 1000003 | zEG406sLKQ2IU8O | viPdlu3DZm | 4 | {"tags":{"nk263020":{"ik203820":"iv817928"}}, "keywords":["abc", "kef", "keh"], "publisher":"7NClevxuTM", "criticrating":2} 1000004 | GCe9NypHYKDH4rD | so6TQDYzZ3 | 4 | {"tags":{"nk780341":{"ik397357":"iv632731"}}, "keywords":["abc", "kef", "keh"], "publisher":"fqaJuAdjP5", "criticrating":2}(2 rows)demo=# explain analyze select * from books where data->'keywords' @> '["abc", "keh"]'::jsonb; QUERY PLAN--------------------------------------------------------------------------------------------------------------------- Bitmap Heap Scan on books (cost=54.75..1049.75 rows=1000 width=158) (actual time=0.026..0.028 rows=2 loops=1) Recheck Cond:((data -> 'keywords'::text) @> '["abc", "keh"]'::jsonb) Heap Blocks:exact=1 -> Bitmap Index Scan on keywords (cost=0.00..54.50 rows=1000 width=0) (actual time=0.014..0.014 rows=2 loops=1) Index Cond:((data -> 'keywords'::text) @&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; '["abc", "keh"]'::jsonb) Planning Time:0.131 ms Execution Time:0.063 ms(7 rows)

The order of the items in the array on the right does not matter. For example, the following query would return the same result as the previous:

demo=# explain analyze select * from books where data->'keywords' @> '["keh","abc"]'::jsonb;

All elements in the right side array of the containment operator need to be present - basically like an "AND" operator. If you want "OR" behavior, you can construct it in the WHERE clause:

demo=# explain analyze select * from books where (data->'keywords' @> '["abc"]'::jsonb OR data->'keywords' @> '["keh"]'::jsonb);

More details on the behavior of the containment operators with arrays can be found in the documentation.

SQL/JSON &JSONPath

SQL standard added support for JSON  in SQL - SQL/JSON Standard-2016. With the PostgreSQL 12/13 releases, PostgreSQL has one of the best implementations of the SQL/JSON standard. For more details refer to the PostgreSQL 12 announcement.

One of the core features of SQL/JSON is support for the JSONPath language to query JSONB data. JSONPath allows you to specify an expression (using a syntax similar to the property access notation in Javascript) to query your JSONB data. This makes it simple and intuitive, but is also very powerful to query your JSONB data. Think of  JSONPath as the logical equivalent of XPath for XML.

.key Returns an object member with the specified key.
[*] Wildcard array element accessor that returns all array elements.
.* Wildcard member accessor that returns the values of all members located at the top level of the current object.
.** Recursive wildcard member accessor that processes all levels of the JSON hierarchy of the current object and returns all the member values, regardless of their nesting level.

Refer to JSONPath documentation for the full list of operators. JSONPath also supports a variety of filter expressions.

JSONPath Functions

PostgreSQL 12 provides several functions to use JSONPath to query your JSONB data. От документите:

  • jsonb_path_exists - Checks whether JSONB path returns any item for the specified JSON стойност.
  • jsonb_path_match - Returns the result of JSONB path predicate check for the specified JSONB value. Only the first item of the result is taken into account. If the result is not Boolean, then null is returned.
  • jsonb_path_query - Gets all JSONB items returned by JSONB path for the specified JSONB value. There are also a couple of other variants of this function that handle arrays of objects.

Let's start with a simple query - finding books by publisher:

demo=# select * from books where data @@ '$.publisher =="ktjKEZ1tvq"';id | author | isbn | rating | data---------+-----------------+------------+--------+----------------------------------------------------------------------------------------------------------------------------------1000001 | 4RNsovI2haTgU7l | GwSoX67gLS | 2 | {"tags":{"nk542369":{"ik55240":"iv305393"}}, "keywords":["abc", "def", "geh"], "publisher":"ktjKEZ1tvq", "criticrating":0}(1 row)demo=# explain analyze select * from books where data @@ '$.publisher =="ktjKEZ1tvq"';QUERY PLAN--------------------------------------------------------------------------------------------------------------------Bitmap Heap Scan on books (cost=21.75..1014.25 rows=1000 width=158) (actual time=0.123..0.124 rows=1 loops=1)Recheck Cond:(data @@ '($."publisher" =="ktjKEZ1tvq")'::jsonpath)Heap Blocks:exact=1-> Bitmap Index Scan on datagin (cost=0.00..21.50 rows=1000 width=0) (actual time=0.110..0.110 rows=1 loops=1)Index Cond:(data @@ '($."publisher" =="ktjKEZ1tvq")'::jsonpath)Planning Time:0.137 msExecution Time:0.194 ms(7 rows)

You can rewrite this expression as a JSONPath filter:

demo=# select * from books where jsonb_path_exists(data,'$.publisher ?(@ =="ktjKEZ1tvq")');

You can also use very complex query expressions. For example, let's select books where print style =hardcover and price =100:

select * from books where jsonb_path_exists(data, '$.prints[*] ?(@.style=="hc" &amp;amp;amp;amp;&amp;amp;amp;amp; @.price ==100)');

However, index support for JSONPath is very limited at this point - this makes it dangerous to use JSONPath in the where clause. JSONPath support for indexes will be improved in subsequent releases.

demo=# explain analyze select * from books where jsonb_path_exists(data,'$.publisher ?(@ =="ktjKEZ1tvq")');QUERY PLAN------------------------------------------------------------------------------------------------------------Seq Scan on books (cost=0.00..36307.24 rows=333340 width=158) (actual time=0.019..480.268 rows=1 loops=1)Filter:jsonb_path_exists(data, '$."publisher"?(@ =="ktjKEZ1tvq")'::jsonpath, '{}'::jsonb, false)Rows Removed by Filter:1000028Planning Time:0.095 msExecution Time:480.348 ms(5 rows)

Projecting Partial JSON

Another great use case for JSONPath is projecting partial JSONB from the row that matches. Consider the following sample JSONB:

demo=# select jsonb_pretty(data) from books where id =1000029;jsonb_pretty-----------------------------------{ "tags":{ "nk678947":{ "ik159670":"iv32358 } }, "prints":[ { "price":100, "style":"hc" }, { "price":50, "style":"pb" } ], "braille":false, "keywords":[ "abc", "kef", "keh" ], "hardcover":true, "publisher":"ppc3YXL8kK", "criticrating":3}

Select only the publisher field:

demo=# select jsonb_path_query(data, '$.publisher') from books where id =1000029;jsonb_path_query------------------"ppc3YXL8kK"(1 row)

Select the prints field (which is an array of objects):

demo=# select jsonb_path_query(data, '$.prints') from books where id =1000029;jsonb_path_query---------------------------------------------------------------[{"price":100, "style":"hc"}, {"price":50, "style":"pb"}](1 row)

Select the first element in the array prints:

demo=# select jsonb_path_query(data, '$.prints[0]') from books where id =1000029;jsonb_path_query-------------------------------{"price":100, "style":"hc"}(1 row)

Select the last element in the array prints:

demo=# select jsonb_path_query(data, '$.prints[$.size()]') from books where id =1000029;jsonb_path_query------------------------------{"price":50, "style":"pb"}(1 row)

Select only the hardcover prints from the array:

demo=# select jsonb_path_query(data, '$.prints[*] ?(@.style=="hc")') from books where id =1000029; jsonb_path_query------------------------------- {"price":100, "style":"hc"}(1 row)

We can also chain the filters:

demo=# select jsonb_path_query(data, '$.prints[*] ?(@.style=="hc") ?(@.price ==100)') from books where id =1000029;jsonb_path_query-------------------------------{"price":100, "style":"hc"}(1 row)

In summary, PostgreSQL provides a powerful and versatile platform to store and process JSON data. There are several gotcha's that you need to be aware of, but we are optimistic that it will be fixed in future releases.

Още съвети за вас

Which Is the Best PostgreSQL GUI?

PostgreSQL graphical user interface (GUI) tools help these open source database users to manage, manipulate, and visualize their data. In this post, we discuss the top 5 GUI tools for administering your PostgreSQL deployments. Научете повече

Managing High Availability in PostgreSQL

Managing high availability in your PostgreSQL hosting is very important to ensuring your clusters maintain exceptional uptime and strong operational performance so your data is always available to your application. Научете повече

PostgreSQL Connection Pooling:Part 1 – Pros &Cons

In modern apps, clients open a lot of connections. Developers are discouraged from holding a database connection while other operations take place. “Open a connection as late as possible, close as soon as possible”. Научете повече


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Използване на INSERT с PostgreSQL база данни с помощта на Python

  2. Как да използвам pg_stat_activity?

  3. PostgreSQL Създаване на схема

  4. Изберете най-добрите три стойности във всяка група

  5. Docker - Как мога да изпълним командата psql в контейнера postgres?