Flatten nested JSON structure in PostgreSQL - json

I'm trying to write a Postgres query that will output my json data in a particular format.
JSON data structure
{
user_id: 123,
data: {
skills: {
"skill_1": {
"title": "skill_1",
"rating": 4,
"description": 'description text'
},
"skill_2": {
"title": "skill_2",
"rating": 2,
"description": 'description text'
},
"skill_3": {
"title": "skill_3",
"rating": 5,
"description": 'description text'
},
...
}
}
}
This is how I need the data to be formatted in the end:
[
{
user_id: 123,
skill_1: 4,
skill_2: 2,
skill_3: 5,
...
},
{
user_id: 456,
skill_1: 1,
skill_2: 3,
skill_3: 4,
...
}
]
So far I'm working with a query that looks like this:
SELECT
user_id,
data#>>'{skills, "skill_1", rating}' AS "skill_1",
data#>>'{skills, "skill_2", rating}' AS "skill_2",
data#>>'{skills, "skill_3", rating}' AS "skill_3"
FROM some_table
There has to be a better way to go about writing my query. There are 400+ rows and 70+ skills. My above query is a little crazy. Any guidance or help would be greatly appreciated.
Some things to note:
Users rated themselves on 70+ skills
Each skill object has the same structure
Each user rated themselves on the exact same set of skills

db<>fiddle
I expanded your test data to (note the array around all users):
[{
"user_id": 123,
"data": {
"skills": {
"skill_1": {
"title": "skill_1",
"rating": 4,
"description": "description text"
},
"skill_2": {
"title": "skill_2",
"rating": 2,
"description": "description text"
},
"skill_3": {
"title": "skill_3",
"rating": 5,
"description": "description text"
}
}
}
},
{
"user_id": 456,
"data": {
"skills": {
"skill_1": {
"title": "skill_1",
"rating": 1,
"description": "description text"
},
"skill_2": {
"title": "skill_2",
"rating": 3,
"description": "description text"
},
"skill_3": {
"title": "skill_3",
"rating": 4,
"description": "description text"
}
}
}
}]
The query:
SELECT
jsonb_pretty(jsonb_agg(user_id || skills)) -- E
FROM (
SELECT
json_build_object('user_id', user_id)::jsonb as user_id, -- D
json_object_agg(skill_title, skills -> skill_title -> 'rating')::jsonb as skills
FROM (
SELECT
user_id,
json_object_keys(skills) as skill_title, -- C
skills
FROM (
SELECT
(datasets -> 'user_id')::text as user_id,
datasets -> 'data' -> 'skills' as skills -- B
FROM (
SELECT
json_array_elements(json) as datasets -- A
FROM (
SELECT '/* the JSON data; see db<>fiddle */'::json
)s
)s
)s
)s
GROUP BY user_id
ORDER BY user_id
)s
A Make all array elements ({user_id: '42', data: {...}}) one row each
B First column safe the user_id. The cast to text ist necessary for the GROUP BY later which cannot group JSON output. For the second column extract the skills data of the user
C Extract the skill titles for using them as keys in (D.1).
D.1 skills -> skill_title -> 'rating' extracts the rating value from each skill
D.2 json_object_agg aggregates the skill_titles and each corresponding rating value into one JSON object; grouped by the user_id
D.3 json_build_object makes the user_id a JSON object again
E.1 user_id || skills aggregates the two json object into one
E.2 jsonb_agg aggregates these json objects into an array
E.3 jsonb_pretty makes the result looking pretty.
Result:
[{
"skill_1": 4,
"skill_2": 2,
"skill_3": 5,
"user_id": "123"
},
{
"skill_1": 1,
"skill_2": 3,
"skill_3": 4,
"skill_4": 42,
"user_id": "456"
}]

Related

Select from mysql array of objects with integer number and value null or source to file without duplicates

Let's say I have a table with rows like number, image. But image is not mandatory, It can be null and when I'm selecting I want to prioritize the row with image over the one with null so i could get clean array with only one row per number.
SELECT DISTINCT number, image FROM table ORDER BY number ASC
What's now with SELECT DISTINCT:
[
{
"number": 1,
"image": null
},
{
"number": 1,
"image": "https://example.com/image1.png"
},
{
"number": 2,
"image": null
},
{
"number": 2,
"image": "https://example.com/image2.png"
},
{
"number": 3,
"image": "https://example.com/image3.png"
},
{
"number": 3,
"image": null
},
{
"number": 4,
"image": null
}
]
What I want to get:
[
{
"number": 1,
"image": "https://example.com/image1.png"
},
{
"number": 2,
"image": "https://example.com/image2.png"
},
{
"number": 3,
"image": "https://example.com/image3.png"
},
{
"number": 4,
"image": null
}
]
Try this:
SELECT number, image FROM table ORDER BY ISNULL(LEFT(image,0));

Create json object from tree structure in Postgresql

I have a nested tree data structure of groups and layers that need to be generated from a couple of joined database tables and then be stored as JSON in a PostgreSQL (version 12.8) database. Groups can contain layer objects, but also (sub)groups. The desired JSON output (generated via SQL and/or function) would look like shown below.
[{
"title": "Folder 1",
"type": "group",
"folded": false,
"layers": [{
"title": "Layer 1",
"type": "WMS",
"visible": true
},
{
"title": "Folder 2",
"type": "group",
"folded": true,
"layers": [{
"title": "Layer 2",
"type": "WMS",
"visible": true
},
{
"title": "Layer 3",
"type": "WMS",
"visible": true
},
{
"title": "Folder 3",
"type": "group",
"folded": true,
"layers": [{
"title": "Layer 4",
"type": "WMS",
"visible": false
},
{
"title": "Layer 5",
"type": "WMS",
"visible": true
}
]
}
]
}
]
}]
These are the database tables and sample data:
CREATE TABLE IF NOT EXISTS jtest.folders
(
id serial,
item_id integer,
title text,
folded boolean NOT NULL
);
CREATE TABLE IF NOT EXISTS jtest.layers
(
id serial,
item_id integer,
title text,
type text,
visible boolean
);
CREATE TABLE IF NOT EXISTS jtest."connect"
(
id serial,
item_id integer,
child_id integer,
layer boolean DEFAULT true
);
--data
INSERT INTO jtest.folders(id, item_id, title, folded) VALUES
(1,1,'Folder 1',false),(2,2,'Folder 2', true),(3,3,'Folder 3', true);
INSERT INTO jtest.layers(item_id,id,title,type,visible) VALUES
(1,4,'Layer 1','WMS',true),(2,5,'Layer 2','WMS',true),(3,6,'Layer 3','WMS',true),(4,7,'Layer 4','WMS',true),(5,8,'Layer 5','WMS',true);
INSERT INTO jtest.connect (item_id, child_id, layer) VALUES
(1,4,true),(1,2,false),(2,5,true),(2,6,true),(2,3,false),(3,7,true),(3,8,true);
I partially succeeded to generate a JSON output for a simple folder-layer tree using queries like the one below, but could not figure out how to correctly handle folders nested into a list of layers (aka sub folders).
SELECT jsonb_agg(sub)
from (
SELECT f.id, f.folded,f.item_id,f.title, to_jsonb(array_agg(l.*)) as layers
FROM jtest.folders f
JOIN jtest.connect c ON f.item_id = c.item_id
JOIN jtest.layers l ON l.item_id = c.child_id
where c.layer is true
GROUP BY f.id,f.folded,f.item_id,f.title
ORDER BY f.item_id ) sub;
Any ideas or examples on how to solve this ?

how to implement sub select with where condition in sequelize

I have these tables:
products
stores
produuctProperties
with this structure
[
"products" :
{
"id": 1,
"orginalName": "146153-0100 ",
"title": null,
"stores": [
{
"id": 1,
"stock": 100,
"minOQ": 1,
"maxOQ": 0
},
{
"id": 2,
"stock": 100,
"minOQ": 1,
"maxOQ": 0,
}
],
"productproperties": [
{
"id": 1,
"productId": 1,
"propertyId": 8,
"propertyOptionId": 5
},
{
"id": 2,
"productId": 1,
"propertyId": 9,
"propertyOptionId": 11
},
{
"id": 3,
"productId": 1,
"propertyId": 10,
"propertyOptionId": 9
}
]
}
]
I want filter my products by selected options , Suppose the selected options are 11 and 9
how to implement below sql query in Sequelize 5.6 with findAll , where and... :
select * from products as p
inner join stores as sr on sr.productId = p.id
where (select count(*) from productProperties where propertyOptionId in (11,9) and productId = p.id) >= 2
I've found that using query builder in sequelize is really confusing,
so if you're good with raw sql you could just run them on as below
if Student is you're model
then
const students = Student.query('Select * from students');

Couchbase/N1QL - Get all tie-in records that don't exist (opposite/inverse of inner join)

Query to get tie-in records that DO exist (essentially, all manufacturer defects that exist):
select
md.f_manufacturerId, md.f_defectId
from productionlines as md
inner join productionlines as m on m.type="manufacturer" and m.manufacturerId=md.f_manufacturerId
inner join productionlines as d on d.type="defect" and d.defectId=md.f_defectId
where md.type="manufacturerdefect"
order by md.f_manufacturerId
I want the OPPOSITE/INVERSE of this...
I want to know the list of DEFECT TO MANUFACTURER tie-ins we DO NOT HAVE.
Structure of the Data...
MANUFACTURER:
{
"name": "Ball Bearings"
"id": "00a4260956d54e46932001d853df843c",
"manufacturerId": 28,
"type": "manufacturer"
}
{
"name": "Wheel Rims"
"id": "3ad4b5c6433d6e8b9c230fdd5dda8b33",
"manufacturerId": 2,
"type": "manufacturer"
}
DEFECT:
{
"name": "Bad Drill Bit"
"id": "c348fd358d10023964e45d6590624a00",
"defectId": 7,
"type": "defect"
}
{
"name": "Bad Shipping"
"id": "33b8add5ddf032c9b8e6d3346c5b4da3",
"defectId": 9,
"type": "defect"
}
MANUFACTURER DEFECTS:
{
"id": "de426435bd10023964e45d6590624a00",
"f_defectId": 7,
"f_manufacturerId": 2,
"type": "manufacturerdefect"
}
{
"id": "de426435bd10023964e45d6590624a01",
"f_defectId": 9,
"f_manufacturerId": 2,
"type": "manufacturerdefect"
}
{
"id": "de426435bd10023964e45d6590624a02",
"f_defectId": 7,
"f_manufacturerId": 28,
"type": "manufacturerdefect"
}
{
"id": "de426435bd10023964e45d6590624a03",
"f_defectId": 9,
"f_manufacturerId": 28,
"type": "manufacturerdefect"
}
So to be clear, if any of the manufacturer defects above DID NOT EXIST, I want to know all manufacturer and defect id combinations that are missing.
STEP 1: Find all combinations.
STEP 2: Exclude all existing manufacturing defects.
(select
m.manufacturerId as manufacturerId, d.defectId as defectId
from productionlines as d
inner join productionlines as m on m.type="manufacturer"
where d.type="defect"
order by m.manufacturerId)
EXCEPT
(select
md.f_manufacturerId as manufacturerId, md.f_defectId as defectId
from productionlines as md
where md.type="manufacturerdefect")
order by manufacturerId

Postgres PLpgSQL JSON SUM

I'm trying to calculate the sum of some JSON values in PLpgSQL (Postgres v9.5.5) but am stuck on the logic.
For this data set:
{
clientOrderId: 'OR836374647',
status: 'PENDING',
clientId: '583b52ede4b1a3668ba0dfff',
sharerId: '583b249417329b5b737ad3ee',
buyerId: 'abcd12345678',
buyerEmail: 'test#test.com',
lineItems: [{
name: faker.commerce.productName(),
description: faker.commerce.department(),
category: 'test',
sku: faker.random.alphaNumeric(),
quantity: 3
price: 40
status: 'PENDING'
}, {
name: faker.commerce.productName(),
description: faker.commerce.department(),
category: 'test',
sku: faker.random.alphaNumeric(),
quantity: 2,
price: 30,
status: 'PENDING'
}
I am trying to get the subtotal of all the lineItems for each row (i.e. quantity * price for each line item, then the sum of these values for the row). So for the above example, the returned value should be 180.
I got this far, but this is returning the totals for all lineItems in the table, not grouped by row.
WITH line_items AS (SELECT jsonb_array_elements(line_items) as line_items FROM public.order),
line_item_totals AS (SELECT line_items->>'quantity' AS quantity, line_items->>'price' AS price FROM line_items)
SELECT (quantity::int * price::numeric) AS sub_total FROM line_item_totals;
I'm sure the fix is simple but I'm not sure how to do this with JSON fields.
Please always include Postgres version you are using. It also looks like your JSON is incorrect. Below is an example of how you can accomplish this with json type and valid json document.
with t(v) as ( VALUES
('{
"clientOrderId": "OR836374647",
"status": "PENDING",
"clientId": "583b52ede4b1a3668ba0dfff",
"sharerId": "583b249417329b5b737ad3ee",
"buyerId": "abcd12345678",
"buyerEmail": "test#test.com",
"lineItems": [{
"name": "name1",
"description": "desc1",
"category": "test",
"sku": "sku1",
"quantity": 3,
"price": 40,
"status": "PENDING"
},
{
"name": "name2",
"description": "desc2",
"category": "test",
"sku": "sku2",
"quantity": 2,
"price": 30,
"status": "PENDING"
}]
}'::JSON)
)
SELECT
v->>'clientOrderId' cId,
sum((item->>'price')::INTEGER * (item->>'quantity')::INTEGER) subtotal
FROM
t,
json_array_elements(v->'lineItems') item
GROUP BY cId;
Result:
cid | subtotal
-------------+----------
OR836374647 | 180
(1 row)