docs: complete SQL reference, enrich Functions drafts, rename OSS to Embedded (#4732)

## Summary

- Complete the SQL reference: `ATTACH DATABASE`, the `CHANGES` clause,
and
`dedupe()`/`duplicate_pairs()` (verified against current sophon source;
these were recently renamed from
`vector_dedup`/`vector_duplicate_pairs`).
  Fix the blob-column default-projection claim and add the previously
  undocumented `blob_descriptor()`.
- Enrich the gated Functions draft (still `.mintignore`'d, pre-release):
  chained Functions over blob outputs, GPU-produced blobs, Secrets,
  multi-input/output binding, `code=[module]` packaging.
- Upgrade the materialized-view and computed-column auto-refresh drafts
with
  precise, verified catch-up semantics, and add the missing `ALTER
  MATERIALIZED VIEW` / `CREATE ... WITH(...)` sections (also gated — the
  automatic-refresh scheduler is still dev/staging-only).
- Thread one running example (video frames → GPU extract/caption → dedup
→
auto-refreshing materialized view) through Functions, materialized
views,
and the LeRobotDataset integration guide instead of a separate tutorial.
- Rename "LanceDB OSS"/"OSS" to "LanceDB Embedded"/"Embedded" throughout
  (52 files); activate the existing availability-comparison mechanism on
  `basics/offerings.mdx` for a full feature-by-feature comparison table.
This commit is contained in:
Jack Ye
2026-10-29 10:16:03 -07:00
committed by GitHub
parent 005a9a353a
commit ddb318567a
133 changed files with 3978 additions and 2918 deletions
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Data Types"
description: "How Lance columns appear to SQL, including the types SQL has no name for."
icon: shapes
keywords: ["sql types", "arrow", "cast", "pseudo-columns", "vector", "json"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
There is no SQL type layer over Lance storage. The table provider hands
DataFusion the dataset's Arrow schema directly, so a column is the same object
to the query engine as it is to the format.
+50
View File
@@ -0,0 +1,50 @@
---
title: "Database"
sidebarTitle: "Database"
description: "ATTACH DATABASE: bind a database to a storage location."
icon: database
keywords: ["attach database", "database", "sql", "ddl"]
availability:
embedded: unavailable
enterprise: available
---
A database is the catalog's top-level container: it holds
[namespaces](/sql/ddl/namespace), which hold tables, views and other objects.
`ATTACH DATABASE` binds a name to a storage location so its objects become
reachable.
## ATTACH DATABASE {#attach}
```sql
ATTACH DATABASE analytics WITH (location = 'analytics')
ATTACH DATABASE IF NOT EXISTS analytics WITH (location = 'analytics')
```
`location` is the only supported option, and the only way to say where a
database lives: a relative subdirectory, never an absolute path or a URI.
<Warning>
An absolute path (`/tmp/db`), an `s3://` or `file://` URI, a Windows path
(`C:/db`), a `..` segment, an empty string and a doubled slash (`sub//db`)
are all refused.
</Warning>
`IF NOT EXISTS` makes the statement a no-op rather than an error when the
database is already attached.
<Note>
A quoted name reaches characters a bare identifier cannot, such as
`` ATTACH DATABASE "team/search" WITH (location = 'team-search') ``.
</Note>
## What to read next {#next}
<CardGroup cols={2}>
<Card title="Namespace" icon="folder-tree" href="/sql/ddl/namespace">
CREATE SCHEMA inside an attached database.
</Card>
<Card title="Table" icon="table" href="/sql/ddl/table">
Creating tables once a database and namespace exist.
</Card>
</CardGroup>
+23
View File
@@ -17,6 +17,15 @@ AS 'def f(x): return x * 2'
WITH (pip = 'numpy==1.26.4', python_version = '3.11')
```
A function takes more than one parameter; each one binds a separate column
or literal where the function is called:
```sql
CREATE FUNCTION ratio(numerator float64, denominator float64) RETURNS float64
LANGUAGE python
AS 'def ratio(numerator, denominator): return numerator / denominator'
```
Parameter and result types are Arrow type names, not SQL ones: `int64`,
`float64`, `utf8`, `bool`, `date32`, and lists such as `list<float32>` or
`fixed_size_list<float32, 768>`. `BIGINT` is refused. A parameter accepts nulls
@@ -51,6 +60,20 @@ version is unaffected.
the function is baked into an image before the version becomes usable.
</Note>
## Call it {#call}
Once registered, a function is callable by name in any query, the same as a
built-in scalar function:
```sql
SELECT id, f(x) AS doubled FROM my_table
```
This is also the fastest way to check a new registration before binding it to
a column: call it over a few rows, or over literal arguments, before
committing to [a computed column](/sql/ddl/table#computed-column) that fills
from it.
## SHOW FUNCTIONS {#show}
```sql
+77
View File
@@ -0,0 +1,77 @@
---
title: "Grant"
sidebarTitle: "Grant"
description: "GRANT and REVOKE: privileges on Secrets and Functions."
icon: user-lock
keywords: ["grant", "revoke", "privileges", "authorization", "sql", "ownership"]
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
```sql
GRANT <privilege>[, ...] ON { SECRET | FUNCTION } <name> TO <grantee>[, ...]
GRANT <privilege>[, ...] ON SCHEMA <namespace> TO <grantee>[, ...]
REVOKE <privilege>[, ...] ON ... FROM <grantee>[, ...]
```
```sql
GRANT USAGE, UPDATE ON SECRET api_key TO ROLE readers, writers
GRANT CREATE FUNCTION ON SCHEMA analytics TO ROLE data_science
REVOKE USAGE ON FUNCTION embed FROM ROLE readers
```
## Privileges {#privileges}
| Privilege | Allows |
| --- | --- |
| `OWNERSHIP` | everything on the object |
| `USAGE` | using it: binding a Secret's value, calling a Function |
| `UPDATE` | changing it |
| `CREATE SECRET` | creating a Secret inside a `SCHEMA` |
| `CREATE FUNCTION` | creating a Function inside a `SCHEMA` |
<Warning>
`OWNERSHIP` cannot be revoked, only granted to someone else, which transfers
it: an object always has exactly one owner. `GRANT OWNERSHIP` is refused with
more than one grantee for the same reason.
</Warning>
These are a subset of the full privilege model; see
[Authorization](/governance/authorization#privileges) for privileges on
tables, views and namespaces, which this statement does not grant.
## Grantees {#grantees}
```sql
<grantee> ::= [ ROLE ] <role> | [ { USER | GROUP } ] { <name> | ID '<id>' }
```
```sql
GRANT USAGE ON SECRET api_key TO ROLE readers, USER alice, GROUP ID 'g1'
```
A grantee is a role, or a user or group named by its identity-directory name
or its directory ID. A grantee with no leading `ROLE`/`USER`/`GROUP` keyword
takes the kind of the one before it, so `TO ROLE a, b` names two roles --
but the first grantee in the list always needs its keyword. An unquoted name
is folded to lowercase before it is looked up; a quoted name is looked up
exactly as written.
## Naming the target {#targets}
A `SECRET` or `FUNCTION` target can be qualified with its namespace and
database, the same as anywhere else: `SECRET analytics.api_key`,
`SECRET db1.analytics.api_key`. A `SCHEMA` target is the namespace itself.
There is no `TABLE` target: table privileges are granted through
[Authorization](/governance/authorization), not this statement.
## What to read next {#next}
<CardGroup cols={2}>
<Card title="Authorization" icon="user-shield" href="/governance/authorization">
The full privilege model, and how creation privileges resolve upward.
</Card>
<Card title="Namespace" icon="folder-tree" href="/sql/ddl/namespace">
Creating the schemas these privileges apply to.
</Card>
</CardGroup>
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Index"
description: "CREATE INDEX and DROP INDEX: one column, with the algorithm chosen by USING."
icon: bolt
keywords: ["create index", "drop index", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
## CREATE INDEX {#create}
```sql
+4 -3
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Job"
description: "SHOW JOBS, SHOW ERRORS, and controlling a running job with PAUSE, RESUME and CANCEL."
icon: hourglass-half
keywords: ["show jobs", "show errors", "pause job", "resume job", "cancel job", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A [job](/basics/jobs) is not a catalog object (it has no schema and is not
namespaced), but it has its own SQL statements, grouped here the same way the
catalog objects are.
@@ -32,7 +33,7 @@ SHOW ERRORS ON documents
SHOW ERRORS ON documents FOR JOB 'job-abc' LIMIT 100
```
Why rows failed. Reach for this when a [refresh](/sql/dml/refresh) reports
Why rows failed. Reach for this when a [refresh](/sql/dml/refresh-column) reports
fewer filled rows than you expected.
**Errors and limits**
+59 -8
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Materialized View"
description: "CREATE MATERIALIZED VIEW and SHOW MATERIALIZED VIEWS; refreshing is a REFRESH statement."
icon: table
keywords: ["materialized view", "create materialized view", "show materialized views", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A materialized view stores a query's result as rows, kept current by a refresh.
It is the counterpart to a [view](/sql/ddl/view), which stores only the query.
@@ -28,6 +29,29 @@ Once created, a materialized view can be queried like a table. Use the
statements below to list, refresh and drop it. For SDK methods, see
[Materialized views](/management/materialized-view#create).
{/*
Draft for automatic materialized-view refresh. Enable this section, and
"ALTER MATERIALIZED VIEW" below, when the server feature is released.
Set scheduling options at creation, continuing the join and chained
Functions from [Functions](/management/function#chain):
```sql
CREATE MATERIALIZED VIEW frames_captioned AS
SELECT f.frame_id, extract_frame(v.video, f.ts) AS jpeg,
caption(extract_frame(v.video, f.ts)) AS caption
FROM videos v JOIN frames f ON v.id = f.video_id
WHERE f.selected = true
WITH (auto_refresh_enabled = false, auto_refresh_interval_seconds = 5)
```
`auto_refresh_enabled` and `auto_refresh_interval_seconds` work the same way
here as for a [computed column](/sql/ddl/table#automatic-refresh-options):
the interval is a minimum pacing after the last successful refresh, and
automatic execution needs the same server-side switch enabled. Omitting
`WITH` leaves automatic refresh at the server's default.
*/}
## SHOW MATERIALIZED VIEWS {#show}
```sql
@@ -45,9 +69,37 @@ on a `db://` connection.
REFRESH MATERIALIZED VIEW chunks
```
See [REFRESH](/sql/dml/refresh#refresh-materialized-view) for the full syntax,
See [REFRESH MATERIALIZED VIEW](/sql/dml/refresh-materialized-view) for the full syntax,
and compatibility notes for older server versions.
{/*
Draft for automatic materialized-view refresh. Enable this section when the
server feature is released; see the note on "CREATE MATERIALIZED VIEW" above.
## ALTER MATERIALIZED VIEW {#alter}
```sql
ALTER MATERIALIZED VIEW frames_captioned SET auto_refresh_enabled = true;
ALTER MATERIALIZED VIEW frames_captioned SET auto_refresh_interval_seconds = 300;
ALTER MATERIALIZED VIEW frames_captioned SET auto_refresh_enabled = false;
```
The whole view toggles together; there is no per-output granularity the way
a multi-output Function column has one setting per output. SET takes effect
immediately and does not itself run a refresh:
- **Disabling** prevents new automatic refreshes. An inflight one may still
finish, and `REFRESH MATERIALIZED VIEW` keeps working manually.
- **Re-enabling catches up the entire backlog in one pass**, the same as a
computed column's [automatic refresh](/sql/ddl/table#automatic-refresh-options):
the next scheduler tick picks up every source change accumulated since the
view was disabled, immediately, with no further write needed to trigger it.
A materialized view's *initial* population, at `CREATE` or when the server
runs it, happens regardless of `auto_refresh_enabled`; only refreshes after
that are gated by the setting.
*/}
## Dropping one {#drop}
A materialized view **is** a table, so it is dropped with
@@ -55,9 +107,8 @@ A materialized view **is** a table, so it is dropped with
stored rows, unlike dropping a [view](/sql/ddl/view#drop).
<Note>
**Enterprise job type:** `mat_view_refresh` for a refresh, or `sql` on a
deployment that rebuilds views through SQL. Dropping one runs the same job as
dropping any table, `drop_table` or `drop_object`.
**Enterprise job type for a refresh:** `sql`. Dropping, by contrast, runs the
same job as dropping any table, `drop_table` or `drop_object`.
</Note>
## What to read next {#next}
@@ -66,7 +117,7 @@ dropping any table, `drop_table` or `drop_object`.
<Card title="Materialized View" icon="table" href="/management/materialized-view">
Creating one from the SDK.
</Card>
<Card title="Refresh a materialized view" icon="arrows-rotate" href="/feature-engineering/materialized-views">
How a refresh works, locally and in LanceDB Enterprise.
<Card title="Refresh a materialized view" icon="arrows-rotate" href="/feature-engineering/refresh-materialized-view">
How a refresh works, in LanceDB Enterprise.
</Card>
</CardGroup>
+6 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Namespace"
description: "CREATE SCHEMA and DROP SCHEMA: the SQL spelling of namespace DDL."
icon: folder-tree
keywords: ["namespace", "schema", "create schema", "drop schema", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A [namespace](/management/namespace) is addressed in SQL as a `SCHEMA`, the
standard's name for the same concept.
@@ -46,6 +47,9 @@ default) states that requirement rather than changing it.
## What to read next {#next}
<CardGroup cols={2}>
<Card title="Database" icon="database" href="/sql/ddl/database">
Attaching the database a namespace lives in.
</Card>
<Card title="Namespace" icon="folder-tree" href="/management/namespace">
The SDK equivalent, and how a namespace fits the catalog hierarchy.
</Card>
+27 -10
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Table"
description: "CREATE, DESCRIBE, ALTER, CLONE and DROP TABLE."
icon: table
keywords: ["create table", "alter table", "drop table", "describe", "clone", "ddl"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
## CREATE TABLE {#create}
```sql
@@ -50,7 +51,7 @@ ALTER TABLE t ADD COLUMN doubled AS value * 2
`AS <expression>` declares a [computed column](/feature-engineering/add-computed-column).
The column is committed empty and the expression is stored, not evaluated:
declaring one on a billion-row table costs what declaring one on an empty table
costs. [`REFRESH COLUMN`](/sql/dml/refresh) fills it.
costs. [`REFRESH COLUMN`](/sql/dml/refresh-column) fills it.
{/*
Draft for automatic computed-column refresh. Enable this section when the
@@ -74,19 +75,35 @@ and is minimum pacing after a successful refresh of that output; unchanged
inputs do not force work. A new column is immediately eligible for its first
refresh. The server's separate automatic-refresh switch must also be enabled.
These options also work beside a Function binding's `version` and `branch`:
These options also work beside a Function binding's `version` and `branch`,
continuing the [`caption` Function](/management/function#chain) from Functions:
```sql
ALTER TABLE t ADD COLUMN (label, score) AS classify(text)
ALTER TABLE frames ADD COLUMN caption AS caption(extract_frame(video, ts))
WITH (version = 'fv_1', auto_refresh_enabled = true,
auto_refresh_interval_seconds = 300);
ALTER TABLE t ALTER COLUMN score SET auto_refresh_enabled = false;
ALTER TABLE frames ALTER COLUMN caption SET auto_refresh_enabled = false;
```
Declaration settings initialize every output in the group; later SET commands
change one output. Disabling automatic refresh prevents new submissions while
allowing inflight jobs to finish and explicit REFRESH COLUMN to run. Column
SUSPEND/RESUME syntax is not supported.
change one output. SET takes effect immediately and does not itself read or
write any row:
- **Disabling** prevents new automatic submissions. Inflight jobs may still
finish, explicit `REFRESH COLUMN` keeps working, and rows written while
disabled commit with no computed value -- they do not queue anywhere
separate.
- **Re-enabling catches up the entire backlog in one pass.** The next
scheduler tick processes every row left unfilled since the column was
disabled, immediately, with no further write required to trigger it: a
column disabled for an hour and re-enabled fills everything that
accumulated during that hour, not just rows written after the moment it
is re-enabled.
Column SUSPEND/RESUME syntax is not supported; `SET auto_refresh_enabled` is
the only toggle. The current setting is in the column's own field metadata,
`computed_column.auto_refresh_enabled`, readable from `DESCRIBE` or the
SDK's `table.schema` without submitting a job to ask.
*/}
{/*
@@ -148,7 +165,7 @@ ALTER TABLE t SET RTE WITH (maintained_indexes = '')
ALTER TABLE t UNSET RTE
```
The [realtime engine](/tables/realtime-engine) puts a write-ahead log in front
The [real time engine](/tables/realtime-engine) puts a write-ahead log in front
of the table, making small frequent writes cheap by batching them out of the
commit path. `sharding` and `buckets` spread the log so concurrent writers do
not contend on one place; `bucket` sharding names its `column`, which must be
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "View"
description: "CREATE VIEW and DROP VIEW: a saved query, evaluated when read."
icon: eye
keywords: ["create view", "drop view", "sql", "catalog"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A view stores a **query**, not rows. Reading it runs the query against current
data, so a view is never stale, and never faster than the query it wraps. It
is the counterpart to a [materialized view](/sql/ddl/materialized-view), which
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Delete"
description: "DELETE FROM ... WHERE: rows are marked, not erased."
icon: trash
keywords: ["delete", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
```sql
DELETE FROM documents WHERE created_at < TIMESTAMP '2025-01-01 00:00:00'
```
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Insert"
description: "INSERT INTO, including INSERT ... SELECT to move rows server-side."
icon: plus
keywords: ["insert", "insert into", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
```sql
INSERT INTO documents (id, title) VALUES (1, 'Merlin'), (2, 'Arthur')
@@ -1,19 +1,18 @@
---
title: "REFRESH"
sidebarTitle: "REFRESH"
description: "Filling computed columns and bringing materialized views up to date."
title: "REFRESH COLUMN"
sidebarTitle: "REFRESH COLUMN"
description: "Filling a computed column: idempotent, scoped, and resumable."
icon: arrows-rotate
keywords: ["refresh column", "refresh materialized view", "backfill", "cascade"]
keywords: ["refresh column", "backfill", "computed column"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
LanceDB extensions with no DataFusion equivalent. They are what makes
A LanceDB extension with no DataFusion equivalent. It is what makes
[feature engineering](/feature-engineering) reachable from SQL: a column
declared by [DDL](/sql/ddl/table#computed-column) starts empty, and a refresh fills it.
## REFRESH COLUMN {#refresh-column}
```sql
REFRESH COLUMN <column> ON [TABLE] <table>
[WHERE <predicate>]
@@ -33,7 +32,7 @@ can recompute values the failed run had already written.
`WHERE` scopes the refresh, which is how you fill a slice and check it before
committing to the whole table.
### Options {#refresh-column-options}
## Options {#options}
| Option | Meaning |
| --- | --- |
@@ -62,39 +61,22 @@ memory and GPU options there.
</Note>
*/}
## REFRESH MATERIALIZED VIEW {#refresh-materialized-view}
```sql
REFRESH MATERIALIZED VIEW <name>
```
```sql
REFRESH MATERIALIZED VIEW chunks
```
A refresh updates the view's stored result. Refresh views in dependency order,
starting with their source views.
<Note>
Some older server versions accepted `WITH (full = true)` and `CASCADE`.
Those options are not accepted by the SQL implementation described here.
Use the basic statement above; do not assume that the options accepted by a
local SDK's `refresh()` method are also SQL options.
</Note>
## Watching a refresh {#watching}
Both statements submit work that outlives the call. Track it with
A refresh submits work that outlives the call. Track it with
[`SHOW JOBS`](/sql/ddl/job#show), and diagnose failures with
[`SHOW ERRORS`](/sql/ddl/job#errors).
## See also {#see-also}
<CardGroup cols={2}>
<Card title="Refresh a column" icon="arrows-rotate" href="/feature-engineering/refresh-column">
<Card title="Refresh a computed column" icon="arrows-rotate" href="/feature-engineering/refresh-column">
The same operation from the SDKs, and what a refresh guarantees.
</Card>
<Card title="Table" icon="table" href="/sql/ddl/table#alter">
Declaring the columns this fills.
</Card>
<Card title="REFRESH MATERIALIZED VIEW" icon="arrows-rotate" href="/sql/dml/refresh-materialized-view">
Bringing a view's stored result up to date, instead of a column.
</Card>
</CardGroup>
@@ -0,0 +1,50 @@
---
title: "REFRESH MATERIALIZED VIEW"
sidebarTitle: "REFRESH MATERIALIZED VIEW"
description: "Bringing a materialized view's stored result up to date."
icon: arrows-rotate
keywords: ["refresh materialized view", "cascade"]
availability:
embedded: unavailable
enterprise: available
---
A LanceDB extension with no DataFusion equivalent.
```sql
REFRESH MATERIALIZED VIEW <name>
```
```sql
REFRESH MATERIALIZED VIEW chunks
```
A refresh updates the view's stored result. Refresh views in dependency order,
starting with their source views.
<Note>
Some older server versions accepted `WITH (full = true)` and `CASCADE`.
Those options are not accepted by the SQL implementation described here.
Use the basic statement above; do not assume that the options accepted by a
local SDK's `refresh()` method are also SQL options.
</Note>
## Watching a refresh {#watching}
A refresh submits work that outlives the call. Track it with
[`SHOW JOBS`](/sql/ddl/job#show), and diagnose failures with
[`SHOW ERRORS`](/sql/ddl/job#errors).
## See also {#see-also}
<CardGroup cols={2}>
<Card title="Materialized View" icon="table" href="/sql/ddl/materialized-view">
Creating the view this refreshes.
</Card>
<Card title="Refresh a materialized view" icon="layer-group" href="/feature-engineering/refresh-materialized-view">
The same operation from the SDKs, and automatic refresh.
</Card>
<Card title="REFRESH COLUMN" icon="arrows-rotate" href="/sql/dml/refresh-column">
Filling a computed column, instead of a view.
</Card>
</CardGroup>
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Update"
description: "UPDATE ... SET ... WHERE, and why it does not recompute derived columns."
icon: pen
keywords: ["update", "sql"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
```sql
UPDATE documents SET title = upper(title) WHERE category = 'film'
```
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Aggregation"
description: "GROUP BY, HAVING, and the aggregate function set."
icon: layer-group
keywords: ["group by", "having", "count", "sum", "avg", "aggregate", "approximate"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
Use SQL aggregates to summarize records, either across a whole table or
within groups. Common examples include counts by category, totals and averages.
+76
View File
@@ -0,0 +1,76 @@
---
title: "Changes since a version"
sidebarTitle: "Changes"
description: "CHANGES: read the rows appended to a table since an earlier version."
icon: clock-rotate-left
keywords: ["changes", "append", "incremental", "cdc", "version"]
availability:
embedded: unavailable
enterprise: available
---
`CHANGES` reads the rows appended to a table since an earlier
[version](/management/table#versioning), without rescanning rows that were
already there:
```sql
SELECT * FROM events CHANGES (INFORMATION => APPEND_ONLY) AT (VERSION => 42)
```
This returns the rows appended after version 42, up to the table's current
version at the time the query runs.
## Strictly append-only {#strict}
The clause is named for what it requires, not just what it does: every
change to the table between the starting version and the current one must
be an append. If any of them inserted with changes other than a pure append
-- an update, a delete, a merge -- the query is refused rather than silently
returning an incomplete or wrong result. Re-run it with a later starting
version that is itself after the non-append write, or read the table in full
instead of incrementally.
`INFORMATION => APPEND_ONLY` is the only supported value: it names what kind
of history the clause is willing to read, not a choice among several. `AT
(VERSION => ...)` takes a positive integer; there is no `AT (TIMESTAMP =>
...)` form for this clause, unlike other version qualifiers.
## Where the clause can go {#placement}
It attaches to a table in `FROM`, including inside a subquery, a CTE, a join,
a set operation, or `EXPLAIN`:
```sql
WITH recent AS (
SELECT * FROM events CHANGES (INFORMATION => APPEND_ONLY) AT (VERSION => 42)
)
SELECT count(*) FROM recent
SELECT * FROM events CHANGES (INFORMATION => APPEND_ONLY) AT (VERSION => 10) a
JOIN events CHANGES (INFORMATION => APPEND_ONLY) AT (VERSION => 20) b
ON a.id = b.id
```
Each occurrence of a table keeps its own starting version, even when the same
table is referenced more than once in one statement.
## Unsupported forms {#refused}
| | |
| --- | --- |
| `CHANGES (INFORMATION => DEFAULT)` | only `APPEND_ONLY` is supported |
| `AT (TIMESTAMP => ...)` | only `AT (VERSION => ...)` is supported |
| `AT (VERSION => 0)` | the starting version must be positive |
| `... END (VERSION => ...)` | not supported; the current version is always the end |
| `CHANGES ... TABLESAMPLE (...)` | cannot be combined on the same table reference |
## What to read next {#next}
<CardGroup cols={2}>
<Card title="Table" icon="table" href="/sql/ddl/table">
CREATE, ALTER, CLONE and versioning basics.
</Card>
<Card title="Select" icon="magnifying-glass" href="/sql/dql/select">
The rest of the query grammar this clause attaches to.
</Card>
</CardGroup>
+99
View File
@@ -0,0 +1,99 @@
---
title: "DEDUPE"
sidebarTitle: "DEDUPE"
description: "The dedupe() table function: one representative row per near-duplicate group, scanned over the index."
icon: layer-group
keywords: ["dedupe", "vector_dedup", "sql", "deduplication", "near-duplicate", "table function"]
availability:
embedded: unavailable
enterprise: available
---
`dedupe` scans a vector column's [index](/indexing/vector-index) partitions
for rows closer than a distance threshold, resolves the overlapping pairs it
finds into duplicate groups, and returns one representative row per group --
without a full pairwise scan or a shuffle. See
[`duplicate_pairs`](/sql/dql/duplicate-pairs) to inspect the pairs themselves
before committing to this resolution.
```sql
SELECT * FROM dedupe(<table>, <vector_column>,
distance_threshold => <float>
[, resolution => 'connected_components' | 'direct_representative']
[, selection => 'all' | 'first' | 'random', count => <int>, seed => <int>]
)
```
```sql
SELECT * FROM dedupe('frames', 'vec', distance_threshold => 0.02)
```
<Note>
Earlier releases spelled this `vector_dedup`; that name is rejected on
current deployments.
</Note>
## Input {#input}
| Argument | Type | Required | Meaning |
| --- | --- | --- | --- |
| table name | string literal | yes | the table to scan |
| vector column | string literal | yes | the indexed vector column to compare |
| `distance_threshold` | float | yes | maximum distance for a pair to count as close |
| `resolution` | `'connected_components'` (default) \| `'direct_representative'` | no | how overlapping pairs group together -- see [Resolution](#resolution) |
| `selection` | `'all'` (default) \| `'first'` \| `'random'` | no | which index partitions to scan |
| `count` | int | no | number of partitions to sample when `selection => 'random'` |
| `seed` | int | no | random seed when `selection => 'random'` |
`id_column` is refused on `dedupe`: it has no separate pair-identifier output
to project onto, only [`duplicate_pairs`](/sql/dql/duplicate-pairs#input)
accepts it.
## Output {#output}
One row per duplicate group, **in the table's own schema** -- every column of
`<table>`, including a BlobV2 column, comes through unchanged on the
representative row Lance selected for that group. Unlike
[`duplicate_pairs`](/sql/dql/duplicate-pairs#output), there is no fixed
`dedupe`-specific schema to document: the output schema is the source
table's schema.
Because the result is already shaped like the table, it composes directly
into a new table or a materialized view:
```sql
CREATE MATERIALIZED VIEW frames_deduped AS
SELECT * FROM dedupe('frames', 'vec', distance_threshold => 0.02)
```
## Resolution {#resolution}
`resolution` chooses how overlapping pairs group together:
| `resolution` | |
| --- | --- |
| `connected_components` (default) | groups transitively: if a~b and b~c are both close, all three resolve together |
| `direct_representative` | only pairs within the threshold of each other resolve together |
## Sampling partitions {#sampling}
`selection` scopes the scan to a sample of partitions instead of all of them,
the same `all` (default) / `first` / `random` choice as
[`ivf_partition`](/sql/scalar-functions#ivf-partition). Use it to estimate a
duplicate rate on a large table before scanning it in full:
```sql
SELECT * FROM dedupe('frames', 'vec',
distance_threshold => 0.02, selection => 'random', count => 100, seed => 42)
```
## What to read next {#next}
<CardGroup cols={2}>
<Card title="DUPLICATE_PAIRS" icon="clone" href="/sql/dql/duplicate-pairs">
Inspect the close pairs themselves instead of the resolved groups.
</Card>
<Card title="Deduplicate near-duplicates" icon="copy" href="/curation/dedupe">
When to reach for this instead of exact-key deduplication.
</Card>
</CardGroup>
+97
View File
@@ -0,0 +1,97 @@
---
title: "DUPLICATE_PAIRS"
sidebarTitle: "DUPLICATE_PAIRS"
description: "The duplicate_pairs() table function: every close pair in a vector column, scanned over the index."
icon: clone
keywords: ["duplicate_pairs", "vector_duplicate_pairs", "sql", "deduplication", "near-duplicate", "table function"]
availability:
embedded: unavailable
enterprise: available
---
`duplicate_pairs` scans a vector column's [index](/indexing/vector-index)
partitions for every pair of rows closer than a distance threshold, without a
full pairwise scan or a shuffle. It returns one row per close pair, not one
row per source row -- use it to inspect what would be collapsed before
committing to [`dedupe`](/sql/dql/dedupe), which resolves these same pairs
into groups.
```sql
SELECT * FROM duplicate_pairs(<table>, <vector_column>,
distance_threshold => <float>
[, id_column => <column>]
[, selection => 'all' | 'first' | 'random', count => <int>, seed => <int>]
)
```
```sql
SELECT * FROM duplicate_pairs('frames', 'vec', distance_threshold => 0.02)
```
<Note>
Earlier releases spelled this `vector_duplicate_pairs`; that name is rejected
on current deployments.
</Note>
## Input {#input}
| Argument | Type | Required | Meaning |
| --- | --- | --- | --- |
| table name | string literal | yes | the table to scan |
| vector column | string literal | yes | the indexed vector column to compare |
| `distance_threshold` | float | yes | maximum distance for a pair to count as close |
| `id_column` | column name | no | project this column's value instead of the internal row ID -- see [Application-level identifiers](#id-column) |
| `selection` | `'all'` (default) \| `'first'` \| `'random'` | no | which index partitions to scan |
| `count` | int | no | number of partitions to sample when `selection => 'random'` |
| `seed` | int | no | random seed when `selection => 'random'` |
`resolution` is refused on `duplicate_pairs`: a pair has no notion of a group,
only [`dedupe`](/sql/dql/dedupe#input) accepts it.
## Output {#output}
One row per close pair found, in a fixed schema independent of the source
table:
| Column | Type | Meaning |
| --- | --- | --- |
| `row_id_a` | uint64 | internal row ID of the first row in the pair, the same identifier as the [`_rowid`](/sql/data-types#pseudo-columns) pseudo-column |
| `row_id_b` | uint64 | internal row ID of the second row in the pair |
| `distance` | float32 | distance between the pair's vectors, at or below `distance_threshold` |
| `segment_id` | int64 | which index segment the pair was found in |
| `partition_id` | int64 | which index partition the pair was found in |
| `dataset_version` | int64 | the dataset version the scan ran against |
### Application-level identifiers {#id-column}
A row ID is internal and not stable across compaction. Pass `id_column`, a
top-level scalar column such as a primary key, to project its value instead:
`row_id_a`/`row_id_b` are replaced by `id_a`/`id_b`, typed like `id_column`.
```sql
SELECT * FROM duplicate_pairs('frames', 'vec', distance_threshold => 0.02, id_column => 'frame_id')
```
## Sampling partitions {#sampling}
`selection` scopes the scan to a sample of partitions instead of all of them,
the same `all` (default) / `first` / `random` choice as
[`ivf_partition`](/sql/scalar-functions#ivf-partition). Use it to estimate a
distance threshold or duplicate rate on a large table before scanning it in
full:
```sql
SELECT * FROM duplicate_pairs('frames', 'vec',
distance_threshold => 0.02, selection => 'random', count => 100, seed => 42)
```
## What to read next {#next}
<CardGroup cols={2}>
<Card title="DEDUPE" icon="layer-group" href="/sql/dql/dedupe">
Resolve these same pairs into one representative row per group.
</Card>
<Card title="Deduplicate near-duplicates" icon="copy" href="/curation/dedupe">
When to reach for this instead of exact-key deduplication.
</Card>
</CardGroup>
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Explain"
description: "EXPLAIN, EXPLAIN ANALYZE and EXPLAIN VERBOSE: reading and measuring a plan from SQL."
icon: map
keywords: ["explain", "explain analyze", "explain verbose", "query plan"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
```sql
EXPLAIN SELECT * FROM documents WHERE identifier < 1000
EXPLAIN ANALYZE SELECT * FROM documents WHERE identifier < 1000
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Full-Text Search"
description: "The fts() table function: match, fuzzy, phrase, boolean, boost and multi-match queries from SQL."
icon: magnifying-glass
keywords: ["fts", "sql", "full text search", "fuzzy", "phrase", "boolean", "boost", "multi-match"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
Full-text search is a **table function**, `fts(table, query)`, rather than a
predicate: it produces a relevance score alongside the matching rows, and a
`WHERE` clause has nowhere to put one. The two string-literal arguments are
+29 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Joins"
description: "Combining rows from more than one table, including across namespaces."
icon: link
keywords: ["join", "inner", "left", "outer", "cross", "namespace"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
Use SQL joins to combine rows from two or more tables.
## The join types {#types}
@@ -70,3 +71,29 @@ Avoid selecting blob or vector columns you do not need on either side of a join.
The join multiplies rows before projection is applied in some plans, so a wide
column can be materialized many more times than the result has rows.
</Warning>
## Late materialization for wide columns {#late-materialization}
The planner can avoid the multiplication above: when a join narrows down to
a key column (such as `_rowid` or another narrow, selective column) before a
wide one is needed, it can defer reading a BlobV2 or vector column until
after the join has already settled which rows survive, instead of reading it
for every candidate row first. In a plan where this applies, the wide column
is read once per surviving row, where the worker runs, and never pulled into
the query node to be joined directly.
```sql
SELECT d.title, b.caption
FROM documents AS d
JOIN (SELECT _rowid, caption FROM captions WHERE source = 'model-v2') AS b
ON b._rowid = d._rowid
```
This is query planning, not a syntax you opt into: a join written this way
is a **candidate** for the planner to defer the wide read, not a guarantee.
Confirm it for a specific query with [`EXPLAIN`](/sql/dql/explain) --
`RemoteTake` in the plan means the deferral happened; its absence means the
wide column was read eagerly, even if the SQL above matches exactly. Neither
a passing query nor an `EXPLAIN` that omits `RemoteTake` tells you how much
I/O a plan did or did not save -- read the plan's own row and byte counts for
that.
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Sampling"
description: "TABLESAMPLE: draw a random subset of a table, by rows or by fragments."
icon: dice
keywords: ["tablesample", "sampling", "bernoulli", "system", "repeatable", "random"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
Use `TABLESAMPLE` to explore a subset of a table or estimate aggregate
statistics. The sampling method determines how much data is read and whether
the table's physical layout influences the sample.
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "SELECT"
description: "Projection, filtering, ordering, and composing queries out of other queries."
icon: table-columns
keywords: ["select", "where", "order by", "limit", "cte", "union", "distinct"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
```sql
[WITH cte AS (...)]
SELECT [ALL | DISTINCT] select_expr [, ...]
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Subqueries"
description: "Queries inside queries: scalar, IN, EXISTS, correlated, and derived tables."
icon: code-branch
keywords: ["subquery", "exists", "in", "correlated", "derived table", "lateral"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A subquery is allowed anywhere an expression or a relation is.
## As a relation {#derived}
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Window Functions"
description: "Compute over a set of rows related to the current one, without collapsing them."
icon: chart-simple
keywords: ["window", "over", "partition by", "row_number", "rank", "lag", "lead"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A window function computes across a set of rows related to the current one and
returns a value **per row**. That is the difference from
[aggregation](/sql/dql/aggregation), which collapses the rows it covers.
+26 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Functions"
description: "Where to find each kind of function, and the dialect version they come from."
icon: function
keywords: ["functions", "datafusion", "scalar", "aggregate", "window", "fts", "table function"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
LanceDB executes SQL with [Apache DataFusion](https://datafusion.apache.org),
currently **version 54**. The function set is DataFusion's default registration
plus what Lance adds.
@@ -77,6 +78,29 @@ like any other column (order by it, filter on it, project it) and exists only
in a relation that came from `fts()`. See
[Data types](/sql/data-types#pseudo-columns).
### `dedupe` and `duplicate_pairs` {#dedupe}
Scan a vector column's [index](/indexing/vector-index) partitions for rows
closer than a distance threshold, without a full pairwise scan or a shuffle:
```sql
SELECT * FROM duplicate_pairs('frames', 'vec', distance_threshold => 0.02)
SELECT * FROM dedupe('frames', 'vec', distance_threshold => 0.02)
```
<Note>
Earlier releases spelled these `vector_duplicate_pairs` and `vector_dedup`;
those names are rejected on current deployments.
</Note>
`duplicate_pairs` returns every close pair it finds, in a fixed schema.
`dedupe` instead resolves overlapping pairs into duplicate groups and returns
one representative row **in the table's own schema** per group, so its result
composes directly into `CREATE TABLE ... AS` or
`CREATE MATERIALIZED VIEW ... AS`. See [`DEDUPE`](/sql/dql/dedupe) and
[`DUPLICATE_PAIRS`](/sql/dql/duplicate-pairs) for the full input and output
schema, resolution grouping, and partition sampling.
### `generate_series` {#generate-series}
```sql
+5 -2
View File
@@ -32,7 +32,7 @@ for batch in reader:
SQL results arrive as Arrow record batches. Iterating over the reader avoids
collecting the entire result in application memory. See
[Queries](/basics/query) for result handling.
[Read and manage results](/basics/sql#results) for more.
## Usage {#usage}
@@ -47,6 +47,7 @@ operations that have no SQL form, such as a merge insert. See
| | |
| --- | --- |
| [Database](/sql/ddl/database) | `ATTACH DATABASE` |
| [Namespace](/sql/ddl/namespace) | `CREATE SCHEMA`, `DROP SCHEMA` |
| [Table](/sql/ddl/table) | `CREATE`, `DESCRIBE`, `ALTER`, `CLONE`, `DROP TABLE` |
| [View](/sql/ddl/view) | `CREATE VIEW`, `DROP VIEW` |
@@ -67,7 +68,8 @@ Draft for the Functions release. Add this row to the table above, after Index:
| [Update](/sql/dml/update) | `UPDATE ... SET` |
| [Delete](/sql/dml/delete) | `DELETE FROM` |
| [Copy](/sql/dml/copy) | `COPY` (refused by recent deployment versions) |
| [Refresh](/sql/dml/refresh) | filling computed columns and materialized views |
| [Refresh column](/sql/dml/refresh-column) | filling a computed column |
| [Refresh materialized view](/sql/dml/refresh-materialized-view) | bringing a view's stored result up to date |
### DQL {#dql}
@@ -80,6 +82,7 @@ Draft for the Functions release. Add this row to the table above, after Index:
| [Aggregation](/sql/dql/aggregation) | `GROUP BY`, `HAVING`, the aggregate set |
| [Window functions](/sql/dql/window) | `OVER`, framing, ranking and offset functions |
| [Sampling](/sql/dql/sampling) | `TABLESAMPLE`, by rows or by fragments |
| [Changes](/sql/dql/changes) | `CHANGES`: rows appended since an earlier version |
| [Full-Text Search](/sql/dql/fts) | the `fts()` table function, and its query forms |
| [Explain](/sql/dql/explain) | `EXPLAIN`, `EXPLAIN ANALYZE`, `EXPLAIN VERBOSE` |
+3 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Operators"
description: "Comparison, logical and arithmetic operators, literals, and precedence."
icon: plus-minus
keywords: ["operators", "literals", "precedence", "null", "cast", "interval"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
LanceDB uses DataFusion's SQL operators. This page describes comparison,
logical and arithmetic operators, literal values, type conversions and
operator precedence.
+10 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Prepared Statements"
description: "What a deployment does when a Flight SQL client prepares a statement or binds parameter values."
icon: bolt
keywords: ["prepared statement", "flight sql", "parameters", "bind"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
A Flight SQL client can prepare a statement before it runs it. A deployment
accepts that, but it keeps no plan between runs and does not accept parameter
values.
@@ -50,6 +51,13 @@ Do not put untrusted input, such as a value from a user or a request, directly
into the statement text. Quoting it does not protect the statement the way
binding a parameter would.
<Note>
This limitation is specific to the Flight SQL protocol's `PREPARE`/bind step.
The Python SDK's `execute_query(sql, parameters=...)` binds parameter values
through a separate, LanceDB-specific mechanism -- see
[Parameters in SQL queries](/basics/sql#parameters).
</Note>
## When to use prepared statements {#when}
Prepare a statement when your client requires it, or to get a statement's
+56 -2
View File
@@ -4,10 +4,11 @@ sidebarTitle: "Scalar Functions"
description: "The per-row function set: math, string, regex, date/time, array, struct, and what Lance adds."
icon: calculator
keywords: ["scalar functions", "string functions", "math functions", "regex", "array functions", "json"]
availability:
embedded: unavailable
enterprise: available
---
<Badge color="red">Enterprise</Badge> [SQL runs in LanceDB Enterprise.](/sql)
Functions that take a value and return a value, one row at a time. The set is
based on DataFusion's registration plus Lance and deployment-specific
functions. The names below are selected examples, not an exhaustive catalog
@@ -98,6 +99,21 @@ functions supported by your deployment and inspect SQL
index-backed plan. See [SQL vector search](/basics/sql#no-vector-sql).
</Warning>
A third, optional argument is a comma-separated options string. `metric`
selects `l2` (default, alias `euclidean`), `cosine`, or `dot`; the
index-search rewrite also reads `nprobes` and `use_index`:
```sql
SELECT id FROM documents
ORDER BY distance(embedding, ARRAY[0.1, 0.2, 0.3], 'metric=cosine,nprobes=20')
LIMIT 10
-- Force a flat scan, bypassing the index:
SELECT id FROM documents
ORDER BY exact_distance(embedding, ARRAY[0.1, 0.2, 0.3], 'use_index=false')
LIMIT 10
```
## Structs and fields {#structs}
```text
@@ -146,6 +162,44 @@ semantics and the simple tokenizer.
query, the planner uses it instead of scanning, which is the reason to prefer
it over `LIKE` for keyword tests.
### `ivf_partition(vector, ...)` {#ivf-partition}
```sql
SELECT ivf_partition(embedding, selection => 'random', count => 100, seed => 42) AS partition,
array_agg(id) AS ids
FROM documents
GROUP BY partition
```
Assigns each row to one of `count` IVF-style partitions over `vector`, using
the named arguments `selection` (partition-center strategy; `'random'` is
sampled with `seed`) and `seed`. Grouping by its result buckets rows by
vector proximity without building or querying a vector index — useful for
blocking a large table into comparable chunks, such as before a pairwise
similarity pass.
### `blob_descriptor(column)` {#blob-descriptor}
```sql
SELECT id, blob_descriptor(video) AS descriptor FROM videos
```
A [BlobV2](/management/table#managed-blobs) column's bytes are not fetched by
a plain `SELECT`: that already returns `large_binary`, the same as any other
binary column. `blob_descriptor` instead returns a small struct describing
where the payload lives, without reading it. Pass one row's descriptor to
the SDK's blob file reader to open that payload directly:
```python
rows = db.execute_query("SELECT id, blob_descriptor(video) AS descriptor FROM videos").read_all().to_pylist()
with table.open_blob_file(rows[0]["descriptor"]) as reader:
payload = reader.read()
```
A descriptor is pinned to the table version it was read at: it keeps
resolving correctly even after the table is appended to and checked out to a
newer version.
### JSON accessors {#json}
| Function | Returns |