Reputation: 3091
I have data which I have to 'group by'. In each resultant group, there are rows with multiple columns which has to be treated as such: for each such given column, return non-null, most current value. So I have to 'group by'(gb) whole table, and find 'max-like(NUM)' for every column(below represented as NUM). max-like function sorts by temporal column, below represented as 'time'. In another words, group by 'gb', sort group by 'time' desc nulls last, get first item in group.
Sorry, for that convoluted description. I hope it's clear. Any idea how to write that sql query (oracle/postgres)?
CREATE TABLE test (
gb integer,
NUM INTEGER,
time integer
);
--rows=groups, columns=time; so in first row=group data
--sorted by time are from left to right the middle value
--in triplet, thus, 2,1,3. Ie. most current non-null value in time is 3.
insert into test VALUES (1,2,1),(1,1,2),(1,3,3);--3
insert into test VALUES (2,1,1),(2,2,2),(2,3,3);--3
insert into test VALUES (3,3,1),(3,2,2),(3,1,3);--1
insert into test VALUES (4,3,1),(4,2,2),(4,null,3);--2
insert into test VALUES (5,2,1),(5,3,2),(5,null,3);--3
insert into test VALUES (6,2,1),(6,null,2),(6,null,3);--2
query
select
t.gb,
'<magic goes here>'
from test t
GROUP BY t.gb ORDER BY t.gb;
is expected to return
1 | 3
2 | 3
3 | 1
4 | 2
5 | 3
6 | 2
Upvotes: 0
Views: 1690
Reputation: 2100
In Oracle the simplest way is:
SELECT gb, max(num) keep (DENSE_RANK LAST ORDER BY nvl2(num,time,NULL) NULLS first ) r
FROM test
GROUP BY gb
There is also a "group-less" approach:
SELECT DISTINCT gb, last_value(num ignore nulls)over(PARTITION BY gb ORDER BY time
RANGE BETWEEN UNBOUNDED preceding AND UNBOUNDED following) num
FROM test ORDER BY gb
GB NUM
--- ----
1 3
2 3
3 1
4 2
5 3
6 2
Upvotes: 5
Reputation: 94914
Here is a solution in standard SQL:
with grp as
(
select distinct gb from mytable
)
SELECT
gb,
(
select num
from mytable m
where m.gb = grp.gb and m.num is not null
order by thetime desc
fetch first row only
) as n
FROM grp
order by grp.gb\\
You need one such subquery per column.
In Oracle this query only works as of version 12c; former versions don't feature the FETCH FIRST n ROWS
clause.
Here is an alternative, also standard SQL, which does work in older Oracle versions:
with grp as
(
select distinct gb from mytable
)
SELECT
gb,
(
select num
from mytable m
where m.gb = grp.gb and thetime =
(
select max(thetime)
from mytable m2
where m2.gb = m.gb and m2.num is not null
)
) as n
FROM grp
order by grp.gb\\
Rextester demo: http://rextester.com/QUO91858
Upvotes: 0
Reputation: 246578
This is probably the simplest solution that works in both Oracle and PostgreSQL:
SELECT DISTINCT
gb,
first_value(num) OVER (PARTITION BY gb ORDER BY time DESC)
FROM test
WHERE num IS NOT NULL
ORDER BY gb;
Upvotes: 0
Reputation: 238086
You could use row_number
to assign an increasing number for each row with the same gb
. Order those rows by time
, and only display the first one:
select gb
, num
from (
select row_number() over (partition by gb order by time desc) rn
, *
from test
where num is not null
) sub
where rn = 1 -- Row with highest time per (gb) group
Working example at SQL Fiddle.
Upvotes: 3