How to get the first or the last in a group with SQL Server

  • Microsoft Access has 2 summary functions that are very useful:
  1. first()
  2. last()

The first() gives you, the first occurrence of a repeating event, answering the question: since. The last() gives you, the last occurrence of a repeating event, answering the question: up to. Other SQL Servers, like Oracle, provide the the first()/last() summary operations.

It's actually fairly easy to replicate that feature with the dense_rank() function.

Applies to:

  • Microsoft SQL Server 2005
  • Microsoft SQL Server 2008

Data used

  1. USE sql911;
  2. go
  3. SELECT counter,emp_short,trans_date,debit,credit
  4. FROM trans02
  5. ORDER BY emp_short, trans_date DESC;
  6. go
counter     emp_short trans_date              debit                 credit
----------- --------- ----------------------- --------------------- ---------------------
130         000010    2007-07-25 00:00:00.000 33.69                 NULL
120         000010    2007-07-24 00:00:00.000 22.56                 NULL
110         000010    2007-07-23 00:00:00.000 11.00                 NULL
70          000030    2007-07-20 00:00:00.000 NULL                  29.50
60          000030    2007-07-18 00:00:00.000 NULL                  41.00
50          000030    2007-07-16 00:00:00.000 NULL                  22.00
40          000030    2007-07-15 00:00:00.000 357.00                NULL
NULL        000070    2007-10-01 00:00:00.000 52.50                 NULL
NULL        000070    2007-09-27 00:00:00.000 55.00                 NULL
NULL        000070    2007-09-26 00:00:00.000 NULL                  11.22
NULL        000070    2007-09-25 00:00:00.000 NULL                  11.11
100         000110    2007-07-23 00:00:00.000 77.00                 NULL
90          000110    2007-07-22 00:00:00.000 66.00                 NULL
80          000110    2007-07-20 00:00:00.000 42.00                 NULL
20          000110    2007-06-01 00:00:00.000 NULL                  100.00
NULL        000160    2007-10-21 00:00:00.000 NULL                  45.00
NULL        000160    2007-10-20 01:00:00.000 11.50                 NULL
NULL        000160    2007-10-20 00:00:00.000 9.00                  NULL
30          000190    2007-06-02 00:00:00.000 25.00                 NULL
10          000190    2007-06-01 00:00:00.000 NULL                  125.00

(20 row(s) affected)

Getting the last

  1. SELECT counter,emp_short,trans_date,debit,credit,ranking
  2. FROM
  3.  (SELECT counter,emp_short,trans_date,debit,credit,
  4.   dense_rank() OVER (partition BY emp_short
  5.                     ORDER BY emp_short, trans_date DESC) AS ranking
  6.   FROM trans02) AS trans
  7. WHERE ranking = 1;
  8. go
counter     emp_short trans_date              debit                 credit                ranking
----------- --------- ----------------------- --------------------- --------------------- --------------------
130         000010    2007-07-25 00:00:00.000 33.69                 NULL                  1
70          000030    2007-07-20 00:00:00.000 NULL                  29.50                 1
NULL        000070    2007-10-01 00:00:00.000 52.50                 NULL                  1
100         000110    2007-07-23 00:00:00.000 77.00                 NULL                  1
NULL        000160    2007-10-21 00:00:00.000 NULL                  45.00                 1
30          000190    2007-06-02 00:00:00.000 25.00                 NULL                  1

(6 row(s) affected)

Getting the last explanations

  1. SELECT counter,emp_short,trans_date,debit,credit,ranking
  2. FROM
  3.  (SELECT counter,emp_short,trans_date,debit,credit,
  4.   dense_rank() OVER (partition BY emp_short
  5.                     ORDER BY emp_short, trans_date DESC) AS ranking
  6.   FROM trans02) AS trans;
  7. go
  • dense_rank() returns a ranking of rows within the partition without any gaps in the ranking.
  • Since we only want the 1st occurrence, I use the: where ranking = 1;
counter     emp_short trans_date              debit                 credit                ranking
----------- --------- ----------------------- --------------------- --------------------- --------------------
130         000010    2007-07-25 00:00:00.000 33.69                 NULL                  1
120         000010    2007-07-24 00:00:00.000 22.56                 NULL                  2
110         000010    2007-07-23 00:00:00.000 11.00                 NULL                  3
70          000030    2007-07-20 00:00:00.000 NULL                  29.50                 1
60          000030    2007-07-18 00:00:00.000 NULL                  41.00                 2
50          000030    2007-07-16 00:00:00.000 NULL                  22.00                 3
40          000030    2007-07-15 00:00:00.000 357.00                NULL                  4
NULL        000070    2007-10-01 00:00:00.000 52.50                 NULL                  1
NULL        000070    2007-09-27 00:00:00.000 55.00                 NULL                  2
NULL        000070    2007-09-26 00:00:00.000 NULL                  11.22                 3
NULL        000070    2007-09-25 00:00:00.000 NULL                  11.11                 4
100         000110    2007-07-23 00:00:00.000 77.00                 NULL                  1
90          000110    2007-07-22 00:00:00.000 66.00                 NULL                  2
80          000110    2007-07-20 00:00:00.000 42.00                 NULL                  3
20          000110    2007-06-01 00:00:00.000 NULL                  100.00                4
NULL        000160    2007-10-21 00:00:00.000 NULL                  45.00                 1
NULL        000160    2007-10-20 01:00:00.000 11.50                 NULL                  2
NULL        000160    2007-10-20 00:00:00.000 9.00                  NULL                  3
30          000190    2007-06-02 00:00:00.000 25.00                 NULL                  1
10          000190    2007-06-01 00:00:00.000 NULL                  125.00                2

(20 row(s) affected)

Getting the first

  1. SELECT counter,emp_short,trans_date,debit,credit,ranking
  2. FROM
  3.  (SELECT counter,emp_short,trans_date,debit,credit,
  4.   dense_rank() OVER (partition BY emp_short
  5.                     ORDER BY emp_short, trans_date) AS ranking
  6.   FROM trans02) AS trans
  7. WHERE ranking = 1;
  8. go
counter     emp_short trans_date              debit                 credit                ranking
----------- --------- ----------------------- --------------------- --------------------- --------------------
110         000010    2007-07-23 00:00:00.000 11.00                 NULL                  1
40          000030    2007-07-15 00:00:00.000 357.00                NULL                  1
NULL        000070    2007-09-25 00:00:00.000 NULL                  11.11                 1
20          000110    2007-06-01 00:00:00.000 NULL                  100.00                1
NULL        000160    2007-10-20 00:00:00.000 9.00                  NULL                  1
10          000190    2007-06-01 00:00:00.000 NULL                  125.00                1

(6 row(s) affected)
  • Getting the last is identical to getting the first, just the order of the sort, that decides which to use, either the first or the last.