Files
lancedb/docs/web/sql/dql/join.mdx
T
Jack Ye ddb318567a 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.
2026-10-29 10:16:03 -07:00

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.