SQL SERVER – How to Retrieve TOP and BOTTOM Rows Together using T-SQL – Part 2 – CTE

A CTE can help retrieve the first and last rows together. The important detail is the ordering. Each endpoint needs its own ordered query before the two results are combined.

Gouache painting of two groups of record cards connected by a stone bridge

This continues the original TOP and BOTTOM rows question. The question came from an inventory project where I needed both endpoints. Thanks to Khadar Khan for contributing a CTE approach to the discussion.

Keep each ORDER BY inside its own CTE

The earlier example placed two ORDER BY clauses around UNION ALL inside one CTE. That form is invalid. The corrected example below uses two CTEs, one for each endpoint.

DECLARE @Items TABLE (ItemId int PRIMARY KEY, ItemName varchar(30));
INSERT INTO @Items (ItemId, ItemName)
VALUES (30, 'Notebook'), (10, 'Pencil'), (20, 'Folder');

;WITH FirstItem AS
(
    SELECT TOP (1) ItemId, ItemName
    FROM @Items
    ORDER BY ItemId ASC
),
LastItem AS
(
    SELECT TOP (1) ItemId, ItemName
    FROM @Items
    ORDER BY ItemId DESC
)
SELECT 'First' AS Boundary, ItemId, ItemName FROM FirstItem
UNION ALL
SELECT 'Last', ItemId, ItemName FROM LastItem
ORDER BY ItemId, Boundary;

The result contains item 10, Pencil, as First and item 30, Notebook, as Last. The insertion order does not decide either endpoint. The ItemId ordering does.

Decide what first and last mean

A table has no inherent first or last row. Choose the column that expresses your requirement. If you sort by a nonunique date, add a unique key to break ties consistently.

The ORDER BY inside each CTE controls which row TOP selects. The final ORDER BY controls how the combined result appears. Those are separate decisions.

Watch the small-table cases

An empty table returns no rows. A table with one row returns it twice, once for each boundary. That is intentional here because the output labels both endpoints.

If you need unique rows instead, remove the Boundary labels and combine the endpoint columns with UNION. A CTE names a query result for one statement. It doesn’t create a stored temporary table or guarantee a faster plan.

Have you used another approach for this requirement? The comments on the original question include several useful alternatives.

The first row is not the first one inserted, it is the first one in your chosen order.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

CTE, SQL Function, SQL Joins, SQL Scripts, SQL Server
Previous Post
SQL SERVER – Find Nth Highest Record from Database Table
Next Post
SQL SERVER – FIX : Error : 3702 Cannot drop database because it is currently in use – Part 2

Related Posts

7 Comments. Leave new

  • Thanks for sharing this.
    I was used to do the same as follows:

    SELECT * FROM
    (
    (SELECT MIN(SalesOrderDetailID) AS ID
    FROM Sales.SalesOrderDetail
    )
    UNION
    (SELECT MAX(SalesOrderDetailID) AS ID
    FROM Sales.SalesOrderDetail
    )
    ) AS t_TopBottomRow;

    Reply
  • Hi ,
    I have one query. How to add two row values togather in one table.

    Reply
  • Min Max solution would not work for retrieving more than top one or bottom row. The original solution however will be able to retrieve the top 10 as well as bottom 10.

    Deep…. There can be multiple scenarios for this. If you want to add values from existing table then you will have to write a statement like….

    INSERT INTO TABLE_NAME SELECT EXISTING_ROWS FROM ANOTHER_TABLE/TABLE_NAME

    To insert static values, you could do something like this….

    INSERT INTO TABLE_NAME
    SELECT 1,2,3,4,5
    UNION
    SELECT 6,7,8,9,10

    Reply
  • how to retrieve a particular column.

    Reply
  • This is an very helpful solution however, can you show how we can do this with a Group By in the top and bottom queries. It appears I cannot use an OrderBy with a group by. .

    WITH TopBottomRow
    AS
    (
    SELECT TOP 1 SalesOrderDetailID, Sum (Sales)
    FROM Sales.SalesOrderDetail
    Group By SalesOrder
    ORDER BY SalesOrderDetailID
    UNION ALL
    SELECT TOP 1 SalesOrderDetailID, Sum(Sales)
    FROM Sales.SalesOrderDetail
    Group By SalesOrder
    ORDER BY SalesOrderDetailID DESC
    )
    SELECT *
    FROM TopBottomRow

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.