Skip to content
Back to skills

Verbose

ASecurity

A window function computes an aggregate or ranking *over a set of rows related to the current row*, without collapsing them the way `GROUP BY` does. The `OVER` clause defines the window; `PARTITION BY` splits rows into independent groups, and `ORDER BY` orders rows within each partition.

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 10, 2026
databasesgosqlexpressperformance

Works with

  • cli

Security analysis

A100/100

Scanned September 10, 2026

npx -y skills add snoodleboot-io/prompticorn --skill verbose --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Verbose?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Verbose
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/snoodleboot-io-verbose-e37b6568/badge)](https://www.skillsdirectory.com/skills/snoodleboot-io-verbose-e37b6568)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# SQL Window Functions And CTEs (Verbose)

## Core Patterns

### Window Functions Keep Every Row

A window function computes an aggregate or ranking *over a set of rows related to
the current row*, without collapsing them the way `GROUP BY` does. The `OVER`
clause defines the window; `PARTITION BY` splits rows into independent groups, and
`ORDER BY` orders rows within each partition.

```sql
SELECT
  region,
  salesperson,
  amount,
  SUM(amount) OVER (PARTITION BY region)                 AS region_total,
  amount * 100.0 / SUM(amount) OVER (PARTITION BY region) AS pct_of_region
FROM sales;
```

Every detail row is preserved, with the region total attached — impossible with a
plain `GROUP BY` without joining the aggregate back.

### Ranking Functions

```sql
SELECT
  category, product, revenue,
  ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) AS rn,
  RANK()       OVER (PARTITION BY category ORDER BY revenue DESC) AS rnk,
  DENSE_RANK() OVER (PARTITION BY category ORDER BY revenue DESC) AS drnk
FROM products;
```

- `ROW_NUMBER` — always unique (1, 2, 3, 4), arbitrary among ties.
- `RANK` — ties share a rank, then a gap (1, 1, 3).
- `DENSE_RANK` — ties share a rank, no gap (1, 1, 2).

Use `ROW_NUMBER` to deduplicate ("keep the newest row per key"); use `RANK` /
`DENSE_RANK` when ties should genuinely tie.

### Top-N Per Group

The canonical use: number rows within each partition, then filter in an outer
query. You cannot filter on a window function in `WHERE` — it is computed after
`WHERE`, so wrap it in a CTE or subquery.

```sql
WITH ranked AS (
  SELECT
    department, employee, salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
  FROM employees
)
SELECT department, employee, salary
FROM ranked
WHERE rn <= 3;               -- top 3 earners per department
```

### LAG And LEAD

`LAG` reads a column from a previous row in the ordered partition; `LEAD` reads
from a following row. They replace awkward self-joins for period-over-period math.

```sql
SELECT
  product_id,
  month,
  revenue,
  LAG(revenue) OVER (PARTITION BY product_id ORDER BY month)             AS prev_month,
  revenue - LAG(revenue) OVER (PARTITION BY product_id ORDER BY month)   AS mom_delta
FROM monthly_revenue;
```

Provide a default for the boundary row: `LAG(revenue, 1, 0)` returns 0 instead of
NULL for the first month.

### Frames: Running Totals And Moving Averages

Within an ordered partition, a frame restricts which rows the aggregate sees. When
you specify `ORDER BY` without an explicit frame, the default is `RANGE BETWEEN
UNBOUNDED PRECEDING AND CURRENT ROW` — often what you want for a running total, but
be explicit to avoid surprises.

```sql
SELECT
  day, amount,
  SUM(amount) OVER w                                      AS running_total,
  AVG(amount) OVER (ORDER BY day ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
                                                          AS avg_7day
FROM ledger
WINDOW w AS (ORDER BY day ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW);
```

`ROWS` counts physical rows; `RANGE` groups peers with equal `ORDER BY` values.
The distinction matters when the ordering column has duplicates. A named `WINDOW`
clause avoids repeating the same specification across several columns.

### CTEs: Naming The Steps

A `WITH` clause (Common Table Expression) names a subquery so a complex query reads
top-to-bottom instead of nesting inside-out. Chain several to build a pipeline.

```sql
WITH paid AS (
  SELECT * FROM invoices WHERE status = 'paid'
),
by_customer AS (
  SELECT customer_id, SUM(total) AS revenue
  FROM paid
  GROUP BY customer_id
)
SELECT c.name, b.revenue
FROM by_customer b
JOIN customers c ON c.id = b.customer_id
ORDER BY b.revenue DESC;
```

CTEs are primarily about readability. On some engines they were once an
optimization fence; on modern PostgreSQL non-recursive CTEs are inlined, so treat
them as a structuring tool rather than a performance lever.

### Recursive CTEs

A recursive CTE has an *anchor* member and a *recursive* member joined by
`UNION ALL`; the recursive part references the CTE itself and runs until it
produces no new rows. It walks hierarchies and generates sequences.

```sql
WITH RECURSIVE subordinates AS (
  SELECT id, name, manager_id, 1 AS level
  FROM employees
  WHERE id = 42                       -- anchor: the starting manager
  UNION ALL
  SELECT e.id, e.name, e.manager_id, s.level + 1
  FROM employees e
  JOIN subordinates s ON e.manager_id = s.id   -- recurse down the tree
)
SELECT * FROM subordinates ORDER BY level;
```

Always ensure the recursion terminates — a cycle in the data (or a missing
stopping condition) produces unbounded rows. Guard with a depth cap or cycle
detection where the graph may contain loops.

### When A Window Beats A Self-Join

"Compare each row to its group's aggregate" and "find the top row per group"
traditionally used correlated subqueries or self-joins that re-scan the table once
per group. A window function does it in a single ordered pass — fewer scans,
clearer intent.

```sql
-- ❌ self-join: scans orders again for every customer
SELECT o.*
FROM orders o
JOIN (
  SELECT customer_id, MAX(created_at) AS latest
  FROM orders GROUP BY customer_id
) m ON m.customer_id = o.customer_id AND m.latest = o.created_at;

-- ✅ window: one pass
SELECT * FROM (
  SELECT o.*,
         ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at DESC) AS rn
  FROM orders o
) t
WHERE rn = 1;
```

## Common Anti-Patterns

❌ **GROUP BY then join back to recover detail**
```sql
SELECT o.*, t.dept_avg
FROM orders o
JOIN (SELECT dept, AVG(amount) AS dept_avg FROM orders GROUP BY dept) t
  ON t.dept = o.dept;
```
✅ **Attach the aggregate with a window**
```sql
SELECT o.*, AVG(amount) OVER (PARTITION BY dept) AS dept_avg FROM orders o;
```

❌ **Filtering on a window function in WHERE**
```sql
SELECT *, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn
FROM players WHERE rn <= 10;         -- error: rn not available in WHERE
```
✅ **Wrap in a CTE or subquery, filter outside**
```sql
WITH r AS (SELECT *, ROW_NUMBER() OVER (ORDER BY score DESC) AS rn FROM players)
SELECT * FROM r WHERE rn <= 10;
```

❌ **ROW_NUMBER when ties should tie**
```sql
ROW_NUMBER() OVER (ORDER BY score DESC)   -- two equal scores get 1 and 2
```
✅ **RANK / DENSE_RANK for shared standings**
```sql
RANK() OVER (ORDER BY score DESC)
```

❌ **Recursive CTE with no termination on cyclic data** — grows without bound.
✅ **Cap depth or detect cycles** (`WHERE level < 100`, or track visited ids).

## Window Functions And CTEs Checklist

- [ ] Window used (not GROUP BY + join back) when detail rows must survive
- [ ] Correct ranking function chosen: ROW_NUMBER unique, RANK/DENSE_RANK for ties
- [ ] Window functions filtered in an outer query/CTE, never in WHERE
- [ ] Frame made explicit (`ROWS` / `RANGE`) for running totals and moving averages
- [ ] LAG/LEAD (with a default) used for row-to-row deltas instead of self-joins
- [ ] CTEs used to structure multi-step queries readably
- [ ] Recursive CTEs have a guaranteed termination / cycle guard
- [ ] Execution tuning (indexes, EXPLAIN, N+1) deferred to the sql-optimization skill

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.