SQL Practice Logo

SQLPractice Online

Window Frames (ROWS vs RANGE): Concept

Module: Window Functions

Think of ROWS and RANGE as two different ways to count items in a line. ROWS is like counting people: "Give me the 3 people in front of me" - you get exactly 3 individuals, regardless of their characteristics. RANGE is like counting by groups: "Give me everyone with the same ticket type as the 3 groups in front of me" - you might get 3 people or 10 people, depending on group sizes. In SQL terms, ROWS counts physical database rows ("exactly 5 rows before this one"), while RANGE groups by ORDER BY values ("all rows with ORDER BY values in this range"). The choice affects both correctness and performance: ROWS is predictable and fast, RANGE handles ties logically but requires more processing.

**

**Understanding the Fundamental Difference:**

ROWS and RANGE represent two completely different approaches to defining window frames:

**ROWS - Physical Row Counting:**

- Counts actual database rows in their physical order

- Ignores the values in ORDER BY columns

- Always gives predictable, exact counts

- Faster performance due to simple row counting

- Best for sequential analysis and moving averages

**RANGE - Logical Value Grouping:**

- Groups rows by ORDER BY column values

- Includes all rows with "tied" (identical) ORDER BY values

- Can include more or fewer rows than specified

- Slower performance due to value comparison logic

- Best for time-based analysis and tie-inclusive calculations

**Demonstrating the Difference with Ties:**

```sql

-- Sample data with duplicate dates (ties)

WITH sales_data AS (

SELECT * FROM VALUES

('2024-01-01'::date, 'Store A', 100),

('2024-01-01'::date, 'Store B', 150), -- Same date (tie)

('2024-01-02'::date, 'Store A', 200),

('2024-01-03'::date, 'Store A', 120),

('2024-01-03'::date, 'Store B', 180), -- Same date (tie)

('2024-01-04'::date, 'Store A', 140)

AS t(sale_date, store, amount)

)

SELECT

sale_date,

store,

amount,

-- ROWS: Counts exactly 2 physical rows before current

STRING_AGG(amount::text, ', ') OVER (

ORDER BY sale_date, store

ROWS BETWEEN 1 PRECEDING AND CURRENT ROW

) as rows_frame_values,

SUM(amount) OVER (

ORDER BY sale_date, store