Postgres Lateral Unnest - At Least 1 Value? - postgresql

I have a table with an optional fields column of type jsonb[]. I am using a lateral unnest to break those fields out into rows, then an aggregate to combine them again in the order I want.
SELECT id, name, ARRAY_AGG(v ORDER BY v->'priority' DESC) as fields
FROM results, LATERAL UNNEST(fields) AS f(v)
GROUP BY 1, 2
But because fields is optional, not all rows have values to unnest to begin with. Is there a way to lateral unnest at least one row even if it is empty? Or is there a better way to apply an order to a jsonb[] column on the way out so I can avoid this lateral unnest all together?

use a left join lateral.
SELECT
id
, name
, ARRAY_AGG(v ORDER BY v->'priority' DESC) as fields
FROM results
LEFT JOIN LATERAL UNNEST(fields) AS f(v) ON TRUE
GROUP BY 1, 2

Related

How to make a Group By in PostgreSQL with only one field?

SELECT table1.field1, table2.field2
FROM table1
LEFT JOIN table2 ON table1.field1, table2.field1
GROUP BY table1.field1
MySQL: ✅ All right! 😁
PostgreSQL: ❌ You must put all Select fields in the Group By! 😭
How to make a Group By in PostgreSQL with only one field?
if table2.field2 value is alpha numeric then use MIN/MAX or table2.field2 is numeric then use any aggregate function as per need for avoiding to use table2.field2 column in GROUP BY clause.
SELECT table1.field1
, MAX(table2.field2) field2
FROM table1
LEFT JOIN table2
ON table1.field1 = table2.field1
GROUP BY table1.field1

How to get a row for each occurrence of Id in IN clause?

Given that I have a list of repeating ids that I need to fetch some additional data to populate xls spreadsheet, how can I do that. "IN" clause returns only 1 match, but I need a row for each occurrence of an Id. I looked at PIVOT, thinking I could create a select list and then do inner join.
Select m.Id, m.LegalName, m.OtherId
from MyTable m
where m.OtherId in (1,2,1,1,3,1,4,4,2,1)
You can use VALUES clause :
SELECT t.id as OtherId, m.id, m.LegalName
FROM ( VALUES (1),(2),(1),(1),(3),(1),(4),(4),(2),(1)
) t(ID) INNER JOIN
MyTable m
ON m.OtherId = t.id;

Posgres: Order by ::timestamp asc and desc of jsonb column giving same results, how to order by last timestamp

I have this query which retrieves 1 row based on UUID doc_id of table documents, which also has a field column type jsonb:
select
DISTINCT ON (doc_id)
*
FROM (
select d.doc_id, c.comments
from documents as d
cross join lateral jsonb_array_elements(comments)
WITH ORDINALITY c(comments)
WHERE (c.comments ->> 'isUser'):: boolean is false
order by (c.comments ->>'timestamp')::timestamp desc
) as s;
When I try:
order by (c.comments ->>'timestamp')::timestamp desc
I get exact same result. I even tried with timestamptz:
order by (c.comments ->>'timestamp')::timestamptz asc
Sample content of jsonb comments column:
[...
{
"text": "30",
"timestamp": "2018-11-11T09:13:23.242Z", // older
"isUser": false
},{
"text": "31",
"timestamp": "2018-11-11T12:53:48.620Z", // LATEST
"isUser": false
}]
as you can see object with text 30 is older, yet it always gets returned in the queries above.
The order is irrelevant to the final outcome because it only applies to the SELECT statement within which it's used, i.e. your subquery. You then perform another query on those results, using DISTINCT ON which will do whatever calculations and return you the results in some order but probably not the one you want.
To allow you to order in the outer query, the fields you want to use in the order must be accessible at that level. That means the subquery will have to also return the timestamp field, then the outer query can order on that but not select it (to keep the returned columns the same).
select
DISTINCT ON (doc_id)
doc_id, comments
FROM (
select d.doc_id, c.comments, (c.comments ->>'timestamp')::timestamp AS comment_timestamp
from documents as d
cross join lateral jsonb_array_elements(comments)
WITH ORDINALITY c(comments)
WHERE (c.comments ->> 'isUser'):: boolean is false
) as s
ORDER BY doc_id, comment_timestamp DESC
I may be missing something but it seems to be you don't need the subquery anyway, wouldn't this work?
select DISTINCT ON (d.doc_id) d.doc_id, c.comments
from documents as d
cross join lateral jsonb_array_elements(comments)
WITH ORDINALITY c(comments)
WHERE (c.comments ->> 'isUser'):: boolean is false
order by d.doc_id, (c.comments ->>'timestamp')::timestamp desc

PostgreSQL percentile_cont and aggregations

I have to compute three 99th%ile with postgresql function percentile_cont.
two of them are related to single column, the third one is about the difference of the two previous columns, BUT I have to compute the last one after an inner join, and it is not clear to me how to do.
SELECT
TableA1.a,
TableA1.b99,
TableA2.b99,
**percentile_cont(0.99) WITHIN GROUP (ORDER BY (TableA1.b - TableA2.b))::bigint**
FROM
(
SELECT
a,
percentile_cont(0.99) WITHIN GROUP (ORDER BY (b))::bigint as b99
FROM TableA
GROUP BY
a
) TableA1
INNER JOIN
(
SELECT
a,
percentile_cont(0.99) WITHIN GROUP (ORDER BY (b))::bigint as b99
FROM TableA
GROUP BY
a
) TableA2
ON
TableA1.a = TableA2.a
The fourth field selected (percentile_cont(0.99) WITHIN GROUP (ORDER BY (TableA1.b - TableA2.b))::bigint) is impossible because the field b from the two subqueries has been already aggregated.
Any suggestion on how to rebuild the query?
Thank you in advance,
Lorenzo

Selecting non-repeating values in Postgres

SELECT DISTINCT a.s_id, select2Result.s_id, select2Result."mNrPhone",
select2Result."dNrPhone"
FROM "Table1" AS a INNER JOIN
(
SELECT b.s_id, c."mNrPhone", c."dNrPhone" FROM "Table2" AS b, "Table3" AS c
WHERE b.a_id = 1001 AND b.s_id = c.s_id
ORDER BY b.last_name) AS select2Result
ON a.a_id = select2Result.student_id
WHERE a.k_id = 11211
It returns:
1001;1001;"";""
1002;1002;"";""
1002;1002;"2342342232123";"2342342"
1003;1003;"";""
1004;1004;"";""
1002 value is repeated twice, but it shouldn't because I used DISTINCT and no other table has an id repeated twice.
You can use DISTINCT ON like this:
SELECT DISTINCT ON (a.s_id)
a.s_id, select2Result.s_id, select2Result."mNrPhone",
select2Result."dNrPhone"
...
But like other persons have told you, the "repeated records" are different really.
The qualifier DISTINCT applies to the entire row, not to the first column in the select-list. Since columns 3 and 4 (mNrPhone and dNrPhone) are different for the two rows with s_id = 1002, the DBMS correctly lists both rows. You have to write your query differently if you only want the s_id = 1002 to appear once, and you have to decide which auxilliary data you want shown.
As an aside, it is strongly recommended that you always use the explicit JOIN notation (which was introduced in SQL-92) in all queries and sub-queries. Do not use the old implicit join notation (which is all that was available in SQL-86 or SQL-89), and especially do not use a mixture of explicit and implicit join notations (where your sub-query uses the implicit join, but the main query uses explicit join). You need to know the old notation so you can understand old queries. You should write new queries in the new notation.
First of all, the query displayed does not work at all, student_id is missing in the sub-query. You use it in the JOIN later.
More interestingly:
Pick a certain row out of a set with DISTINCT
DISTINCT and DISTINCT ON return distinct values by sorting all rows according to the set of columns to be distinct, then it picks the first row from every set. It sorts by all rows for a general DISTINCT and only the specified rows for DISTINCT ON. Here lies the opportunity to pick certain rows out of a set over other.
For instance if you prefer rows with not-empty "mNrPhone" in your example:
SELECT DISTINCT ON (a.s_id) -- sure you didn't want a.a_id?
,a.s_id AS a_s_id -- use aliases to avoid dupe name
,s.s_id AS s_s_id
,s."mNrPhone"
,s."dNrPhone"
FROM "Table1" a
JOIN (
SELECT b.s_id, c."mNrPhone", c."dNrPhone", ??.student_id -- misssing!
FROM "Table2" b
JOIN "Table3" c USING (s_id)
WHERE b.a_id = 1001
-- ORDER BY b.last_name -- pointless, DISTINCT will re-order
) s ON a.a_id = s.student_id
WHERE a.k_id = 11211
ORDER BY a.s_id -- first col must agree with DISTINCT ON, could add DESC though
,("mNrPhone" <> '') DESC -- non-empty first
ORDER BY cannot disagree with DISTINCT on the same query level. To get around this you can either use GROUP BY instead or put the whole query in a sub-query and run another SELECT with ORDER BY on it.
The ORDER BY you had in the sub-query is voided now.
In this particular case, if - as it seems - the dupes come only from the sub-query (you'd have to verify), you could instead:
SELECT a.a_id, s.s_id, s."mNrPhone", s."dNrPhone" -- picking a.a_id over s_id
FROM "Table1" a
JOIN (
SELECT DISTINCT ON (b.s_id)
,b.s_id, c."mNrPhone", c."dNrPhone", ??.student_id -- misssing!
FROM "Table2" b
JOIN "Table3" c USING (s_id)
WHERE b.a_id = 1001
ORDER BY b.s_id, (c."mNrPhone" <> '') DESC -- pick non-empty first
) s ON a.a_id = s.student_id
WHERE a.k_id = 11211
ORDER BY a.a_id -- now you can ORDER BY freely