mirror of
https://github.com/lancedb/lancedb.git
synced 2026-10-30 23:36:52 +00:00
## 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.
100 lines
3.6 KiB
Plaintext
100 lines
3.6 KiB
Plaintext
---
|
|
title: "Joins"
|
|
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
|
|
---
|
|
|
|
Use SQL joins to combine rows from two or more tables.
|
|
|
|
## The join types {#types}
|
|
|
|
```sql
|
|
SELECT d.title, c.name
|
|
FROM documents AS d
|
|
JOIN categories AS c ON c.id = d.category_id
|
|
```
|
|
|
|
| | What it keeps |
|
|
| --- | --- |
|
|
| `[INNER] JOIN` | rows matching on both sides |
|
|
| `LEFT [OUTER] JOIN` | every left row, nulls where the right has no match |
|
|
| `RIGHT [OUTER] JOIN` | every right row |
|
|
| `FULL [OUTER] JOIN` | every row from both |
|
|
| `CROSS JOIN` | every combination (the product of the two) |
|
|
|
|
`USING (col)` is shorthand for an equality join on a shared column name, and
|
|
avoids repeating it in the select list.
|
|
|
|
## Across namespaces {#namespaces}
|
|
|
|
Tables in different [namespaces](/management/namespace) join by their dotted paths. The
|
|
catalog is one catalog, so there is nothing to federate:
|
|
|
|
```sql
|
|
SELECT c.id, u.email
|
|
FROM prod.events.clicks AS c
|
|
JOIN prod.reference.users AS u ON u.id = c.user_id
|
|
```
|
|
|
|
## Joining a search result {#search}
|
|
|
|
[`fts()`](/sql/functions#fts) is a table function, so its result is a relation
|
|
and joins like any other. This example assumes `my_docs` has `text` and
|
|
`category_id` columns, a full-text index on `text`, and a `categories` table
|
|
with `id` and `name` columns:
|
|
|
|
```sql
|
|
SELECT c.name, count(*) AS hits
|
|
FROM fts('my_docs', '{"match":{"column":"text","terms":"puppy"}}') AS m
|
|
JOIN categories AS c ON c.id = m.category_id
|
|
GROUP BY c.name
|
|
ORDER BY hits DESC
|
|
```
|
|
|
|
That composition is the reason full-text search is a relation rather than a
|
|
predicate: a `WHERE` clause has nowhere to put the relevance score.
|
|
|
|
## Performance {#performance}
|
|
|
|
A [scalar index](/indexing/scalar-index) can accelerate supported filters on
|
|
the input tables. An index on a join key does not by itself guarantee an
|
|
index lookup join. Use SQL [`EXPLAIN`](/sql/dql/explain) to inspect the scans,
|
|
filters, and join chosen for the complete statement.
|
|
|
|
<Warning>
|
|
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.
|