Files
lancedb/docs/web/curation/windowing.mdx
T
Jack Ye 52684d50b9 docs: flip ownership of View/SQL-client/FTS-SQL/AuthZ/AuthN to OSS, restructure SQL Reference
OSS docs now own the full usage content for features that only run against
an Enterprise deployment, since the client interface itself lives here;
Enterprise docs keep only the parts specific to its own behavior. Sophon's
overlay for these five pages is dropped since the OSS versions are now the
complete reference.

SQL Reference is restructured to match an object-type SQL manual (DDL by
object, then DML, then DQL, then Expressions) instead of the old flat,
loosely-ordered page list, absorbing the SQL client-connection guide and
FTS-via-SQL examples into it.
2026-09-28 17:17:09 -05:00

125 lines
3.8 KiB
Plaintext

---
title: "Ranking and deduplication"
sidebarTitle: "Windowing"
description: "Picking one row per thing, ranking within a group, and splitting a dataset."
icon: chart-simple
keywords: ["window", "deduplication", "row_number", "rank", "train test split", "per class"]
---
<Badge color="red">Enterprise</Badge> Window functions are SQL, and
[SQL needs a deployment](/basics/apis).
A window function computes across a set of related rows and returns a value
**per row** — nothing is collapsed. That is what makes it the tool for "which of
these do I keep", where an aggregate would throw away the rows you are choosing
between.
## One row per thing {#dedup}
The most common curation query there is. You have several rows per document —
revisions, re-uploads, repeated annotations — and want one.
```sql
WITH ranked AS (
SELECT *,
row_number() OVER (PARTITION BY doc_id ORDER BY version DESC) AS rn
FROM revisions
)
SELECT * FROM ranked WHERE rn = 1
```
`PARTITION BY` says what "per thing" means; `ORDER BY` says which one wins. The
outer query is not optional — a window function cannot appear in `WHERE`,
because windows are computed after filtering, so the predicate would have
nothing to read.
<Note>
Swap `row_number()` for `rank()` when ties should both survive. `row_number()`
always picks exactly one; `rank()` gives tied rows the same rank, so `rank = 1`
can return several.
</Note>
## A fixed number per class {#per-group}
Class imbalance found by [profiling](/curation/aggregate#balance) is fixed here:
rank within each class, then take the top N.
```sql
WITH ranked AS (
SELECT *, row_number() OVER (PARTITION BY label ORDER BY score DESC) AS rn
FROM images
)
SELECT * FROM ranked WHERE rn <= 1000
```
That gives at most 1,000 rows per label, taking the highest-scoring ones. Order
by something arbitrary but stable if you want an unbiased draw instead, or
combine with [sampling](/curation/sampling) to randomize within each class.
## Splitting a dataset {#splits}
`ntile` divides each partition into equal buckets, which is a train/test split
that stays balanced per class:
```sql
WITH split AS (
SELECT *, ntile(10) OVER (PARTITION BY label ORDER BY id) AS bucket
FROM images
)
SELECT * FROM split WHERE bucket <= 8 -- train
```
Buckets 9 and 10 are your validation and test sets. Because the partition is by
label, each split keeps the class proportions of the whole.
<Warning>
Order by something stable. Ordering by a value that changes — a score you
recompute, a timestamp that gets updated — means the split changes with it, and
a row can move from train to test between runs.
</Warning>
## Comparing a row to its neighbours {#offset}
`lag` and `lead` read the previous and next row in the window, which is how you
find gaps and changes without a self-join:
```sql
SELECT
id,
created_at,
created_at - lag(created_at) OVER (ORDER BY created_at) AS gap
FROM events
```
Useful for spotting ingestion gaps, duplicate timestamps, and runs of identical
values that suggest a broken upstream job.
## Running totals {#running}
Any [aggregate](/curation/aggregate) becomes a window function with `OVER`,
which is how cumulative coverage is expressed:
```sql
SELECT
label,
count(*) AS n,
sum(count(*)) OVER (ORDER BY count(*) DESC) AS cumulative
FROM images
GROUP BY label
```
Reading down the `cumulative` column tells you how many classes cover most of
your data — usually far fewer than you expect.
## Cost {#cost}
Window functions sort within each partition, so on a large table they are among
the more expensive things you can ask for. Filter first, partition on something
selective, and check the plan with
[Explain Analyze](/search/explain).
## Reference {#reference}
The full window syntax, framing, and function list are in
[Window functions](/sql/dql/window).