PostgreSQL how to merge jsonb keys and count values - postgresql

I have a table that looks somthing like the following:
id
name
category
specs (jsonb)
1
product1
phones
{ "brand": "brand1", "color": "red", "size": 5, "memory": "8GB"}
2
product2
phones
{ "brand": "brand1", "color": "white", "size": 7, "memory": "8GB"}
3
product3
laptops
{ "brand": "brand20", "storage": "SSD", "os": "os1" , "memory": "32GB"}
My desired output given a specific category
{
"brand": {
"brand1": 1,
"brand2": 1
},
"color": {
"red": 1,
"white": 5,
},
"memory": {
"8gb": 2,
}
}

Blow out the specs column with jsonb_each_text(), calculate the counts, and then reassemble with jsonb_object_agg() (the first two CTEs could be combined into one, but I left them verbose for illustration):
with blowout as (
select s.category, j.key, j.value
from somthing s
cross join lateral jsonb_each_text(s.specs) as j(key, value)
), counts as (
select category, key, value, count(1) as cnt
from blowout
group by category, key, value
), agg_specs as (
select category, key, jsonb_object_agg(value, cnt) as counts
from counts
group by category, key
)
select category, jsonb_object_agg(key, counts) as output
from agg_specs
group by category
;
db<>fiddle here

Related

How to reverse query for a record, returning all of its parent and their parents?

Suppose the following,
create schema if not exists my_schema;
CREATE TABLE IF NOT EXISTS my_schema.category (
id serial PRIMARY KEY,
category_name VARCHAR (255) NOT NULL,
subcategories BIGINT[] DEFAULT ARRAY[]::BIGINT[]
);
INSERT INTO my_schema.category VALUES
(
1,
'Pickup/dropoff',
'{}'
),
(
2,
'Electrician',
'{}'
),
(
3,
'Around the house',
'{2}'
),
(
4,
'Personal',
'{3}'
);
I'm trying to create breadcrumbs on the frontend. For instance, if Electrician is the selected category, I'd like to render something like Personal > Around the house > Electrician.
Ideally, I'd like the query to return something like,
{
"id": 2,
"breadcrumbs": "Personal.Around the house.Electrician"
}
This way, I could just do breadcrumbs.split('.').map(. . .).
Another result that would work is something like,
{
"id": 2,
"category_name": "Electrician"
"breadcrumbs": [
{
"id": 4,
"category_name": "Personal"
},
{
"id": 3,
"category_name": "Around the house"
},
]
}
Or even,
{
"breadcrumbs": [
{
"id": 4,
"category_name": "Personal"
},
{
"id": 3,
"category_name": "Around the house"
},
{
"id": 2,
"category_name": "Electrician"
}
]
}
How can I attain such a result?
You can do it using WITH RECURSIVE as follows :
to get any breadcrumbs just put its id on the first select where id = 2
WITH RECURSIVE cte(n, id, selectedCat) AS
(
SELECT 1, id, id::text
from category
where id = 2
UNION ALL
SELECT n+1, e.id, ep.selectedCat
FROM cte AS ep JOIN category AS e
ON ep.id = ANY (e.subcategories)
)
SELECT selectedCat, string_agg(category_name, ',') as breadcrumbs FROM (
SELECT selectedCat, category_name
FROM cte
inner join category c on c.id = cte.id
order by n desc
) AS s
GROUP BY selectedCat
demo here

How do I create a Postgres table with a column that holds an array of objects?

The documentation shows that arrays can be created as either text or integer. I did not find an example for creating an object array. An example is the items column:
CREATE TABLE cart (
_id serial PRIMARY KEY,
user_id Integer UNIQUE NOT NULL,
items varchar [],
FOREIGN KEY (user_id)
REFERENCES users (_id)
Object stored in the items array have quotes around them making them strings instead of objects. A code segment creating objects in items is shown below:
update cart
set items =
array_append(items,
'{product_id: ${cartItem.productId}, size: ${cartItem.size}, quantity: ${cartItem.quantity}}')
where _id = ${cart._id}
and user_id = ${userId}
RETURNING *
I am compelled to put quotes around the object value. As a result it is stored as a string in the column with quotes around it.
cart {
_id: 1,
user_id: 1,
items: [
'{product_id: 1, size: Small, quantity: 1}',
'{product_id: 1, size: Small, quantity: 1}',
'{product_id: 1, size: Small, quantity: 1}'
]
}
Because the items in the items column are stored as string instead of objects, I cannot correctly iterate over them in my program. For example,
items.product_id does not exist.
How do I fix this?
I wrote some examples for you
Example 1. (Insert into JSONB type using converting String to JSONB)
INSERT INTO cart
(
product_id,
quantity,
"size",
user_id,
items
)
values
(
1,
1,
'Small',
1,
'[
{"size": "Small", "quantity": 1, "product_id": 1},
{"size": "Small", "quantity": 1, "product_id": 1},
{"size": "Small", "quantity": 1, "product_id": 1}
]'::jsonb
);
Example 2. (Convert fields of selected table to JSONB using row_to_json)
select row_to_json(t1) as your_json from (
select product_id, "size", quantity from cart
) t1
Result:
your_json
--------------------------------------------
{"product_id":1,"size":"Small","quantity":1}
{"product_id":1,"size":"Small","quantity":1}
{"product_id":1,"size":"Small","quantity":1}
Example 3. (Concat all rows of type JSONB)
select jsonb_agg(row_to_json(t1)) as your_json from (
select product_id, "size", quantity from cart
) t1
Result:
your_json
---------------------------------------------------------------------------------------------------------------------------------------------------------
[{"size": "Small", "quantity": 1, "product_id": 1}, {"size": "Small", "quantity": 1, "product_id": 1}, {"size": "Small", "quantity": 1, "product_id": 1}]
Example 4. (Similar of row_to_json)
select
json_build_object('product_id', product_id, 'size', size, 'quantity', quantity) as your_json
from cart;
Result:
your_json
--------------------------------------------
{"product_id":1,"size":"Small","quantity":1}
{"product_id":1,"size":"Small","quantity":1}
{"product_id":1,"size":"Small","quantity":1}
Example 5. (Concatenate many JSONB objects using ||)
select jsonb_build_array('{"product_id":1,"size":"Small","quantity":1}'::jsonb) || jsonb_build_array('{"product_id":1,"size":"Small","quantity":1}'::jsonb) as your_json
-- or
select jsonb_build_array('{"product_id":1,"size":"Small","quantity":1}'::jsonb, '{"product_id":1,"size":"Small","quantity":1}'::jsonb) as your_json
Result:
your_json
------------------------------------------------------------------------------------------------------
[{"product_id": 1, "size": "Small", "quantity": 1}, {"product_id": 1, "size": "Small", "quantity": 1}]
Example 6. (Similar example to your update query (using concatenating JSONB types)
update cart
set
items = jsonb_build_array(items) || jsonb_build_array('{"product_id":1,"size":"Small","quantity":1}'::jsonb)
where id = 1
Result:
items
------------------------------------------------------------------------------------------------------
[{"product_id": 1, "size": "Small", "quantity": 1}, {"product_id": 1, "size": "Small", "quantity": 1}]
Recommended using JSON or JSONB types. In JSON types you can perform any operations on keys or values, such as filtering, selecting, joining. If you need you can even show JSON keys and values as table record view.
Examples:
items: '[
{"product_id": 1, "size": "Small", "quantity": 1},
{"product_id": 1, "size": "Small", "quantity": 1},
{"product_id": 1, "size": "Small", "quantity": 1}
]'
select
json_array_elements(items)->'product_id' as product_id,
json_array_elements(items)->'size' as "size",
json_array_elements(items)->'quantity' as quantity
from cart
where user_id = 1;
Result:
product_id
size
quantity
1
"Small"
1
1
"Small"
1
1
"Small"
1

How to return data in JSON hierarchy?

For the following data tables and function in pg12
create table orders
(
orderid integer, grandtotal numeric(10, 2)
)
create table odetails
(
orderid integer, detailid integer, description text
)
create function jorder() returns json as
begin
return query select od.orderid, od.grandtotal, ds.detailid, ds.description
from orders od
join odetails ds on od.orderid = ds.orderid;
end;
How do I get return data in a JSON hierarchy like below?
[{
"orderid": 1,
"grandtotal": 100.00,
"details": [{
"detailid": 11,
"description": "pen"
},
{
"detailid": 12,
"description": "orange"
}
]
}, {
"orderid": 2,
"grandtotal": 200.00,
"details": [{
"detailid": 21,
"description": "book"
},
{
"detailid": 22,
"description": "coffee"
},
{
"detailid": 23,
"description": "tea"
}
]
}]
You should look into json functions . You need json_build_object to form the objects and json_agg to aggregate them into json array:
CREATE FUNCTION jorder()
RETURNS json
LANGUAGE sql
AS $$
SELECT
json_agg(orders.order)
FROM (
SELECT
json_build_object(
'orderid', od.orderid,
'grandtotal', od.grandtotal,
'details', array_agg(
json_build_object(
'detailid', ds.detailid,
'description', ds.description
)
)
) as order
FROM
orders od
JOIN odetails ds on od.orderid = ds.orderid
GROUP BY
od.orderid, od.grandtotal
) as orders
$$;
Example at db<>fiddle

JSONB Represent jsonb key value like array

I have field extra with type jsonb in my table product. And I need get uniq key with uniq values for each key from all products row. Example data in extra field
{"SIZE": "110/116", "COLOUR": "Vit", "GENDER": "female", "AGE_GROUP": "Kids", "ALTERNATIVE_IMAGE": "some_path"}
for now I use query like this
select DISTINCT e.key, array_agg(DISTINCT e.value) as fields
from products AS p
join jsonb_each_text(p.extras) e on true
GROUP BY e.key
Ad have respnse (small part with some keys in full response all keys are present) like this
[
{
"key": "AGE_GROUP",
"fields": "{Adult,children,Kids}"
},
{
"key": "GENDER",
"fields": "{female,male,man}"
}
]
how to change it to array for fields alias ?
like this
[
{
"AGE_GROUP": ["Adult","children","Kids"]
},
{
"GENDER": ["female","male","man"]
}
]
or maybe whould be great like this
[
"some_alias": [{"AGE_GROUP": "Adult", "AGE_GROUP": "children", "AGE_GROUP": "Kids"}],
"some_alias": [{"GENDER": "female", "GENDER": "male", "GENDER": "man"}]
]
This will get you the former form I think:
select jsonb_agg(jsonb_build_object(k,v)) from (
select DISTINCT e.key, jsonb_agg(DISTINCT e.value) as fields
from products AS p
join jsonb_each_text(p.extras) e on true
GROUP BY e.key
) b(k,v);
Best regards,
Bjarni

Is there a magic function with can extract all select keys/nested keys including array from jsonb

Given a jsonb and set of keys how can I get a new jsonb with required keys.
I've tried extracting key-values and assigned to text[] and then using jsonb_object(text[]). It works well, but the problem comes when a key has a array of jsons.
create table my_jsonb_table
(
data_col jsonb
);
insert into my_jsonb_table (data_col) Values ('{
"schemaVersion": "1",
"Id": "20180601550002",
"Domains": [
{
"UID": "29aa2923",
"quantity": 1,
"item": "book",
"DepartmentDomain": {
"type": "paper",
"departId": "10"
},
"PriceDomain": {
"Price": 79.00,
"taxA": 6.500,
"discount": 0
}
},
{
"UID": "bbaa2923",
"quantity": 2,
"item": "pencil",
"DepartmentDomain": {
"type": "wood",
"departId": "11"
},
"PriceDomain": {
"Price": 7.00,
"taxA": 1.5175,
"discount": 1
}
}
],
"finalPrice": {
"totalTax": 13.50,
"total": 85.0
},
"MetaData": {
"shopId": "1405596346",
"locId": "95014",
"countryId": "USA",
"regId": "255",
"Date": "20180601"
}
}
')
This is what I am trying to achieve :
SELECT some_magic_fun(data_col,'Id,Domains.UID,Domains.DepartmentDomain.departId,finalPrice.total')::jsonb FROM my_jsonb_table;
I am trying to create that magic function which extracts the given keys in a jsonb format, as of now I am able to extract scalar items and put them in text[] and use jsonb_object. but don't know how can I extract all elements of array
expected output :
{
"Id": "20180601550002",
"Domains": [
{
"UID": "29aa2923",
"DepartmentDomain": {
"departId": "10"
}
},
{
"UID": "bbaa2923",
"DepartmentDomain": {
"departId": "11"
}
}
],
"finalPrice": {
"total": 85.0
}
}
I don't know of any magic. You have to rebuild it yourself.
select jsonb_build_object(
-- Straight forward
'Id', data_col->'Id',
'Domains', (
-- Aggregate all the "rows" back together into an array.
select jsonb_agg(
-- Turn each array element into a new object
jsonb_build_object(
'UID', domain->'UID',
'DepartmentDomain', jsonb_build_object(
'departId', domain#>'{DepartmentDomain,departId}'
)
)
)
-- Turn each element of the Domains array into a row
from jsonb_array_elements( data_col->'Domains' ) d(domain)
),
-- Also pretty straightforward
'finalPrice', jsonb_build_object(
'total', data_col#>'{finalPrice,total}'
)
) from my_jsonb_table;
This probably is not a good use of a JSON column. Your data is relational and would better fit traditional relational tables.