DB2 Avoid Duplicates in JSON_ARRAY - db2

I am using DB2LUW 11.5.
I build a json and gets an output like this
{
"ID": 1,
"NAME": "a",
"B_OBJECTS": [{
"ID": 1,
"SIZE": 10
}, {
"ID": 1,
"SIZE": 20
}
]
}
But I want the id from B_OBJECTS listed only once.
{
"ID": 1,
"NAME": "a",
"B_OBJECTS": [{
"ID": 1,
"SIZE": 10
}
]
}
Here is my query...
WITH TABLE_A(ID,NAME) AS (
VALUES (1, 'a')),
TABLE_B(ID, ID_A, SIZE) AS (
VALUES (1, 1, 10), (1, 1, 20)),
JSON_STEP_1 AS (
SELECT A.ID AS A_ID, A.NAME AS A_NAME, B.ID AS B_ID,
JSON_OBJECT('ID' VALUE B.ID, 'SIZE' VALUE B.SIZE) B_JSON
FROM TABLE_A A
JOIN TABLE_B B ON B.ID_A = A.ID
GROUP BY A.ID, A.NAME, B.ID, B.SIZE),
JSON_STEP_2 AS (
SELECT JSON_OBJECT ('ID' VALUE A_ID,
'NAME' VALUE A_NAME,
'B_OBJECTS' VALUE JSON_ARRAY (LISTAGG(B_JSON, ', ') WITHIN GROUP (ORDER BY B_ID) FORMAT JSON) FORMAT JSON
) JSON_OBJS
FROM JSON_STEP_1
GROUP BY A_ID, A_NAME
)
SELECT JSON_ARRAY (SELECT JSON_OBJS FROM JSON_STEP_2 FORMAT JSON) FROM SYSIBM.SYSDUMMY1;
I just updated the query with additional table TABLE_C
WITH
TABLE_A(ID,NAME) AS
(
VALUES (1, 'a')
)
, TABLE_B(ID, ID_A, SIZE) AS
(
VALUES (1, 1, 10), (1, 1, 20), (2, 1, 10), (2, 1, 20)
), TABLE_C(ID, ID_A, SIZE) AS
(
VALUES (1, 1, 5), (2,1,10), (3,1,15)
)
, JSON_STEP_1 AS
(
SELECT A_ID, A_NAME, B_ID
, JSON_OBJECT('ID' VALUE B_ID, 'SIZE' VALUE B_SIZE) B_JSON
, JSON_OBJECT('ID' VALUE C_ID, 'SIZE' VALUE C_SIZE) C_JSON
FROM
(
SELECT
A.ID AS A_ID, A.NAME AS A_NAME, B.ID AS B_ID, B.SIZE AS B_SIZE, C.ID AS C_ID, C.SIZE AS C_SIZE
, ROW_NUMBER () OVER (PARTITION BY B.ID, B.ID_A, B.SIZE) AS RN_
, ROW_NUMBER () OVER (PARTITION BY C.ID, C.ID_A, C.SIZE) AS RN1_
FROM TABLE_A A
JOIN TABLE_B B ON B.ID_A = A.ID
JOIN TABLE_C C ON C.ID_A = A.ID
)
WHERE RN_ = 1 AND RN1_ = 1
GROUP BY A_ID, A_NAME, B_ID, B_SIZE, B_ID, B_SIZE, C_ID, C_SIZE
)
, JSON_STEP_2 AS
(
SELECT
JSON_OBJECT
(
'ID' VALUE A_ID,
'NAME' VALUE A_NAME,
'B_OBJECTS' VALUE JSON_ARRAY (LISTAGG(B_JSON, ', ') WITHIN GROUP (ORDER BY B_ID) FORMAT JSON) FORMAT JSON,
'C_OBJECTS' VALUE JSON_ARRAY (LISTAGG(C_JSON, ', ') WITHIN GROUP (ORDER BY B_ID) FORMAT JSON) FORMAT JSON
) JSON_OBJS
FROM JSON_STEP_1
GROUP BY A_ID, A_NAME
)
SELECT JSON_ARRAY (SELECT JSON_OBJS FROM JSON_STEP_2 FORMAT JSON) FROM SYSIBM.SYSDUMMY1
The output should be like
{
"ID": 1,
"NAME": "a",
"B_OBJECTS": [{
"ID": 1,
"SIZE": 10
},
{
"ID": 1,
"SIZE": 20
},
{
"ID": 2,
"SIZE": 10
},
{
"ID": 2,
"SIZE": 20
}
],
"C_OBJECTS": [{
"ID": 1,
"SIZE": 5
},
{
"ID": 2,
"SIZE": 10
},
{
"ID": 3,
"SIZE": 15
}
]
}

WITH
TABLE_A (ID,NAME) AS
(
VALUES (1, 'a')
)
, TABLE_B (ID, ID_A, SIZE) AS
(
VALUES (1, 1, 10), (1, 1, 10), (2, 1, 10), (2, 1, 20)
)
, TABLE_C (ID, ID_A, SIZE) AS
(
VALUES (1, 1, 5), (2, 1, 10), (3, 1, 15)
)
SELECT
JSON_OBJECT
(
'ID' VALUE A.ID
, 'NAME' VALUE A.NAME
, 'B_OBJECTS' VALUE
JSON_ARRAY
(
(
SELECT JSON_OBJECT ('ID' VALUE ID, 'SIZE' VALUE SIZE FORMAT JSON)
FROM TABLE (SELECT DISTINCT ID, SIZE FROM TABLE_B B WHERE B.ID_A = A.ID ORDER BY ID)
)
FORMAT JSON
) FORMAT JSON
, 'C_OBJECTS' VALUE
JSON_ARRAY
(
(
SELECT JSON_OBJECT ('ID' VALUE ID, 'SIZE' VALUE SIZE FORMAT JSON)
FROM TABLE (SELECT DISTINCT ID, SIZE FROM TABLE_C C WHERE C.ID_A = A.ID ORDER BY ID)
)
FORMAT JSON
) FORMAT JSON
) JSON_OBJ
FROM TABLE_A A
{
"ID":1,"NAME":"a"
,"B_OBJECTS":
[
{"ID":1,"SIZE":10},
{"ID":2,"SIZE":10},
{"ID":2,"SIZE":20}
]
,"C_OBJECTS":
[
{"ID":1,"SIZE":5},
{"ID":2,"SIZE":10},
{"ID":3,"SIZE":15}
]
}

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 to convert json to string by concatenating certain keys in postgres

Input:
[{"name": "X", "strength": "10"}, {"name": "Y", "strength": "30"}]
Desired output:
X-Y 10-30
First aggregate into arrays and then convert the arrays to strings.
select array_to_string(array_agg(j ->> 'name'), ','),
array_to_string(array_agg(j ->> 'strength'), ',')
from jsonb_array_elements
('[
{"name": "X", "strength": "10"},
{"name": "Y", "strength": "30"},
{"name": "Z", "strength": "20"}
]') j;
When doing on a table column:
select col1,
(select array_to_string(array_agg(j ->> 'name'), ',')
from json_array_elements(cast(col1 as json)) j),
(select array_to_string(array_agg(j ->> 'strength'), ',')
from json_array_elements(cast(col1 as json)) j)
from table1;

PostgreSQL how to merge jsonb keys and count values

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

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

not understand json_agg in this context

Reference: Query jsonb column containing array of JSON objects
begin;
CREATE TEMP TABLE segments (segments_id serial PRIMARY KEY, payload jsonb);
INSERT INTO segments (payload)
VALUES ('[{"kind": "person", "limit": "1"}, {"kind": "B", "filter_term": "fin"}]');
INSERT INTO segments (payload)
VALUES ('[{"kind": "person", "limit": "3"}, {"kind": "A", "filter_term": "abc"}]');
INSERT INTO segments (payload)
VALUES ('[{"kind": "person", "limit": "2"}, {"kind": "C", "filter_term": "def"}]');
commit;
CTE query:
with a as (select jsonb_array_elements(s.payload) j from segments s)
SELECT json_agg(a.j) AS filtered_payload from a
where j #> '{"kind":"person"}';
Return: [{"kind": "person", "limit": "1"}, {"kind": "person", "limit": "3"}, {"kind": "person", "limit": "2"}]
This QueryA:
SELECT a.filtered_payload,
a.ct_elem_row
, sum(ct_elem_row) OVER () AS ct_elem_total
, count(*) OVER () AS ct_rows
FROM segments s
JOIN LATERAL (
SELECT json_agg(j.elem) AS filtered_payload, count(*) AS ct_elem_row
FROM jsonb_array_elements(s.payload) j(elem)
WHERE j.elem #> '{"kind":"person"}'
) a ON ct_elem_row > 0
WHERE s.payload #> '[{"kind":"person"}]';
return :
In QueryA, the structure is like: select ... from segments s join lateral filtered_payload.... segments is 3 rows lateral join with one row (filtered_payload). filtered_payload return only row as per CTE query, as an a consolidate JSON array. So overall I am very confused with json_agg in the QueryA.
Edit at 2021-10-05 16:36 +5:30:
Even following code, a.filtered_payload return 3 jsonb array, instead of one arrgregate json array. I don't know when already aggregated jsonb array (using json_agg function) unnested to serveal jsonb arrays.
SELECT a.filtered_payload, s.*
FROM segments s
cross JOIN LATERAL (
SELECT json_agg(j.elem) AS filtered_payload
FROM jsonb_array_elements(s.payload) j(elem)
WHERE j.elem #> '{"kind":"person"}') a;
I believe the LATERAL JOIN is doing the trick there.
In your original query, you're using json_agg against the whole table dataset (filtered by '{"kind":"person"}')
with a as
(
select jsonb_array_elements(s.payload) j
from segments s
)
SELECT json_agg(a.j) AS filtered_payload
from a
where j #> '{"kind":"person"}';
Meanwhile in the second instance, you are playing with one row at the time using the LATERAL. That's why you end up having 3 rows with single "kind":"person" values instead of an unique row with 3 values.
Not sure about what you're trying to achieve but the following could put you in the right direction
SELECT a.filtered_payload,
a.ct_elem_row,
sum(ct_elem_row) OVER () AS ct_elem_total,
count(*) OVER () AS ct_rows
FROM segments s
JOIN LATERAL (
SELECT json_agg(j.elem) AS filtered_payload,
count(*) AS ct_elem_row
FROM segments d, lateral jsonb_array_elements(d.payload) j(elem)
WHERE j.elem #> '{"kind":"person"}'
) a ON ct_elem_row > 0
WHERE s.payload #> '[{"kind":"person"}]';
Results
filtered_payload | ct_elem_row | ct_elem_total | ct_rows
--------------------------------------------------------------------------------------------------------+-------------+---------------+---------
[{"kind": "person", "limit": "1"}, {"kind": "person", "limit": "3"}, {"kind": "person", "limit": "2"}] | 3 | 9 | 3
[{"kind": "person", "limit": "1"}, {"kind": "person", "limit": "3"}, {"kind": "person", "limit": "2"}] | 3 | 9 | 3
[{"kind": "person", "limit": "1"}, {"kind": "person", "limit": "3"}, {"kind": "person", "limit": "2"}] | 3 | 9 | 3
(3 rows)