Relix

Language reference

Limit (λ / LIMIT)

Syntax

λ <count> (Relation)             -- first N rows
λ <offset>, <count> (Relation)   -- skip offset rows, then take count
λ <count> WITH TIES (τ ... (Relation))   -- and every row tied with the last
LIMIT <count> OFFSET <offset> (Relation)                       -- SQL's order
OFFSET <offset> ROWS [FETCH FIRST|NEXT [<count>] ROW|ROWS ONLY|WITH TIES] (Relation)
FETCH FIRST|NEXT [<count>] ROW|ROWS ONLY|WITH TIES (Relation)
LIMIT ...

λ 10 (Orders)
λ 20, 10 (Orders)                -- skip 20, take 10 (pagination)
LIMIT 10 OFFSET 20 (Orders)      -- the same page
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY (Orders)   -- the same page

Description

Limit restricts how many rows come out. The one-number form keeps the first N rows; the two-number form skips some rows first and then keeps the next batch — exactly what you need for paging through results a screenful at a time.

Limit is almost always paired with SORT so that "first N" means something predictable (e.g. the 10 largest, the 5 most recent).

WITH TIES keeps going past the Nth row while the rows that follow tie with it on every sort key, so that two rows the order cannot tell apart are kept or dropped together: the top 3 scores with a tie for third is four rows.

SQL's two paging spellings are written as SQL writes them, and are the same λ: LIMIT 10 OFFSET 20, and the standard's OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY.

Technical Description

λ applies an optional offset and a row cap to its input. It is a streaming operator that stops pulling once the cap is reached — which is what makes it safe over an unbounded generator (it bounds the stream). The optimizer pushes λ below projections (LIM-001) and folds it into SQL as LIMIT/OFFSET (dialect-aware) or Mongo as $skip/$limit.

λ n WITH TIES takes a τ as its input, since the sort keys are what decide a tie, and the pair is the global TOP n WITH TIES with those keys: the offset is skipped, n rows are taken, and then every further row that compares equal to the last one taken on all the keys. Equal means equal to the sort, so two NULLs tie and 5 ties with 5.0. It folds into SQL as FETCH FIRST n ROWS WITH TIES on PostgreSQL (13 or later) and runs in the engine elsewhere.

LIMIT n OFFSET m and OFFSET m ROWS FETCH NEXT n ROWS ONLY parse to λ m, n and print back that way. In the standard's form ROW and ROWS are the same word, as are FIRST and NEXT; ROWS after OFFSET m may be left out. A FETCH with no count fetches one row, a FETCH with no OFFSET skips none, and an OFFSET with no FETCH keeps every row after the offset. FETCH … WITH TIES is λ n WITH TIES, and takes a τ as its input in the same way.

Examples

First 10 orders:

λ 10 (Orders)

Page 3 of results, 10 per page (skip 20, take 10):

λ 20, 10 (Orders)

The same page, in SQL's order:

LIMIT 10 OFFSET 20 (Orders)

The same page, in the standard's spelling:

OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY (Orders)

Every order after the first 20:

OFFSET 20 ROWS (Orders)

Top 10 salaries (sort then limit):

λ 10 (τ salary DESC (Employees))

Take a finite prefix of an infinite generator — first 100 prime numbers:

λ 100 (Primes)

The 3 highest scores, and anyone tied with the third:

λ 3 WITH TIES (τ score DESC (Results))

Worked Example

Four rows of sales, two of them with no bonus recorded — the NULLs are there deliberately, because they are what makes the rules below visible.

Query
Sales := [
| region | rep  | amount | bonus |
|--------|------|--------|-------|
| east   | Ada  | 120    | 10    |
| east   | Bo   | 80     | NULL  |
| west   | Cy   | 200    | 25    |
| west   | Dee  | 50     | NULL  |
];

query { λ 2 (Sales) };
Result
 region  rep  amount  bonus
 ──────  ───  ──────  ─────
 east    Ada     120     10
 east    Bo       80  NULL
(2 rows)
What the engine did

Data flow

Sales
regionrepamountbonus
eastAda12010
eastBo80NULL
westCy20025
westDee50NULL
Result
regionrepamountbonus
eastAda12010
eastBo80NULL

Rewrites applied

None — the optimiser found nothing to improve.

Physical plan

Limit 2  ~2 rows
└─ Scan Sales  ~4 rows

The first two rows — and "first" is only meaningful if the input has an order. Without a τ beneath it, a limit takes whatever the source happens to deliver, which for a database table is not guaranteed to be stable between runs. τ amount DESC (Sales) then λ 2 is the "top 2" idiom; for top-N per group reach for TOP … PER instead. Writing the idiom costs nothing: the optimizer fuses λ over τ into a bounded top-N rather than sorting the whole input (LIM-003, see the optimizer page).

With WITH TIES, a cut that falls between two rows of equal value takes both. The two sales with no bonus tie with each other:

Query
query { λ 1 WITH TIES (τ bonus DESC (Sales)) };
Result
 region  rep  amount  bonus
 ──────  ───  ──────  ─────
 east    Bo       80  NULL
 west    Dee      50  NULL
(2 rows)
What the engine did

Data flow

Sales
regionrepamountbonus
eastAda12010
eastBo80NULL
westCy20025
westDee50NULL
Result
regionrepamountbonus
eastBo80NULL
westDee50NULL

Rewrites applied

None — the optimiser found nothing to improve.

Physical plan

TOP 1 WITH TIES  ~1 rows
└─ Scan Sales  ~4 rows

Limitations

Without a SORT below it, "first N" is whatever order the source happens to deliver — not guaranteed. Limit caps rows but does not sample randomly; for that use SAMPLE.

The count and the offset are whole numbers. A fraction, or a number too large for a 64-bit integer, is a parse error:

query { λ 2.5 (Orders) };

A FETCH ends with ONLY or WITH TIES:

query { FETCH FIRST 10 ROWS (Orders) };

WITH TIES needs a sorted input, since without one nothing ties:

query { λ 3 WITH TIES (Orders) };

Alternatives

For a random subset use SAMPLE p (probabilistic) or SAMPLE n ROWS (exact count). For "N per group" rather than N overall use TOP … PER.

SQL names

SQL's LIMIT n OFFSET m and OFFSET m ROWS FETCH NEXT n ROWS ONLY are written as they are, and MySQL's LIMIT m, n is λ m, n, the offset first. SQL's LIMIT n, TOP n, FETCH FIRST n ROWS ONLY and ROWNUM <= n are LIMIT n (R). Its ORDER BY is a sort below it: ORDER BY amount DESC LIMIT 10 is LIMIT 10 (SORT amount DESC (R)), and FETCH FIRST n ROWS WITH TIES is FETCH FIRST n ROWS WITH TIES (SORT … (R)). Relix's TOP is the top rows per group, SQL's ROW_NUMBER() OVER (PARTITION BY …) filtered to <= n; ORDER BY RANDOM() LIMIT n is SAMPLE n ROWS.

See Also

sort, top, sample-bernoulli, sample-reservoir

Notes

λ is the canonical "bound" that rescues an otherwise-blocking query over an unbounded source: γ count (λ 1000 (Primes)) is allowed, γ count (Primes) is a plan-time error.