SQLServerCentral Article

SQL Window Functions Series: NTILE()

,

Continuing our journey through SQL's handy window functions, we now tackle the NTILE() function. After exploring LAG(), LEAD(), RANK(), and DENSE_RANK(), NTILE() stands out for splitting data into clear, ordered groups. It's perfect for grading performance, figuring out percentiles, and organizing data.

Introduction to NTILE()

NTILE() is a window function that allows you to divide an ordered result set into a specified number of approximately equal parts. It's an excellent tool for ranking items into buckets or quantiles, such as quartiles, quintiles, deciles, or any other 'tile' you need.

Imagine you have a list of employees with their sales figures for the quarter, and you want to divide them into four groups (quartiles) based on their performance. NTILE() helps you do exactly that, with ease and efficiency.

Syntax of NTILE()

The basic syntax of the NTILE() function is straightforward:

NTILE(number_of_tiles) OVER (
[PARTITION BY partition_expression, ... ]
ORDER BY sort_expression [ASC|DESC], ...
)
  • number_of_tiles specifies the number of groups into which the result set should be divided.
  • PARTITION BY clause is optional and divides the result set into partitions to which NTILE() is applied.
  • ORDER BY clause determines the order in which the NTILE() function assigns the tile numbers.

Working Example

Imagine we have a Sales dataset from a company's quarterly performance. We want to categorize our salespeople into quartiles based on their sales figures to quickly identify top performers and those who may need more support.

| EmployeeID | SalesAmount |
|------------|-------------|
| 1          | 15000       |
| 2          | 20000       |
| 3          | 10000       |
| 4          | 30000       |
| 5          | 25000       |
| 6          | 5000        |

We'll use the NTILE() function to divide the salespeople into four groups (quartiles), from the highest to the lowest sales figures.

SELECT 
  EmployeeID,
  SalesAmount,
  NTILE(4) OVER (ORDER BY SalesAmount DESC) AS Quartile
FROM Sales;

Breaking Down the Query

  • NTILE(4): This tells SQL Server to divide the data into four equal groups.
  • OVER: This clause specifies how to partition the rows of the query's result set.
  • (ORDER BY SalesAmount DESC): This orders the rows in descending order based on the SalesAmount before applying the NTILE function.

The result set will look like this:

| EmployeeID | SalesAmount | Quartile |
|------------|-------------|----------|
| 4          | 30000       | 1        |
| 5          | 25000       | 1        |
| 2          | 20000       | 2        |
| 1          | 15000       | 2        |
| 3          | 10000       | 3        |
| 6          | 5000        | 4        |

Each row now includes a Quartile number, from 1 to 4. The Quartile number indicates which quartile each sales amount falls into:

  • Quartile 1 contains the top 25% of sales amounts.
  • Quartile 2 contains the next 25%.
  • So on, until Quartile 4, which contains the lowest 25%.

"Note that if the number of rows in the result set is not evenly divisible by the number of tiles, NTILE() will distribute the extra rows starting from the first tile."

In our example, there are 6 rows and 4 quartiles. Thus, the first two quartiles have 2 members each, and the next two have 1 member each, ensuring all quartiles are as evenly distributed as possible.

Advanced Use Case: NTILE() for Departmental Performance Analysis

Let's explore an advanced scenario where we want to evaluate sales performance within multiple departments of an organization. We have a more complex dataset that includes a Department column:

Our Sales table now has the following structure, where every sales employees is categorized into two departments: Electronics and Cosmetics.

| EmployeeID | Department | SalesAmount |
|------------|------------|-------------|
| 1          | Electronics|   15000     |
| 2          | Electronics|   20000     |
| 3          | Cosmetics  |   10000     |
| 4          | Cosmetics  |   30000     |
| 5          | Electronics|   25000     |
| 6          | Cosmetics  |   5000      |
| 7          | Electronics|   8000      |
| 8          | Cosmetics  |   12000     |

To evaluate the performance within each department, we can modify our NTILE() function to use the PARTITION BY clause:

SELECT 
  EmployeeID,
  Department,
  SalesAmount,
  NTILE(4) OVER (PARTITION BY Department ORDER BY SalesAmount DESC) AS Quartile
FROM Sales;

Breaking Down the Query

  • PARTITION BY Department: This clause divides the result set into partitions based on the Department column. NTILE() will be applied separately to each partition.
  • The rest of the query remains the same, ordering the data within each partition by SalesAmount in descending order and dividing each partition into quartiles.

Executing the above query, we would obtain:

| EmployeeID | Department   | SalesAmount | Quartile |
|------------|--------------|-------------|----------|
|    5       | Electronics  | 25000       | 1        |
|    2       | Electronics  | 20000       | 1        |
|    1       | Electronics  | 15000       | 2        |
|    7       | Electronics  | 8000        | 3        |
|    4       | Cosmetics    | 30000       | 1        |
|    8       | Cosmetics    | 12000       | 2        |
|    3       | Cosmetics    | 10000       | 3        |
|    6       | Cosmetics    | 5000        | 4        |

The Quartile column now shows the ranking of each employee within their respective department. Employees in the Electronics department have been divided into quartiles as have those in the Cosmetics department, but independently of each other.

  • In Electronics, Employees 5 and 2 are top performers (Quartile 1), while Employee 7 needs the most improvement (Quartile 3).
  • In Cosmetics, Employee 4 stands out in Quartile 1, whereas Employee 6 is in the bottom quartile (Quartile 4).

Integrating NTILE with RANK(), DENSE_RANK(), LAG(), and LEAD()

In our past articles, we looked at how SQL's RANK(), DENSE_RANK() , LAG(), and LEAD() help us understand our data. Now, let's use these tools together with NTILE() in a new example to dig deeper and find even more useful information.

Imagine we're analyzing a dataset from a multinational company to identify the sales trends and performance of employees across different regions. We want to create a performance report that not only categorizes sales employees into quartiles within their regions but also shows their relative ranks and provides insights into sales trends. Our Sales table has been expanded to include Region and Quarter:

| EmployeeID | Region | Quarter | SalesAmount |
|------------|--------|---------|-------------|
|      1     |  North |   Q1    |    15000    |
|      2     |  North |   Q2    |    20000    |
|      3     |  South |   Q1    |    10000    |
|      4     |  South |   Q2    |    30000    |
|      5     |  North |   Q1    |    25000    |
|      6     |  South |   Q1    |    5000     |
|      7     |  North |   Q2    |    8000     |
|      8     |  South |   Q2    |    12000    |

The SQL Query Integrating NTILE with Other Window Functions

WITH RankedSales AS (
  SELECT 
    EmployeeID,
    Region,
    Quarter,
    SalesAmount,
    NTILE(4) OVER (PARTITION BY Region ORDER BY SalesAmount DESC) AS Quartile,
    RANK() OVER (PARTITION BY Region ORDER BY SalesAmount DESC) AS Rank,
    DENSE_RANK() OVER (PARTITION BY Region ORDER BY SalesAmount DESC) AS DenseRank,
    LAG(SalesAmount, 1) OVER (PARTITION BY EmployeeID ORDER BY Quarter) AS PrevQuarterSales,
    LEAD(SalesAmount, 1) OVER (PARTITION BY EmployeeID ORDER BY Quarter) AS NextQuarterSales
FROM Sales
)
SELECT * FROM RankedSales;

Understanding the Query

  • WITH RankedSales AS (...): This Common Table Expression (CTE) allows us to create a temporary result set that we can then reference in our main SELECT statement.
  • NTILE(4): Divides the data into four groups within each region.
  • RANK(): Assigns a unique rank within each region, with gaps for ties.
  • DENSE_RANK(): Similar to RANK(), but without gaps in the ranking sequence.
  • LAG(...): Provides the sales amount from the previous quarter for the same employee.
  • LEAD(...): Provides the sales amount for the next quarter for the same employee.
| EmployeeID | Region | Quarter | SalesAmount | Quartile | Rank | DenseRank | PrevQuarterSales | NextQuarterSales |
|------------|--------|---------|-------------|----------|------|-----------|------------------|------------------|
| 5          | North  | Q1      | 25000       | 1        | 1    | 1         | NULL             | 15000            |
| 2          | North  | Q2      | 20000       | 2        | 2    | 2         | NULL             | 8000             |
| 1          | North  | Q1      | 15000       | 3        | 3    | 3         | NULL             | NULL             |
| 7          | North  | Q2      | 8000        | 4        | 4    | 4         | NULL             | NULL             |
| 4          | South  | Q2      | 30000       | 1        | 1    | 1         | 10000            | NULL             |
| 8          | South  | Q2      | 12000       | 2        | 2    | 2         | NULL             | NULL             |
| 3          | South  | Q1      | 10000       | 3        | 3    | 3         | NULL             | 30000            |
| 6          | South  | Q1      | 5000        | 4        | 4    | 4         | NULL             | NULL             |

 

  • Employees are now ranked and quartiled within their regions, providing a localized view of performance.
  • RANK and DENSE_RANK show their relative positions, revealing how competitive each quartile is.
  • LAG and LEAD offer a glimpse into sales dynamics, showing how each employee's performance is changing over time.

Common Pitfalls and Solutions

  • Choosing Wrong Number of Buckets with NTILE - It's common to pick a number for NTILE that doesn't fit well with your data, and this can make your bucket sizes uneven. So, first, look at your data to decide the right number of buckets for useful results. Then, try out several numbers to see how they split up your data.
  • Misreading NTILE's Output - People often think NTILE splits data into equal buckets, but if your data doesn't split evenly, some buckets will be bigger. Remember, the last few buckets might be smaller. Write this down to make sure everyone using your data knows what to expect.
  • Overlooking NULL Values - NTILE might not sort NULL values the way you think, causing mistakes in your data groups. Make sure to deal with NULLs. You can use COALESCE to replace them or remove them before you use NTILE.

Performance Considerations

Using NTILE on large datasets requires careful consideration, as the function necessitates sorting data, which can be computationally expensive.

  • Proper Indexing: Create indexes on columns used in ORDER BY clauses within NTILE's OVER() partition.
  • Window Function Optimization: Combine window functions under the same OVER() clause when possible.
  • Limit the Scope: Apply filters to narrow down the result set before partitioning and ranking.

Summing Up

We've explored SQL window functions and seen how handy they are, especially with the NTILE function, for sorting data into groups, checking ranks, and spotting trends with great accuracy.

Next up, get ready!

We'll dive into SQL's CTEs (Common Table Expressions) next. They make complicated queries simpler, clearer, and more organized.

Call to Action to the Reader

  • Test yourself by using the NTILE function on your data. Try different numbers of groups and see how your data sorts itself out.
  • Then, go back to the articles on RANK(), DENSE_RANK(), LAG(), and LEAD() to brush up on SQL window functions.
  • Found a smart way to use NTILE or something unexpected? Tell us about it! Join in the conversation online, post in forums, or write your own piece.
  • Keep an eye out for our next piece on Common Table Expressions (CTEs) in SQL. We’ll break down how they work and show how they simplify complex queries.

Rate

4.67 (3)

You rated this post out of 5. Change rating

Share

Share

Rate

4.67 (3)

You rated this post out of 5. Change rating