11

My table:

ID   NUM   VAL
1    1     Hello
1    2     Goodbye
2    2     Hey
2    4     What's up?
3    5     See you

If I want to return the max number for each ID, it's really nice and clean:

SELECT MAX(NUM) FROM table GROUP BY (ID)

But what if I want to grab the value associated with the max of each number for each ID?

Why can't I do:

SELECT MAX(NUM) OVER (ORDER BY NUM) FROM table GROUP BY (ID) 

Why is that an error? I'd like to have this select grouped by ID, rather than partitioning separately for each window...

EDIT: The error is "not a GROUP BY expression".

user1327961
  • 452
  • 2
  • 8
Jeremy
  • 5,365
  • 14
  • 51
  • 80
  • 1
    We cannot see your error message from here. – RBarryYoung Apr 27 '12 at 23:05
  • Can you show the output you expect, the description didn't make sense to me... `I want to grab the value associated with the max of each number for each ID`. I'm also not sure what difference an ORDER BY would make to a MAX() function... – MatBailie Apr 27 '12 at 23:20
  • Not an exact answer to the question but a good introduction about oracle Analytic functions can be found at [orafaq](http://www.orafaq.com/node/55). The post helps to understand the concepts with simple examples. – Anver Sadhat Jun 18 '13 at 06:01

3 Answers3

16

You could probably use the MAX() KEEP(DENSE_RANK LAST...) function:

with sample_data as (
  select 1 id, 1 num, 'Hello' val from dual union all
  select 1 id, 2 num, 'Goodbye' val from dual union all
  select 2 id, 2 num, 'Hey' val from dual union all
  select 2 id, 4 num, 'What''s up?' val from dual union all
  select 3 id, 5 num, 'See you' val from dual)
select id, max(num), max(val) keep (dense_rank last order by num)
from sample_data
group by id;
WoMo
  • 7,136
  • 2
  • 29
  • 36
  • 3
    +1 Wow, this answer is nice. Didn't that know there's KEEP functionality on windowing function, or maybe it's proprietary to Oracle, nevertheless this is elegant solution. This answer works on most database though http://stackoverflow.com/a/10359164 Oracle: http://www.sqlfiddle.com/#!4/a9a07/19 Sql Server: http://www.sqlfiddle.com/#!3/a9a07/1 Postgresql: http://www.sqlfiddle.com/#!1/a9a07/6 – Michael Buen Apr 27 '12 at 23:57
  • I love how I constantly find questions about Oracle with just a query and no explanation about what the devil it's doing and how it works. Very helpful for applying the solution to other situations. – jpmc26 May 20 '15 at 19:38
4

When you use windowing function, you don't need to use GROUP BY anymore, this would suffice:

select id, 
     max(num) over(partition by id) 
from x 

Actually you can get the result without using windowing function:

select * 
from x
where (id,num) in
  (
     select id, max(num) 
     from x 
     group by id
  )

Output:

ID  NUM VAL
1   2   Goodbye
2   4   What's up
3   5   SEE YOU

http://www.sqlfiddle.com/#!4/a9a07/7


If you want to use windowing function, you might do this:

select id, val, 
     case when num =  max(num) over(partition by id) then
        1
     else
        0
     end as to_select
from x 
where to_select = 1

Or this:

select id, val 
from x 
where num =  max(num) over(partition by id) 

But since it's not allowed to do those, you have to do this:

with list as
(
  select id, val, 
     case when num =  max(num) over(partition by id) then
        1
     else
        0
     end as to_select
  from x
)
select * 
from list 
where to_select = 1

http://www.sqlfiddle.com/#!4/a9a07/19

Michael Buen
  • 38,643
  • 9
  • 94
  • 118
  • What does the `ORDER BY num` achieve? `MAX()` of `1,2,3` is the same as `MAX()` of `2,3,1`... For `MAX()`, surely only a `PARTITION BY` makes sense? – MatBailie Apr 27 '12 at 23:42
  • 1
    yep that's true, only PARTITION BY makes sense on MAX. the first code (MAX OVER ORDER BY) code on my answer was just quickly copy-pasted from the OP's question. ORDER BY on windowing function makes sense only on running total, e.g. SUM – Michael Buen Apr 27 '12 at 23:48
  • Well, ROW_NUMBER(), RANK(), etc, *(as they return different values dependent on position/order)* but not MIN(), MAX(), SUM()? – MatBailie Apr 28 '12 at 00:05
  • that's why I said *e.g.*, not *i.e.* :-) so order matters also on row_number,rank,etc. Order matters on SUM too, see this example: http://www.ienablemuch.com/2010/06/running-total.html – Michael Buen Apr 28 '12 at 00:25
  • @Dems Check this query, I just found a use case for using an ORDER BY on `MAX OVER` clause, maybe one of rare occasions where using an `ORDER BY` clause in `MAX OVER` clause makes sense http://stackoverflow.com/a/10401572 CTE forbid placing ORDER BY at the end of the query http://www.sqlfiddle.com/#!6/db290/8 The work-around is put the ORDER BY on windowing function instead http://www.sqlfiddle.com/#!6/db290/9 – Michael Buen May 01 '12 at 17:38
3

If you're looking to get the rows which contain the values from MAX(num) GROUP BY id, this tends to be a common pattern...

WITH
  sequenced_data
AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY id ORDER BY num DESC) AS sequence_id,
    *
  FROM
    yourTable
)
SELECT
  *
FROM
  sequenced_data
WHERE
  sequence_id = 1


EDIT

I don't know if TeraData will allow this, but the logic seems to make sense...

SELECT
  *
FROM
  yourTable
WHERE
  num = MAX(num) OVER (PARTITION BY id)

Or maybe...

SELECT
  *
FROM
(
  SELECT
    *,
    MAX(num) OVER (PARTITION BY id) AS max_num_by_id
  FROM
    yourTable
)
  AS sub_query
WHERE
  num = max_num_by_id 

This is slightly different from my previous answer; if multiple records are tied with the same MAX(num), this will return all of them, the other answer will only ever return one.


EDIT

In your proposed SQL the error relates to the fact that the OVER() clause contains a field not in your GROUP BY. It's like trying to do this...

SELECT id, num FROM yourTable GROUP BY id

num is invalid, because there can be multiple values in that field for each row returned (with the rows returned being defined by GROUP BY id).

In the same way, you can't put num inside the OVER() clause.

SELECT

  id,

  MAX(num),                <-- Valid as it is an aggregate

  MAX(num)                 <-- still valid
  OVER(PARTITION BY id),   <-- Also valid, as id is in the GROUP BY

  MAX(num)                 <-- still valid
  OVER(PARTITION BY num)   <-- Not valid, as num is not in the GROUP BY

FROM
  yourTable
GROUP BY
  id


See this question for when you can't specify something in the OVER() clause, and an answer showing when (I think) you can: over-partition-by-question

Community
  • 1
  • 1
MatBailie
  • 83,401
  • 18
  • 103
  • 137
  • 1
    a quick trip to [http://sqlfiddle.com](http://www.sqlfiddle.com/#!4/a9a07/23) could verify that this doesn't work: `select id, val from yourTable where num = max(num) over(partition by id)`. It would be nice if it work though, very concise – Michael Buen Apr 28 '12 at 00:39