PostgreSQL LATERAL JOIN to LIMIT GROUP BY - sql

Sorry I'm just failing to do the lateral join!
I got a table like this:
ID | NUMBER | VALUE
-------------------
20 | 12 | 0.7
21 | 12 | 0.8
22 | 13 | 0.8
23 | 13 | 0.7
24 | 13 | 0.9
25 | Null | 0.9
Now I would like to get the first 2 rows for each NUMBER sorted by decreasing order of VALUE.
ID | NUMBER | VALUE
-------------------
21 | 12 | 0.8
20 | 12 | 0.7
24 | 13 | 0.9
22 | 13 | 0.8
The code I tried so far looks like this:
(Found: Grouped LIMIT in PostgreSQL: show the first N rows for each group?)
SELECT DISTINCT t_outer.id, t_top.number, t_top.value
FROM table t_outer
JOIN LATERAL (
SELECT * FROM table t_inner
WHERE t_inner.number NOTNULL
AND t_inner.id = t_outer.id
AND t_inner.number = t_outer.number
ORDER BY t_inner.value DESC
LIMIT 2
) t_top ON TRUE
order by t_outer.value DESC;
Everything is fine so far, it just seems like the LIMIT 2 is not working. I get all the rows for all NUMBER elements back.

Make use of windows analytical function row_number
Rextester Demo
select "ID", "NUMBER", "VALUE" from
(select t.*
,row_number() over (partition by "NUMBER"
order by "VALUE" desc
) as rno
from table1 t
) t1
where t1.rno <=2;
Output
ID NUMBER VALUE
21 12 0,8000
20 12 0,7000
24 13 0,9000
22 13 0,8000
25 NULL 0,9000
Explanation:
Inner query t1, will assing rno order by value desc for each number group. Then in outer query, you can select rno <= 2 to get your output.

Related

How to make sure the sql result is continued range?

I have table like:
id | low_number | high_number
-------------------------------
1 | 12 | 32
-------------------------------
2 | 13 | 33
-------------------------------
3 | 15 | 36
-------------------------------
4 | 33 | 50
-------------------------------
5 | 35 | 52
...
-------------------------------
17 | 52 | 80
I want to get result like:
id | low_number | high_number
-------------------------------
1 | 12 | 32
-------------------------------
4 | 33 | 50
-------------------------------
17 | 52 | 80
that is because the low_number bigger than the pervious row high_number.
How to write sql to get these result? I use postgresql
This seems like a recursive CTE problem. You want to choose the first row (by id) and then choose the next row based on that.
The idea is to cycle through the rows, one at a time. Then when the condition is met, transition to that row. And so on.
As a query, this looks like:
with recursive tt as (
select id, low_number, high_number, row_number() over (order by id) as seqnum
from t
),
cte as (
select id, low_number, high_number, seqnum, true as is_change, id as grouping_id
from tt
where seqnum = 1
union all
select tt.id, tt.low_number, tt.high_number, tt.seqnum, tt.low_number > t.high_number,
(case when tt.low_number > t.high_number then tt.id else cte.grouping_id end)
from cte join
t
on cte.grouping_id = t.id join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte
where is_change;
Here is a db<>fiddle.
Use the window function LAG() to get a value of a previous row, e.g.
WITH j AS (
SELECT
id,low_number,high_number,
LAG(high_number) OVER (ORDER BY id) AS prev_high_number
FROM t)
SELECT id,low_number,high_number FROM j
WHERE low_number > prev_high_number OR prev_high_number IS NULL;
Demo: db<>fiddle

Group by and fetch column that is not in group by clause

I have (sample) data:
equipment_id | node_id | value (type: jsonb)
------------------------------
1 | 1 | 0.3
1 | 2 | 0.4
2 | 3 | 0.7
2 | 4 | 0.6
2 | 5 | 0.7
And I want to get the rows that has max value within the same equipment_id:
equipment_id | node_id | value
------------------------------
1 | 2 | 0.4
2 | 3 | 0.7
2 | 5 | 0.7
There is query that does what I want but I'm afraid of performance degradation because of casting jsonb to float:
with cte as (
select
equipment_id,
max(value::text::float) as val
from metrics
group by equipment_id
)
select cte.equipment_id, m.node_id, cte.val
from cte
join metrics m on cte.equipment_id = m.equipment_id and cte.val = m.value::text::float
How can I avoid casting?
Use distinct on:
select distinct on (equipement_id) m.*
from metrics m
order by equipment_id, value desc;
If your value is actually stored as a string, then use:
order by equipment_id, value::numeric desc;
You can use row_number()
select * from
(
select *, row_number() over(partition by equipment_id order by value::text::float desc) as rn
from tablename
)A where rn=1

query for column that are within a variable + or 1 of another column

I have a table that has 2 columns, and I am trying to determine a way to select the records where the two columns are CLOSE to one another. Maybe based on standard deviation if i can think about how to do that. But for now, this is what my table looks like:
ID| PCT | RETURN
1 | 20 | 1.20
2 | 15 | 0.90
3 | 0 | 3.00
The values in the pct field is a percent number (for example 20%). The value in the return field is a not fully calculated % number (so its supposed to be 20% above what the initial value was). The query I am working with so far is this:
select * from TABLE1 where ((pct = ((return - 1)* 100)));
What I'd like to end up with are the rows where both are within a set value of each other. For example If they are within 5 points of each other, then the row would be returned and the output would be:
ID| PCT | RETURN
1 | 20 | 1.20
2 | 15 | 0.90
In the above, ID 1 should work out to be PCT = 20 and Return = 20, and ID 2, is PCT = 15 and RETURN = 10. Because it was within 5 points of each other, it was returned.
ID 3 was not returned because 0 and 200 are way above the 5 point threshold.
Is there any way to set a variable that would return a +- 5 when comparing the two values from the above attributes? Thanks.
RexTester Example:
Use Lead() over (Order by PCT) to look ahead and LAG() to look back to the next row do the math and evaluate results...
WITH CTE (ID, PCT , RETURN) as (
SELECT 1 , 20 , 1.20 FROM DUAL UNION ALL
SELECT 2 , 15 , 0.90 FROM DUAL UNION ALL
SELECT 3 , 0 , 3.00 FROM DUAL),
CTE2 as (SELECT A.*, LEAD(PCT) Over (ORDER BY PCT) LEADPCT, LAG(PCT) Over (order by PCT) LAGPCT
FROM CTE A)
SELECT * FROM CTE2
WHERE LEADPCT-PCT <=5 OR PCT-LAGPCT <=5
Order by ID
Giving us:
+----+----+-----+--------+---------+--------+
| | ID | PCT | RETURN | LEADPCT | LAGPCT |
+----+----+-----+--------+---------+--------+
| 1 | 1 | 20 | 1,20 | NULL | 15 |
| 2 | 2 | 15 | 0,90 | 20 | 0 |
+----+----+-----+--------+---------+--------+
or use the return value instead of PCT... just depends on what you're after. But maybe I don't fully understand the question..

Find first record before or after missing record in one table

I have only one big table Samples with columns Id and Values.
I need get only one record before missing record from table for each gap.
I need help to build query.
Table:
Id | Values
---------
1 | 45
2 | 45
3 | 44
5 | 89
6 | 21
7 | 59
9 | 23
10 | 78
11 | 12
12 | 16
15 | 19
Result of query:
Id | Values
---------
3 | 44
7 | 59
12 | 16
As you do not tagged the database name, you can consider this following logic as generic for any database-
SELECT * FROM your_table A
LEFT JOIN your_table B ON A.Id = B.Id - 1
WHERE B.Id IS NULL
AND A.Id < (SELECT MAX(ID) FROM your_table)
ORDER BY A.Id
If you are using MSSQL 2012 or Newer version, you can use LEAD to achieve your desired output with below script-
SELECT A.Id,A.[Values]
FROM
(
SELECT *,LEAD(ID) OVER(ORDER BY ID) Lead_Value
FROM your_table
)A
WHERE Lead_Value- ID >1
Something like this:
SELECT s1.*
FROM samples s1
WHERE NOT EXISTS (SELECT id
FROM samples s2
WHERE s2.id = s1.id + 1)
In order to avoid the last row to be selected always you should add:
AND EXISTS (SELECT id
FROM samples s3
WHERE s3.id > s1.id)

ORDER BY distance to another value

Lets say we have a table like that
id|value
--------
1 | 50
2 | 19
3 | 100
4 | 21
5 | -10
How can I use ORDER BY operator to order values by their distance to another value?
SELECT * FROM table ORDER BY nearest(value,30) DESC
To get this table:
id|value
--------
4 | 21
1 | 50
2 | 19
5 | -10
3 | 100
You may use:
SELECT * FROM table ORDER BY abs(value - 30) ASC
Not sure that all sql dialect accepts answer of Paul92.
Here is another solution:
SELECT *
FROM (
SELECT
t.*,
abs(value - 30) AS abs_value
FROM table t
) temp
ORDER BY abs_value