Postgresql jsonb_agg subquery sort - json

How can I sort the results of a subquery that's using a json aggregate?
If I had a schema like this:
CREATE TABLE plans( id integer NOT NULL, name character varying(255));
CREATE TABLE plan_items ( id integer NOT NULL, plan_id integer NOT NULL, expected_at date, status integer);
I'm aggregating the plan_items result on a json column through a subquery.
Like this:
SELECT
plans.id,
plans.name,
jsonb_agg((SELECT pi_cols FROM
(SELECT plan_items.id, plan_items.expected_at, plan_items.status) pi_cols
)) AS plan_items_data
FROM
plans
INNER JOIN plan_items ON plan_items.plan_id = plans.id
GROUP BY
plans.id,
plans.name
ORDER BY plans.id;
The JSON aggregate is working as expected and give me the results that I need. Ok.
But I can't order the results.
I've tried:
jsonb_agg((SELECT pi_cols FROM
(SELECT plan_items.id, plan_items.expected_at, plan_items.status ORDER BY plan_items.expected_at) pi_cols
)) AS plan_items_data
and also:
jsonb_agg((SELECT pi_cols FROM
(SELECT plan_items.id, plan_items.expected_at, plan_items.status) pi_cols ORDER BY pi_cols.expected_at
)) AS plan_items_data
But none of these solved.
Any ideas?

As Abelisto suggests, just use a simple aggregate expression with ordering:
jsonb_agg(plan_items ORDER BY plan_items.expected_at) AS plan_items_data

Join the tables with the desirable sort order and use lateral join to select columns for jsonb_agg():
select s.plan_id id, name, jsonb_agg(pi_col)
from (
select p.id plan_id, p.name, pi.id, expected_at, status
from plans p
join plan_items pi
on p.id = pi.plan_id
order by p.id, expected_at
) s,
lateral (
select plan_id id, expected_at, status
) pi_col
group by 1, 2
order by 1;
The above query seems to be more natural and flexible (and a bit faster in most cases) than the one with a subquery in a select list. However for better performance you should also apply Abelisto's suggestion:
select s.plan_id id, name, json_agg(pi_col order by pi_col.expected_at)
from (
select p.id plan_id, p.name, pi.id, expected_at, status
from plans p
join plan_items pi
on p.id = pi.plan_id
) s,
lateral (
select plan_id id, expected_at, status
) pi_col
group by 1, 2
order by 1;

Related

Getting a value from the same row as the row the min() value comes from

Here's my query.
There's a complicated system, which requires this query. I'd like to know, how do I get the currency value from the same row the min(j.price) value comes from?
I know the query is a little messy but I'm essentially asking how to get a value from the same row that the value from a min() function comes from?
SELECT
count(p.name), p.name, p.image_url, p.type, p.number, p.productCode, p.productType,
p.material, p.blueprint, min(j.price) as price
FROM products AS p
left join (
SELECT
p.token_id, p.name, p.image_url, p.type, p.number, p.productCode,
p.productType, p.material, p.blueprint, o2.order_id, o2.currency,
o2.eth_price, o2.price, o2.usd_price, o2.quantity, o2.isOurs,
o2.order_status, o2.updated_timestamp, ROW_NUMBER()
OVER (PARTITION BY order_status ORDER BY usd_price ASC) rn
FROM products AS p
left JOIN (
SELECT token_id, MAX(updated_timestamp) AS Latest_order
FROM orders
GROUP BY token_id
) o1 ON p.token_id = o1.token_id
left JOIN orders o2 ON o1.token_id = o2.token_id AND o1.Latest_order = o2.updated_timestamp
where o2.order_status = 'active'
ORDER BY name asc
) j on p.token_id = j.token_id
group by p.name, p.image_url, p.type, p.number, p.productCode, p.productType, p.material, p.blueprint
order by name
For #FanoFN
Perhaps SUBSTRING_INDEX() and GROUP_CONCAT() can help. So, instead of:
... , min(j.price) as price
How about something like this:
...,
SUBSTRING_INDEX(GROUP_CONCAT(j.price ORDER BY j.price ASC),',',1) AS price,
SUBSTRING_INDEX(GROUP_CONCAT(j.currency ORDER BY j.price ASC),',',1) AS currency
It is quite long but it should work.
Refer this demo fiddle

how to enhance efficiency of my query

I have such a query:
SELECT
*,
(
SELECT COUNT(DISTINCT client_id)
FROM 1097_course_students_tbl
WHERE course_cycl_id=id
AND stts_id <> 8
AND client_id IN(SELECT id FROM 1097_clients_tbl WHERE is_removed=0)
) AS cnt
FROM 1097_course_cycle_tbl
WHERE (course_id IN (SELECT id FROM 1097_courses_tbl WHERE is_removed=2))
ORDER BY start_date DESC
I need to make it more efficient because it takes too long
any suggestions ?
thanks
Try the following
SELECT cc.*,IFNULL(q.cnt,0) cnt
FROM 1097_course_cycle_tbl cс
JOIN 1097_courses_tbl с ON c.id=cc.course_id AND c.is_removed=2
LEFT JOIN
(
SELECT cs.course_cycl_id,COUNT(DISTINCT cs.client_id) cnt
FROM 1097_course_students_tbl cs
JOIN 1097_clients_tbl c ON cs.client_id=c.id AND c.is_removed=0
WHERE cs.stts_id<>8
GROUP BY cs.course_cycl_id
) q
ON q.course_cycl_id=cс.id
ORDER BY cc.start_date DESC
I think id in 1097_courses_tbl and 1097_clients_tbl is primary key.
Therefore I replaced IN into JOIN.
And I converted the subquery from SELECT block wich executed for each rows into the subquery with GROUP BY which using in LEFT JOIN. Here it'll execute only one time and return all the necessary information.

Joining three tables, one join uses cte, other join is normal. How do i join this third table to my existing query which uses cte?

The data is simple. tblLog is a log with UserID, Time, and Action. tblRole has UserID and Role and a Date field. tblActionDesc has Action and ActionDesc (description). I want the query to give me the information in tblLog but also include the Role from tblRole (for each UserID) and the ActionDesc from tblActionDesc (for each Action).
The first problem I had was that the data in tblRole was not unique. It contained many roles per user but it also had a date field. I figured out how to get a unique UserID by utilizing a cte. (HT #Siyual)
How can I join tblActionDesc to the results of this cte?
This is the cte:
;With Cte As
(
Select L.[ID],
L.[UserID],
L.[Time],
L.[Action],
R.[Role],
Row_Number() Over (Partition By [L].[UserId] Order By [R].[TransDate] Desc) Row_Number
From [TEST111].[dbo].[tblLog] as L
Join [TEST111].[dbo].[tblRole] as R On L.[UserID] = R.[UserID]
)
Select [Id], [UserId], [Time], [Action], [Role]
From Cte
Where [Row_Number] = 1
This is the code that would work if I did not have the "many" problem in tblRole
SELECT L.[ID]
,L.[UserID]
,L.[Time]
,L.[Action]
,R.Role
,A.ActionDesc
FROM [TEST111].[dbo].[tblLog] as L
Join [TEST111].[dbo].[tblRole] as R
On L.[UserID] = R.[UserID]
Join [TEST111].[dbo].[tblActionDesc] as A
On L.[Action] = A.[Action]
I think that's all the information I need for the question. Here is the question that gave me the cte: Need query to relate unique parent to child that is not unique but can be made unique with MAX
How about this:
with cte1 as (
-- Get the most recent TransDate for each UserID.
select UserID, max(TransDate) as max_trans_date
from tblRole
group by UserID
),
cte2 as (
-- Now that we know the most recent record for each user,
-- get the actual data (i.e. "Role") for each UserID.
select r.UserID, r.[Role]
from tblRole as r
inner join cte1 on r.UserID = cte1.UserID and r.TransDate = cte1.max_trans_date
)
select l.ID, l.UserID, l.[Time], l.[Action], cte2.[Role], ad.ActionDesc
from tblLog as l
left join cte2 on l.UserID = cte2.UserID
left join tblActionDesc as ad on l.[Action] = ad.[Action]
Edit: Updated for question in comments.
;With Cte As
(
Select ID, UserID, Role, TransDate,
Row_Number OVER (PARTITION BY UserID ORDER BY TransDate DESC) Row_Number
From tblRole
)
SELECT L.[ID]
,L.[UserID]
,L.[Time]
,L.[Action]
,R.Role
,A.ActionDesc
FROM [TEST111].[dbo].[tblLog] as L
Join cte as R
On L.[UserID] = R.[UserID]
Join [TEST111].[dbo].[tblActionDesc] as A
On L.[Action] = A.[Action]
WHERE R.Row_Number = 1

can someone help me why this code fails at c_orders (c_custkey, c_count) when executed in Mysql

select c_count, count(*) as custdist
from
(
select c_custkey, count(o_orderkey)
from customer left outer join orders
on c_custkey = o_custkey and o_comment not like '%special%requests%'
group by c_custkey
) as c_orders (c_custkey, c_count)
group by c_count
order by custdist desc, c_count desc;
Try this query:
select c_orders.c_count, count(*) as custdist
from
(
select c_custkey, count(o_orderkey) as c_count
from customer left outer join orders
on c_custkey = o_custkey and o_comment not like '%special%requests%'
group by c_custkey
) as c_orders
group by c_orders.c_count
order by custdist desc, c_orders.c_count desc;
I saw two problems with your original query. First, you were referring to a column in your outer query which does not exist in the temporary table in your inner query:
select c_count, ...
But this column does not exist in the inner temporary table. Next, you had some strange syntax next to the alias for your temporary table:
) as c_orders (c_custkey, c_count)
You don't need whatever you had in parentheses, so I removed it.

How do I make an SQL Query to find the max of an aggregate function like "count"?

This is the query I need in English:
Display the animal id, name, and number of exams of the animal(s) with the most examinations done on them.
Consider there might be ties for first place. In that case all tied animals should be returned.
Here's some relevant SQL:
select an_id, an_name, count(distinct ex_id) as NumberExams
from vt_animals
join vt_exam_headers using (an_id)
How can I do this without using desc and limit and ideally with group by? I thought of using max, but it doesn't seem to work with count.
If I understand well the query, something like this would return the group of animals if more than one have the most number of examinations:
SELECT a.an_id, a.an_name, a.number_exams
FROM (SELECT an_id, an_name, COUNT(ex_id) as number_exams
FROM vt_animals
JOIN vt_exam_headers USING (an_id)
GROUP BY an_id) AS a
HAVING a.number_exams >= MAX(a.number_exams)
You have to use group by clause to the column names which are not in the aggregate functions
select an_id, an_name, count(distinct ex_id) as NumberExams
from vt_animals
group by an_id, an_name
First select animal with most examinations:
SELECT an_id,count(ex_id) FROM animals GROUP BY an_id ORDER BY count(*) DESC LIMIT 1
Then you can use it as a subquery.
Explanation: you sort this table descending by count(*) and then you choose top 1, which is maximum.
Depending on the database product you're using, this could vary in complexity. For example, emibloque's answer will not work in MS SQL Server because the having clause needs to correspond with the group by clause. In this case, you'd have to do something along these lines:
select * from
(
select an_name, count(*) exams
from vt_animals a join vt_exam_headers e on a.an_id = e.an_id
group by an_name
) sub1
where exams =
(
select max(exams) from
(
select an_id, count(*) exams
from vt_exam_headers
group by an_id
) sub2
)
or if you prefer the use of variables:
declare #max_exams int;
select #max_exams = (
select max(exams) from
(
select an_id, count(*) exams
from vt_exam_headers
group by an_id
) sub
);
select * from
(
select an_name, count(*) exams
from vt_animals a join vt_exam_headers e on a.an_id = e.an_id
group by an_name
) sub1
where exams = #max_exams