简体   繁体   中英

Oracle SQL — What's wrong with this grouping?

I am trying to grab a row that has the max of some column. Normally I'd use Rank for this and just select rank = 1 but that seems pointless when I know I just need the max of a column. Here is my SQL:

SELECT
  name,
  value,
  MAX(version)
FROM
  my_table t
WHERE
  person_type = "STUDENT"
GROUP by NAME,VALUE
HAVING version = max(version)

This returns the "You've done something wrong involving grouping error" ie "not a GROUP BY expression" when trying to run. If I add version to the group by field, this SQL runs, but it obviously returns all rows instead of just the max version of each.

So my question is mostly "Why doesn't this work?" I am selecting the max of version so I don't see why I need to group by it. I know there are other solutions (partition over, rank ...) but I am more interested in why this in particular is flawed syntactically.

EDIT: More explicit about the use of this having clause.

Let's say there are these two rows in table t:

NAME    VALUE    VERSION
JEREMY  C        1
JEREMY  A        2

What is returned from this query should be:

JEREMY A 2

But if I remove having then I would get:

JEREMY A 2
JEREMY C 2

The HAVING clause, in general, needs to contain columns that are produced by the group by. In fact, you can think of the HAVING clause as a WHERE on the group by.

That is, the query:

select <whatever>
from t
group by <whatever>
having <some condition>

is equivalent to:

select <whatever>
from (select <whatever>
      from t
      group by <whatever
     ) t
where <some condition>

If you think about it this way, you'll realize that max(version) makes sense because it is an aggregated value. However, "version" does not make sense, since it is neither a calculated value nor a group by column.

You seem to know how to fix this. The one other comment is that some databases (notably mysql) would accept your syntax. They treat "HAVING version = max(version)" as "HAVING any(version) = max(version)".

You're trying to use version in your HAVING clause, but it's not being grouped by.

If all you want is the name, value and max version, you don't need the HAVING clause at all.

SELECT
  name,
  value,
  MAX(version)
FROM
  my_table t
WHERE
  person_type = "STUDENT"
GROUP by NAME,VALUE

The HAVING clause is for when you want to have a "Where" clause after aggregation, like

HAVING max(version) > 5

EDIT:

Based on your sample data, you're grouping by VALUE but what you really want to do is identify the VALUE that has the MAX(VERSION) for each NAME.

To do this, you need to use a WHERE EXISTS or self join, like so:

select name, value, version from t 
where exists
(
  select 1 from
  (select name, max(version) version
     from t 
    group by name) s
  where s.name = t.name and s.version = t.version
)

This SQL statement fails because the HAVING clause runs after the GROUP BY -- it can only operate on either aggregates or columns that are listed in the GROUP BY clause. If you have only grouped by NAME and VALUE , VERSION alone has no meaning-- it has many possible values for every combination of NAME and VALUE at that point so it doesn't make sense to compare it to MAX(version) or any other aggregate which has exactly 1 value for every NAME and VALUE pair.

Another way of getting what you want:

select *
from (select name
        , value
        , version
        , max(version) over 
            (partition by name) as max_version
    from t)
where version = max_version;

Sample execution: SQL> create table t (name varchar2(30) 2 , value varchar2(1) 3 , version number not null 4 , constraint t_pk primary key (name, version));

Table created.

SQL> insert into t select 'JEREMY', 'C', 1 from dual
  2  union all select 'JEREMY', 'A', 2 from dual
  3  union all select 'SARAH', 'D', 2 from dual
  4  union all select 'SARAH', 'X', 1 from dual;

4 rows created.

SQL> commit;

Commit complete.

SQL> select name, value, version
  2  from (select name
  3          , value
  4          , version
  5          , max(version) over
  6              (partition by name) as max_version
  7      from t)
  8  where version = max_version;

NAME                           V    VERSION
------------------------------ - ----------
JEREMY                         A          2
SARAH                          D          2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM