SQL Keywords

SQL Keywords

ROW_NUMBER

Feb 23, 2026

·

5

min read

Category: Window/Analytic Functions

Platform Support:

✅ Snowflake | ✅ BigQuery | ✅ Databricks

Description

ROW_NUMBER assigns a unique sequential integer to rows within a partition of a result set, starting at 1 for the first row in each partition. Unlike RANK and DENSE_RANK, ROW_NUMBER always produces unique values even when there are ties.

Syntax

ROW_NUMBER() OVER (
    [PARTITION BY partition_expression]
    ORDER BY sort_expression [ASC|DESC]
)
ROW_NUMBER() OVER (
    [PARTITION BY partition_expression]
    ORDER BY sort_expression [ASC|DESC]
)
ROW_NUMBER() OVER (
    [PARTITION BY partition_expression]
    ORDER BY sort_expression [ASC|DESC]
)

Platform-Specific Notes

Snowflake:

  • Fully supported

  • Can be used with QUALIFY clause for filtering

  • Deterministic ordering requires unique ORDER BY values

BigQuery:

  • Fully supported

  • Can be used with QUALIFY clause

  • Works efficiently with partitioned tables

Databricks:

  • Fully supported

  • Standard Spark SQL implementation

  • Cannot use QUALIFY (must use subquery)

Example 1: Ranking Sales by Amount

All Platforms:

SELECT 
    employee_name,
    sale_amount,
    sale_date,
    ROW_NUMBER() OVER (ORDER BY sale_amount DESC) as overall_rank
FROM

SELECT 
    employee_name,
    sale_amount,
    sale_date,
    ROW_NUMBER() OVER (ORDER BY sale_amount DESC) as overall_rank
FROM

SELECT 
    employee_name,
    sale_amount,
    sale_date,
    ROW_NUMBER() OVER (ORDER BY sale_amount DESC) as overall_rank
FROM

Sample Data (sales table):

employee_name

sale_amount

sale_date

Alice

5000

2024-01-15

Bob

3500

2024-01-16

Charlie

4200

2024-01-17

Diana

3500

2024-01-18

Eric

6100

2024-01-19

Result:

employee_name

sale_amount

sale_date

overall_rank

Eric

6100

2024-01-19

1

Alice

5000

2024-01-15

2

Charlie

4200

2024-01-17

3

Bob

3500

2024-01-16

4

Diana

3500

2024-01-18

5

Note: Bob and Diana have the same sale_amount but get different row numbers (4 and 5) based on the secondary ordering.

Example 2: Row Number Within Partitions

All Platforms:

SELECT 
    department,
    employee_name,
    salary,
    hire_date,
    ROW_NUMBER() OVER (
        PARTITION BY department 
        ORDER BY salary DESC, hire_date ASC
    ) as dept_rank
FROM

SELECT 
    department,
    employee_name,
    salary,
    hire_date,
    ROW_NUMBER() OVER (
        PARTITION BY department 
        ORDER BY salary DESC, hire_date ASC
    ) as dept_rank
FROM

SELECT 
    department,
    employee_name,
    salary,
    hire_date,
    ROW_NUMBER() OVER (
        PARTITION BY department 
        ORDER BY salary DESC, hire_date ASC
    ) as dept_rank
FROM

Sample Data (employees table):

department

employee_name

salary

hire_date

Sales

Alice

75000

2022-03-15

Sales

Bob

65000

2021-06-10

Sales

Carol

75000

2023-01-20

IT

Diana

90000

2020-08-05

IT

Eric

85000

2021-11-12

IT

Frank

80000

2022-02-28

Marketing

Grace

72000

2021-09-15

Result:

department

employee_name

salary

hire_date

dept_rank

IT

Diana

90000

2020-08-05

1

IT

Eric

85000

2021-11-12

2

IT

Frank

80000

2022-02-28

3

Marketing

Grace

72000

2021-09-15

1

Sales

Alice

75000

2022-03-15

1

Sales

Carol

75000

2023-01-20

2

Sales

Bob

65000

2021-06-10

3

Example 3: Deduplication with QUALIFY (Snowflake/BigQuery)

Snowflake/BigQuery:

-- Keep only the most recent record per customer
SELECT 
    customer_id,
    email,
    updated_at
FROM customer_updates
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) = 1

-- Keep only the most recent record per customer
SELECT 
    customer_id,
    email,
    updated_at
FROM customer_updates
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) = 1

-- Keep only the most recent record per customer
SELECT 
    customer_id,
    email,
    updated_at
FROM customer_updates
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) = 1

Databricks Alternative:

-- Databricks: use subquery
SELECT customer_id, email, updated_at
FROM (
    SELECT 
        customer_id,
        email,
        updated_at,
        ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) as rn
    FROM customer_updates
)
WHERE rn = 1

-- Databricks: use subquery
SELECT customer_id, email, updated_at
FROM (
    SELECT 
        customer_id,
        email,
        updated_at,
        ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) as rn
    FROM customer_updates
)
WHERE rn = 1

-- Databricks: use subquery
SELECT customer_id, email, updated_at
FROM (
    SELECT 
        customer_id,
        email,
        updated_at,
        ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY updated_at DESC) as rn
    FROM customer_updates
)
WHERE rn = 1

Sample Data (customer_updates table):

customer_id

email

updated_at

101

john.old@email.com

2024-01-10

101

john.new@email.com

2024-02-15

102

maria@email.com

2024-01-05

103

sarah.v1@email.com

2024-01-20

103

sarah.v2@email.com

2024-02-01

103

sarah.current@email.com

2024-03-10

Result:

customer_id

email

updated_at

101

john.new@email.com

2024-02-15

102

maria@email.com

2024-01-05

103

sarah.current@email.com

2024-03-10

ROW_NUMBER vs RANK vs DENSE_RANK

Function

Behavior with Ties

Example (values: 100, 95, 95, 90)

ROW_NUMBER

Assigns unique numbers arbitrarily

1, 2, 3, 4

RANK

Skips numbers after ties

1, 2, 2, 4

DENSE_RANK

No gaps in numbering

1, 2, 2, 3

Common Use Cases

  • Deduplication: Remove duplicate records, keeping the most recent

  • Pagination: Implement efficient page-based data retrieval

  • Top N per group: Get top performers in each department/category

  • Sequential numbering: Generate invoice numbers, ticket IDs

  • Time-series analysis: Number events in chronological order

Performance Tips

  • Use specific ORDER BY columns for deterministic results

  • Combine with PARTITION BY to limit the scope of numbering

  • In Snowflake/BigQuery, use QUALIFY instead of subqueries for better performance

  • Index columns used in PARTITION BY and ORDER BY for better performance

Interested to Learn More?
Try Out the Free 14-Days Trial

More Articles

decorative icon

Experience Analytics for the AI-Era

Start your 14-day trial today - it's free and no credit card needed

decorative icon

Experience Analytics for the AI-Era

Start your 14-day trial today - it's free and no credit card needed

decorative icon

Experience Analytics for the AI-Era

Start your 14-day trial today - it's free and no credit card needed

Copyright © 2026 Paradime Labs, Inc.

Made with ❤️ in San Francisco ・ London

*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.

Copyright © 2026 Paradime Labs, Inc.

Made with ❤️ in San Francisco ・ London

*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.

Copyright © 2026 Paradime Labs, Inc.

Made with ❤️ in San Francisco ・ London

*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.