JSON Hierarchy Problems - sql

I don't have a recursive relationship here, but a problem of several child tables having the same parent, and having multiple records in each child table. Here's an example:
create table #Schedules (
ScheduleId int,
ScheduleName varchar(20)
);
create table #ScheduleThings (
ScheduleThingId int,
ScheduleId int,
Thing decimal(18,2));
create table #ScheduleOtherThings (
ScheduleOtherThingId int,
ScheduleId int,
OtherThing varchar(50));
I insert some typical sample data:
insert into #Schedules (
ScheduleId,
ScheduleName )
values
(1, 'A'),
(2, 'B');
insert into #ScheduleThings (
ScheduleThingId,
ScheduleId,
Thing )
values
(1, 1, 10.22),
(2, 1, 11.02),
(3, 1, 11.89),
(4, 2, 19.23),
(5, 2, 20.04),
(6, 2, 20.76),
(7, 2, 21.37);
insert into #ScheduleOtherThings (
ScheduleOtherThingId,
ScheduleId,
OtherThing )
values
(1, 1, 'Always'),
(2, 1, 'Sometimes'),
(3, 2, 'Seldom'),
(4, 2, 'Always'),
(5, 2, 'Never');
declare #results table (result xml);
I've then tried 2 similar approaches (3 or 4 actually), but here is one:
insert into #Results (
result )
select fr.result from (
select
s.ScheduleId as [schedules.schedule_id],
s.ScheduleName as [schedules.schedule_name],
st.ScheduleThingId as [schedules.schedule_things.schedule_thing_id],
st.Thing as [schedules.schedule_things.thing],
sot.ScheduleOtherThingId as [schedules.schedule_other_things.schedule_other_thing_id],
sot.OtherThing as [schedules.schedule_other_things.other_thing]
from #Schedules s
join #ScheduleThings st
on st.ScheduleId = s.ScheduleId
join #ScheduleOtherThings sot
on sot.ScheduleId = s.ScheduleId
where s.ScheduleId = 1
and st.ScheduleThingId < 3
for json path, root('schedules') ) fr(result) ;
select * from #Results;
This attempt gives me:
{
"schedules": [
{
"schedules": {
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 1,
"other_thing": "Always"
}
}
},
{
"schedules": {
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 2,
"other_thing": "Sometimes"
}
}
},
and removing 'schedules' from the dot notation entirely has no significant impact:
{
"schedules": [
{
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 1,
"other_thing": "Always"
}
},
{
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 2,
"other_thing": "Sometimes"
}
},
What I need (and what I think is the proper JSON structure) is like:
{
"schedules": [
{
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": [
{
"schedule_thing_id": 1,
"thing": 10.22
},
{
"schedule_thing_id": 2,
"thing": 11.02
},
]
"schedule_other_things": [
{
"schedule_other_thing_id": 1,
"other_thing": "Always"
},
{
"schedule_other_thing_id": 2,
"other_thing": "Sometimes"
}
]
}
]
}
In other words, the attributes of the parent 'Schedule' record appear one time, an object of ScheduleThings follows, including all child ScheduleThings, followed by an object of ScheduleOtherThings, etc.
I don't understand yet why my dot specifications don't make it clear which attributes belong to the root object, and therefore that I don't need those attributes repeated. But, I especially don't understand why the entire dataset is flattened--even when I think I've used the dot notation to make the parent-child relationships very explicit.

You could try nesting the calls to for json
Such as...
select
fr.result
from
(
select
s.ScheduleId as [schedules.schedule_id],
s.ScheduleName as [schedules.schedule_name],
(
SELECT ScheduleThingId, Thing
FROM #ScheduleThings
WHERE ScheduleId = s.ScheduleId
AND ScheduleThingId < 3
FOR JSON PATH
)
AS [schedules.schedule_things],
(
SELECT ScheduleOtherThingId, OtherThing
FROM #ScheduleOtherThings
WHERE ScheduleId = s.ScheduleId
FOR JSON PATH
)
AS [schedules.schedule_other_things]
from
#Schedules s
where
s.ScheduleId = 1
for json path, root('schedules')
)
fr(result) ;
Demo : https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=e9a9c55b2daaac4e0f48d52a87bfede9

Related

SQL Server JSON output of nested tables

I have the following tables:
create table students
(
id int,
name varchar(10)
)
create table subjects
(
subjectId int,
studentId int,
subject varchar(12)
)
create table marks
(
studentId int,
subjectId int,
marks int
)
create table sports
(
sportId int,
studentId int,
name varchar(12)
)
with the following data:
insert into students values(1, 'Rusty');
insert into subjects values(1, 1, 'math')
insert into subjects values(2, 1, 'science')
insert into marks values(1,1,50)
insert into marks values(1,2,60)
insert into sports values(1, 1, 'soccer')
insert into sports values(2, 1, 'baseball')
I want to write a query in SQL Server to get the following output:
studentId = 1
{
"id": 1,
"name": "Rusty",
"subjects" : [
{
"name": "math",
"marks": 50
},
{
"name": "science",
"marks": 60
}
],
"sports": [
{
"name": "soccer"
},
{
"name": "baseball"
}
]
}
I tried the following query
select *
from students s
join subjects su on (s.id = su.studentId)
join sports sp on (s.id = sp.studentId)
where s.id = 1
for json auto
and here is the output:
[
{
"id": 1,
"name": "Rusty",
"su": [
{
"subjectId": 1,
"studentId": 1,
"subject": "math",
"sp": [
{
"sportId": 1,
"studentId": 1,
"name": "soccer"
}
]
},
{
"subjectId": 1,
"studentId": 1,
"subject": "science",
"sp": [
{
"sportId": 1,
"studentId": 1,
"name": "soccer"
}
]
},
{
"subjectId": 1,
"studentId": 1,
"subject": "math",
"sp": [
{
"sportId": 1,
"studentId": 1,
"name": "baseball"
}
]
},
{
"subjectId": 1,
"studentId": 1,
"subject": "science",
"sp": [
{
"sportId": 1,
"studentId": 1,
"name": "baseball"
}
]
}
]
}
]
For your desired output you can use correlated subqueries for sports and subjects that generate their own JSON, using FOR JSON PATH, and include that information as an array of nested objects in your main JSON output by way of JSON_QUERY (Transact-SQL), e.g.:
/*
* Data setup...
*/
create table students (
id int,
name varchar(10)
);
create table subjects (
subjectId int,
studentId int,
subject varchar(12)
);
create table marks (
studentId int,
subjectId int,
marks int
);
create table sports (
sportId int,
studentId int,
name varchar(12)
);
insert into students (id, name) values
(1, 'Rusty');
insert into subjects (subjectId, studentId, subject) values
(1, 1, 'math'),
(2, 1, 'science');
insert into marks (studentId, subjectId, marks) values
(1,1,50),
(1,2,60);
insert into sports (sportId, studentId, name) values
(1, 1, 'soccer'),
(2, 1, 'baseball');
/*
* Example query...
*/
select
students.id,
students.name,
json_query(( --<<-- doubled brakcets
select
subjects.subject,
marks.marks
from subjects
join marks
on marks.subjectId = subjects.subjectId
and marks.studentId = subjects.studentId
where subjects.studentId = students.id
for json path
)) as [subjects],
json_query(( --<<-- doubled brackets
select
sports.name
from sports
where sports.studentId = students.id
for json path
)) as [sports]
from students
where students.id = 1
for json path, without_array_wrapper;
Which yields the JSON output:
{
"id": 1,
"name": "Rusty",
"subjects": [
{
"subject": "math",
"marks": 50
},
{
"subject": "science",
"marks": 60
}
],
"sports": [
{
"name": "soccer"
},
{
"name": "baseball"
}
]
}

Data population from database as nested array object

I have a table structure as below in SQL Server database,
I want to populate the data from database something similar to JSON like as below:
id: 1
aname: xyz
categories: bus
{
arnam: res
street: [s1,s2]
},
{
arnam: com
street: [c1,c2]
}
Can someone please guide me as to how I can do this in the database using normal SQL query or procedure.
Your json is not valid, but according to your table I think you want to know how to parse data from nested JSON with array of values with this structure:
WITH cte AS (
SELECT * FROM (VALUES
('{"id": 1, "aname": "xyz",
"categories": {
"bus": [
{"aname": "res",
"street": ["c1", "c2"]
},
{"aname": "res",
"street": ["s1", "s2"]
}]
}
}'),
('{"id": 2, "aname": "abc",
"categories": {
"bus": [
{"aname": "foo",
"street": ["c1", "c2"]
},
{"aname": "zoo",
"street": ["s1", "s2"]
}]
}
}')
) t1 ([json])
)SELECT
ROW_NUMBER() OVER(ORDER BY [id]) AS RN,
*
FROM cte AS e
CROSS APPLY OPENJSON(e.[json]) WITH (
[id] int '$.id',
[aname] VARCHAR(100) '$.aname',
[categories_jsn] NVARCHAR(MAX) '$.categories.bus' AS JSON
) AS jsn
CROSS APPLY OPENJSON([categories_jsn]) WITH (
[street_arr] NVARCHAR(MAX) '$.street' AS JSON,
[aname_lvl2] VARCHAR(20) '$.aname'
) AS jsn2
CROSS APPLY OPENJSON([street_arr]) WITH (
[street] VARCHAR(20) '$'
)
Output:

Parsing JSON from SQL Server

I have a table with the following columns, one column being a JSON blob. I'm unclear how to parse the JSON blob as a series of columns alongside the other columns. I know there's something called OPENJSON, but not sure how to apply it to this case.
ID | ORGANIZATION | DEVICE_TIME | DEVICE | DATA
--------------------------------------------------------------------
011 015 2021-07-20 015 (JSON COLUMN)
012 016 2021-08-20 016 (JSON COLUMN)
The json string example is below, from the DATA column above
{
"device": {
"battery_level": 98,
"rssi": -105,
"boot_cnt": 5,
"apn": "teal",
"ip_addr": "10.176.30.171",
"fw_ver": "1.00",
"modem_fw": "mfw_nrf9160_1.3.0",
"imsi": "234500024531391",
"imei": "352656101040510",
"iccid": "8901990000000534985"
},
"data": {
"Temperature": 77.563942718505871,
"Humidity": 29.100597381591797,
"pressure": 28.883883226248145,
"air_quality": 37.067466735839844,
"SoilMoisture": 0.42462845010615713,
"Lat": 0,
"Long": 0,
"Alt": 0
}
}
openjson returns a table (possibly with many rows, although not for your sample).
To put something into a column you need a scalar. Try this example. Yes you need to explicitly list the columns out.
/* Create a sample table */
WITH MySampleTable
AS (
SELECT 1 as col1, 2 as col2, 'Hi There' as col3,
CAST('
{
"device": {
"battery_level": 98,
"rssi": -105,
"boot_cnt": 5,
"apn": "teal",
"ip_addr": "10.176.30.171",
"fw_ver": "1.00",
"modem_fw": "mfw_nrf9160_1.3.0",
"imsi": "234500024531391",
"imei": "352656101040510",
"iccid": "8901990000000534985"
},
"data": {
"Temperature": 77.563942718505871,
"Humidity": 29.100597381591797,
"pressure": 28.883883226248145,
"air_quality": 37.067466735839844,
"SoilMoisture": 0.42462845010615713,
"Lat": 0,
"Long": 0,
"Alt": 0
}
}
'
AS NVARCHAR(MAX)
) as myjsoncolumn
UNION ALL
SELECT 5,6,'Test','
{
"device": {
"battery_level": 2,
"rssi": -105,
"boot_cnt": 5,
"apn": "teal"
},
"data": {
"Humidity": 29.100597381591797,
"pressure": 28.883883226248145
}
}
'
)
SELECT *,
JSON_VALUE(myjsoncolumn,'$.device.battery_level') as battery_level,
JSON_VALUE(myjsoncolumn,'$.data.Temperature') as Temp
FROM MySampleTable
The statement usually depends on the structure of the parsed JSON data. In this case, a possible option is OPENJSON() with explicit schema (the WITH clause with columns definitions), using the appropriate data types and path expressions:
JSON and table:
DECLARE #json varchar(max) = '{
"device": {
"battery_level": 98,
"rssi": -105,
"boot_cnt": 5,
"apn": "teal",
"ip_addr": "10.176.30.171",
"fw_ver": "1.00",
"modem_fw": "mfw_nrf9160_1.3.0",
"imsi": "234500024531391",
"imei": "352656101040510",
"iccid": "8901990000000534985"
},
"data": {
"Temperature": 77.563942718505871,
"Humidity": 29.100597381591797,
"pressure": 28.883883226248145,
"air_quality": 37.067466735839844,
"SoilMoisture": 0.42462845010615713,
"Lat": 0,
"Long": 0,
"Alt": 0
}
}'
SELECT *
INTO Data
FROM (VALUES
('011', '015', '2021-07-20', '015', #json),
('012', '016', '2021-08-20', '016', #json)
) v (ID, ORGANIZATION, DEVICE_TIME, DEVICE, DATA)
Statement:
SELECT d.*, j.*
FROM Data d
OUTER APPLY OPENJSON(d.DATA) WITH (
batery_level int '$.device.battery_level',
rssi int '$.device.rssi',
boot_cnt int '$.device.boot_cnt',
apn varchar(10) '$.device.boot_apn',
ip_addr varchar(19) '$.device.ip_addr',
fw_ver varchar(5) '$.device.fw_ver',
modem_fw varchar(59) '$.device.modem_fw',
imsi varchar(15) '$.device.imsi',
imei varchar(15) '$.device.imei',
iccid varchar(50) '$.device.iccid',
Temperature numeric(20, 15) '$.data.Temperature',
Humidity numeric(22, 18) '$.data.Humidity',
pressure numeric(22, 18) '$.data.pressure',
air_quality numeric(22, 18) '$.data.air_quality',
SoilMoisture numeric(22, 18) '$.data.SoilMoisture',
Lat numeric(9, 6) '$.data.Lat',
Long numeric(9, 6) '$.data.Long',
Alt numeric(9, 6) '$.data.Alt'
) j

showing articles using category filter sql

I want to select articles containing certain category I'm using typeorm ( SQL )
let's imagine i have
articleTable: [
{
id: 1,
title: 'string'
},
{
id: 2,
title: 'string'
}
];
categoryTable: [
{
id: 1,
title: 'string'
}, {
id: 2,
title: 'string'
}
]
article_categories: [
{
categoryId: 1,
articleId: 2
},{
categoryId: 1,
articleId: 1
},{
categoryId: 2,
articleId: 2
}
]
so the question is I want to select all articles that have exactly category = [1,2] and more
that means if an article has only category 1 I don't want to select it.
using this query I'm getting results but its not exact match of ids but instead it returns articles that have id 1 and 2 separately
async filterData(filterArticleDto: FilterArticlesDto, categoryIds: number[]) {
const qb = this.createQueryBuilder('article');
qb.distinct(true);
if (categoryIds.length) {
qb.innerJoin(
'article.categories',
'categories',
`categories.category IN (:...ids)`,
{
ids: categoryIds,
},
);
qb.leftJoinAndSelect('categories.category', 'catsData');
}
qb.take(filterArticleDto.limit);
return await qb.getMany();
}
raw sql query
SELECT article.*
FROM articles AS article
WHERE EXISTS
(SELECT count(DISTINCT category_id)
FROM article_categories AS subcts
WHERE subcts.article_id = article.id
AND subcts.category_id IN (1,
2)
HAVING count(DISTINCT category_id) = 2)
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=86f8d3a8bdc9248a918a4c37d8da5ea0
OR
SELECT article.*
FROM articles AS article
INNER JOIN
(SELECT article_id,
array_agg(category_id) AS cts
FROM article_categories
GROUP BY article_id) AS category ON article.id = category.article_id
WHERE ARRAY[1,
2::integer] = category.cts
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=fdde52c1d11d69b690f9fe9345272786

Converting Parent child rows to JSON in oracle

Is there a way to create JSON object in Oracle, for parent child relationship data? For example an organizational structure. Table contains
EmpId Name Title ManagerId
1 John GM 0
2 Smith Manager 1
3 Jason Manager 1
4 Will IP1 3
5 Jade AM 3
6 Mark IP2 5
7 Jane AM2 5
8 Tamara M1 1
9 Dory M2 1
Something like below JSON object is expected.
{
'name': 'John',
'title': 'GM',
'children': [
{ 'name': 'Smith', 'title': 'manager' },
{ 'name': 'Jason', 'title': 'manager',
'children': [
{ 'name': 'Will', 'title': 'IP1' },
{ 'name': 'Jade', 'title': 'AM',
'children': [
{ 'name': 'Mark', 'title': 'IP2' },
{ 'name': 'Jane', 'title': 'AM2' }
]
}
]
},
{ 'name': 'Tamara', 'title': 'M1' },
{ 'name': 'Dory', 'title': 'M2' }
]
}
Oracle Database 12.2 does have a number of JSON generation functions. But these are of limited use. You need to build up the document recursively.
Which I believe requires a bit of hand-crafting.
First use a recursive query to create the org chart, adding which level each person is in the hierarchy.
Then build the JSON by:
If level for the next row is greater than the current, the employee is a manager. And you need to start a child array. Otherwise return a JSON object for the current row
If the current row is the last in the tree, you need to close N arrays and objects. N is how deep the row is in the tree minus one.
Otherwise if the next row is a lower level than the current, you need to close ( current level - next level ) arrays and objects
Then if the next level equals or is less than the current, add a comma
Which gives something like:
create table t (
EmpId int,
Name varchar2(10),
Title varchar2(10),
ManagerId int
);
insert into t values (1, 'John', 'GM' , 0 );
insert into t values (2, 'Smith', 'Manager' , 1 );
insert into t values (3, 'Jason', 'Manager' , 1 );
insert into t values (4, 'Will', 'IP1' , 3 );
insert into t values (5, 'Jade', 'AM' , 3 );
insert into t values (6, 'Mark', 'IP2' , 5 );
insert into t values (7, 'Jane', 'AM2' , 5 );
insert into t values (8, 'Tamar', 'M1' , 1 );
insert into t values (9, 'Dory', 'M2' , 1 );
commit;
with chart (
empid, managerid, name, title, lvl
) as (
select empid, managerid,
name, title, 1 lvl
from t
where empid = 1
union all
select t.empid, t.managerid,
t.name, t.title,
lvl + 1 lvl
from chart c
join t
on c.empid = t.managerid
) search depth first by empid set seq,
jdata as (
select case
/* The employee has reports */
when lead ( lvl ) over ( order by seq ) > lvl then
'{"name": "' || name ||
'", "title": "' || title ||
'", "children": ['
else
json_object ( 'name' value name, 'title' value title )
end ||
case
/* Close arrays & objects */
when lead ( lvl ) over ( order by seq ) is null then
lpad ( ']}', ( lvl - 1 ) * 2, ']}' )
when lead ( lvl ) over ( order by seq ) < lvl then
lpad ( ']}', ( lvl - lead ( lvl ) over ( order by seq ) ) * 2, ']}' )
end ||
case
/* Add closing commas */
when lead ( lvl ) over ( order by seq ) <= lvl then
','
end j,
lead ( lvl ) over ( order by seq ) nlvl,
seq, lvl
from chart
)
select json_query (
listagg ( j )
within group ( order by seq ),
'$' returning varchar2 pretty
) chart_json
from jdata;
CHART_JSON
{
"name" : "John",
"title" : "GM",
"children" :
[
{
"name" : "Smith",
"title" : "Manager"
},
{
"name" : "Jason",
"title" : "Manager",
"children" :
[
{
"name" : "Will",
"title" : "IP1"
},
{
"name" : "Jade",
"title" : "AM",
"children" :
[
{
"name" : "Mark",
"title" : "IP2"
},
{
"name" : "Jane",
"title" : "AM2"
}
]
}
]
},
{
"name" : "Tamar",
"title" : "M1"
},
{
"name" : "Dory",
"title" : "M2"
}
]
}