SQL Keywords

SQL Keywords

RANK / DENSE_RANK / NTILE

Feb 23, 2026

·

5

min read

Category: Window/Analytic Functions

Platform Support:

✅ Snowflake | ✅ BigQuery | ✅ Databricks

Description

Ranking functions assign ranks to rows within a partition. RANK assigns ranks with gaps after ties, DENSE_RANK assigns ranks without gaps, and NTILE distributes rows into a specified number of groups. Essential for top-N queries, percentile analysis, and data segmentation.

Syntax

RANK() OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

DENSE_RANK() OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

NTILE(n) OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)
RANK() OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

DENSE_RANK() OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

NTILE(n) OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)
RANK() OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

DENSE_RANK() OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

NTILE(n) OVER (
    [PARTITION BY partition_column]
    ORDER BY order_column [ASC|DESC]
)

Platform-Specific Notes

All Platforms:

  • Identical syntax and behavior

  • All are standard SQL window functions

  • Require ORDER BY clause

  • Work with PARTITION BY for group-wise ranking

Ranking Functions Comparison

Sample Data:

employee

sales

Alice

100

Bob

95

Carol

95

David

90

Emma

85

Comparison Results:

employee

sales

ROW_NUMBER

RANK

DENSE_RANK

Alice

100

1

1

1

Bob

95

2

2

2

Carol

95

3

2

2

David

90

4

4

3

Emma

85

5

5

4

Key Differences:

  • ROW_NUMBER: Always unique (1,2,3,4,5)

  • RANK: Skips ranks after ties (1,2,2,4,5)

  • DENSE_RANK: No gaps in ranks (1,2,2,3,4)

Example 1: RANK - Top Salespeople by Region

All Platforms:

SELECT 
    region,
    employee_name,
    sales_amount,
    RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) as sales_rank,
    DENSE_RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) as dense_sales_rank
FROM sales_data
ORDER BY region,

SELECT 
    region,
    employee_name,
    sales_amount,
    RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) as sales_rank,
    DENSE_RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) as dense_sales_rank
FROM sales_data
ORDER BY region,

SELECT 
    region,
    employee_name,
    sales_amount,
    RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) as sales_rank,
    DENSE_RANK() OVER (PARTITION BY region ORDER BY sales_amount DESC) as dense_sales_rank
FROM sales_data
ORDER BY region,

Sample Data (sales_data table):

region

employee_name

sales_amount

East

Alice

150000

East

Bob

120000

East

Carol

120000

East

David

95000

West

Emma

180000

West

Frank

165000

West

Grace

140000

Result:

region

employee_name

sales_amount

sales_rank

dense_sales_rank

East

Alice

150000

1

1

East

Bob

120000

2

2

East

Carol

120000

2

2

East

David

95000

4

3

West

Emma

180000

1

1

West

Frank

165000

2

2

West

Grace

140000

3

3

Example 2: Top 3 Products per Category with QUALIFY

Snowflake/BigQuery:

SELECT 
    category,
    product_name,
    revenue,
    RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as revenue_rank
FROM product_sales
QUALIFY RANK() OVER (PARTITION BY category ORDER BY revenue DESC) <= 3
ORDER BY category,

SELECT 
    category,
    product_name,
    revenue,
    RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as revenue_rank
FROM product_sales
QUALIFY RANK() OVER (PARTITION BY category ORDER BY revenue DESC) <= 3
ORDER BY category,

SELECT 
    category,
    product_name,
    revenue,
    RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as revenue_rank
FROM product_sales
QUALIFY RANK() OVER (PARTITION BY category ORDER BY revenue DESC) <= 3
ORDER BY category,

Databricks:

SELECT category, product_name, revenue, revenue_rank
FROM (
    SELECT 
        category,
        product_name,
        revenue,
        RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as revenue_rank
    FROM product_sales
)
WHERE revenue_rank <= 3
ORDER BY category,

SELECT category, product_name, revenue, revenue_rank
FROM (
    SELECT 
        category,
        product_name,
        revenue,
        RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as revenue_rank
    FROM product_sales
)
WHERE revenue_rank <= 3
ORDER BY category,

SELECT category, product_name, revenue, revenue_rank
FROM (
    SELECT 
        category,
        product_name,
        revenue,
        RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as revenue_rank
    FROM product_sales
)
WHERE revenue_rank <= 3
ORDER BY category,

Sample Data (product_sales table):

category

product_name

revenue

Electronics

Laptop

50000

Electronics

Mouse

8000

Electronics

Keyboard

12000

Electronics

Monitor

30000

Furniture

Desk

25000

Furniture

Chair

18000

Furniture

Shelf

8000

Result:

category

product_name

revenue

revenue_rank

Electronics

Laptop

50000

1

Electronics

Monitor

30000

2

Electronics

Keyboard

12000

3

Furniture

Desk

25000

1

Furniture

Chair

18000

2

Furniture

Shelf

8000

3

Example 3: NTILE - Quartile Analysis

All Platforms:

SELECT 
    customer_id,
    customer_name,
    lifetime_value,
    NTILE(4) OVER (ORDER BY lifetime_value DESC) as quartile,
    CASE NTILE(4) OVER (ORDER BY lifetime_value DESC)
        WHEN 1 THEN 'Top 25% (VIP)'
        WHEN 2 THEN 'Upper 25%'
        WHEN 3 THEN 'Lower 25%'
        WHEN 4 THEN 'Bottom 25%'
    END as customer_segment
FROM customers
ORDER BY lifetime_value DESC

SELECT 
    customer_id,
    customer_name,
    lifetime_value,
    NTILE(4) OVER (ORDER BY lifetime_value DESC) as quartile,
    CASE NTILE(4) OVER (ORDER BY lifetime_value DESC)
        WHEN 1 THEN 'Top 25% (VIP)'
        WHEN 2 THEN 'Upper 25%'
        WHEN 3 THEN 'Lower 25%'
        WHEN 4 THEN 'Bottom 25%'
    END as customer_segment
FROM customers
ORDER BY lifetime_value DESC

SELECT 
    customer_id,
    customer_name,
    lifetime_value,
    NTILE(4) OVER (ORDER BY lifetime_value DESC) as quartile,
    CASE NTILE(4) OVER (ORDER BY lifetime_value DESC)
        WHEN 1 THEN 'Top 25% (VIP)'
        WHEN 2 THEN 'Upper 25%'
        WHEN 3 THEN 'Lower 25%'
        WHEN 4 THEN 'Bottom 25%'
    END as customer_segment
FROM customers
ORDER BY lifetime_value DESC

Sample Data (customers table):

customer_id

customer_name

lifetime_value

1

Alice

5000

2

Bob

4500

3

Carol

4000

4

David

3500

5

Emma

3000

6

Frank

2500

7

Grace

2000

8

Henry

1500

Result:

customer_id

customer_name

lifetime_value

quartile

customer_segment

1

Alice

5000

1

Top 25% (VIP)

2

Bob

4500

1

Top 25% (VIP)

3

Carol

4000

2

Upper 25%

4

David

3500

2

Upper 25%

5

Emma

3000

3

Lower 25%

6

Frank

2500

3

Lower 25%

7

Grace

2000

4

Bottom 25%

8

Henry

1500

4

Bottom 25%

Example 4: Multiple Rankings with Different Criteria

All Platforms:

SELECT 
    employee_id,
    employee_name,
    sales_amount,
    customer_satisfaction,
    -- Rank by sales
    RANK() OVER (ORDER BY sales_amount DESC) as sales_rank,
    -- Rank by satisfaction
    RANK() OVER (ORDER BY customer_satisfaction DESC) as satisfaction_rank,
    -- Combined score rank
    RANK() OVER (ORDER BY (sales_amount * 0.6 + customer_satisfaction * 0.4) DESC) as combined_rank,
    -- Decile grouping
    NTILE(10) OVER (ORDER BY sales_amount DESC) as sales_decile
FROM employee_performance
ORDER BY

SELECT 
    employee_id,
    employee_name,
    sales_amount,
    customer_satisfaction,
    -- Rank by sales
    RANK() OVER (ORDER BY sales_amount DESC) as sales_rank,
    -- Rank by satisfaction
    RANK() OVER (ORDER BY customer_satisfaction DESC) as satisfaction_rank,
    -- Combined score rank
    RANK() OVER (ORDER BY (sales_amount * 0.6 + customer_satisfaction * 0.4) DESC) as combined_rank,
    -- Decile grouping
    NTILE(10) OVER (ORDER BY sales_amount DESC) as sales_decile
FROM employee_performance
ORDER BY

SELECT 
    employee_id,
    employee_name,
    sales_amount,
    customer_satisfaction,
    -- Rank by sales
    RANK() OVER (ORDER BY sales_amount DESC) as sales_rank,
    -- Rank by satisfaction
    RANK() OVER (ORDER BY customer_satisfaction DESC) as satisfaction_rank,
    -- Combined score rank
    RANK() OVER (ORDER BY (sales_amount * 0.6 + customer_satisfaction * 0.4) DESC) as combined_rank,
    -- Decile grouping
    NTILE(10) OVER (ORDER BY sales_amount DESC) as sales_decile
FROM employee_performance
ORDER BY

Sample Data (employee_performance table):

employee_id

employee_name

sales_amount

customer_satisfaction

1

Alice

150000

4.8

2

Bob

120000

4.9

3

Carol

180000

4.5

4

David

95000

4.7

Result:

employee_id

employee_name

sales_amount

customer_satisfaction

sales_rank

satisfaction_rank

combined_rank

sales_decile

3

Carol

180000

4.5

1

4

1

1

1

Alice

150000

4.8

2

2

2

1

2

Bob

120000

4.9

3

1

3

2

4

David

95000

4.7

4

3

4

2

Example 5: Percentile-Based Segmentation

All Platforms:

WITH customer_segments AS (
    SELECT 
        customer_id,
        order_count,
        total_revenue,
        NTILE(100) OVER (ORDER BY total_revenue DESC) as percentile
    FROM customer_metrics
)
SELECT 
    CASE 
        WHEN percentile <= 10 THEN 'Top 10%'
        WHEN percentile <= 25 THEN 'Top 25%'
        WHEN percentile <= 50 THEN 'Top 50%'
        ELSE 'Bottom 50%'
    END as segment,
    COUNT(*) as customer_count,
    AVG(total_revenue) as avg_revenue,
    MIN(total_revenue) as min_revenue,
    MAX(total_revenue) as max_revenue
FROM customer_segments
GROUP BY 
    CASE 
        WHEN percentile <= 10 THEN 'Top 10%'
        WHEN percentile <= 25 THEN 'Top 25%'
        WHEN percentile <= 50 THEN 'Top 50%'
        ELSE 'Bottom 50%'
    END
ORDER BY MIN(total_revenue) DESC

WITH customer_segments AS (
    SELECT 
        customer_id,
        order_count,
        total_revenue,
        NTILE(100) OVER (ORDER BY total_revenue DESC) as percentile
    FROM customer_metrics
)
SELECT 
    CASE 
        WHEN percentile <= 10 THEN 'Top 10%'
        WHEN percentile <= 25 THEN 'Top 25%'
        WHEN percentile <= 50 THEN 'Top 50%'
        ELSE 'Bottom 50%'
    END as segment,
    COUNT(*) as customer_count,
    AVG(total_revenue) as avg_revenue,
    MIN(total_revenue) as min_revenue,
    MAX(total_revenue) as max_revenue
FROM customer_segments
GROUP BY 
    CASE 
        WHEN percentile <= 10 THEN 'Top 10%'
        WHEN percentile <= 25 THEN 'Top 25%'
        WHEN percentile <= 50 THEN 'Top 50%'
        ELSE 'Bottom 50%'
    END
ORDER BY MIN(total_revenue) DESC

WITH customer_segments AS (
    SELECT 
        customer_id,
        order_count,
        total_revenue,
        NTILE(100) OVER (ORDER BY total_revenue DESC) as percentile
    FROM customer_metrics
)
SELECT 
    CASE 
        WHEN percentile <= 10 THEN 'Top 10%'
        WHEN percentile <= 25 THEN 'Top 25%'
        WHEN percentile <= 50 THEN 'Top 50%'
        ELSE 'Bottom 50%'
    END as segment,
    COUNT(*) as customer_count,
    AVG(total_revenue) as avg_revenue,
    MIN(total_revenue) as min_revenue,
    MAX(total_revenue) as max_revenue
FROM customer_segments
GROUP BY 
    CASE 
        WHEN percentile <= 10 THEN 'Top 10%'
        WHEN percentile <= 25 THEN 'Top 25%'
        WHEN percentile <= 50 THEN 'Top 50%'
        ELSE 'Bottom 50%'
    END
ORDER BY MIN(total_revenue) DESC

Result:

segment

customer_count

avg_revenue

min_revenue

max_revenue

Top 10%

100

15250.00

10000.00

25000.00

Top 25%

150

7200.00

5000.00

9999.00

Top 50%

250

3500.00

2000.00

4999.00

Bottom 50%

500

850.00

100.00

1999.00

Example 6: Year-over-Year Ranking Comparison

All Platforms:

SELECT 
    product_id,
    product_name,
    year,
    annual_sales,
    RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC) as yearly_rank,
    LAG(RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC)) 
        OVER (PARTITION BY product_id ORDER BY year) as previous_year_rank,
    RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC) -
    LAG(RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC)) 
        OVER (PARTITION BY product_id ORDER BY year) as rank_change
FROM product_annual_sales
ORDER BY year DESC,

SELECT 
    product_id,
    product_name,
    year,
    annual_sales,
    RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC) as yearly_rank,
    LAG(RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC)) 
        OVER (PARTITION BY product_id ORDER BY year) as previous_year_rank,
    RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC) -
    LAG(RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC)) 
        OVER (PARTITION BY product_id ORDER BY year) as rank_change
FROM product_annual_sales
ORDER BY year DESC,

SELECT 
    product_id,
    product_name,
    year,
    annual_sales,
    RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC) as yearly_rank,
    LAG(RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC)) 
        OVER (PARTITION BY product_id ORDER BY year) as previous_year_rank,
    RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC) -
    LAG(RANK() OVER (PARTITION BY year ORDER BY annual_sales DESC)) 
        OVER (PARTITION BY product_id ORDER BY year) as rank_change
FROM product_annual_sales
ORDER BY year DESC,

Sample Result:

product_id

product_name

year

annual_sales

yearly_rank

previous_year_rank

rank_change

1

Laptop

2024

500000

1

3

-2

3

Monitor

2024

450000

2

1

1

2

Keyboard

2024

400000

3

2

1

When to Use Each Function

Function

Use When

Example

RANK

Need standard competition ranking

Olympics medals (ties share rank, next rank skipped)

DENSE_RANK

Need continuous ranking

Grade levels (A, B, C with no gaps)

ROW_NUMBER

Need unique row numbers

Pagination, deduplication

NTILE

Need equal-sized groups

Quartiles, deciles, percentiles

Common Use Cases

  1. Top N analysis: Best sellers, top performers

  2. Leaderboards: Gaming scores, sales rankings

  3. Customer segmentation: VIP, premium, standard tiers

  4. Performance reviews: Employee rankings

  5. ABC analysis: Inventory categorization

  6. Percentile analysis: Test scores, benchmarking

Performance Tips

  1. Use appropriate function: RANK faster than DENSE_RANK for large datasets

  2. Limit partitions: Fewer partitions = better performance

  3. Index ORDER BY columns: Improves sorting performance

  4. Use QUALIFY: More efficient than subquery in Snowflake/BigQuery

  5. Filter early: Apply WHERE before window functions

Best Practices

  1. Choose right function: Understand tie-handling needs

  2. Define clear ORDER BY: Ensure deterministic rankings

  3. Use PARTITION BY wisely: Group rankings appropriately

  4. Handle ties explicitly: Document how ties are handled

  5. Combine with filters: Use QUALIFY or WHERE for top-N

  6. Test edge cases: Verify behavior with ties and NULLs

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.