Create parent-children query from many-to-many relationship - sql

Good day,
I struggle to create a query in Postgres to obtain a many-to-many relationship in parent-children form.
There are two tables:
'Projects'
projects table
'Services'
services table
They are connected with the third project_service table
project_service
The query SELECT projects.id_project, projects.title, projects.level, services.id_service, services.service FROM projects, services, project_service WHERE projects.id_project = project_service.id_project AND services.id_service = project_service.id_service return this table:
query result table
How to write a query to get this table in parent-children form like this:
[
{ "id": "1000", "level": "Projects", "title": "Project 1", "children": [
{ "id": "2000", "service": "Service 1},
{ "id": "2001", "service": "Service 2},
{ "id": "2002", "service": "Service 3},
{ "id": "2003", "service": "Service 4}
]},
{ "id": "1001", "level": "Projects", "title": "Project 2", "children": [
{ "id": "2004", "service": "Service 5}
]}]
Thank you for help

Is this what you mean?
with svc_json as (
select id_service, to_jsonb(services) as svc
from services
), together as (
select p.*, jsonb_agg(s.svc) as children
from project_service ps
join svc_json s on s.id_service = ps.id_service
join projects p on p.id_project = ps.id_project
group by p.id_project, p.title, p.level
)
select jsonb_pretty(jsonb_agg(to_jsonb(together)))
from together;
jsonb_pretty
-----------------------------------------
[ +
{ +
"level": "Projects", +
"title": "Project 1", +
"children": [ +
{ +
"service": "Service 1",+
"id_service": 2000 +
}, +
{ +
"service": "Service 2",+
"id_service": 2001 +
}, +
{ +
"service": "Service 3",+
"id_service": 2002 +
}, +
{ +
"service": "Service 4",+
"id_service": 2003 +
} +
], +
"id_project": 1000 +
}, +
{ +
"level": "Projects", +
"title": "Project 2", +
"children": [ +
{ +
"service": "Service 5",+
"id_service": 2004 +
} +
], +
"id_project": 1001 +
} +
]
(1 row)

Related

Parse Google API JSON file to rows and columns with OPENJSON in T-SQL

So I am trying to create a query than can handle a json file that we get with a data factory web request from the Google Analytics API 4 and store the result in an Azure sql table. The following query is the closest I got.
The dimension and metric headers seems to be column names and the values in the rows part should be the rows.
DECLARE #jsonexample NVARCHAR(MAX) =
N'{
"dimensionHeaders": [
{
"name": "date"
},
{
"name": "country"
}
],
"metricHeaders": [
{
"name": "totalUsers",
"type": "TYPE_INTEGER"
}
],
"rows": [
{
"dimensionValues": [
{
"value": "20230207"
},
{
"value": "Netherlands"
}
],
"metricValues": [
{
"value": "3"
}
]
},
{
"dimensionValues": [
{
"value": "20230208"
},
{
"value": "Netherlands"
}
],
"metricValues": [
{
"value": "2"
}
]
},
{
"dimensionValues": [
{
"value": "20230208"
},
{
"value": "United States"
}
],
"metricValues": [
{
"value": "1"
}
]
}
]
}'
DECLARE #jsonexample2 NVARCHAR(MAX) = (SELECT [value] FROM OPENJSON(#jsonexample) where [key]= 'rows' )
SELECT *
from OPENJSON(#jsonexample2)
This blog seemed to have a good explanation but I still not got it working.
https://levelup.gitconnected.com/how-to-easily-parse-and-transform-json-in-sql-server-c0b091a964de
You can shred it down to something like this:
DECLARE #jsonexample NVARCHAR(MAX) =
N'{
"dimensionHeaders": [
{
"name": "date"
},
{
"name": "country"
}
],
"metricHeaders": [
{
"name": "totalUsers",
"type": "TYPE_INTEGER"
}
],
"rows": [
{
"dimensionValues": [
{
"value": "20230207"
},
{
"value": "Netherlands"
}
],
"metricValues": [
{
"value": "3"
}
]
},
{
"dimensionValues": [
{
"value": "20230208"
},
{
"value": "Netherlands"
}
],
"metricValues": [
{
"value": "2"
}
]
},
{
"dimensionValues": [
{
"value": "20230208"
},
{
"value": "United States"
}
],
"metricValues": [
{
"value": "1"
}
]
}
]
}'
;with cols as (
select cast([key] as int) AS k, JSON_VALUE(value, '$.name') AS v
from openjson(#jsonexample, '$.dimensionHeaders') x
)
, metrics as (
select cast([key] as int) AS k, JSON_VALUE(value, '$.name') AS v
from openjson(#jsonexample, '$.metricHeaders') x
)
select CAST(x.[key] AS INT) AS id, c.v AS dimName, JSON_VALUE(dim.value, '$.value') AS dimValue
, m.v AS metName, JSON_VALUE(metr.value, '$.value') AS metValue
from openjson(#jsonexample, '$.rows') x
cross apply openjson(x.value, '$.dimensionValues') dim
cross apply openjson(x.value, '$.metricValues') metr
inner join cols c
ON c.k = dim.[key]
inner join metrics m
ON m.k = metr.[key]
Then you can probably figure out the rest.
Here is a bit code that will dynamically parse the metrics and dimension for Google Analytics. It should give you a good starting point :)
SELECT TOP 1 #json = JSON_QUERY(RawJson, '$.reports[0].columnHeader')
FROM TableName
SET #WithClause =
(
SELECT STRING_AGG(Line, ',')
FROM
(
SELECT REPLACE(r.value, 'ga:', '')+' '+CASE
WHEN r.value = 'ga:DATE' THEN 'DATE'
ELSE 'NVARCHAR(255)'
END+' '+'''$.dimensions['+r.[key]+']''' AS Line
FROM OPENJSON(#json, '$.dimensions') AS r
UNION ALL
SELECT REPLACE(JSON_VALUE(r.value, '$.name'), 'ga:', '')+' '+CASE
WHEN JSON_VALUE(r.value, '$.type') = 'TIME' THEN 'FLOAT'
WHEN JSON_VALUE(r.value, '$.type') = 'CURRENCY' THEN 'DECIMAL(9,2)'
ELSE JSON_VALUE(r.value, '$.type')
END+' '+'''$.metrics[0].values['+r.[key]+']'''
FROM OPENJSON(#json, '$.metricHeader.metricHeaderEntries') AS r
) AS a
);
SET #Query = '
SELECT d.*
INTO #temp_table
FROM TableNAme AS cm
CROSS APPLY OPENJSON(RawJson, ''$.reports[0].data.rows'') WITH ( '+#WithClause+ ') AS d';
--PRINT #Query;
EXECUTE (#Query);

Get Hieararchical data as JSON from SQL Server

I have following table:
Id
HieararchyId
Name
1
/
MHz
2
/1/
Finance
3
/1/
IT
4
/1/3/
Software Development
5
/1/3/
QA
6
/1/2/
Accountant
and I want to get data for my TreeView like:
[
{
"Id": 1,
"HierarchyId": "/"
"Name":"MHz",
"Children":[
{
"Id": 3,
"HierarchyId": "/1/"
"Name":"IT",
"Children":[
{
"Id": 4,
"HierarchyId": "/1/3/"
"Name":"Software Development",
"Children":[]
}
{
"Id": 5,
"HierarchyId": "/1/3/"
"Name":"QA",
"Children":[]
}
]
},
{
"Id": 2,
"HierarchyId": "/1/"
"Name":"Finance",
"Children":[
{
"Id": 6,
"HierarchyId": "/1/2/"
"Name":"Accountant",
"Children":[]
}
]
}
]
}
]
I tried with this: How to generate hierarchical JSON data
Got error: No more lock classes available from transaction.
Or is it better creating recursive function and filling the Tree on client side?
Update
CREATE FUNCTION dbo.GetJson (#departmentId hierarchyid)
RETURNS nvarchar(max)
AS BEGIN
RETURN (
SELECT
Id,
HierarchyId,
Name,
children = JSON_QUERY(dbo.GetJson(HierarchyId))
FROM Departments p
WHERE p.HierarchyId.IsDescendantOf(#departmentId ) = 1
FOR JSON PATH
);
END;
The best way I have found to do this is to use a recursive function.
You can self-join all nodes and check if they are a child using GetAncestor
CREATE FUNCTION dbo.GetJson (#parentID hierarchyid)
RETURNS nvarchar(max)
AS BEGIN
RETURN (
SELECT
t.Id,
t.HierarchyId,
t.Name,
children = JSON_QUERY(dbo.GetJson(t.HierarchyId))
FROM Tree t
WHERE t.HierarchyId <> #parentID
AND t.HierarchyId.GetAncestor(1) = #parentID
FOR JSON PATH
);
END;
go
SELECT dbo.GetJson(hierarchyid::GetRoot());
db<>fiddle
Result
[
{
"Id": 1,
"HierarchyId": "/1/",
"Name": "MHz",
"children": [
{
"Id": 2,
"HierarchyId": "/1/2/",
"Name": "Finance",
"children": [
{
"Id": 6,
"HierarchyId": "/1/2/6/",
"Name": "Accountant"
}
]
},
{
"Id": 3,
"HierarchyId": "/1/3/",
"Name": "IT",
"children": [
{
"Id": 4,
"HierarchyId": "/1/3/4/",
"Name": "Software Development"
},
{
"Id": 5,
"HierarchyId": "/1/3/5/",
"Name": "QA"
}
]
}
]
}
]

Problem with using of FOR JSON AUTO in SQL Server

I am using FOR JSON AUTO in SQL server database, to convert my query's result to the JSON format.
in my query, I joined order table to two other tables.
SELECT
orders.[Code], orders.[Total], orders.[Discount],
customer.[Name], customer.[PhoneNumber],
store.[Name], store.[Address]
FROM
Orders orders
INNER JOIN
Customers customer ON (orders.[CustomerID] = customer.[ID])
INNER JOIN
Stores store ON (orders.[StoreID] = store.[ID])
FOR JSON AUTO
Result:
[
{
"Code": "1528",
"Total": 5000,
"Discount": 20,
"customer": [
{
"Name": "Alex",
"PhoneNumber": "(548) 123-5555",
"store": [
{
"Name": "Apple",
"Address": "E. Santa rd"
}
]
}
]
},
{
"Code": "1687",
"Total": 3000,
"Discount": 10,
"customer": [
{
"Name": "John",
"PhoneNumber": "(226) 354-7896",
"store": [
{
"Name": "Sony",
"Address": "W. Atlantic ave"
}
]
}
]
}
]
But it's not correct, because in this scenario customer and store are sibling and they have same parent, and both of them joined with the order table directly, correct JSON must be such as this:
[
{
"Code": "1528",
"Total": 5000,
"Discount": 20,
"customer": [
{
"Name": "Alex",
"PhoneNumber": "(548) 123-5555"
}
],
"store": [
{
"Name": "Apple",
"Address": "E. Santa rd"
}
]
},
{
"Code": "1687",
"Total": 3000,
"Discount": 10,
"customer": [
{
"Name": "John",
"PhoneNumber": "(226) 354-7896"
}
],
"store": [
{
"Name": "Sony",
"Address": "W. Atlantic ave"
}
]
}
]
how can I do that? Are there any option for this in SQL? (I don't want to use inner select.)
If there are one-to-one relationships between Orders and Customer and between Orders and Store then you can make the desired output by using PATH option and dot-separated column names:
SELECT
orders.[Code], orders.[Total], orders.[Discount],
customer.[Name] AS [Customer.Name], customer.[PhoneNumber] AS [Customer.PhoneNumber],
store.[Name] AS [Store.Name], store.[Address] AS [Store.Address]
FROM
Orders orders
INNER JOIN
Customers customer ON (orders.[CustomerID] = customer.[ID])
INNER JOIN
Stores store ON (orders.[StoreID] = store.[ID])
FOR JSON PATH
But if there are one-to-many relationships then you have to use nested queries:
SELECT
orders.[Code], orders.[Total], orders.[Discount],
(SELECT [Name], [PhoneNumber] FROM Customers WHERE Customers.ID=Orders.CustomerID FOR JSON AUTO) AS Customers,
(SELECT [Name], [Address] FROM Stores WHERE Stores.ID=Orders.StoreID FOR JSON AUTO) AS Stores
FROM
Orders orders
FOR JSON AUTO

Postgres combine 3 CTEs causes duplicate rows

I'm trying to combine 2 select queries on 2 different tables which have a foreign key in common project_id included with a condition and returned in a single result set with the project_id a json_array called sprints and a json_array called backlog. The output should look something like this.
{
"id": "1920c79d-69d7-4b63-9662-ed5333e9b735",
"name": "Test backend v1",
"backlog_items": [
{
"id": "961b2438-a16b-4f30-83f1-723a05592d68",
"name": "Another User Story 1",
"type": "User Story",
"backlog": true,
"s3_link": null,
"sprint_id": null
},
{
"id": "a2d93017-ab87-4ec2-9589-71f6cebba936",
"name": "New Comment",
"type": "Comment",
"backlog": true,
"s3_link": null,
"sprint_id": null
}
],
"sprints": [
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62ed4",
"name": "Test name 2",
"sprint_items": [
{
"id": "1285825b-1669-40f2-96b8-de02ec80d8bd",
"name": "As an admin I should be able to delete an organization",
"type": "User Story",
"backlog": false,
"s3_link": null,
"sprint_id": "1cd165c7-68f7-4a1d-b018-609989d62ed4"
}
]
},
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62f44",
"name": "Test name 1",
"sprint_items": []
}
]
}
In case there are no backlog items associated with the project_id or sprints with the project_id I want to return an empty list. I figured using Postgres COALESCE function might help in this case but I'm not sure how to use is to achieve what I want.
Sprint table
id | end_date | start_date | project_id | name
--------------------------------------+----------+------------+--------------------------------------+-------------
1cd165c7-68f7-4a1d-b018-609989d62ed4 | | | 1920c79d-69d7-4b63-9662-ed5333e9b735 | Test name 2
Sprint item table
id | sprint_id | name | type | s3_link | backlog | project_id
--------------------------------------+--------------------------------------+--------------------------------------------------------+------------+---------+---------+--------------------------------------
961b2438-a16b-4f30-83f1-723a05592d68 | | Another User Story 1 | User Story | | t | 1920c79d-69d7-4b63-9662-ed5333e9b735
a2d93017-ab87-4ec2-9589-71f6cebba936 | | New Comment | Comment | | t | 1920c79d-69d7-4b63-9662-ed5333e9b735
1285825b-1669-40f2-96b8-de02ec80d8bd | 1cd165c7-68f7-4a1d-b018-609989d62ed4 | As an admin I should be able to delete an organization | User Story | | f | 1920c79d-69d7-4b63-9662-ed5333e9b735
The query I'm using right now which returns multiple duplicates in the result set.
with si as (
select si.id, si.name, si.backlog, si.project_id
from sprint_items si
), s as (
select s.id, s.name, s.project_id, jsonb_agg(to_jsonb(si) - 'project_id') as sprint_items
from sprints s
left join sprint_items si
on si.sprint_id = s.id
group by s.id, s.name, s.project_id
), p as (
select p.id, p.name, jsonb_agg(to_jsonb(s) - 'project_id') as sprints,
jsonb_agg(to_jsonb(case when si.backlog = true then si end) - 'project_id') as backlog_items
from projects p
left join s
on s.project_id = p.id
left join si
on si.project_id = p.id
group by p.id, p.name
)
select to_jsonb(p) from p
where p.id = '1920c79d-69d7-4b63-9662-ed5333e9b735'
Updated
This is what the above query is producing in terms of duplicating the sprint items and sprints
{
"id": "1920c79d-69d7-4b63-9662-ed5333e9b735",
"name": "Test backend v1",
"sprints": [
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62ed4",
"name": "Test name 2",
"sprint_items": [
{
"id": "1285825b-1669-40f2-96b8-de02ec80d8bd",
"name": "As an admin I should be able to delete an organization",
"type": "User Story",
"backlog": false,
"s3_link": null,
"sprint_id": "1cd165c7-68f7-4a1d-b018-609989d62ed4"
}
]
},
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62ed4",
"name": "Test name 2",
"sprint_items": [
{
"id": "1285825b-1669-40f2-96b8-de02ec80d8bd",
"name": "As an admin I should be able to delete an organization",
"type": "User Story",
"backlog": false,
"s3_link": null,
"sprint_id": "1cd165c7-68f7-4a1d-b018-609989d62ed4"
}
]
},
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62ed4",
"name": "Test name 2",
"sprint_items": [
{
"id": "1285825b-1669-40f2-96b8-de02ec80d8bd",
"name": "As an admin I should be able to delete an organization",
"type": "User Story",
"backlog": false,
"s3_link": null,
"sprint_id": "1cd165c7-68f7-4a1d-b018-609989d62ed4"
}
]
},
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62f44",
"name": "Test name 1",
"sprint_items": [
null
]
},
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62f44",
"name": "Test name 1",
"sprint_items": [
null
]
},
{
"id": "1cd165c7-68f7-4a1d-b018-609989d62f44",
"name": "Test name 1",
"sprint_items": [
null
]
}
],
"backlog_items": [
null,
{
"id": "961b2438-a16b-4f30-83f1-723a05592d68",
"name": "Another User Story 1",
"backlog": true
},
{
"id": "a2d93017-ab87-4ec2-9589-71f6cebba936",
"name": "New Comment",
"backlog": true
},
null,
{
"id": "961b2438-a16b-4f30-83f1-723a05592d68",
"name": "Another User Story 1",
"backlog": true
},
{
"id": "a2d93017-ab87-4ec2-9589-71f6cebba936",
"name": "New Comment",
"backlog": true
}
]
}
Any pointers to what functions I should read up would be greatly appreciated.

How to filter Cosmos DB data based on value of an element in an array of values Using SQL API

I have a cosmosDB collection with below Data in it.
I have to find out the data only for EVENT named ABC and its value using SQL query.
[
{
"ID": "01XXXXX",
"EVENTS": [
{
"Name": "ABC",
"Value": 0
},
{
"Name": "XYZ",
"Value": 4
},
{
"Name": "PQR",
"Value": 5
}
]
},
{
"ID": "02XXXXX",
"EVENTS": [
{
"Name": "ABC",
"Value": 1
},
{
"Name": "XYZ",
"Value": 2
},
{
"Name": "PQR",
"Value": 3
}
]
}
]
I have tried the below code but it is not working since EVENT is an array.
SELECT * FROM c where c.EVENTS.Name = 'ABC'
Is there any way to find filter out the data only with Event Name as ABC using SQL?
Try using join
SELECT c FROM c
join l in c.EVENTS
where l.Name = 'ABC'