0

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)?

example

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
6
  • No, this is not clear. The verbal description is confusing, and the sample returns only one column, so I get no clue what <magic goes here> should produce. Commented Mar 23, 2018 at 14:31
  • @LaurenzAlbe: The question says "group by 'gb', sort group by 'time' desc nulls last, get first item in group." That's enough information to answer. There's even example output! Commented Mar 23, 2018 at 14:36
  • what if the group has all nulls? should you still show null in the output? Commented Mar 23, 2018 at 14:37
  • sorry for confusing text, thanks laurenz for proper reformulation. if all are nulls, then null could be included. Well should for my usecase, but I will be glad if I see both solutions. Commented Mar 23, 2018 at 15:02
  • Do you need a solution that works both in Oracle and PostgreSQL? If so, do you want it to work in particular versions of the DBMSs? Commented Mar 23, 2018 at 15:23

4 Answers 4

5

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

SQLfiddle

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 

SQLfiddle


GB  NUM 
--- ----
1   3   
2   3   
3   1   
4   2   
5   3   
6   2   
Sign up to request clarification or add additional context in comments.

5 Comments

Interesting! The OP's VALUES (1,2,1),(1,1,2),(1,3,3) suggests he is not using Oracle
The first query does not work, because you are accessing the last record regardless of the value being null or not. The second query is a good solution. A pity that LAST_VALUE is not yet available as an aggregate function (LAST_VALUE WITHIN GROUP).
@Andomar I'm looking for answer for postgres/oracle, example from question was taken from my postgress shell. Oracle solution is indeed welcomed, I just have to test all of suggestions here.
@ThorstenKettner you've been right, I've fixed my answer.
Thanks, this solution(I tested the first one) seems to work for multiple columns, which at some row can be null, and seems to correctly find max number for each column. Also if all rows are null for given column, null is returned. Perfect for me. And I totally understand it, except for the first line (joke) ~ actually I have no idea how it works. But it works!
3

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.

1 Comment

I tried something like this, but I thew that out, because there are multiple columns like 'num' which makes where clause impossible to write ... (well I assume).
0

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;

1 Comment

same as for Andomar above -- there are multiple columns like num, which has to have same treatment. So where clause would have to be like: where num1 is not null or num2 is not null ... which IIUC wouldn't work, allowing nullvalued num1 into the result set (for row with non-null num2)
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.