I have this Warehouse collection I got from the database
[
{
"id": 1,
"warehouse": "India"
"sales": [
{
"id": 1,
"warehouse_id": 1,
"price": "120.00",
"quantity": 1000,
"status": 1
},
{
"id": 2,
"warehouse_id": 1,
"price": "20.00",
"quantity": 100,
"status": 1
},
{
"id": 3,
"warehouse_id": 1,
"price": "40.00",
"quantity": 1000,
"status": 2
}
]
},
{
"id": 2,
"warehouse": "Malaysia"
"sales": [
{
"id": 4,
"warehouse_id": 2,
"price": "160.00",
"quantity": 100,
"status": 1
}
]
}
]
I want to calculate the total income for each warehouse
Total income is calculated based on the sale status attribute
If status = 1, the products are delivered so it should add price * quantity to the total income
If status = 2, the products are returned so it should subtract price * quantity from the total income
A basic example for India warehouse:
total_income = 120*1000 + 20*100 - 40*1000
And for Malaysia:
total_income = 160*100
I tried using Warehouse::withSum(); but it didn't get me anywhere.
I'm wondering if there's a good way to do with collections
You could just pass a few callbacks to the collection's sum() method:
$warehouses_collection->map(function ($warehouse) {
return (object) [
'id' => $warehouse->id,
'warehouse' => $warehouse->warehouse,
'total_income' => collect($warehouse->sales)->sum(function ($sale) {
((int) $sale->price) * $sale->quantity * ($sale->status == 1 ? 1 : -1)
})
];
});
WithSum is a bit tricky to use here but calling withAggregate works.
Warehouse::withAggregate(
'sales as total_income',
'sum(case when status = 1 then price * quantity when status = 2 then price * quantity * -1 else 0 end)'
)->get()
I honestly would go the route below:
Obviously my solution would have to be modified slightly if you aren't using completely whole numbers (as in your example). The if/else can be broadened out as well if you have more then the 2 statuses.
$total_income = 0;
foreach($warehouses as $warehouse)
{
foreach($warehouse->sales as $sale)
{
if($sale->status = 1)
{
$total_income += ($sale->price * $sale->quantity);
}else{
$total_income -= ($sale->price * $sale->quantity);
}
}
}
This is a crude example of what how I would do it. It seems that each 'warehouse' has a different location ex. India vs. Malaysia. My example is more about the grand total, but you could always save the results of each different warehouse in different variables, or as a key/value pair in an array (which is how I would go).
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.
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"
}]