mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-14 06:36:54 +00:00
## Depends on merged #4951 Phase 1 merged as `4d22c0ce2b6825c880c818e737b079db494b4053`, so this PR now targets `main`. It reuses the canonical `FunctionWorkerResourceBudget` introduced there rather than copying the type. For Function `add_columns` declarations, Python and Rust now persist `auto_refresh_resource_budget` in computed-column metadata and send it as `auto_refresh_resource_budget` in the remote request. CPU milliseconds and memory bytes are required positive integers. Omitting `gpu_count` is accepted as zero and the canonical outgoing JSON still contains `"gpu_count": 0`. Unknown fields, missing CPU or memory, wrong types, zero CPU or memory, negative values, and overflow are rejected. The same budget is applied to every public output binding of a multi-output Function declaration. Existing metadata is retained, and SQL-expression declarations or local execution reject the budget instead of silently ignoring it. Node is unchanged because its current `addColumns` surface only accepts SQL computed expressions and has no Function declaration path. This change does not implement fleet call concurrency limits or enforce the budget in the runtime. The Enterprise server must support the nested Function worker resource-budget contract before these values can act as execution limits. ## Validation - Python extension build passed. - Remote Function declaration and local unsupported-path rejection each passed. - Canonical metadata round-trip passed, including omitted GPU decoding to zero and explicit `gpu_count: 0` on encode. - Function multi-output remote wire test passed with `--features remote`. - Repository-scoped Python type check passed with 0 errors. - All runtime source blobs match the native-tested source. The post-native deltas are one documentation example line and an existing routing-test stub update for the new optional argument; the affected parameterized test passed 3/3 against the existing extension build. No private test runners are part of this PR.
266 lines
9.6 KiB
Plaintext
266 lines
9.6 KiB
Plaintext
---
|
|
title: "Create a computed column"
|
|
sidebarTitle: "Create Computed Column"
|
|
description: "Declare a column from a SQL expression. Nothing is evaluated until you refresh it."
|
|
icon: table-columns
|
|
keywords: ["computed columns", "add_columns", "transforms", "schema"]
|
|
availability:
|
|
embedded: unavailable
|
|
enterprise: available
|
|
---
|
|
|
|
import {
|
|
PySchemaAddSetup,
|
|
} from '/snippets/tables.mdx';
|
|
|
|
Lance can write a new column without rewriting the columns already in the
|
|
table. That matters most on a table that also holds large images, video or
|
|
embeddings, where rewriting everything just to add one small derived field
|
|
would be wasteful. See [Lance format](/basics/lance) for the storage model
|
|
this relies on.
|
|
|
|
A computed column is how LanceDB formalizes that capability for feature
|
|
engineering specifically: instead of writing the derived values immediately,
|
|
it is declared with an expression and starts with no values. Declaring it is
|
|
cheap on any table size, because nothing is evaluated until
|
|
[a refresh](/feature-engineering/refresh-column) asks for it.
|
|
|
|
<Note>
|
|
`computed` requires LanceDB Enterprise. Ordinary eager `add_columns`
|
|
transforms, without `computed`, remain available in LanceDB Embedded.
|
|
</Note>
|
|
|
|
## Fill a column with a Function {#functions}
|
|
|
|
<Badge color="red">Enterprise</Badge>
|
|
|
|
When the value cannot be expressed in SQL -- an embedding, a model call, an
|
|
image decode -- the column is filled by a registered
|
|
[function](/management/function) instead of a SQL expression. It needs a
|
|
deployment with Functions enabled.
|
|
|
|
Connect with an [Enterprise connection](/basics/catalog#enterprise).
|
|
Continuing the [`caption` Function](/curation/create-function#chain)
|
|
registered in Create a Function, against a `frames` table that already has
|
|
a `jpeg` column:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
version = db.create_function_async(caption).wait()
|
|
table.add_columns({"caption": version(jpeg=col("jpeg"))})
|
|
```
|
|
|
|
```sql SQL icon="database"
|
|
ALTER TABLE frames ADD COLUMN caption AS caption(jpeg) WITH (version = 'fv_1')
|
|
```
|
|
</CodeGroup>
|
|
|
|
A Function application is called with its parameters bound to source
|
|
columns through `col(...)`, keyed by the output column name. It commits a
|
|
new, empty table version the same way a SQL expression does, so the usual
|
|
[versioning](/management/table#versioning) applies.
|
|
|
|
Both a Function application and a SQL expression accept
|
|
`auto_refresh_enabled` and `auto_refresh_interval_seconds`, and both refresh
|
|
and toggle the same way; see [Auto Refresh](/enterprise/auto-refresh).
|
|
|
|
Python and Rust Function declarations can also set
|
|
`auto_refresh_resource_budget` to give their automatic refreshes an aggregate
|
|
CPU, memory, and optional GPU budget. CPU and memory are required. The nested
|
|
`gpu_count` defaults to zero, so omitting it requests CPU-only workers:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
table.add_columns(
|
|
{"caption": version(jpeg=col("jpeg"))},
|
|
auto_refresh_resource_budget={
|
|
"cpu_millis": 8000,
|
|
"memory_bytes": 16 * 1024**3,
|
|
},
|
|
)
|
|
```
|
|
|
|
```rust Rust icon="rust"
|
|
table
|
|
.add_columns()
|
|
.function(function_application)
|
|
.computed_options(ComputedColumnOptions {
|
|
auto_refresh_enabled: true,
|
|
auto_refresh_interval_seconds: None,
|
|
auto_refresh_resource_budget: Some(
|
|
FunctionWorkerResourceBudget::new(8000, 17_179_869_184),
|
|
),
|
|
})
|
|
.execute()
|
|
.await?;
|
|
```
|
|
</CodeGroup>
|
|
|
|
This setting applies to every output in the Function binding. SQL-expression
|
|
columns do not use Function workers, so they reject
|
|
`auto_refresh_resource_budget` instead of accepting an option that has no
|
|
effect. The separate `resource_budget` refresh option configures one explicit
|
|
refresh and does not change this automatic-refresh declaration. The Node.js
|
|
SDK does not currently expose a Function declaration through `addColumns`, so
|
|
this option is not added to an unsupported surface.
|
|
|
|
## Fill a column with a SQL expression {#expressions}
|
|
|
|
A value that SQL can compute doesn't need a Function. Declare it with
|
|
`computed` instead, as an expression evaluated over the row:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
table = db.create_table("computed_demo", [{"x": 1}, {"x": 2}])
|
|
|
|
table.add_columns(computed={"doubled": "x * 2"})
|
|
# AddColumnsResult(version=2)
|
|
```
|
|
|
|
```sql SQL icon="database"
|
|
ALTER TABLE computed_demo ADD COLUMN doubled AS x * 2
|
|
```
|
|
</CodeGroup>
|
|
|
|
That commits a new table version with the column present and empty. It is an
|
|
ordinary table version, so the usual [versioning](/management/table#versioning) applies,
|
|
including that you can travel back to before the column existed.
|
|
|
|
The expression can reference any column in the table, including another
|
|
computed column -- declaration order is dependency order, so it may
|
|
reference one declared earlier in the same mapping, but not one declared
|
|
after it:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
table.add_columns(computed={
|
|
"power_score": "cast(x as double) / 2.0",
|
|
"is_strong": "power_score > 0.5",
|
|
})
|
|
```
|
|
|
|
```sql SQL icon="database"
|
|
ALTER TABLE computed_demo ADD COLUMN power_score AS cast(x as double) / 2.0
|
|
ALTER TABLE computed_demo ADD COLUMN is_strong AS power_score > 0.5
|
|
```
|
|
</CodeGroup>
|
|
|
|
The functions available are the ones the
|
|
[SQL dialect](/sql/dql/functions) documents.
|
|
|
|
A literal in the expression has to fit the column it meets: `x / 2.0` is
|
|
refused when `x` is an integer column, so write `cast(x as double) / 2.0`.
|
|
|
|
<Warning>
|
|
Keep the expression to top-level columns. A column refresh can fail on an
|
|
expression that reads a field inside a struct, such as
|
|
`stats.magic + stats.strength`. Try a new expression on a small table first.
|
|
</Warning>
|
|
|
|
## Compute now or on refresh {#computed-vs-transforms}
|
|
|
|
`add_columns` takes either, and the difference matters:
|
|
|
|
- **SQL expressions in `transforms`** are evaluated *now*, for every row, as
|
|
part of the call. Good for a small table, or a column you want populated
|
|
immediately. A `FunctionApplication` passed through `transforms` instead
|
|
declares empty Function-bound columns, which a refresh fills; that needs a
|
|
deployment with Functions enabled, and the embedded library refuses it.
|
|
- **`computed`** stores the expression and evaluates nothing. The column is
|
|
committed empty and filled later by
|
|
[`refresh_column`](/feature-engineering/refresh-column).
|
|
|
|
`computed` and `transforms` cannot be combined in one call. Reach for `computed` when the table is
|
|
large enough that you would rather control when the work happens.
|
|
|
|
Automatic refresh is on by default for each computed column, and takes effect
|
|
when your Enterprise deployment enables its refresh scheduler. Until then,
|
|
call [`refresh_column`](/feature-engineering/refresh-column) yourself. Pass
|
|
`auto_refresh_enabled=False` to opt a column out, or
|
|
`auto_refresh_interval_seconds` to set how often it refreshes:
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
table.add_columns(
|
|
computed={"doubled": "x * 2"},
|
|
auto_refresh_interval_seconds=300,
|
|
)
|
|
```
|
|
|
|
```sql SQL icon="database"
|
|
ALTER TABLE computed_demo ADD COLUMN doubled AS x * 2
|
|
WITH (auto_refresh_interval_seconds = 300)
|
|
```
|
|
</CodeGroup>
|
|
|
|
See [Auto Refresh](/enterprise/auto-refresh) for the
|
|
full behavior, including how to change the setting later.
|
|
|
|
An eager `transforms` call looks like this, starting from a small product
|
|
table:
|
|
|
|
<CodeGroup>
|
|
<CodeBlock filename="Python" language="Python" icon="python">
|
|
{PySchemaAddSetup}
|
|
</CodeBlock>
|
|
</CodeGroup>
|
|
|
|
<CodeGroup>
|
|
```python Python icon="python"
|
|
# Add a discounted price column (10% discount)
|
|
table.add_columns({"discounted_price": "cast((price * 0.9) as float)"})
|
|
```
|
|
|
|
```sql SQL icon="database"
|
|
ALTER TABLE schema_evolution_add_example ADD COLUMN discounted_price
|
|
AS cast((price * 0.9) as float)
|
|
```
|
|
</CodeGroup>
|
|
|
|
SQL's `ADD COLUMN ... AS` always declares the column empty and defers the
|
|
expression to a refresh, the same as `computed` does -- there is no SQL
|
|
form of eager `transforms`. The Python call above is the only way to get
|
|
discounted_price filled immediately; declaring it in SQL fills it only
|
|
once [`REFRESH COLUMN`](/sql/dml/refresh-column) runs.
|
|
|
|
For feature engineering on large existing tables, group related derived
|
|
features into one `add_columns` call instead of running many separate
|
|
writes: this creates one new table version for the schema change and
|
|
computes every new column from the existing rows in it, rather than growing
|
|
the table's version history with many small updates.
|
|
|
|
<Info>
|
|
On local tables, Python `add_columns` also accepts pandas or Polars
|
|
DataFrames, PyArrow Tables, RecordBatches, RecordBatchReaders, and iterables
|
|
of batches. Pass only the new columns, with exactly one row per current live
|
|
table row in storage scan order. DataFrame indexes are ignored, and reader
|
|
batch boundaries need not match table fragments. Keep the table unchanged
|
|
between computing the values and adding them. Readers are consumed once.
|
|
|
|
Data inputs cannot be combined with `computed` columns and are not supported
|
|
on remote tables. Python callables and batch UDFs are not accepted; compute
|
|
those values first, then pass the result as a DataFrame or reader.
|
|
</Info>
|
|
|
|
## Change columns used by a computed column {#input-constraints}
|
|
|
|
<Warning>
|
|
While a computed column's declaration references another column, that column
|
|
cannot be renamed, retyped, or dropped. Drop the computed column first
|
|
if you need to change one of its inputs.
|
|
</Warning>
|
|
|
|
This is worth knowing before deriving a column from something you expect to
|
|
reshape later. Dropping the computed column releases the constraint.
|
|
|
|
## Related guides {#next}
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Refresh a computed column" icon="arrows-rotate" href="/feature-engineering/refresh-column">
|
|
Fill the rows that lack values.
|
|
</Card>
|
|
<Card title="Functions" icon="code" href="/management/function">
|
|
Fill a column with Python.
|
|
</Card>
|
|
</CardGroup>
|