How to generate nested nth level JSON object in T-SQL? - json

I have below data
against below query
declare #t table
(
Id int identity,
name varchar(50),
rootid int,
level int
);
insert into #t(name, rootid, level)
values
('Home', 0, 0)
,('Transaction', 0, 0)
, ('Settings', 0, 0)
,('Purchase Request', 2, 1)
,('Purchase Order', 2, 1)
,('Inventory', 2, 1)
,('Payment Advice', 2, 1)
,('Setup', 3, 1)
,('Budget', 3, 1)
,('CRC', 3, 1)
,('Create PR', 4, 3);
select * from #t;
Desire output:
[{
"Id": 1,
"name": "Home",
"rootid": 0,
"level": 0
}, {
"Id": 2,
"name": "Transaction",
"rootid": 0,
"level": 0,
"children": [{
"Id": 4,
"name": "Purchase Request",
"rootid": 2,
"level": 1,
"children": [{
"Id": 11,
"name": "Create PR",
"rootid": 4,
"level": 3
}]
}, {
"Id": 5,
"name": "Purchase Order",
"rootid": 2,
"level": 1
}, {
"Id": 6,
"name": "Inventory",
"rootid": 2,
"level": 1
}, {
"Id": 7,
"name": "Payment Advice",
"rootid": 2,
"level": 1
}]
}, {
"Id": 3,
"name": "Settings",
"rootid": 0,
"level": 0,
"children": [{
"Id": 8,
"name": "Setup",
"rootid": 3,
"level": 1
}, {
"Id": 9,
"name": "Budget",
"rootid": 3,
"level": 1
}, {
"Id": 10,
"name": "CRC",
"rootid": 3,
"level": 1
}]
}]
Also Tried #Iptr answer:
;WITH result (id, name, rootId, parent, Level) AS
(
SELECT id,
name,
RootId,
Id as Parent,
0 as Level
FROM #t
WHERE RootId= 0
UNION ALL
SELECT t.id,
t.Name,
t.RootId,
r.Parent,
r.Level + 1
FROM #t t
INNER JOIN result r ON r.id = t.RootId
)
SELECT t.*, json_query(nullif(c.children, '[{}]')) as children
FROM #t as t
outer apply (
select
(
select r.*
from result as r
where r.parent = t.Id
and r.level > 0
order by r.id
for json auto
) as children
) as c
where t.level = 0
order by t.Level
for json auto;
Output
[{
"Id": 1,
"name": "Home",
"rootid": 0,
"level": 0
}, {
"Id": 2,
"name": "Transaction",
"rootid": 0,
"level": 0,
"children": [{
"id": 4,
"name": "Purchase Request",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 5,
"name": "Purchase Order",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 6,
"name": "Inventory",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 7,
"name": "Payment Advice",
"rootId": 2,
"parent": 2,
"Level": 1
}, {
"id": 11,
"name": "Create PR",
"rootId": 4,
"parent": 2,
"Level": 2
}]
}, {
"Id": 3,
"name": "Settings",
"rootid": 0,
"level": 0,
"children": [{
"id": 8,
"name": "Setup",
"rootId": 3,
"parent": 3,
"Level": 1
}, {
"id": 9,
"name": "Budget",
"rootId": 3,
"parent": 3,
"Level": 1
}, {
"id": 10,
"name": "CRC",
"rootId": 3,
"parent": 3,
"Level": 1
}]
}]
Above query is not returning nth json child objects, let say if I have nth level of menu items, Parent have multiple Childs and Childs have multiple Childs like treeview.
Tried #Naveen Arora answer:
select ID,name,'' as id,'' as name from Navigations where id not in (select rootid from Navigations) and rootid=0
union
select B.id,B.name,A.id,A.name from Navigations A join Navigations B on A.rootid=B.id
FOR JSON AUTO;
But output
[{
"ID": 1,
"name": "Home",
"id": 0,
"name": ""
}, {
"ID": 2,
"name": "Transaction",
"id": 4,
"name": "Create PR"
}, {
"ID": 2,
"name": "Transaction",
"id": 5,
"name": "Generate PO"
}, {
"ID": 2,
"name": "Transaction",
"id": 6,
"name": "Create Receipt"
}, {
"ID": 2,
"name": "Transaction",
"id": 7,
"name": "Create Issue Request"
}, {
"ID": 2,
"name": "Transaction",
"id": 8,
"name": "Create Issue Note"
}, {
"ID": 2,
"name": "Transaction",
"id": 9,
"name": "Approve Payment Advice"
}, {
"ID": 3,
"name": "Settings",
"id": 11,
"name": "Navigation Management"
}, {
"ID": 11,
"name": "Navigation Management",
"id": 12,
"name": "Navigation & Form Mapping"
}]
Above output it's not include Childs node. Like in Settings I have Navigation Management -> Navigation & Form Mapping

If the sql server version is 2016 or newer than 2016 then you can use FOR JSON PATH.
Assuming that results are stored in test table. This is just to give you an idea how you can do this, may not give you the exact output but you can change it as per your requirement.
SELECT
t.Id AS 'Id',
t.Name AS 'Name',
children = (
SELECT A.id,A.name from test A join test B on A.rootid=B.id
FOR JSON PATH
)
FROM Test t
FOR JSON PATH;
And if it is older than 2016 then you may refer this.

declare #t table
(
Id int identity,
name varchar(50),
rootid int,
level int
);
insert into #t(name, rootid, level)
values
('Home', 0, 0),('Transaction', 0, 0), ('Settings', 0, 0),
('Create PR', 2, 1), ('Generate PO', 2, 1), ('Create Receipt', 2, 1), ('Create Issue Request', 2, 1), ('Create Issue Note', 2, 1), ('Approve Payment Advice', 2, 1),
('Navigation Management', 3, 1), ('Navigation & Form Mapping', 3, 1);
select * from #t;
;WITH result (id, name, rootId, parent, Level) AS
(
SELECT id,
name,
RootId,
Id as Parent,
0 as Level
FROM #t
WHERE RootId= 0
UNION ALL
SELECT t.id,
t.Name,
t.RootId,
r.Parent,
r.Level + 1
FROM #t t
INNER JOIN result r ON r.id = t.RootId
)
SELECT t.*, json_query(nullif(c.children, '[{}]')) as children
FROM #t as t
outer apply (
select
(
select r.*
from result as r
where r.parent = t.Id
and r.level > 0
order by r.id
for json auto
) as children
) as c
where t.level = 0
order by t.Level
for json auto;
SELECT t.*, json_query(nullif(c.children, '[{}]')) as children
FROM #t as t
outer apply (
select
(
select r.*
from #t as r
where r.rootid = t.Id
and r.level > 0
order by r.id
for json auto
) as children
) as c
where t.level = 0
order by t.Level
for json auto;

My apology for late posting my answer. But I really appreciate the efforts of #Iptr and #NaveenArora answer on my post. After I do some brain storming on my case I've finally found the way to do it.
Create this function:
create function [dbo].[fnUDFCreateJSON](#currentId int)
returns varchar(max)
begin
declare #json nvarchar(max)
IF #currentId <> 0
BEGIN
set #json =
(
select [ID], [Name], CSSClass, RouteURL, json_query(dbo.fnUDFCreateJSON([ID])) as SubNavigation
from dbo.Navigations
where RootId = #currentId
for json auto
);
END
ELSE
BEGIN
set #json =
(
select [ID], [Name], CSSClass, RouteURL, '' as SubNavigation from dbo.Navigations where RootId = 0
for json auto
);
END
return #json
end
and call it by using stored procedure:
CREATE PROCEDURE [dbo].[spGetStartupNavigations]
AS
BEGIN
SELECT
(SELECT
ID, Name, CSSClass, RouteURL,
JSON_QUERY (dbo.fnUDFCreateJSON(ID)) AS SubNavigation
FROM
dbo.Navigations
WHERE
RootId = 0
FOR JSON AUTO) AS Navigation
END
That's it.

Related

Adding more data in array of object in PostgreSQL

I have a table of cart with 2 columns (user_num, data).
user_num will have the phone number of user and
data will have an array of object like [{ "id": 1, "quantity": 1 }, { "id": 2, "quantity": 2 }, { "id": 3, "quantity": 3 }] here id is product id.
user_num | data
----------+--------------------------------------------------------------------------------------
1 | [{ "id": 1, "quantity": 1 }, { "id": 2, "quantity": 2 }, { "id": 3, "quantity": 3 }]
I want to add more data of products in above array of objects in PostgreSQL.
Thanks!
To add the value use the JSONB array append operator ||
Demo
update
test
set
data = data || '[{"id": 4, "quantity": 4}, {"id": 5, "quantity": 5}]'
where
user_num = 1;

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');

json_agg() with pseudo row

CREATE TABLE content
(
code INTEGER NOT NULL
, nameid TEXT NOT NULL
)
INSERT INTO content(code, nameid) VALUES
( 0, 'Support')
, ( 1, 'Adrenaline')
, ( 2, 'Aquapark')
;
Need select all content as JSON array-object named 'content' with pseudo row with code = '' and nameid = 'All'
What i did:
1) Simple select
SELECT
json_build_object('content', (SELECT json_agg(json_build_object(
'code', s.code::TEXT
, 'nameid', s.nameid::TEXT
))
FROM content s
)
);
Result is
{
"content": [{
"code": "0",
"nameid": "Support"
},
{
"code": "1",
"nameid": "Adrenaline"
},
{
"code": "2",
"nameid": "Aquapark"
}
]
}
All fine, but how to add pseudo row?
2) Best i can did
SELECT
json_build_object('content', json_build_array(
json_build_object(
'code', ''
, 'nameid', 'All'::TEXT),
(SELECT json_agg(json_build_object(
'code', s.code::TEXT
, 'nameid', s.nameid::TEXT
))
FROM content s
))
);
Result is
{
"content": [{
"code": "",
"nameid": "All"
},
[{
"code": "0",
"nameid": "Support"
},
{
"code": "1",
"nameid": "Adrenaline"
},
{
"code": "2",
"nameid": "Aquapark"
}
]
]
}
And we have array in array, as in sql as in json, but i can not understand how to combine json_agg with pseudo row.
Use to_json() and union all:
select jsonb_build_object('content', json_agg(to_json))
from (
select to_json(c)
from (select '' as code, 'All' as nameid) c
union all
select to_json(c)
from content c
) s
Output:
{
"content": [
{
"code": "",
"nameid": "All"
},
{
"code": 0,
"nameid": "Support"
},
{
"code": 1,
"nameid": "Adrenaline"
},
{
"code": 2,
"nameid": "Aquapark"
}
]
}
The version with an alias in the derived table:
select jsonb_build_object('content', json_agg(codes))
from (
select to_json(c) as codes
from (select '' as code, 'All' as nameid) c
union all
select to_json(c)
from content c
) s

Flatten nested JSON structure in PostgreSQL

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"
}]

For JSON results

Sorry for the basic of this question, I just cannot wrap my head around this one.
I need the output from SQL Server to look like this.
In a little more human readable format:
var data = [
{
name: '2017', id: -1,
children: [
{ name: '01-2017', id: 11 },
{ name: '02-2017', id: 12 },
{ name: '03-2017', id: 13 },
{ name: '04-2017', id: 14 },
{ name: '05-2017', id: 15 },
]
},
{
name: '2018', id: -1,
children: [
{ name: '01-2018', id: 6 },
{ name: '02-2018', id: 7 },
{ name: '03-2018', id: 8 },
{ name: '04-2018', id: 9 },
{ name: '05-2018', id: 10 },
]
}
];
This is a snapshot of the data:
The group I will be working with is userid = 1.
My first thought was to use a cursor to loop through all the distinct reportYear for userid = 1, then a select based on the year and the userid to fill in the sub-query.
There has to be a way without using a cursor.
You can achieve the desired output joining your table to a query that extracts all the years to be used at the top level elements and then generating the json using FOR JSON AUTO:
declare #tmp table (monthlyReportID int, userID int, reportMonth int, reportYear int)
insert into #tmp values
( 6, 1, 1, 2018),
( 7, 1, 2, 2018),
( 8, 1, 3, 2018),
( 9, 1, 4, 2018),
(10, 1, 5, 2018),
(11, 1, 1, 2017),
(12, 1, 2, 2017),
(13, 1, 3, 2017),
(14, 1, 4, 2017),
(15, 1, 5, 2017)
select years.[name], children.[name], children.[id] from
(
select distinct reportYear as [name] from #tmp
) as years
left join
(
select monthlyReportID as [id]
,right('0' + cast(reportMonth as varchar(2)),2) + '-' + cast(reportYear as varchar(4)) as [name]
,reportYear as [year]
from #tmp
) as children
on children.[Year] = years.[name]
for json auto
I omitted the ID field because in your desired output it is always set to -1 and I was not able to understand the logic behind it.
Nonetheless you should be able to easily edit the script above to obtain the value you need.
Here are the results:
[
{
"name": 2017,
"children": [
{"name": "01-2017", "id": 11},
{"name": "02-2017", "id": 12},
{"name": "03-2017", "id": 13},
{"name": "04-2017", "id": 14},
{"name": "05-2017", "id": 15}
]
},
{
"name": 2018,
"children": [
{"name": "01-2018", "id": 6},
{"name": "02-2018", "id": 7},
{"name": "03-2018", "id": 8},
{"name": "04-2018", "id": 9},
{"name": "05-2018", "id": 10}
]
}
]