Recently PostgreSQL introduced the JSON and JSONB data types. At first, we ignored this news, we could not see how the JSON types could be of use to us. But then we used it once and then twice and then we almost went overboard with JSONs. This prompted us to stop, take a step back and analyze when a JSON type is useful and when it causes unnecessary hassle.
3. Postgre SQL and NoSQL
Postgre 9.4 (December 2014) JSONB: Binary format, slower input, faster processing
JSON: Exact copy, same orderPostgre 9.2
No explicit
schema
Key-Value pairs in JSON are equal to
Key-Value pairs in columns
11. JSON Operators: ->
arrays objects
int text
SELECT '["a",2,"3"]'::json->0 as data
data (json)
------------------
"a"
SELECT '["a",2,"3"]'::json->1 as data
data (json)
------------------
2
SELECT '["a",2,"3"]'::json->2 as data
data (json)
------------------
"3"
SELECT '["a",2,"3"]'::json->'a' as data
data (json)
------------------
SELECT '{"a":1,"b":2}'::json->'b' as data
data (json)
------------------
2
SELECT '{"a":1,"b":{"c":[1,2]}}'::json->'b'
data (json)
------------------
{"c":[1,2]}
json json
12. JSON Operators: ->>
arrays
int
SELECT '["a",2,"3"]'::json->>0 as data
data (text)
------------------
"a"
SELECT '["a",2,"3"]'::json->>1 > 1
ERROR: operator does not exist: text >
integer
SELECT ('["a",2,"3"]'::json->>1)::int > 1
data (boolean)
---------------
t
SELECT ('["a",2,"3"]'::json->1)::int > 1
ERROR: cannot cast type json to integer
text
13. JSON Operators: ->>
SELECT '{"a":1,"b":"word"}'::json->>'b'
data (text)
------------------
"word"
SELECT '{"a":1,"b":"word"}'::json->>'b' = 'word'
data(boolean)
-----------
t
SELECT
'{
"identifier":1mo3j4,
"items":[
{
"mon":"Monday",
"tue":"Tuesday"
},
{
"jan":"january",
"feb":"february"
}
]
}'::json->'items'->0->>'tue'
data (text)
--------------
"Tuesday"
objects
text text
14. JSON Operators: #>, #>>
SELECT
'{
"id":1,
"items":[
{
"mon":"Monday",
"tue":"Tuesday"
},
{
"jan":"january",
"feb":"february"
}
]
}'::json #>> '{items,0,tue}'
data (text)
--------------
"Tuesday"
SELECT
'{
"id":1,
"items":[
{
"mon":"Monday",
"tue":"Tuesday"
},
{
"jan":"january",
"feb":"february"
}
]
}'::json ->'items'->0->>'tue'
data (text)
--------------
"Tuesday"
objects
array of text text
16. SELECT count("ID") as number_of_products_promo_was_used
FROM "PaymentRequestEntry"
WHERE "ShopProductJSON" @> '{"Promotion":{"Code": "SUMMER17"}}'
{
"SKU": "LJCRD-24-ADL",
"IsVoucher": false,
"Promotion": {
"Code": "SUMMER17",
},
"PriceNoTax": "19.67",
"TaxRateNice": "0.22",
"PriceWithTax": "24.00",
"DiscountPriceNoTax": "10.00",
"DiscountPriceWithTax": "10.00",
}
SELECT
"PaymentRequestID",
sum(("ShopProductJSON"->>'PriceWithTax')::float)
- sum(("ShopProductJSON"->>'DiscountPriceWithTax')::float) as money_saved
FROM "PaymentRequestEntry"
WHERE "ShopProductJSON" @> '{"Promotion":{"Code": "SUMMER17"}}‘
GROUP BY "PaymentRequestID"
PaymentRequestID money_saved
845623 5.12
845622 6.01
845625 2.2
...
17. SELECT
SUM("CartCount") AS number_of_products,
SUM(("ShopProductJSON"->>'PriceWithTax')::float) -
SUM(("ShopProductJSON"->>'DiscountPriceWithTax')::float) AS
money_saved,
r."DeliveryAddressJSON"->>'Country' AS country
FROM "PaymentRequestEntry" e
INNER JOIN "PaymentRequest" r ON r."ID" = e."PaymentRequestID"
WHERE "ShopProductJSON"->'Promotion'->>'Code' = 'SUMMER17'
GROUP BY r."DeliveryAddressJSON"->>'Country'
ORDER BY money_saved
number_of_products money_saved country
481 1503.72 DE
512 1438.56 AT
503 1289.05 IT
...
18. SELECT
sp."CalculatedPrice",
e."ShopProductJSON"->>'PriceWithTax' as bought_price,
e."Created"
FROM "PaymentRequestEntry" e
INNER JOIN "ShopProduct" sp
ON sp."SKU" = e."ShopProductJSON"->>'SKU'
AND e."ShopProductJSON" @> '{"SKU": "LJCRD-24-ADL"}'
ORDER BY e."Created" desc
CalculatedPrice bought_price Created
24 24 2017-04-21 15:23:01
24 21.6 2017-04-21 15:22:12
...
24 22 2016-12-01 07:17:53
...
19. EXPLAIN SELECT *
FROM "PaymentRequestEntry"
WHERE "ShopProductJSON" @> '{"Promotion":{"Code": "SUMMER17"}}'
Seq Scan on "PaymentRequestEntry" (cost=0.00..21.02 rows=3 width=100)
Filter: ((("ShopProductJSON" -> 'Promotion'::text) ->> 'Code'::text) =
'SUMMER17'::text)
GIN index
over the
WHOLE JSON
for only 1 key
in the JSON
jsonb_ops :
bigger & more
versatily
jsonb_path_ops:
smaller & supports
only @> querys
20. EXPLAIN SELECT * FROM "PaymentRequestEntry"
WHERE "ShopProductJSON" @> '{"Promotion":{"Code": "SUMMER17"}}'
Seq Scan on "PaymentRequestEntry" (cost=0.00..511091.51 rows=3662
width=85)
table "PaymentRequestEntry" size: 3635 MB, 2.807.147 rows
CREATE index json_index ON "PaymentRequestEntry"
USING GIN (("ShopProductJSON"->'Promotion'->'Code'))
CREATE index json_index ON "PaymentRequestEntry"
USING GIN ("ShopProductJSON")
@> <@
? ?| ?&
CREATE index json_index ON "PaymentRequestEntry"
USING GIN ("ShopProductJSON" jsonb_path_ops)
@>
indexname num_rows index_size
json_index_ops 2.807.147 10 MB
json_index_path_ops 2.807.147 7.832 MB (-20%)
json_index_column 2.807.147 3.112 MB (-70%)
json_index_column_path 2.807.147 3.112 MB (-70%)
1.
2.
3.