mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
`take_row_ids` used an `IN` filter, so the backend could reorder rows and collapse duplicate IDs. That can silently misalign training rows with labels or sample weights. Restore the requested order and duplicate occurrences in the shared Rust take path for native and remote tables, and retain that information through Python query serialization. Missing or deleted IDs are omitted. Explicit Rust `order_by` overrides request order, and pagination applies after restoring occurrences. Empty projections and `_rowid` projection aliases retain their output schema. RTE tables reject unsupported takes with the existing base-table guidance before checking the internal row-ID projection used for ordering. An explicit `use_rte(False)` reads the base table with the same requested order and occurrences. A pre-existing fixture defect also blocked CI: `test_table_add_in_threadpool` responded without reading uploads, allowing a connection reset during response decoding. This PR includes a fix confined to that test's handler: consume each request body before responding and verify one create row plus ten inserted rows. This was not a regression in the take implementation. Production write retries, concurrency and HTTP client code are unchanged. The selective sampling guide scopes the row-ID guarantees to SDK releases containing this fix, preserves the distinct offset-take semantics, and explains that ordered takes collect and combine matching rows before output batching. It recommends smaller ID requests and narrow projections because `max_batch_length` does not cap working memory. This uses the existing REST query protocol and requires a new SDK release; no deployed service was changed. Validation: - Docs follow-up: the changed page compiles with the Mint 4.2.888 MDX compiler; Typos 1.26.0 passes and Vale 3.24.0 has no findings on changed lines. Full-site Mint validation reports four existing snippet-import warnings on the reranking/vector-search pages, identical with the original page restored in a baseline comparison. - The applied threadpool test passed locally after the fixture change. The identical candidate previously passed 10 trials with the ordinary reader and 10 with an unbuffered reader; each trial verifies all uploaded rows. Those checks used macOS arm64/Python 3.14/default features, not the full failing CI environment. The normal new-head macOS Python 3.13/experimental-functions job remains the required platform check. - Python package Ruff format/check passed. Repository-root Ruff still reports 59 existing errors and six formatting differences in unchanged `docs/web-tests` files; none are in changed files. - At `a569d3f0`, the RTE diagnostic regression was reproduced and fixed; 5 targeted cases and strict workspace Clippy passed. The fixture-only follow-up changes no production Rust/Python code, so that evidence is reused. - Earlier validation at `6bcc0878`: 439 Rust query/remote-table tests and 24 Python take/getitems tests passed; the focused 49-test suite also covered offset dataloaders. A local 9,216-row, four-fragment reproduction returned 200 seeded random batches of 64 rows with zero ordering or label-alignment mismatches. The ordering and projection implementation is unchanged by the follow-ups. - No live Enterprise run was performed. Fixes [ENT-3469](https://linear.app/lancedb/issue/ENT-3469).
129 lines
5.3 KiB
Plaintext
129 lines
5.3 KiB
Plaintext
---
|
|
title: "Selective Sampling"
|
|
sidebarTitle: "Selective Sampling"
|
|
description: "Fetch specific rows by row ID, row address or offset instead of drawing a random fraction."
|
|
icon: crosshairs
|
|
keywords: ["sampling", "row id", "row address", "offset", "take", "rowid", "rowaddr", "rowoffset"]
|
|
availability:
|
|
embedded: available
|
|
enterprise: available
|
|
---
|
|
|
|
Selective sampling fetches specific, already-known rows -- by row ID, row
|
|
address or offset -- rather than drawing a random fraction of the table.
|
|
Use it to re-fetch rows a previous query identified, resume from a saved
|
|
position, or pull a batch by index for a data loader. For a random subset
|
|
instead, see [Statistical Sampling](/curation/statistical-sampling).
|
|
|
|
LanceDB exposes three ways to address a row:
|
|
|
|
| Identifier | Stability | Typical source |
|
|
| --- | --- | --- |
|
|
| Row ID (`_rowid`) | Stable across compaction, with [stable row IDs](/basics/guarantees) enabled | A previous query's result, or `function_errors()` |
|
|
| Row address (`_rowaddr`) | Changes on compaction; encodes `(fragment_id << 32) \| row_offset` | A previous query's result, when you need the physical location |
|
|
| Offset | Table-wide position; **not** stable -- a row's offset shifts when an earlier row is deleted | `range(len(table))`, since every valid offset is known in advance |
|
|
|
|
## From Python {#python}
|
|
|
|
The SDK takes a list of row IDs or offsets directly, without going through
|
|
SQL:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
# By row ID -- from a previous search or scan that selected "_rowid"
|
|
rows = tbl.take_row_ids([1001, 1002, 1005]).to_arrow()
|
|
|
|
# By offset -- the full range is always [0, len(tbl))
|
|
rows = tbl.take_offsets([0, 1, 2]).to_arrow()
|
|
```
|
|
|
|
```typescript TypeScript icon="square-js"
|
|
// By row ID -- from a previous search or scan that selected "_rowid"
|
|
const rowsById = await tbl.takeRowIds([1001n, 1002n, 1005n]).toArray();
|
|
|
|
// By offset -- the full range is always [0, tbl.countRows())
|
|
const rowsByOffset = await tbl.takeOffsets([0, 1, 2]).toArray();
|
|
```
|
|
|
|
```rust
|
|
use lancedb::query::ExecutableQuery;
|
|
|
|
// By row ID -- from a previous search or scan that selected "_rowid"
|
|
let rows_by_id = tbl.take_row_ids(vec![1001, 1002, 1005]).execute().await?;
|
|
|
|
// By offset -- the full range is always [0, tbl.count_rows().await?)
|
|
let rows_by_offset = tbl.take_offsets(vec![0, 1, 2]).execute().await?;
|
|
```
|
|
</CodeGroup>
|
|
|
|
Both methods return a query builder; call `.to_arrow()`, `.to_list()` or
|
|
similar to execute it.
|
|
|
|
<Note>
|
|
The guarantees for row IDs below apply to SDK releases containing
|
|
[the ordering fix in #4826](https://github.com/lancedb/lancedb/pull/4826).
|
|
Earlier releases may collapse repeated IDs and return rows in a different
|
|
order.
|
|
</Note>
|
|
|
|
`take_row_ids` returns matching rows in the order of the input IDs and
|
|
preserves repeated IDs in both LanceDB Embedded and LanceDB Enterprise. For example,
|
|
`take_row_ids([1005, 1001, 1005])` returns row 1005, row 1001, then row 1005
|
|
again if both IDs exist. Missing or deleted IDs are omitted. This supports
|
|
sampling with replacement. In Rust, an explicit nonempty
|
|
`QueryBase::order_by` list overrides the requested order; limits and offsets
|
|
apply after restoring repeated rows.
|
|
|
|
`take_offsets` also preserves repeated offsets, but does not guarantee
|
|
input order. It tracks `_rowoffset` internally to restore repeated rows
|
|
and drops that column from the output unless you explicitly `select` it.
|
|
Add `_rowoffset` to your selection when you need to align a `take_offsets`
|
|
result with the input offsets.
|
|
|
|
<Note>
|
|
Ordered takes by row ID collect all matching batches, combine them into one
|
|
batch and reorder the rows before returning output batches. Even
|
|
`to_batches(max_batch_length=1)` only limits the returned batch size; it
|
|
does not bound working memory. For large requests or rows with large
|
|
column values, split the input IDs into smaller requests and use
|
|
`.select(...)` to fetch only the columns you need.
|
|
</Note>
|
|
|
|
Offsets are mostly useful for sampling: the valid range is always
|
|
`[0, len(table))`, so you can draw a random sample of offsets without
|
|
reading the table first. Row IDs require you to already have them, from an
|
|
earlier query's `_rowid` column -- but unlike offsets, a row ID keeps
|
|
addressing the same logical row even after compaction, as long as the
|
|
table has stable row IDs enabled.
|
|
|
|
## From SQL {#sql}
|
|
|
|
The same three identifiers are available as
|
|
[pseudo-columns](/sql/data-types#pseudo-columns) -- select or filter on
|
|
them like any other column:
|
|
|
|
<CodeGroup>
|
|
```sql SQL icon="database"
|
|
-- By row ID
|
|
SELECT * FROM documents WHERE _rowid IN (1001, 1002, 1005);
|
|
|
|
-- By row address
|
|
SELECT * FROM documents WHERE _rowaddr IN (4294967296, 4294967297);
|
|
|
|
-- Including the identifier in the result, to save for a later take
|
|
SELECT _rowid, * FROM documents WHERE score > 0.9;
|
|
```
|
|
</CodeGroup>
|
|
|
|
There is no dedicated Python method for taking by row address -- use
|
|
`.where("_rowaddr IN (...)")` on the SDK query builder, or the SQL form
|
|
above, since the expression language is the same either way.
|
|
|
|
## Related guides {#reference}
|
|
|
|
[Query plans](/basics/sql#query-plans) show whether a row-ID or offset
|
|
filter reads the whole table or seeks directly to the requested rows. See
|
|
[Statistical Sampling](/curation/statistical-sampling) for a random
|
|
fraction instead of specific rows, and [Permutation](/training/permutation)
|
|
for shuffled, batched reads through the Python SDK.
|