Search code examples
sqltheoryrelational

Retrieve maximal / minimal record


A rather complicated SQL query I was working on got me thinking about a limitation of (ANSI) SQL:

Is there a way to retrieve a record that is maximal or minimal with respect to an arbitrary ordering?

In other words:

Given a query like this:

SELECT * FROM mytable WHERE <various conditions> ORDER BY <order clause>

is it possible to write a query that returns only the first row (possibly by transforming the order clause into something else)?

I know you can do this using LIMIT (MySQL) / ROWNUM (Oracle) or similar, but that's not standard SQL.

I also know you can do this by fetching the max/min value you are interested in in a subquery (using MIN()/MAX()), then use that result as a criterion in your main SELECT, i.e.:

SELECT * FROM mytable WHERE <various conditions> AND myMaxColumn=(
  SELECT MAX(myMaxColumn) FROM mytable WHERE <various conditions>
)

But that only works if I want to sort by a single column. I see no way to generalize this to multiple columns (other than nesting the above solution, but that would mean 2^n SELECTs when ordering by n coluns).

So is there a better way in standard SQL than nesting multiple subselects?

A related question is asked in Create a SQL query to retrieve most recent records. However, the answers there suggest either using LIMIT & friends, or to use a subquery with a MAX() as explained above, both of which are not solutions to my question.


Solution

  • SQL:2003 defines concept of window functions, one of which:

    SELECT  *
    FROM    (
            SELECT  *, ROW_NUMBER() OVER (ORDER BY col1, col2, col3) AS rn
            FROM    mytable
            ) q
    WHERE   rn = 1
    

    will return you this first record.

    As for now, it's supported by SQL Server, Oracle and since Jul 01, 2009, by PostgreSQL 8.4

    Note, however, that ROW_NUMBER() in Oracle is less efficient than the native way to limit records (i. e. ROWNUM).

    See this article in my blog for performance comparison:

    SQL:2008 offers another clause to do this:

    SELECT  *
    FROM    mytable
    ORDER BY
            col1, col2, col3
    FETCH FIRST 1 ROW ONLY
    

    , but as for now, this exact syntax is supported by DB2 only (AFAIK).