Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL group by: select value where another column has its min/max

Tags:

sql

sql-server

I want to group by one column, get both min and max of a second column, and (this is the tricky part!) get the value from a third column where the second column has its min value in the group.

Example:

MyTable:

ID     TS     GRP
==================
 1     20      A
 2     20      B
 3     10      A
 4     30      A
 5     10      B
 6     40      A

Desired result (ID should be the value from the record where TS has its minimum):

ID    MIN_TS   MAX_TS   GRP
============================
 3      10       40      A
 5      10       20      B

In general, the grouping query is very easy:

SELECT <???> AS ID, MIN(TS) AS MIN_TS, MAX(TS) AS MAX_TS, GRP
FROM MyTable
GROUP BY GRP

But what about the ID part? It doesn't work this way with grouping, right? But why? And what's the best workaround?

like image 997
Andy Avatar asked Sep 05 '17 07:09

Andy


People also ask

Can we use max with GROUP BY?

MySQL MAX() function with GROUP BY retrieves maximum value of an expression which has undergone a grouping operation (usually based upon one column or a list of comma-separated columns).

Can GROUP BY used with where clause?

GROUP BY Clause is utilized with the SELECT statement. GROUP BY aggregates the results on the basis of selected column: COUNT, MAX, MIN, SUM, AVG, etc. GROUP BY returns only one result per group of data. GROUP BY Clause always follows the WHERE Clause.

Can we use GROUP BY and where clause together in SQL?

Absolutely. It will result in filtering the records on your date range and then grouping it by each day where there is data.

Can we use SELECT * with GROUP BY?

Cannot use an aggregate or a subquery in an expression used for the group by list of a GROUP BY clause. The original idea was to create the table in beginning of the query, so the (SELECT * FROM #TBL) could be used on the query itself, instead of defining the names on each GROUP BY.


1 Answers

Do the aggregation in a subquery, then look up the ID for each group in another subquery:

SELECT
  (SELECT TOP(1) id FROM MyTable WHERE grp = agg.grp ORDER BY ts DESC) AS id,
  min_ts, max_ts, grp
FROM (SELECT min(ts) AS min_ts, max(ts) AS max_ts, grp
      FROM MyTable
      GROUP BY grp) agg

Or use window functions:

SELECT id, min_ts, max_ts, grp
FROM (SELECT 
        id,
        min(ts) OVER (PARTITION BY grp) min_ts,
        max(ts) OVER (PARTITION BY grp) max_ts,
        grp,
        row_number OVER (PARTITION BY grp ORDER BY ts) rn
      FROM MyTable)
WHERE rn = 1;

This query uses window functions to calculate min_ts and max_ts for each group, and then filters to only include the first row for each group (ordered by ts).

like image 137
markusk Avatar answered Sep 19 '22 15:09

markusk