I have 3 tables with 1-n relations between them
CREATE TABLE Province
(
Id BIGINT PRIMARY KEY,
Name NVARCHAR(255),
-- ...
)
CREATE TABLE District
(
Id BIGINT PRIMARY KEY,
Name NVARCHAR(255),
ProvinceId BIGINT REFERENCES [Province](Id)
)
CREATE TABLE Ward
(
Id BIGINT PRIMARY KEY,
Name NVARCHAR(255),
DistrictId BIGINT REFERENCES [District](Id)
)
I want to get detail data of a province which looks like this
{
"id": 1,
"province_name": "province name",
"districts": [
{
"id": 1,
"district_name": "district-1",
"wards": [1,2,3]
},
{
"id": 2,
"district_name": "district-2",
"wards": [4,5]
}
]
}
(1 province has many districts, 1 district has many wards)
Here's what I implemented using the STRING_AGG
built-in function
select CONCAT(
'{"id":"', p.[id], '"',
',"province_name":', p.ProvinceName,
',"districts":',
CONCAT(
'[{',
STRING_AGG(
CONCAT('"id":', CAST(po.Id AS VARCHAR(10)),
',"name":"', po.[Name],
'","district_name":[', STRING_AGG(CAST(w.id AS VARCHAR(10)), ','), ']'), '},{'),
'}]'),
'}')
from
province p
left join
district d on d.provinceId = p.id
left join
ward w on w.districtId = d.id
where
p.id = @id
But SQL Server throws this exception:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery
I tried to search similar issue from this site, but couldn't find expect results.
How can I implement to get data from 3 1-n layers?
You can pre-group ward.Id, as shown in Cte.
Or use cross apply with concatenated ward.id's
See example
declare @id int =1;
with wardList as(
select d.Id as DistrictId,string_agg(w.id,',') wList
from district d
left join ward w on w.DistrictId=d.Id
where d.ProvinceId=@id
group by d.id
)
select CONCAT(
'{"id":"', p.[id], '"',
',"province_name":', max(p.Name),
',"districts":',
CONCAT(
'[{',
STRING_AGG(
CONCAT('"id":', CAST(d.Id AS VARCHAR(10)),
',"district_name":"', d.Name,
'","wards":[', wList, ']'), '},{'),
'}]'),
'}')
from province p
left join district d on d.provinceId = p.id
left join wardList w on w.DistrictId = d.id
where p.id = @id
group by P.ID
Result is
{"id":"1","province_name":Province1,
"districts":[
{"id":1,"district_name":"P1District1","wards":[1,2,3]},
{"id":2,"district_name":"P1District2","wards":[4,5,6]},
{"id":3,"district_name":"P1District3","wards":[]}
]
}
Similar query
select CONCAT(
'{"id":"', p.[id], '"',
',"province_name":', max(p.Name),
',"districts":',
CONCAT(
'[{',
STRING_AGG(
CONCAT('"id":', CAST(d.Id AS VARCHAR(10)),
',"district_name":"', d.Name,
'","wards":[', wList, ']'), '},{'),
'}]'),
'}')
from province p
left join district d on d.provinceId = p.id
cross apply (
select string_agg(w.id,',') wList
from district d2
left join ward w on w.DistrictId=d.Id
where d2.ProvinceId=p.id and w.DistrictId=d2.Id
group by d2.id
) wardList
where p.id = @id
group by P.ID
Output is
{"id":"1","province_name":Province1
,"districts":[
{"id":1,"district_name":"P1District1","wards":[1,2,3]},
{"id":2,"district_name":"P1District2","wards":[4,5,6]}
]
}
With test data
insert into Province values
(1,'Province1')
,(2,'Province1')
insert into District values
(1, 'P1District1', 1)
,(2, 'P1District2', 1)
,(3, 'P1District3', 1)
,(4, 'P2District1', 2)
;
insert into Ward values
(1, 'P1D1Ward1', 1)
,(2, 'P1D1Ward2', 1)
,(3, 'P1D1Ward3', 1)
,(4, 'P1D2Ward1', 2)
,(5, 'P1D2Ward2', 2)
,(6, 'P1D2Ward3', 2)
;