Computes the cumulative sum of values in a column. For this method to work properly, ensure your data is sorted first.
The name of the column storing the values to be accumulated.
The name of the new column in which the computed cumulative values will be stored.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to partition by. Accumulation is performed independently within each group.
The table, so methods can be chained.
// Compute the cumulative sum of 'sales' in a new 'cumulativeSales' column
// Ensure the table is sorted by a relevant column (e.g., date) before calling this method.
await table.accumulate("sales", "cumulativeSales").log();
Adds one or more summary rows to the table. Each row is calculated from the original rows before any summary rows are added. This is useful for preparing totals and other statistics before exporting tabular data.
Passing "all" selects every numeric column. Columns that are neither
summarized nor used for labels contain NULL in the added rows. A stat
string is also used as its row label; pass an object to customize that
label. If options.stats is omitted, every supported stat is added.
The numeric column name, an array of numeric column names, or "all" to summarize every numeric column.
The existing string column in which stat row labels will be written.
An optional object with configuration options:
Optionalposition?: "top" | "bottom"Whether to add the summary rows at the "top" or "bottom" of the table. Defaults to "bottom".
Optionalstats?: A stat, stat configuration, or array of either. Supported stats are "countDistinct", "countNull", "min", "max", "mean", "median", "sum", "skew", "stdDev", and "variance". An object's label defaults to its stat. If omitted, all supported stats are added.
The table, so methods can be chained.
// Add a total row for every numeric column, labelled "sum" in "region".
await table.addSummaryRows("all", "region", { stats: "sum" }).log();
Adds numeric start and end columns for bins of the specified interval size.
Each bin includes its start and excludes its end: start <= value < end.
A value exactly on an end boundary belongs to the next bin. Null source
values produce null in both output columns.
The numeric column containing values from which bins will be computed.
The finite, positive interval size for binning the values.
The required name of the new numeric column containing inclusive bin starts.
The required name of the new numeric column containing exclusive bin ends. Must differ from startColumn.
An optional object with configuration options:
OptionalstartValue?: numberThe finite starting value for binning, no greater than the minimum source value. Defaults to the minimum value in the specified column.
The table, so methods can be chained.
Computes proportions vertically over a column's values, relative to the sum of all values in that column or group.
The column containing values for which proportions will be computed. The proportions are calculated based on the sum of values in the specified column.
The name of the new column where the proportions will be stored.
An optional object with configuration options:
Optionalbase?: numberA finite positive value that the proportions in the column or each group sum to before rounding. Defaults to 1.
Optionalby?: string | string[]The column name or an array of column names to partition by. Proportions are calculated independently within each group.
Optionaldecimals?: numberThe number of decimal places to round the computed proportions. Defaults to undefined (no rounding).
The table, so methods can be chained.
// Add a new column 'perc' with each 'column1' value divided by the sum of all 'column1' values
await table.columnProportions("column1", "perc").log();
// Compute proportions for 'column1' by 'column2', rounded to two decimal places
await table.columnProportions("column1", "perc", { by: "column2", decimals: 2 }).log();
Calculates correlations between columns. If no x and y columns are specified, the method computes the correlations for all numeric column combinations.
Note that correlation is symmetrical: the correlation of x with y is the same as y with x.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to group by. Correlations are calculated independently within each group.
Optionaldecimals?: numberThe number of decimal places to round the correlation values. Defaults to undefined (no rounding).
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
Optionalx?: stringThe name of the column for the x-values. If omitted, correlations will be computed for all numeric columns.
Optionaly?: stringThe name of the column for the y-values. It can be provided only when options.x is also set. If both are omitted, correlations will be computed for all numeric column pairs.
A table instance containing the correlation results (either the current table or a new table), so methods can be chained.
// Compute correlations between all numeric columns, overwriting the current table
await table.correlations().log();
// Compute correlations between 'column1' and all other numeric columns
await table.correlations({ x: "column1" }).log();
// Compute the correlation between 'column1' and 'column2'
await table.correlations({ x: "column1", y: "column2" }).log();
Indexes a numeric column by dividing each value by a reference value and multiplying the result by a base value.
The reference can be calculated from the indexed column with a statistic, read from exactly one row selected by another column's value, or read from the unique row where another column reaches its minimum or maximum. With options.by, references are calculated or selected independently within each group. Null values in the indexed column remain null when their group has a valid reference. The operation throws when a group has no unique selected row or its reference value is null or zero.
Exact temporal references are compared at their full DuckDB precision. JavaScript Date objects only have millisecond precision and always represent an instant. Construct them with an explicit timezone, such as new Date("2001-01-01T00:00:00Z"); date-time strings without Z or an offset use the user's local timezone.
The numeric column containing the values to index.
The name of the new column where indexed values will be stored.
A statistic calculated from column, a column and exact non-null equals value selecting a row, or a column and at set to min or max selecting its unique extreme row. The selected row's column value becomes the reference.
Optionalat?: undefinedSelects the unique row where reference.column reaches its minimum or maximum.
Optionalcolumn?: undefinedThe column used to select an exact reference row or its unique minimum or maximum row.
Optionalequals?: undefinedThe non-null value used to select an exact reference row. Its JavaScript type must be compatible with the reference column's DuckDB type; string, numeric, boolean, and temporal values are not coerced across type families. Date values should be constructed with an explicit timezone.
The statistic used to calculate the reference directly from the indexed column.
Optionalat?: undefinedSelects the unique row where reference.column reaches its minimum or maximum.
The column used to select an exact reference row or its unique minimum or maximum row.
The non-null value used to select an exact reference row. Its JavaScript type must be compatible with the reference column's DuckDB type; string, numeric, boolean, and temporal values are not coerced across type families. Date values should be constructed with an explicit timezone.
Optionalstat?: undefinedThe statistic used to calculate the reference directly from the indexed column.
Selects the unique row where reference.column reaches its minimum or maximum.
The column used to select an exact reference row or its unique minimum or maximum row.
Optionalequals?: undefinedThe non-null value used to select an exact reference row. Its JavaScript type must be compatible with the reference column's DuckDB type; string, numeric, boolean, and temporal values are not coerced across type families. Date values should be constructed with an explicit timezone.
Optionalstat?: undefinedThe statistic used to calculate the reference directly from the indexed column.
An optional object with configuration options.
Optionalbase?: numberThe finite positive value assigned to the reference. Defaults to 100.
Optionalby?: string | string[]A column name or an array of column names to partition by. The reference is calculated independently within each group.
Optionaldecimals?: numberA finite non-negative integer specifying the number of decimal places to retain. By default, values are not rounded.
The table, so methods can be chained.
// Index each country's average home price to its January 2001 value.
await table.indexValues(
"homePrice",
"homePriceIndexed",
{
column: "date",
equals: new Date("2001-01-01T00:00:00Z"),
},
{
by: "country",
base: 100,
decimals: 1,
},
).log();
Performs linear regression analysis. The results include the slope, the y-intercept, and the R-squared value.
If no x and y columns are specified, the method computes linear regression analysis for all numeric column permutations.
Note that linear regression analysis is asymmetrical: the linear regression of x over y is not the same as y over x.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to group by. Linear regressions are calculated independently within each group.
Optionaldecimals?: numberThe number of decimal places to round the regression values (slope, intercept, r-squared). Defaults to undefined (no rounding).
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
Optionalx?: stringThe name of the column for the independent variable (x-values). If omitted, linear regressions will be computed for all numeric columns as x.
Optionaly?: stringThe name of the column for the dependent variable (y-values). It can be provided only when options.x is also set. If both are omitted, linear regressions will be computed for all numeric column permutations.
A table instance containing the linear regression results (either the current table or a new table), so methods can be chained.
// Compute all linear regressions between all numeric columns, overwriting the current table
await table.linearRegressions().log();
// Compute linear regressions with 'column1' as the independent variable and all other numeric columns as dependent variables
await table.linearRegressions({ x: "column1" }).log();
// Compute the linear regression of 'sales' (y) over 'advertising' (x)
await table.linearRegressions({ x: "advertising", y: "sales" }).log();
Normalizes the values in a column using min-max normalization.
The name of the column in which values will be normalized.
The name of the new column where normalized values will be stored.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to partition by. Normalization is performed independently within each group.
Optionaldecimals?: numberThe number of decimal places to round the normalized values. Defaults to undefined (no rounding).
Optionalrange?: [number, number]The inclusive range to scale normalized values to, as [minimum, maximum]. Both values must be finite and the minimum must be less than the maximum. Defaults to [0, 1].
The table, so methods can be chained.
// Normalize the values in 'column1' and store them in a new 'normalizedColumn1' column
await table.normalize("column1", "normalizedColumn1").log();
// Normalize 'value' by 'group'
await table.normalize("value", "normalizedValue", { by: "group" }).log();
Identifies outliers in a specified column using the Interquartile Range (IQR) method.
The name of the column in which outliers will be identified.
The name of the new column where the boolean results (TRUE for outlier, FALSE otherwise) will be stored.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to partition by. Outliers are detected independently within each group.
The table, so methods can be chained.
Assigns quantiles to rows in a new column based on specified column values.
The column containing values from which quantiles will be assigned.
The number of quantiles to divide the data into (e.g., 4 for quartiles, 10 for deciles).
The name of the new column where the assigned quantiles will be stored.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to partition by. Quantiles are assigned independently within each group.
The table, so methods can be chained.
// Assigns a quantile from 1 to 10 for each row in a new 'quantiles' column, based on 'column1' values.
await table.quantiles("column1", 10, "quantiles").log();
Assigns ranks to rows in a new column based on the values of a specified column.
The column containing the values to be used for ranking.
The name of the new column where the ranks will be stored.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to rank by. Ranks are assigned independently within each group.
Optionaldense?: booleanA boolean indicating whether to use dense ranking (no gaps). If true, ranks will be consecutive integers (e.g., 1, 2, 2, 3). If false (default), ranks might have gaps (e.g., 1, 2, 2, 4).
Optionalorder?: "asc" | "desc"The order of values for ranking: "asc" for ascending (default) or "desc" for descending.
The table, so methods can be chained.
// Compute ranks in a new 'rank' column based on 'score' values (ascending)
await table.ranks("score", "rank").log();
// Compute ranks in a new 'descRank' column based on 'score' values (descending)
await table.ranks("score", "descRank", { order: "desc" }).log();
Computes rolling aggregations (e.g., rolling average, min, max) over a specified column.
For rows without enough preceding or following rows to form a complete window, NULL will be returned.
For this method to work properly, ensure your data is sorted by the relevant column(s) first.
The name of the column storing the values to be aggregated.
The name of the new column in which the computed rolling values will be stored.
The aggregation function to apply: "min", "max", "mean", "median", or "sum".
The number of preceding rows to include in the rolling window.
The number of following rows to include in the rolling window.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to partition by. Rolling statistics are computed independently within each group.
Optionaldecimals?: numberThe number of decimal places to round the aggregated values. Defaults to undefined (no rounding).
The table, so methods can be chained.
// Compute a 7-day rolling average of 'sales' with 3 preceding and 3 following rows
// (total window size of 7: 3 preceding + current + 3 following)
await table.rolling("sales", "rollingAvgSales", "mean", 3, 3).log();
Computes proportions horizontally across specified columns for each row.
For example, given a table showing counts of men, women, and non-binary individuals per year:
| Year | Men | Women | NonBinary |
|---|---|---|---|
| 2021 | 564 | 685 | 145 |
| 2022 | 354 | 278 | 56 |
| 2023 | 856 | 321 | 221 |
This method computes the proportion of men, women, and non-binary individuals on each row, adding new columns for these proportions.
An array of column names for which proportions will be computed on each row.
An optional object with configuration options:
Optionalbase?: numberA finite positive value that the proportions in each row sum to before rounding. Defaults to 1.
Optionaldecimals?: numberThe number of decimal places to round the computed proportions. Defaults to undefined (no rounding).
Optionalsuffix?: stringA string suffix to append to the names of the new columns storing the computed proportions. Defaults to "Perc".
The table, so methods can be chained.
// Compute horizontal proportions for 'Men', 'Women', and 'NonBinary' columns, rounded to 2 decimal places
await table.rowProportions(["Men", "Women", "NonBinary"], { decimals: 2 }).log();
The table will then look like this:
| Year | Men | Women | NonBinary | MenPerc | WomenPerc | NonBinaryPerc |
|---|---|---|---|---|---|---|
| 2021 | 564 | 685 | 145 | 0.4 | 0.49 | 0.10 |
| 2022 | 354 | 278 | 56 | 0.51 | 0.4 | 0.08 |
| 2023 | 856 | 321 | 221 | 0.61 | 0.23 | 0.16 |
By default, the new columns will be named with a suffix of "Perc". You can customize this suffix using the suffix option.
// Compute horizontal proportions with a custom suffix "Prop"
await table.rowProportions(["Men", "Women", "NonBinary"], { suffix: "Prop", decimals: 2 }).log();
The table will then look like this:
| Year | Men | Women | NonBinary | MenProp | WomenProp | NonBinaryProp |
|---|---|---|---|---|---|---|
| 2021 | 564 | 685 | 145 | 0.4 | 0.49 | 0.10 |
| 2022 | 354 | 278 | 56 | 0.51 | 0.4 | 0.08 |
| 2023 | 856 | 321 | 221 | 0.61 | 0.23 | 0.16 |
Selects a ranked numeric value within each row and adds its source column name, its value, or both as new columns.
Values are ranked from highest to lowest by default. Null values are
ignored. By default, a tie at the requested rank throws an error. Set
options.ties to "first" to select the first tied column in the supplied
order, or to "all" to produce one row for each tied column. The "all"
option can therefore increase the table's row count. If null values leave
a row without the requested rank, the new columns contain null.
The numeric columns to rank within each row.
The output columns and ranking configuration. At least one of nameColumn or valueColumn is required.
The name of a new column containing the selected source column's name.
OptionalvalueColumn?: stringThe name of a new column containing the selected source column's value.
OptionalnameColumn?: stringThe name of a new column containing the selected source column's name.
The name of a new column containing the selected source column's value.
Optionalorder?: "asc" | "desc"The ranking order: "desc" ranks the highest value first and "asc"
ranks the lowest value first. Defaults to "desc".
Optionalrank?: numberThe one-based rank to select. Must not exceed the number of supplied
columns. Defaults to 1.
Optionalties?: "all" | "strict" | "first"How to handle a tie at the requested rank: "strict" throws, "first"
selects the first supplied column, and "all" produces one row per
tied column. Defaults to "strict".
The table, so methods can be chained.
Measures numeric profile similarity using each row's Mahalanobis distance
from a supplied reference point and stores it in a new DOUBLE column.
Smaller distances indicate more similar profiles. Optionally adds a
dataset-relative similarity score, where larger values mean more similar.
Sample covariance (n - 1) is estimated from the dataset, independently
of the reference point.
Accounts for differences in feature scales and correlations between features, making it useful for comparing profiles with measurements in different units.
Pass one numeric LIST or ARRAY column, or an array of numeric scalar columns. Reference arrays follow the same dimension order. For scalar columns, a reference object supplies its own finite numeric values by column name; extra fields are ignored. Reference values are captured when this method is called. Inputs are converted privately to DOUBLE, which can lose precision for large integers and exact decimals; source columns and types remain unchanged.
Requires more rows than dimensions and finite, non-null, consistent features with invertible, numerically stable covariance. Invalid inputs leave the source unchanged.
A numeric vector column, or numeric scalar columns in feature order.
An array of finite numbers in feature order, or an object with an own finite numeric value for each scalar feature column; may be outside the dataset.
The name of the new DOUBLE distance column.
Optional output settings.
OptionalsimilarityColumn?: string | booleanA custom name, or true for a new DOUBLE column named "similarity"; false or omitted adds no score. The dataset-relative
score is 1 - distance / maxDistance. Exact matches score 1 and the farthest
rows score 0; if all distances are zero, every score is 1.
The table, so methods can be chained.
// Measure distance from a reference height and weight.
await table
.similarityMahalanobis(["height", "weight"], [175, 70], "distance")
.log();
Creates a summary table from selected columns, optionally grouped by other columns. This method allows you to aggregate data, calculate statistics (e.g., count, mean, sum), and group results by categorical columns.
An object with configuration options for summarization:
Optionalby?: string | string[]The column name or an array of column names to group by.
Optionalcolumns?: string | string[]The column name or an array of column names to summarize. If omitted, only the row count is returned.
OptionaldatesToMs?: booleanIf true, timestamps, dates, and times will be converted to milliseconds before summarizing. This is useful when summarizing mixed data types (numbers and dates) as columns must be of the same type for aggregation.
Optionaldecimals?: numberThe number of decimal places to round the summarized columns. Defaults to undefined (no rounding).
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
Optionalstats?: The statistics to compute. Can be a single statistic (e.g., "mean"), an array (e.g., ["min", "max"]), or an object mapping output column names to statistics (e.g., { avgSalary: "mean" }). Supported statistics are "count", "countDistinct", "countNull", "min", "max", "mean", "median", "sum", "skew", "stdDev", and "variance".
A table instance containing the summarized data (either the current table or a new table), so methods can be chained. When summarizing more than one column, a column column identifies which input column each row summarizes.
// Summarize all columns with all available statistics, overwriting the current table
const columns = await table.getColumns();
await table.summarize({ columns }).log();
// Summarize all columns and store the results in a new table with a generated name
const columns = await table.getColumns();
const summaryTable = await table.summarize({ columns, outputTable: true }).log();
// Summarize all columns and store the results in a new table named 'mySummary'
const columns = await table.getColumns();
const mySummaryTable = await table.summarize({ columns, outputTable: "mySummary" }).log();
// Summarize a single column ('sales') with all available statistics
await table.summarize({ columns: "sales" }).log();
// Summarize multiple columns ('sales' and 'profit') with all available statistics
await table.summarize({ columns: ["sales", "profit"] }).log();
// Summarize 'sales' by 'region' (single category)
await table.summarize({ columns: "sales", by: "region" }).log();
// Summarize 'sales' by 'region' and 'product_type'
await table.summarize({ columns: "sales", by: ["region", "product_type"] }).log();
// Summarize 'sales' by 'region' with a specific statistic (mean)
await table.summarize({ columns: "sales", by: "region", stats: "mean" }).log();
// Summarize 'sales' by 'region' with specific statistics (mean and sum)
await table.summarize({ columns: "sales", by: "region", stats: ["mean", "sum"] }).log();
// Summarize 'sales' by 'region' with custom named statistics
await table.summarize({ columns: "sales", by: "region", stats: { averageSales: "mean", totalSales: "sum" } }).log();
Computes the Z-score for values in a specified column.
The name of the column for which Z-scores will be calculated.
The name of the new column where the computed Z-scores will be stored.
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to partition by. Z-scores are calculated independently within each group.
Optionaldecimals?: numberThe number of decimal places to round the Z-score values. Defaults to undefined (no rounding).
The table, so methods can be chained.
Caches the results of computations in ./.sda-cache.
You should add ./.sda-cache to your .gitignore file.
Callback code and function/class inputs ignore comments, indentation, and line wrapping where safe.
cache() automatically tracks whether earlier SDA operations changed the
table. It also records every other already registered SimpleTable read
through SimpleTable methods while compute runs and invalidates the
cached step when any of their generations change. Tables created inside
compute are part of the computation itself and are not dependencies.
Inside compute, you may modify the table being cached and create or
modify temporary tables, provided you remove them before the callback
finishes. Other tables that already existed may be read but not modified,
renamed, or removed. customQuery() bypasses enforcement of these rules.
SimpleDB.customQuery() bypasses this tracking. Reading or changing a
table with customQuery() can therefore return stale cached data. Include
a value that identifies the custom query's dependencies in options.inputs
(such as a table content hash), or use tracked SimpleTable methods.
Cache entries are stored as DuckDB database files. Full-text search (FTS) indexes are persisted in the cache file and restored directly on a cache hit. Vector similarity search (VSS/HNSW) indexes are not persisted in the cache file; their definitions are stored as metadata and used to rebuild the indexes on every cache hit. If loading the entry or restoring its indexes fails, the computation runs again and replaces the cache entry.
A function wrapping the computations to be cached. It receives the table on which cache() was called. This function will be executed on the first run or if the cached data is invalid/expired.
An optional object with configuration options:
Optionalinputs?: readonly unknown[]An ordered array of additional values captured by compute that affect its result. Each position is compared structurally across runs, so adding, removing, moving, or changing an input invalidates the cache. Functions and class constructors are compared by source. SimpleTable dependencies read by compute are tracked automatically, and the table being cached is already tracked, so neither needs to be included here.
Optionalttl?: numberCache lifetime in seconds. Omit for no expiration, use 0 to refresh the matching cache entry immediately, or provide a positive value to refresh once the entry reaches that age. The cache is also invalidated when the compute function, the table, or an input changes.
A promise that resolves to the table, so methods can be chained.
// Computations are re-run if the callback changes or earlier operations modify the table
const sdb = new SimpleDB();
const items = await sdb.newTable("items").cache((table) => {
table
.loadData("items.csv")
.summarize({
columns: "price",
by: "department",
stats: ["min", "max", "mean"],
});
});
await items.log();
// It's important to call close() on the SimpleDB instance to clean up the cache.
// This prevents the cache from growing indefinitely.
await sdb.close();
// Cache with a Time-To-Live (TTL) of 60 seconds
// The computations will be re-run if the cached data is older than 1 minute, the callback changes, or the table changes.
const sdb = new SimpleDB();
const table = await sdb.newTable().cache((table) => {
table
.loadData("items.csv")
.summarize({
columns: "price",
by: "department",
stats: ["min", "max", "mean"],
});
}, { ttl: 60 });
await table.log();
await sdb.close();
// Enable verbose logging for cache operations via SimpleDB instance
const sdb = new SimpleDB({ cacheVerbose: true });
const table = await sdb.newTable().cache((table) => {
table
.loadData("items.csv")
.summarize({
columns: "price",
by: "department",
stats: ["min", "max", "mean"],
});
});
await table.log();
await sdb.close();
// Read-only table dependencies are tracked automatically. Other captured values go in inputs.
const year = 2026;
const summary = await sdb.newTable("summary").cache(async (table) => {
table.loadArray(
await fires.getData({ conditions: `year = ${year}` }),
);
}, { inputs: [year] });
await summary.log();
Adds a new column to the table based on a specified data type (JavaScript or SQL types) and a SQL definition.
With the default SimpleDB.expressionSyntax: "js", expressions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The name of the new column to be added.
The data type for the new column. Can be a JavaScript type (e.g., "number", "string") or a SQL type (e.g., "integer", "varchar", "JSON", "FLOAT[3]", "GEOMETRY('EPSG:4326')").
A SQL expression defining how the values for the new column should be computed (e.g., "column1 + column2", "ST_Centroid(geom_column)").
The table, so methods can be chained.
// Add a new column 'total' as a float, calculated from 'column1' and 'column2'
await table.addColumn("total", "float", "column1 + column2").log();
// Add a new geometry column 'centroid' using the centroid of an existing 'country' geometry column
await table.addColumn("centroid", "geometry('EPSG:4326')", `ST_Centroid("country")`).log();
Adds a unique, non-null ID for each row in the current table. IDs start at
0 and identify rows within this table, including rows with identical
contents. Without a prefix, the new column contains numbers. When
a prefix is supplied, the new column contains strings with the
exact prefix followed by the row number. An empty prefix therefore creates
the strings "0", "1", and so on.
IDs are not globally unique: separate tables can generate the same IDs.
The name of the new ID column.
An optional object with configuration options.
Optionalprefix?: stringText to place before each row number. Supplying this option, including an empty string, creates string IDs.
The table, so methods can be chained.
Adds a new column to the table containing the row number, starting at 0 (like an index).
The name of the new column that will store the row number.
An optional object with configuration options:
Optionalby?: string | string[]A column name or an array of column names to partition by. The row number restarts at 0 within each group.
The table, so methods can be chained.
Cleans column names by removing non-alphanumeric characters and formatting them to camel case.
The table, so methods can be chained.
Clones an existing column in this table, creating a new column with identical values.
The name of the original column to clone.
The name of the new column to be created.
The table, so methods can be chained.
Clones a column in the table and offsets its values by a specified number of rows. This is useful for time-series analysis or comparing values across different time points.
Important: The offset is applied based on the current row order in the table. For meaningful results, ensure your data is sorted appropriately (e.g., by date/time for time-series analysis) before calling this method.
The name of the original column.
The name of the new column to be created with offset values.
An optional object with configuration options:
Optionalby?: string | string[]A column name or an array of column names to partition by. The offset is applied independently within each group.
Optionaloffset?: numberThe number of rows to offset the values. A positive number shifts values downwards (later rows), a negative number shifts values upwards (earlier rows). Defaults to 1.
The table, so methods can be chained.
// Clone 'value' as 'previous_value', offsetting by 1 row (value of row N-1 goes to row N)
await table.cloneColumnWithOffset("value", "previous_value").log();
// Clone 'sales' as 'sales_2_days_ago', offsetting by 2 rows
await table.cloneColumnWithOffset("sales", "sales_2_days_ago", { offset: 2 }).log();
Extracts one or more components from a temporal column into new columns. Pass a single part to create a column with that part's name, or pass an object mapping custom new-column names to parts. Existing columns are not overwritten.
dayOfWeek uses Sunday as 0 through Saturday as 6. week follows
ISO week numbering, and dayOfYear starts at 1. DuckDB DATE, TIME,
TIMESTAMP, and TIMESTAMP WITH TIME ZONE columns are supported when the
requested component applies to that type: date parts apply to dates and
timestamps, while time parts apply to times and timestamps. NULL input
values produce NULL extracted values. Parts extracted from
TIMESTAMP WITH TIME ZONE values use UTC.
The temporal column from which to extract components.
A part to extract using its name as the new column, or an object mapping each custom new-column name to the part it should contain.
The table, so methods can be chained.
Checks if a column with the specified name exists in the table.
The name of the column to check.
A promise that resolves to true if the column exists, false otherwise.
Removes one or more columns from this table.
The name or an array of names of the columns to be removed.
The table, so methods can be chained.
Renames one or more columns in the table. Throws if a source column does not exist, so a typo fails loudly instead of being silently ignored.
An object mapping old column names to their new column names (e.g., { "oldName": "newName", "anotherOld": "anotherNew" }).
Configuration options.
Optionalstrict?: booleanWhether to verify the source columns exist before renaming. Defaults to true. Set to false to skip the check and its schema lookup when you know the columns exist and are renaming across many tables where the extra round-trip adds up.
The table, so methods can be chained.
Creates an instance of SimpleTable.
The name of the table.
The SimpleDB instance that this table belongs to.
An optional object with configuration options:
OptionalcharsToLog?: numberThe maximum characters to display for text and stringified nested cells, including truncation markers (default: 75). Independent of column width. Use Infinity for unlimited text.
OptionalrowsToLog?: numberThe number of rows to log when displaying table data.
OptionaltypesToLog?: booleanA boolean indicating whether to include data types when logging a table.
Writes the table's data to a file in various formats (CSV, JSON, Parquet, DuckDB, SQLite).
Tables with geometry columns must use writeGeoData() instead.
If the specified path does not exist, it will be created.
The absolute path to the output file (e.g., "./output.csv", "./output.json").
An optional object with configuration options:
Optionalcompression?: booleanA boolean indicating whether to compress the output file. If true, CSV and JSON files will be compressed with GZIP, while Parquet files will use ZSTD. Defaults to false.
OptionaldataAsArrays?: booleanFor JSON files only. If true, JSON files are written as a single object with arrays for each column (e.g., { "col1": [v1, v2], "col2": [v3, v4] }) instead of an array of objects. This can reduce file size for web projects. You can use the arraysToData function from the journalism-format library to convert it back.
OptionalformatDates?: booleanFor CSV and JSON files only. If true, date and timestamp columns will be formatted as ISO 8601 strings (e.g., "2025-01-01T01:00:00.000Z"). Defaults to false.
A promise that resolves to the table, so methods can be chained.
Writes the table's geospatial data to a file in GeoJSON, GeoParquet, or Shapefile format. If the specified path does not exist, it will be created.
The absolute path to the output file (e.g., "./output.geojson", "./output.geoparquet", "./shapefile-folder/output.shp", "./output.shp.zip"). A .shp.zip extension writes a ZIP archive using fast DEFLATE compression. Creating the archive temporarily requires enough disk space for both the uncompressed Shapefile and the ZIP, and ZIP archives are limited to 4 GB.
An optional object with configuration options:
Optionalcompression?: booleanFor GeoParquet, if true, uses ZSTD compression; otherwise, uses DuckDB's default SNAPPY compression. SNAPPY prioritizes faster compression, while ZSTD typically produces smaller files but takes longer to write. Read performance depends on the data and storage because smaller files can reduce I/O. This option is not supported for GeoJSON or Shapefiles. Defaults to false.
OptionalformatDates?: booleanFor GeoJSON, if true, formats date and timestamp columns to ISO 8601 strings. Defaults to false.
Optionalmetadata?: unknownFor GeoJSON, an object to be added as top-level metadata to the GeoJSON output.
Optionalprecision?: numberFor GeoJSON, the maximum number of figures after the decimal separator to write in coordinates. Defaults to undefined (full precision).
Optionalrewind?: booleanFor GeoJSON, if true, rewinds the coordinates of polygons to follow the right-hand rule (RFC 7946). Defaults to false.
A promise that resolves to the table, so methods can be chained.
// Write geospatial data to a ZSTD-compressed GeoParquet file
await table.writeGeoData("./output.geoparquet", { compression: true });
// Write geospatial data to a Shapefile with all relevant files in the same folder
await table.writeGeoData("./shapefile-folder/output.shp");
Adds the bounding box coordinates of geometries in a specified column as four new columns: minLon, minLat, maxLon, and maxLat.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries for which the bounding box will be computed. If omitted, the method will automatically attempt to find a geometry column.
Optionaldecimals?: numberThe number of decimal places to round the bounding box coordinates. Defaults to undefined (no rounding).
The table, so methods can be chained.
Adds a column with boolean values indicating whether geometries are closed (e.g., polygons) or open (e.g., linestrings).
The name of the new column where the boolean results (TRUE for closed, FALSE for open) will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Moves EPSG:4326 point geometries to random locations within a maximum great-circle distance of their original positions.
Points are sampled uniformly within the requested distance using the spherical direct geodesic (destination-point) formula and the same spherical Earth model as DuckDB's ST_Distance_Sphere(). This accounts for longitude scale changing with latitude. This method adds random jitter; it does not provide anonymization or differential privacy guarantees.
This method supports only POINT geometries in EPSG:4326. Null and empty geometries are preserved.
The maximum great-circle displacement in the selected unit. Must be a finite number greater than or equal to 0 and no greater than half Earth's circumference.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the EPSG:4326 point geometry column. If omitted, the method will automatically attempt to find a geometry column.
OptionalonlyDuplicates?: booleanIf true, moves only points whose complete original geometry occurs more than once in the selected geometry column. Every point in a duplicated group is moved. Defaults to false.
Optionalunit?: "m" | "km"The unit of maxDistance: "m" for metres or "km" for kilometres. Defaults to "m".
The table, so methods can be chained.
Adds a column with the geometry type (e.g., "POINT", "LINESTRING", "POLYGON") for each geometry.
The name of the new column where the geometry types will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Adds a column with boolean values indicating the validity of geometries.
The name of the new column where the boolean results (TRUE for valid, FALSE for invalid) will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries to be checked. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Adds a column with the number of vertices (points) in each geometry.
The name of the new column where the vertex counts will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Aggregates geometries in a specified column based on a chosen aggregation method.
The aggregation method to apply: "union" (combines all geometries into a single multi-geometry) or "intersection" (computes the intersection of all geometries).
An optional object with configuration options:
Optionalby?: string | string[]The column name or an array of column names to group by. Geometries are aggregated independently within each group.
Optionalcolumn?: stringThe name of the column storing the geometries to be aggregated. If omitted, the method will automatically attempt to find a geometry column.
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
A table instance containing the aggregated geometries (either the current table or a new table), so methods can be chained.
Computes the area of geometries in square meters ("m2") or optionally square kilometers ("km2").
The input geometry is assumed to be in EPSG:4326 (WGS84).
The name of the new column where the computed areas will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
Optionaldecimals?: numberThe number of decimal places to round the computed areas. Defaults to undefined (no rounding).
Optionalunit?: "m2" | "km2"The unit for the computed area: "m2" (square meters) or "km2" (square kilometers). Defaults to "m2".
The table, so methods can be chained.
// Compute the area of geometries in square meters and store in 'area_m2'
await table.area("area_m2").log();
// Compute the area of geometries in square kilometers and store in 'area_km2'
await table.area("area_km2", { unit: "km2" }).log();
Computes a buffer (a polygon representing a specified distance around a geometry) for geometries in a specified column. The distance is in the Spatial Reference System (SRS) unit of the input geometries.
The name of the new column where the buffered geometries will be stored.
The distance for the buffer. This value is in the units of the geometry's SRS.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Computes the centroid of geometries. The values are returned in the SRS unit of the input geometries.
The name of the new column where the computed centroid geometries will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Returns TRUE if every point of a geometry in column is covered by a geometry in containerColumn, including their boundaries, and FALSE otherwise.
The name of the column storing the geometries to be tested for containment.
The name of the column storing the geometries to be tested as containers. Both columns must have the same projection.
The name of the new column where the boolean results (TRUE when covered, FALSE otherwise) will be stored.
The table, so methods can be chained.
Creates point geometries from latitude (y) and longitude (x) columns.
The name of the column storing the latitude (y-coordinate) values.
The name of the column storing the longitude (x-coordinate) values.
The name of the new column where the point geometries will be stored.
An optional object with configuration options:
Optionalprojection?: stringThe projection of the coordinates. Defaults to EPSG:4326 (WGS84), passed as "EPSG:4326".
The table, so methods can be chained.
Computes the geometric difference between two geometries, returning the portion of the first geometry that does not intersect the second.
The name of the column storing the geometries from which the second geometries will be subtracted.
The name of the column storing the geometries to subtract. Both columns must have the same projection.
The name of the new column where the geometric differences will be stored.
The table, so methods can be chained.
Computes the distance between geometries in two specified columns.
By default, the distance is calculated in the Spatial Reference System (SRS) unit of the input geometries.
You can optionally specify "spheroid" or "haversine" methods to get results in meters or kilometers.
If using "spheroid" or "haversine", the input geometries must be in EPSG:4326 (WGS84).
The name of the first column storing geometries.
The name of the second column storing geometries.
The name of the new column where the computed distances will be stored.
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the distance values. Defaults to undefined (no rounding).
Optionalmethod?: "srs" | "haversine" | "spheroid"The method to use for distance calculations: "srs" (default, uses SRS unit), "haversine" (meters, requires EPSG:4326 (WGS84)), or "spheroid" (meters, requires EPSG:4326 (WGS84), most accurate but slowest).
Optionalunit?: "m" | "km"If method is "spheroid" or "haversine", you can choose between "m" (meters, default) or "km" (kilometers).
The table, so methods can be chained.
// Compute distance between 'geomA' and 'geomB' in SRS units, store in 'distance_srs'
await table.distance("geomA", "geomB", "distance_srs").log();
// Compute Haversine distance in meters between 'point1' and 'point2', store in 'distance_m'
// Input geometries must be in EPSG:4326 (WGS84).
await table.distance("point1", "point2", "distance_m", { method: "haversine" }).log();
Extracts the latitude (y) and longitude (x) coordinates from point geometries. The input geometry is assumed to be in EPSG:4326 (WGS84).
The name of the column storing the point geometries.
The name of the new column where the extracted latitude (y-coordinate) values will be stored.
The name of the new column where the extracted longitude (x-coordinate) values will be stored.
The table, so methods can be chained.
Fills holes in polygon geometries.
Optionalcolumn: string
The name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Attempts to make invalid geometries valid without removing any vertices.
Optionalcolumn: string
The name of the column storing the geometries to be fixed. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Flips the coordinate order of geometries in a specified column (e.g., from [longitude (x), latitude (y)] to [latitude (y), longitude (x)] or vice-versa).
Warning: This method should be used with caution as it directly manipulates coordinate order and can affect the accuracy of geospatial operations if not used correctly.
Optionalcolumn: string
The name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Returns the bounding box of geometries in [minLon, minLat, maxLon, maxLat] order.
By default, the method will try to find the column with the geometries. The input geometry is assumed to be in EPSG:4326 (WGS84).
Optionalcolumn: string
The name of the column storing geometries. If omitted, the method will automatically attempt to find a geometry column.
A promise that resolves to an array [minLon, minLat, maxLon, maxLat] representing the bounding box.
Retrieves the projection of a specified geospatial column.
The name of the geospatial column for which to retrieve the projection.
A promise that resolves to the projection of the specified column.
Computes the intersection of two sets of geometries, creating new geometries where they overlap.
The name of the first column storing geometries.
The name of the second column storing geometries. Both columns must have the same projection.
The name of the new column where the computed intersection geometries will be stored.
The table, so methods can be chained.
Returns TRUE if two geometries intersect (overlap in any way), and FALSE otherwise.
The name of the first column storing geometries.
The name of the second column storing geometries. Both columns must have the same projection.
The name of the new column where the boolean results (TRUE for intersection, FALSE otherwise) will be stored.
The table, so methods can be chained.
Merges the data of this table (considered the left table) with another table (the right table) based on a spatial relationship.
Note that the order of rows in the returned data is not guaranteed to be the same as in the original tables.
This operation might create temporary files in a .tmp folder; consider adding .tmp to your .gitignore.
The SimpleTable instance to be joined with this table.
The spatial join method to use: "intersect" (geometries overlap), "inside" (geometries of the left table are entirely within geometries of the right table), or "withinDistance" (geometries of the left table are within a specified distance of geometries in the right table).
An optional object with configuration options:
Optionaldistance?: numberRequired if method is "withinDistance". The target distance for the spatial join. The unit depends on distanceMethod.
OptionaldistanceMethod?: "srs" | "haversine" | "spheroid"The method for distance calculations: "srs" (default, uses the SRS unit), "haversine" (uses meters, requires EPSG:4326 (WGS84) input), or "spheroid" (uses meters, requires EPSG:4326 (WGS84) input, most accurate but slowest).
OptionalexcludeLeftGeometry?: booleanWhether to exclude the selected leftColumn geometry from the result. Defaults to false.
OptionalexcludeRightGeometry?: booleanWhether to exclude the selected rightColumn geometry from the result. Defaults to false.
OptionalleftColumn?: stringThe name of the column storing geometries in the left table (this table). If omitted, the method attempts to find one.
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
OptionalrightColumn?: stringThe name of the column storing geometries in the right table. If omitted, the method attempts to find one.
Optionaltype?: "left" | "right" | "inner" | "full"The type of join operation to perform: "inner", "left" (default), "right", or "full". For some types (like "inside"), the table order is important.
A table instance containing the spatially joined data (either the current table or a new table), so methods can be chained.
// Merge data based on intersecting geometries, overwriting tableA
await tableA.joinGeo(tableB, "intersect").log();
// Merge data where geometries in tableA are inside geometries in tableB
await tableA.joinGeo(tableB, "inside").log();
// Join using both geometries without copying them into the result
await tableA.joinGeo(tableB, "intersect", {
excludeLeftGeometry: true,
excludeRightGeometry: true,
}).log();
// Merge data where geometries in tableA are within 10 units (SRS) of geometries in tableB
await tableA.joinGeo(tableB, "withinDistance", { distance: 10 }).log();
Computes the length of line geometries in meters ("m") or optionally kilometers ("km").
The input geometry is assumed to be in EPSG:4326 (WGS84).
The name of the new column where the computed lengths will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
Optionaldecimals?: numberThe number of decimal places to round the computed lengths. Defaults to undefined (no rounding).
Optionalunit?: "m" | "km"The unit for the computed length: "m" (meters) or "km" (kilometers). Defaults to "m".
The table, so methods can be chained.
// Compute the length of line geometries in meters and store in 'length_m'
await table.length("length_m").log();
// Compute the length of line geometries in kilometers and store in 'length_km'
await table.length("length_km", { unit: "km" }).log();
Transforms closed linestring geometries into polygon geometries.
Optionalcolumn: string
The name of the column storing the linestring geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Loads geospatial data from an external file or URL into the table.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The path or URL of the external file containing the geospatial data.
An optional object with configuration options:
Optionalcolumns?: string[]The columns to load. Include the geometry column that should remain in the resulting table, usually "geom". By default, all columns are loaded.
Optionalconditions?: stringA SQL WHERE clause expression, without the WHERE keyword, to filter source rows before materialization and reprojection. Can reference source columns excluded from columns. Geometry conditions use the source coordinate system.
OptionaltoEPSG4326?: booleanIf true, the method will attempt to reproject the data to EPSG:4326 (WGS84).
The table, so methods can be chained.
// Load geospatial data from a URL
await table.loadGeoData("https://some-website.com/some-data.geojson").log();
// Load geospatial data from a local file
await table.loadGeoData("./some-data.geojson").log();
// Load only the name and geometry columns
await table
.loadGeoData("./boundaries.geojson", {
columns: ["name", "geom"],
})
.log();
// Load geospatial data from a shapefile (with relevant files in the same folder) and reproject to EPSG:4326 (WGS84)
await table.loadGeoData("./some-data/some-data.shp", { toEPSG4326: true }).log();
Loads OpenStreetMap data into the table from a local .osm or .osm.pbf file, a remote file URL, or an Overpass bounding-box query. Pass a path or URL string to load an existing file, or pass a bounding box with filters to download matching features.
DuckDB's Osmium community extension materializes the complete result and reconstructs geom as EPSG:4326. By default, that canonical result is cached as GeoParquet in .sda-cache/osm. Distinct paths, local file fingerprints, normalized URLs, endpoints, queries, and bounding boxes retain independent entries. The cache has no expiration unless ttl is set and can be cleared by removing .sda-cache/osm. Immutable URLs can safely use the default indefinite cache; mutable URLs should set a TTL.
Bounding-box filters use the standard Overpass QL filter syntax. Equality filters are passed as [key, value] tuples and serialized by the method. A raw filter fragment string can be used for advanced Overpass filters.
The default Overpass endpoint is a shared public service. Follow the Overpass public-instance usage guidelines, and configure another endpoint or run your own instance for high-volume usage.
OpenStreetMap data is licensed under the Open Data Commons Open Database License. Public use requires OpenStreetMap attribution, and distributing OSM or derivative databases can trigger the licence's share-alike requirements.
The local path or remote URL of an existing .osm or .osm.pbf file, or a bounding box to query through Overpass.
The eastern longitude, between -180 and 180 and greater than west.
The northern latitude, between -90 and 90 and greater than south.
The southern latitude, between -90 and 90 and less than north.
The western longitude, between -180 and 180 and less than east.
Loading options. When source is a bounding box, options.filters must be provided.
Optionalcache?: booleanWhether to read and write the processed GeoParquet cache. Defaults to true.
Optionalendpoint?: stringThe Overpass interpreter endpoint. Defaults to https://overpass-api.de/api/interpreter.
Optionalfilters?: string | [string, string] | [string, string][]For a bounding-box query, one [key, value] tuple or an array of tuples. Array entries are combined as a union. A raw Overpass QL filter fragment string is also accepted.
Optionalretries?: numberAdditional network retries after the initial request. Defaults to 3, for up to four attempts. Network-only; passing it for a local path throws an error.
OptionalretryDelay?: numberBase retry delay in seconds. Defaults to 5, producing deterministic delays of 5, 10, and 20 seconds. A longer valid Retry-After value takes precedence. Network-only; passing it for a local path throws an error.
Optionaltimeout?: numberA positive integer timeout in seconds, applied to both the Overpass query and HTTP request. If omitted, the endpoint's default query timeout applies and no HTTP request timeout is set.
Optionalttl?: numberCache lifetime in seconds. Omit for no expiration, use 0 to refresh the matching cache entry immediately, or provide a positive value to refresh once the processed entry reaches that age. Cannot be combined with cache: false.
Optionalverbose?: booleanWhether to log OpenStreetMap cache, network, Osmium, cleanup, and timing lifecycle details. Defaults to false. SimpleDB({ cacheVerbose: true }) also enables these messages and is not overridden by verbose: false.
The table, so methods can be chained.
// Load an existing local OpenStreetMap PBF file
await table.loadOpenStreetMap("./montreal.osm.pbf").log();
// Filter and project after the complete canonical source has loaded
await table
.loadOpenStreetMap("./montreal.osm.pbf", { verbose: true })
.filter("tags['amenity'] = 'school'")
.selectColumns(["id", "tags", "geom"])
.log();
// Download features matching one equality filter within a bounding box
await table
.loadOpenStreetMap(
{ west: -73.587799, south: 45.445078, east: -73.552265, north: 45.471086 },
{ filters: ["amenity", "school"] },
)
.log();
Computes the perimeter of polygon geometries in meters ("m") or optionally kilometers ("km").
The input geometry is assumed to be in EPSG:4326 (WGS84).
The name of the new column where the computed perimeters will be stored.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
Optionaldecimals?: numberThe number of decimal places to round the computed perimeters. Defaults to undefined (no rounding).
Optionalunit?: "m" | "km"The unit for the computed perimeter: "m" (meters) or "km" (kilometers). Defaults to "m".
The table, so methods can be chained.
// Compute the perimeter of polygon geometries in meters and store in 'perimeter_m'
await table.perimeter("perimeter_m").log();
// Compute the perimeter of polygon geometries in kilometers and store in 'perimeter_km'
await table.perimeter("perimeter_km", { unit: "km" }).log();
Reduces the precision of geometries in a specified column to a given number of decimal places.
The number of decimal places to keep in the coordinates of the geometries.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Reprojects the geometries in a specified column to another Spatial Reference System (SRS).
The target SRS (e.g., "EPSG:3347", or "EPSG:4326" for EPSG:4326 (WGS84)).
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Simplifies geometries while preserving their overall coverage. A higher tolerance results in more significant simplification.
A numeric value representing the simplification tolerance. A higher value leads to greater simplification.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the method will automatically attempt to find a geometry column.
OptionalsimplifyBoundary?: booleanIf true (default), the boundary of the geometries will also be simplified. If false, only the interior of the geometries will be simplified, preserving the original boundary.
The table, so methods can be chained.
Computes the union of two geometries, creating a new geometry that represents the merged area of both.
The name of the first column storing geometries.
The name of the second column storing geometries. Both columns must have the same projection.
The name of the new column where the computed union geometries will be stored.
The table, so methods can be chained.
Unnests geometries recursively, transforming multi-part geometries (e.g., MultiPolygon) into individual single-part geometries (e.g., Polygon).
Optionalcolumn: string
The name of the column storing the geometries to be unnested. If omitted, the method will automatically attempt to find a geometry column.
The table, so methods can be chained.
Returns the bottom n rows of the table, optionally filtered by SQL conditions.
By default, the last row will be returned first. To preserve the original order, use the originalOrder option.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Temporal values use the same JavaScript representations as getData().
The number of rows to return from the bottom of the table.
An optional object with configuration options:
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Books'").
OptionaloriginalOrder?: booleanA boolean indicating whether the rows should be returned in their original order (true) or in reverse order (last row first, false). Defaults to false.
A promise that resolves to an array of objects representing the bottom n rows.
Returns the total number of characters in a column storing strings. Counts are based on Unicode code points, not grapheme clusters. A user-perceived character composed of multiple code points, such as some emoji or decomposed accented letters, counts as multiple characters.
The name of the string column to count characters from.
A promise that resolves to the total number of characters across all rows in the specified column.
Returns the data from the table as an array of objects, optionally filtered by SQL conditions.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Top-level DuckDB DATE and TIMESTAMP columns are returned as JavaScript
Date objects interpreted in UTC. TIMESTAMP WITH TIME ZONE values are
returned as UTC strings, preserving DuckDB's microsecond precision;
JavaScript Date supports only milliseconds.
Top-level BIGINT, UBIGINT, HUGEINT, and UHUGEINT columns are
returned as JavaScript numbers. Values outside Number.MIN_SAFE_INTEGER
through Number.MAX_SAFE_INTEGER throw instead of losing precision.
Use convert({ columnName: "string" }) before getData() to retrieve
their exact digits as strings.
SQL arrays and lists, including float vectors, return their actual elements as JavaScript arrays. Null cells and null elements remain null.
Nested SQL lists and structs become JavaScript arrays and objects. Within
them, BIGINT, UBIGINT, HUGEINT, UHUGEINT, and DECIMAL values remain
strings to preserve precision, even for small integers.
An optional object with configuration options:
Optionalcolumns?: string | string[]An array of column names to include in the result. If omitted, all columns will be included.
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Book'").
Optionallimit?: numberThe maximum number of rows to return. Must be an integer greater than or equal to 0.
A promise that resolves to an array of objects, where each object represents a row in the table.
// Get data filtered by a condition (using JS or SQL syntax)
const booksData = await table.getData({ conditions: `category === 'Book'` });
console.log(booksData);
// Get data filtered by a condition and specific columns
const booksData = await table.getData({ columns: ["title", "author"], conditions: `category === 'Book'` });
console.log(booksData);
// Read nested integers without losing digits. SQL creates the nested types.
await sdb.customQuery(`CREATE TABLE nested_example AS SELECT
[42::BIGINT, 9007199254740993::BIGINT] AS ids,
{'count': 2::INTEGER, 'amount': 123.450::DECIMAL(6,3)} AS details`);
const nested = sdb.newTable("nested_example");
const rows = await nested.getData();
// [{ ids: ["42", "9007199254740993"], details: { count: 2, amount: "123.450" } }]
await nested.log();
Returns the data from the table as a CSV string, optionally filtered by SQL conditions.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Temporal values are first converted as they are in getData(), then
serialized using UTC date and timestamp text.
An optional object with configuration options:
Optionalcolumns?: string | string[]An array of column names to include in the CSV. If omitted, all columns will be included.
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Book'").
A promise that resolves to a CSV-formatted string representation of the table data.
// Get all data from the table as CSV
const allDataCSV = await table.getDataAsCSV();
console.log(allDataCSV);
Returns descriptive statistical information about the columns, including details like data types, number of null values, and distinct values.
A promise that resolves to an array of objects, each representing descriptive statistics for a column.
Returns the extent (minimum and maximum values) of a specific column as an array.
Temporal values use the same JavaScript representations as getData().
The name of the column from which to retrieve the extent.
A promise that resolves to an array [min, max] containing the minimum and maximum values of the specified column.
Returns the first row of the table, optionally filtered by SQL conditions.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Temporal values use the same JavaScript representations as getData().
An optional object with configuration options:
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Book'").
A promise that resolves to an object representing the first row, or null if no rows match the conditions.
Returns the table's geospatial data as a GeoJSON FeatureCollection. Each row becomes a Feature: the selected geometry column becomes its geometry, and the remaining columns become its properties. If the table has multiple geometry columns, you must specify which one to use.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries. If omitted, the table must have exactly one geometry column, which will be selected automatically.
Optionalrewind?: booleanIf true, rewinds the coordinates of polygons to follow the spherical winding order (important for D3.js). Defaults to false.
A promise that resolves to a GeoJSON object representing the table's geospatial data.
Returns a deterministic hash of the table's ordered schema and contents. Pending operations are executed before the hash is calculated. Computing the hash scans the complete table inside DuckDB without transferring its rows to JavaScript.
A promise that resolves to a SHA-256 hash string.
Returns the last row of the table, optionally filtered by SQL conditions.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Temporal values use the same JavaScript representations as getData().
An optional object with configuration options:
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Book'").
A promise that resolves to an object representing the last row, or null if no rows match the conditions.
Returns the maximum value from a specific column.
Temporal values use the same JavaScript representations as getData().
The name of the column from which to retrieve the maximum value.
A promise that resolves to the maximum value of the specified column.
Returns the mean (average) value from a specific numeric column.
The name of the numeric column from which to retrieve the mean value.
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the result to. Defaults to undefined (no rounding).
A promise that resolves to the mean value of the specified column.
Returns the median value from a specific numeric column.
The name of the numeric column from which to retrieve the median value.
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the result to. Defaults to undefined (no rounding).
A promise that resolves to the median value of the specified column.
Returns the minimum value from a specific column.
Temporal values use the same JavaScript representations as getData().
The name of the column from which to retrieve the minimum value.
A promise that resolves to the minimum value of the specified column.
Returns the value of a specific quantile from the values in a given numeric column.
The name of the numeric column from which to calculate the quantile.
The quantile to calculate, expressed as a number between 0 and 1 (e.g., 0.25 for the first quartile, 0.5 for the median, 0.75 for the third quartile).
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the result to. Defaults to undefined (no rounding).
A promise that resolves to the quantile value of the specified column.
Returns a single row that matches the specified conditions. Always throws if no row matches. By default, also throws if more than one row matches.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Temporal values use the same JavaScript representations as getData().
The conditions to match, specified as a SQL WHERE clause.
Optional settings:
Optionalstrict?: booleanIf false, returns the first row when multiple rows match. A missing match always throws. Defaults to true.
A promise that resolves to an object representing the matched row.
// Get a row where 'name' is 'John'
const johnsRow = await table.getRow(`name = 'John'`);
console.log(johnsRow);
// Get a row where 'id' is 123 (using JS syntax)
const rowById = await table.getRow(`id === 123`);
console.log(rowById);
Returns the number of rows in the table.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
An optional object with configuration options:
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Book'").
A promise that resolves to a number representing the total count of rows.
Returns the schema of the table, including column names and their data types.
A promise that resolves to an array of objects, where each object represents a column with its name and data type.
Returns the skewness of values from a specific numeric column.
The name of the numeric column from which to retrieve the skewness.
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the result to. Defaults to undefined (no rounding).
A promise that resolves to the skewness value of the specified column.
Returns the standard deviation of values from a specific numeric column.
The name of the numeric column from which to retrieve the standard deviation.
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the result to. Defaults to undefined (no rounding).
A promise that resolves to the standard deviation value of the specified column.
Returns the sum of values from a specific numeric column.
The name of the numeric column from which to retrieve the sum.
A promise that resolves to the sum of values in the specified column.
Returns the top n rows of the table, optionally filtered by SQL conditions.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Temporal values use the same JavaScript representations as getData().
The number of rows to return from the top of the table.
An optional object with configuration options:
Optionalconditions?: stringThe filtering conditions specified as a SQL WHERE clause (e.g., "category = 'Books'").
A promise that resolves to an array of objects representing the top n rows.
Returns the data types of all columns in the table.
A promise that resolves to an object where keys are column names and values are their corresponding data types (e.g., { "id": "BIGINT", "name": "VARCHAR" }).
Returns unique values from a specific column. The values are returned in ascending order.
Temporal values use the same JavaScript representations as getData().
The name of the column from which to retrieve unique values.
A promise that resolves to an array containing the unique values from the specified column, sorted in ascending order.
Returns the total number of values in the table (number of columns multiplied by the number of rows).
A promise that resolves to a number representing the total count of values.
Returns all values from a specific column.
Temporal values use the same JavaScript representations as getData().
The name of the column from which to retrieve values.
A promise that resolves to an array containing all values from the specified column.
Returns the variance of values from a specific numeric column.
The name of the numeric column from which to retrieve the variance.
An optional object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round the result to. Defaults to undefined (no rounding).
A promise that resolves to the variance value of the specified column.
Streams the table rows one by one as an async iterator, without
materializing the whole table in memory. Values are converted to
JavaScript types the same way as getData().
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The underlying DuckDB result is streamed chunk by chunk, so tables larger than the available memory can be iterated. Avoid running other queries on the same database while iterating.
An optional object with configuration options:
Optionalcolumns?: string | string[]The column name or an array of column names to include. If omitted, all columns are streamed.
Optionalconditions?: stringA SQL WHERE clause condition to filter the rows.
An async generator yielding one row object at a time.
Finds the neighbors shared by two different nodes. The direction option
lets you find neighbors from source to target, from target to source, or
in either direction. The result has one node column, sorted in
ascending order.
If either node ID is unknown, or the nodes have no shared neighbors, the result has no rows. Each shared neighbor appears once, even if connections are repeated. A self-connection can make one of the requested nodes a shared neighbor. Using the same ID for both requested nodes throws an error.
The next three examples each start with this data:
| origin | destination |
|---|---|
| A | C |
| A | C |
| B | C |
| B | C |
| D | A |
| D | B |
By default, the method finds shared neighbors from source to target. Both A and B connect to C:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
The first node ID.
The second node ID, which must differ from nodeA.
An optional object with direction and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing".
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
The result table, so methods can be chained.
await connections
.commonNeighbors("origin", "destination", "A", "B")
.log();
| node |
|---|
| C |
C appears only once, even though the input repeats the connections.
With direction: "incoming", the method follows connections from target
to source. D connects to both A and B:
await connections
.commonNeighbors("origin", "destination", "A", "B", { direction: "incoming" })
.log();
| node |
|---|
| D |
With direction: "both", the method finds shared neighbors in either
direction:
Groups nodes that are connected to one another, directly or through other
nodes. By default, connection direction is ignored. With the mode
option set to "strong", nodes belong to the same group only if each can
reach all the others by following connections from source to target.
The result has componentId and node columns. Without startTimeColumn
or endTimeColumn, each node appears once, and rows are sorted by node.
Groups are numbered from zero in order of their smallest node ID.
Use the startTimeColumn or endTimeColumn options to follow connections
in chronological order. The minGapMs option sets the minimum gap between
consecutive connections, in milliseconds. With both time columns, gaps are
measured from one connection's end to the next connection's start; with one
column, between their timestamps. The strictOrdering option defaults to
true, rejecting zero-duration gaps. These options require an explicitly
supplied mode: "strong".
With these options, nodes are grouped when each can reach all the others
in chronological order. Groups can overlap, so a node may appear in
several rows with different componentId values.
For the first example, we start with this data:
| origin | destination |
|---|---|
| A | B |
| C | D |
By default, the method groups connected nodes without considering direction. B and D are included even though they only appear as destinations:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
An optional object with component, time, and result configuration.
OptionalendTimeColumn?: stringThe name of the column containing each connection's end time. Use this or startTimeColumn to follow connections in chronological order.
OptionalminGapMs?: numberThe minimum gap between consecutive connections, in milliseconds. Must be a non-negative integer. Defaults to 0. Requires a time column.
Optionalmode?: "weak" | "strong"Whether to find "weak" or "strong" components. Defaults to "weak". Using startTimeColumn or endTimeColumn requires explicitly setting mode: "strong".
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
OptionalstartTimeColumn?: stringThe name of the column containing each connection's start time. Use this or endTimeColumn to follow connections in chronological order.
OptionalstrictOrdering?: booleanWhether to reject zero-duration gaps between consecutive connections (equal timestamps). Defaults to true. Requires a time column.
The result table, so methods can be chained.
await table
.connectedComponents("origin", "destination")
.log();
| componentId | node |
|---|---|
| 0 | A |
| 0 | B |
| 1 | C |
| 1 | D |
The next two examples each start with this chain of connections:
| source | target |
|---|---|
| A | B |
| B | C |
By default, all three nodes belong to the same group:
await graph
.connectedComponents("source", "target")
.log();
| componentId | node |
|---|---|
| 0 | A |
| 0 | B |
| 0 | C |
With the mode option set to "strong", each node forms its own group. A
can reach B and C, but neither can get back to A:
await graph
.connectedComponents("source", "target", {
mode: "strong",
})
.log();
| componentId | node |
|---|---|
| 0 | A |
| 1 | B |
| 2 | C |
Adding C -> A would let every node reach the others, so all three would belong to the same group in "strong" mode too.
Chronological strong groups can overlap. The next example uses one instantaneous timestamp column:
| time | source | target |
|---|---|---|
| 2025-01-01 08:00 | B | C |
| 2025-01-01 09:00 | C | B |
| 2025-01-01 10:00 | A | B |
| 2025-01-01 11:00 | B | A |
A and B can reach each other, as can B and C, but A cannot reach C after
its first event. This produces the maximal groups {A, B} and {B, C}:
Counts the incoming and outgoing connections for every node found in the
source and target columns. By default, each input row is counted separately,
even when several rows connect the same two nodes. Set the count option to
"neighbors" to count distinct neighboring nodes instead. Use the weight
option to sum values from a numeric column instead of counting connections.
The weight option cannot be combined with count: "neighbors".
The result has node, incoming, outgoing, and total columns. The
total is the sum of incoming and outgoing. Results are sorted by
highest total first, then by node in ascending order when totals are equal.
A self-connection contributes once to incoming and once to outgoing, so
it contributes twice to the total. With count: "neighbors", a neighbor
connected in both directions also contributes once in each direction.
Endpoint IDs must be non-null strings or whole numbers in compatible
columns. Supplied weights must be non-null, finite, and non-negative.
The next three examples each start with these flights:
| origin | destination | passengers |
|---|---|---|
| Montreal | Toronto | 100 |
| Montreal | Toronto | 200 |
| Montreal | Vancouver | 50 |
| Toronto | Montreal | 150 |
By default, the method counts connections and overwrites the input table:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
An optional object with counting and result configuration.
Optionalcount?: "neighbors" | "edges"Whether to count connection rows ("edges") or distinct adjacent nodes ("neighbors"). Defaults to "edges".
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
Optionalweight?: stringThe name of the numeric edge-weight column to sum. This requires edge counting.
The result table, so methods can be chained.
await flights
.degree("origin", "destination")
.log();
| node | incoming | outgoing | total |
|---|---|---|---|
| Montreal | 1 | 3 | 4 |
| Toronto | 2 | 1 | 3 |
| Vancouver | 1 | 0 | 1 |
With count: "neighbors", the two Montreal-to-Toronto flights count as
one outgoing neighbor for Montreal:
await flights
.degree("origin", "destination", { count: "neighbors" })
.log();
| node | incoming | outgoing | total |
|---|---|---|---|
| Montreal | 1 | 2 | 3 |
| Toronto | 1 | 1 | 2 |
| Vancouver | 1 | 0 | 1 |
With weight: "passengers", the method adds passenger counts for each
city's incoming and outgoing flights:
Finds the shortest routes from each starting node to every reachable
node. Without options, shortest means the fewest connections. Use weight
to report total cost and elapsedTime to report duration including waiting
between connections. Use minimize to choose which metric defines shortest.
Each row contains start, node, and steps, plus total when weight
is supplied and elapsedTimeMs when enabled. Routes tied on the chosen
metric produce separate rows only when their reported summaries differ;
identical summaries appear once.
Routes cannot repeat nodes except for a final return to the start. A start
appears in its own results only through an actual return or self-connection,
never with zero steps. With direction: "both", a return can follow the
same connection out and back. Use shortestPath() to inspect individual
connections between two different nodes.
Reporting weight or elapsed time can require substantial time and memory on dense networks because many routes may need to be explored.
The next five examples each start with this data:
| origin | destination | minutes |
|---|---|---|
| A | B | 4 |
| B | C | 1 |
| D | B | 2 |
By default, the method follows connections from source to target and counts how many connections are needed:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
One starting node ID or a non-empty array of distinct IDs. Unknown IDs and starts with no connections to follow produce no rows. Empty arrays and duplicate IDs throw an error.
An optional object with direction, time, and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing". Chronological traversal supports only "outgoing" and "incoming".
OptionalelapsedTime?: booleanWhether to add cumulative elapsedTimeMs in milliseconds, including connection gaps. Defaults to false. Requires both time columns. Optimization compares native timestamp precision before conversion to milliseconds.
OptionalendTimeColumn?: stringThe name of the column containing each connection's end time. Use this or startTimeColumn to follow connections in chronological order.
OptionalminGapMs?: numberThe minimum gap between consecutive connections, in milliseconds; does not apply before the first connection. With both time columns, measured from one connection's end to the next's start; with one column, between timestamps. Must be a non-negative integer. Defaults to 0. Requires a time column.
Optionalminimize?: "weight" | "steps" | "elapsedTime"The metric used to select optimal routes: "steps", "weight", or "elapsedTime". Defaults to "weight" when only weight is enabled, "elapsedTime" when only elapsed-time reporting is enabled, and "steps" when neither is enabled. Required when both are enabled. "steps" is always available; the other metrics require their corresponding options.
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
OptionalstartTimeColumn?: stringThe name of the column containing each connection's start time. Use this or endTimeColumn to follow connections in chronological order.
OptionalstrictOrdering?: booleanWhether to reject zero-duration gaps between consecutive connections (equal timestamps). Defaults to true. Requires a time column.
Optionalweight?: stringThe name of the numeric column used as the cost of each connection. Values must be non-null, finite, and non-negative. Adds total, the cumulative sum of those costs. If omitted, no total column is generated.
The table of distinct optimal summaries with start, node, and steps, plus total when weight is supplied and elapsedTimeMs when enabled, so methods can be chained. Sorted by start, the chosen metric, node, then remaining metrics in the order steps, total, elapsed time.
await connections
.distances("origin", "destination", "A")
.log();
| start | node | steps |
|---|---|---|
| A | B | 1 |
| A | C | 2 |
With weight: "minutes", total is the sum of travel times:
await connections
.distances("origin", "destination", "A", { weight: "minutes" })
.log();
| start | node | steps | total |
|---|---|---|---|
| A | B | 1 | 4 |
| A | C | 2 | 5 |
With direction: "incoming", connections are followed from target to
source. Starting at C:
await connections
.distances("origin", "destination", "C", { direction: "incoming" })
.log();
| start | node | steps |
|---|---|---|
| C | B | 1 |
| C | A | 2 |
| C | D | 2 |
With direction: "both", connections can be followed in either
direction:
await connections
.distances("origin", "destination", "A", { direction: "both" })
.log();
| start | node | steps |
|---|---|---|
| A | B | 1 |
| A | A | 2 |
| A | C | 2 |
| A | D | 2 |
A appears with 2 steps because A → B → A follows a connection out and back.
An array of starting nodes gives separate distances for each start:
await connections
.distances("origin", "destination", ["D", "A"], {
weight: "minutes",
})
.log();
| start | node | steps | total |
|---|---|---|---|
| A | B | 1 | 4 |
| A | C | 2 | 5 |
| D | B | 1 | 2 |
| D | C | 2 | 3 |
A direct self-connection and a longer return route can both lead back to the start. This example uses different data:
| origin | destination | minutes |
|---|---|---|
| A | A | 10 |
| A | B | 2 |
| B | A | 3 |
The shortest return to A takes 5 minutes through B, compared with 10 minutes for the direct self-connection:
await connections
.distances("origin", "destination", "A", { weight: "minutes" })
.log();
| start | node | steps | total |
|---|---|---|---|
| A | B | 1 | 2 |
| A | A | 2 | 5 |
Chronological options can rule out a cheaper sequence that departs too early. Here, B → C leaves before A → B arrives, while B → D meets the one-hour minimum exactly:
| origin | destination | departureTime | arrivalTime | minutes |
|---|---|---|---|---|
| A | B | 2025-01-01 08:00 | 2025-01-01 10:00 | 2 |
| B | C | 2025-01-01 09:00 | 2025-01-01 10:00 | 1 |
| B | D | 2025-01-01 11:00 | 2025-01-01 12:00 | 3 |
await scheduledFlights
.distances("origin", "destination", "A", {
startTimeColumn: "departureTime",
endTimeColumn: "arrivalTime",
minGapMs: 60 * 60 * 1000,
weight: "minutes",
})
.log();
| start | node | steps | total |
|---|---|---|---|
| A | B | 1 | 2 |
| A | D | 2 | 5 |
For the next four examples, flights contains these rows, with departure
and arrival stored as timestamps on the same day:
| flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|
| F1 | A | B | 2025-01-01 09:00 | 2025-01-01 10:00 | 40 |
| F2 | B | C | 2025-01-01 12:00 | 2025-01-01 13:00 | 40 |
| F3 | A | C | 2025-01-01 09:00 | 2025-01-01 12:00 | 120 |
Find the fastest journey from A to each reachable destination, including layovers:
await flights
.distances("origin", "destination", "A", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
})
.log();
| start | node | steps | elapsedTimeMs |
|---|---|---|---|
| A | B | 1 | 3600000 |
| A | C | 1 | 10800000 |
The direct flight to C takes three hours (10,800,000 milliseconds). The route through B takes four hours including its layover.
Find the fastest journeys from A while also adding up their prices:
await flights
.distances("origin", "destination", "A", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
minimize: "elapsedTime",
})
.log();
| start | node | steps | total | elapsedTimeMs |
|---|---|---|---|---|
| A | B | 1 | 40 | 3600000 |
| A | C | 1 | 120 | 10800000 |
Use minimize: "weight" to find the cheapest journeys instead. The
selected route to C now takes two steps through B and costs 80:
await flights
.distances("origin", "destination", "A", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
minimize: "weight",
})
.log();
| start | node | steps | total | elapsedTimeMs |
|---|---|---|---|---|
| A | B | 1 | 40 | 3600000 |
| A | C | 2 | 80 | 14400000 |
Choose the fewest connections while still reporting price and elapsed time. The direct flight to C has one step, even though it costs more:
await flights
.distances("origin", "destination", "A", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
minimize: "steps",
})
.log();
| start | node | steps | total | elapsedTimeMs |
|---|---|---|---|---|
| A | B | 1 | 40 | 3600000 |
| A | C | 1 | 120 | 10800000 |
When two routes to C both take two steps but have different prices, minimizing steps while reporting price returns both summaries:
await connections
.loadArray([
{ origin: "A", destination: "B", price: 20 },
{ origin: "B", destination: "C", price: 30 },
{ origin: "A", destination: "D", price: 40 },
{ origin: "D", destination: "C", price: 40 },
])
.distances("origin", "destination", "A", {
weight: "price",
minimize: "steps",
})
.filter("node = 'C'")
.log();
| start | node | steps | total |
|---|---|---|---|
| A | C | 2 | 50 |
| A | C | 2 | 80 |
Finds all cycles starting and ending at each node in startNodes.
Pass one node ID or an array of node IDs. A cycle cannot visit the same
node twice, except to return to its start, or reuse a connection.
Different cycles can share nodes. The direction option lets you
follow connections from source to target, from target to source, or in
either direction. By default, connections are followed from source to
target.
Each connection needs a unique, non-null ID. Pass the name of the column
containing these IDs as the edgeId argument. If your table is missing
an ID column, you can easily create one with addId().
The result starts with the start, pathId, step, weight, and
total columns, followed by all original columns. The start column
identifies the requested starting node. Steps start at one and follow
the search direction. The weight column is the connection cost, and
total is the sum of those costs up to and including the current step.
Without the weight option, each connection costs one.
If an input column is already named start, pathId, step, weight,
or total (regardless of capitalization), the method throws an error.
Rename it with renameColumns() first.
Each row is one connection, including the final connection back to the start. Weights must be non-null, finite, and non-negative.
If a cycle can start at several requested nodes, each starting node
produces a separate result. The pathId starts at zero for each starting
node; start and pathId together identify a cycle. Passing an empty
array or duplicate IDs in startNodes throws an error. Unknown IDs or
starts with no cycles produce no rows.
With direction: "both", a cycle and its reverse are returned once per
starting node, choosing the smaller sequence of connection IDs. Within
each start, cycles are numbered by connection-ID sequence. Rows are
sorted by start, then pathId, then step.
Use the startTimeColumn or endTimeColumn options to follow connections
in chronological order. The minGapMs option sets the minimum gap between
consecutive connections, in milliseconds. With both time columns, gaps are
measured from one connection's end to the next connection's start; with one
column, between their timestamps. The strictOrdering option defaults to
true, rejecting zero-duration gaps. These options only work with
direction: "outgoing" or direction: "incoming".
Time rules are checked from each requested starting node. A cycle may therefore work from one starting node but not another. No gap is checked from the final connection back to the first. Incoming searches follow connections backward in chronological order.
A self-connection forms a one-step cycle. With direction: "both", two
separate connections between the same nodes can form a two-step cycle. A
single connection cannot be followed out and back to create a cycle.
Cycles using different connections remain separate, even if they visit
the same nodes. If there are no cycles, the result has no rows.
There is no limit on cycle length or the number of cycles returned. Finding all cycles can take a long time and use substantial memory.
The next four examples each start with these connections:
| edgeId | source | target |
|---|---|---|
| E1 | A | B |
| E2 | B | C |
| E3 | C | A |
By default, connections are followed from source to target:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
The name of the column uniquely identifying each connection (edge).
One starting node ID or an array of IDs. Finds cycles that start and end at each requested node.
An optional object with direction, time, cost, and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing".
OptionalelapsedTime?: booleanWhether to add cumulative elapsedTimeMs in milliseconds, including connection gaps. Defaults to false. Requires both time columns.
OptionalendTimeColumn?: stringThe name of the column containing each connection's end time. Use this or startTimeColumn to follow connections in chronological order.
OptionalminGapMs?: numberThe minimum gap between consecutive connections, in milliseconds. Must be a non-negative integer. Defaults to 0. Requires a time column.
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
OptionalstartTimeColumn?: stringThe name of the column containing each connection's start time. Use this or endTimeColumn to follow connections in chronological order.
OptionalstrictOrdering?: booleanWhether to reject zero-duration gaps between consecutive connections (equal timestamps). Defaults to true. Requires a time column.
Optionalweight?: stringThe name of the numeric column used as the cost of each connection. If omitted, each connection costs one.
The result table, so methods can be chained.
await triangle
.findCycles("source", "target", "edgeId", "A")
.log();
| start | pathId | step | weight | total | edgeId | source | target |
|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | E1 | A | B |
| A | 0 | 2 | 1 | 2 | E2 | B | C |
| A | 0 | 3 | 1 | 3 | E3 | C | A |
Requesting both A and B returns the cycle separately for each start:
await triangle
.findCycles("source", "target", "edgeId", ["A", "B"])
.log();
| start | pathId | step | weight | total | edgeId | source | target |
|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | E1 | A | B |
| A | 0 | 2 | 1 | 2 | E2 | B | C |
| A | 0 | 3 | 1 | 3 | E3 | C | A |
| B | 0 | 1 | 1 | 1 | E2 | B | C |
| B | 0 | 2 | 1 | 2 | E3 | C | A |
| B | 0 | 3 | 1 | 3 | E1 | A | B |
With direction: "incoming", connections are followed from target to
source:
await triangle
.findCycles("source", "target", "edgeId", "A", { direction: "incoming" })
.log();
| start | pathId | step | weight | total | edgeId | source | target |
|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | E3 | C | A |
| A | 0 | 2 | 1 | 2 | E2 | B | C |
| A | 0 | 3 | 1 | 3 | E1 | A | B |
With direction: "both", either direction is allowed. This cycle appears
once, in the direction that starts with E1:
await triangle
.findCycles("source", "target", "edgeId", "A", { direction: "both" })
.log();
| start | pathId | step | weight | total | edgeId | source | target |
|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | E1 | A | B |
| A | 0 | 2 | 1 | 2 | E2 | B | C |
| A | 0 | 3 | 1 | 3 | E3 | C | A |
Two separate connections between A and B form a cycle with direction: "both". The self-connection at C forms another cycle. For this input:
| edgeId | source | target | cost |
|---|---|---|---|
| P1 | A | B | 1 |
| P2 | A | B | 2 |
| L1 | C | C | 4 |
await parallelAndLoop
.findCycles("source", "target", "edgeId", ["A", "C"], {
direction: "both",
weight: "cost",
})
.log();
| start | pathId | step | weight | total | edgeId | source | target | cost |
|---|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | P1 | A | B | 1 |
| A | 0 | 2 | 2 | 3 | P2 | A | B | 2 |
| C | 0 | 1 | 4 | 4 | L1 | C | C | 4 |
With the weight option, total is a running total of the selected
values. For these flights:
| flightId | origin | destination | minutes |
|---|---|---|---|
| F1 | A | B | 1 |
| F2 | B | C | 2 |
| F3 | C | A | 3 |
await flights
.findCycles("origin", "destination", "flightId", "A", {
weight: "minutes",
})
.log();
| start | pathId | step | weight | total | flightId | origin | destination | minutes |
|---|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | F1 | A | B | 1 |
| A | 0 | 2 | 2 | 3 | F2 | B | C | 2 |
| A | 0 | 3 | 3 | 6 | F3 | C | A | 3 |
For an input without connection IDs, use addId() first:
| source | target |
|---|---|
| A | B |
| B | C |
| C | A |
await unnumberedConnections
.addId("edgeId", { prefix: "edge-" })
.findCycles("source", "target", "edgeId", "A")
.log();
| start | pathId | step | weight | total | source | target | edgeId |
|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | A | B | edge-0 |
| A | 0 | 2 | 1 | 2 | B | C | edge-1 |
| A | 0 | 3 | 1 | 3 | C | A | edge-2 |
For these instantaneous events, requesting A and B as starting nodes returns only the cycle B → C → A → B. Starting at A would break chronological order:
| flightId | origin | destination | departureTime |
|---|---|---|---|
| F1 | B | C | 2025-01-01 09:00 |
| F2 | C | A | 2025-01-01 10:00 |
| F3 | A | B | 2025-01-01 11:00 |
await timedFlights
.findCycles("origin", "destination", "flightId", ["A", "B"], {
startTimeColumn: "departureTime",
})
.log();
| start | pathId | step | weight | total | flightId | origin | destination | departureTime |
|---|---|---|---|---|---|---|---|---|
| B | 0 | 1 | 1 | 1 | F1 | B | C | 2025-01-01 09:00:00 |
| B | 0 | 2 | 1 | 2 | F2 | C | A | 2025-01-01 10:00:00 |
| B | 0 | 3 | 1 | 3 | F3 | A | B | 2025-01-01 11:00:00 |
Searching the same events from B in the incoming direction follows connections backward, from the latest to the earliest:
await timedFlights
.findCycles("origin", "destination", "flightId", "B", {
direction: "incoming",
startTimeColumn: "departureTime",
})
.log();
| start | pathId | step | weight | total | flightId | origin | destination | departureTime |
|---|---|---|---|---|---|---|---|---|
| B | 0 | 1 | 1 | 1 | F3 | A | B | 2025-01-01 11:00:00 |
| B | 0 | 2 | 1 | 2 | F2 | C | A | 2025-01-01 10:00:00 |
| B | 0 | 3 | 1 | 3 | F1 | B | C | 2025-01-01 09:00:00 |
For the next two examples, flights contains these rows, with departure
and arrival stored as timestamps on the same day:
| flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|
| F1 | A | B | 2025-01-01 09:00 | 2025-01-01 10:00 | 40 |
| F2 | B | A | 2025-01-01 12:00 | 2025-01-01 13:00 | 60 |
Report elapsed time for every cycle, including layovers:
await flights
.findCycles("origin", "destination", "flightId", "A", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
})
.log();
| start | pathId | step | weight | total | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 1 | 1 | 3600000 | F1 | A | B | 2025-01-01 09:00:00 | 2025-01-01 10:00:00 | 40 |
| A | 0 | 2 | 1 | 2 | 14400000 | F2 | B | A | 2025-01-01 12:00:00 | 2025-01-01 13:00:00 | 60 |
The return journey takes four hours (14,400,000 milliseconds), including the two-hour layover at B.
Add up prices while continuing to report elapsed time for every cycle:
await flights
.findCycles("origin", "destination", "flightId", "A", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
})
.log();
| start | pathId | step | weight | total | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A | 0 | 1 | 40 | 40 | 3600000 | F1 | A | B | 2025-01-01 09:00:00 | 2025-01-01 10:00:00 | 40 |
| A | 0 | 2 | 60 | 100 | 14400000 | F2 | B | A | 2025-01-01 12:00:00 | 2025-01-01 13:00:00 | 60 |
Finds the distinct nodes directly connected to one or more starting nodes.
The direction option lets you find neighbors from source to target,
from target to source, or in either direction. The result has start and
node columns, sorted in ascending order by start, then node.
Unknown starting IDs and starts with no neighbors in the selected direction produce no rows. A self-connection includes the starting node once. Empty start arrays and duplicate starting IDs throw an error.
The next three examples each start with this data:
| origin | destination |
|---|---|
| A | B |
| A | B |
| C | A |
By default, the method finds neighbors from source to target:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
One starting node ID or an array of distinct starting node IDs.
An optional object with direction and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing".
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
The result table, so methods can be chained.
await table
.neighbors("origin", "destination", "A")
.log();
| start | node |
|---|---|
| A | B |
B appears only once, even though the input has two connections from A to B.
With direction: "incoming", the method finds neighbors from target to
source. Here, C connects to A:
await table
.neighbors("origin", "destination", "A", { direction: "incoming" })
.log();
| start | node |
|---|---|
| A | C |
With direction: "both", the method finds neighbors in either direction:
Finds all routes between two different nodes. A route cannot visit the same node twice, to avoid loops. Different routes can still share the same nodes.
The direction option lets you follow connections from source to target,
from target to source, or in either direction.
Each connection needs a unique, non-null ID. Pass the name of the column
containing these IDs as the edgeId argument. If your table is missing
an ID column, you can easily create one with addId().
The result starts with the pathId, step, weight, and total
columns, followed by all original columns. Steps start at one and follow
the search direction. The weight column is the connection cost, and
total is the sum of those costs up to and including the current step.
Without the weight option, each connection costs one.
If an input column is already named pathId, step, weight, or total
(regardless of capitalization), the method throws an error. Rename it
with renameColumns() first.
Each row is one connection along a route.
Use the startTimeColumn or endTimeColumn options to follow connections
in chronological order. The minGapMs option sets the minimum gap between
consecutive connections, in milliseconds. With both time columns, gaps are
measured from one connection's end to the next connection's start; with one
column, between their timestamps. The strictOrdering option defaults to
true, rejecting zero-duration gaps. These options only work with
direction: "outgoing" or direction: "incoming".
The minimum gap applies only between consecutive connections, not before the first connection.
Routes are numbered from zero by comparing their sequences of connection
IDs, element by element. Rows are sorted by pathId, then step.
Unknown IDs or nodes with no route between them produce no rows. The start and end must differ. Self-connections are ignored. Weights must be non-null, finite, and non-negative.
There is no limit on route length or the number of routes returned. Finding all routes can take a long time and use substantial memory.
For the first example, we start with these connections:
| edgeId | source | target |
|---|---|---|
| E1 | A | B |
| E2 | A | C |
| E3 | B | D |
| E4 | C | D |
By default, connections are followed from source to target. Both routes from A to D are returned:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
The name of the column uniquely identifying each connection (edge).
The starting node ID.
The ending node ID, which must differ from start.
An optional object with direction, time, and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing".
OptionalelapsedTime?: booleanWhether to add cumulative elapsedTimeMs in milliseconds, including connection gaps. Defaults to false. Requires both time columns.
OptionalendTimeColumn?: stringThe name of the column containing each connection's end time. Use this or startTimeColumn to follow connections in chronological order.
OptionalminGapMs?: numberThe minimum gap between consecutive connections, in milliseconds. Must be a non-negative integer. Defaults to 0. Requires a time column.
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
OptionalstartTimeColumn?: stringThe name of the column containing each connection's start time. Use this or endTimeColumn to follow connections in chronological order.
OptionalstrictOrdering?: booleanWhether to reject zero-duration gaps between consecutive connections (equal timestamps). Defaults to true. Requires a time column.
Optionalweight?: stringThe name of the numeric column used as the cost of each connection. If omitted, each connection costs one.
The result table, so methods can be chained.
await connections
.paths("source", "target", "edgeId", "A", "D")
.log();
| pathId | step | weight | total | edgeId | source | target |
|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | E1 | A | B |
| 0 | 2 | 1 | 2 | E3 | B | D |
| 1 | 1 | 1 | 1 | E2 | A | C |
| 1 | 2 | 1 | 2 | E4 | C | D |
For an input without connection IDs, use addId() first:
| source | target |
|---|---|
| A | B |
| B | D |
The generated IDs identify the connections in the result:
await unnumberedConnections
.addId("edgeId", { prefix: "edge-" })
.paths("source", "target", "edgeId", "A", "D")
.log();
| pathId | step | weight | total | source | target | edgeId |
|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | A | B | edge-0 |
| 0 | 2 | 1 | 2 | B | D | edge-1 |
The next three examples each start with these flights. The direct flight takes ten minutes; the route through B takes three:
| flightId | origin | destination | minutes |
|---|---|---|---|
| F1 | A | D | 10 |
| F2 | A | B | 1 |
| F3 | B | D | 2 |
With weight: "minutes", both routes are returned with their travel
times:
await flights
.paths("origin", "destination", "flightId", "A", "D", {
weight: "minutes",
})
.log();
| pathId | step | weight | total | flightId | origin | destination | minutes |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 10 | 10 | F1 | A | D | 10 |
| 1 | 1 | 1 | 1 | F2 | A | B | 1 |
| 1 | 2 | 2 | 3 | F3 | B | D | 2 |
Without the weight option, the same routes are returned and total
counts connections:
await flights
.paths("origin", "destination", "flightId", "A", "D")
.log();
| pathId | step | weight | total | flightId | origin | destination | minutes |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | F1 | A | D | 10 |
| 1 | 1 | 1 | 1 | F2 | A | B | 1 |
| 1 | 2 | 1 | 2 | F3 | B | D | 2 |
With direction: "incoming", the search runs from D to A. Steps follow
that direction, while the original origin and destination values stay
unchanged:
await flights
.paths("origin", "destination", "flightId", "D", "A", {
direction: "incoming",
weight: "minutes",
})
.log();
| pathId | step | weight | total | flightId | origin | destination | minutes |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 10 | 10 | F1 | A | D | 10 |
| 1 | 1 | 2 | 2 | F3 | B | D | 2 |
| 1 | 2 | 1 | 3 | F2 | A | B | 1 |
Chronological routes enforce the connection time between every pair of steps. For the next two examples, the 09:00 flight leaves before F1 arrives, while F3 meets the one-hour minimum exactly:
| flightId | origin | destination | departureTime | arrivalTime | cost |
|---|---|---|---|---|---|
| F1 | A | B | 2025-01-01 08:00 | 2025-01-01 10:00 | 2 |
| F2 | B | C | 2025-01-01 09:00 | 2025-01-01 10:00 | 9 |
| F3 | B | D | 2025-01-01 11:00 | 2025-01-01 12:00 | 3 |
The route from A to D contains F1 and F3:
await scheduledFlights
.paths("origin", "destination", "flightId", "A", "D", {
startTimeColumn: "departureTime",
endTimeColumn: "arrivalTime",
minGapMs: 60 * 60 * 1000,
weight: "cost",
})
.log();
| pathId | step | weight | total | flightId | origin | destination | departureTime | arrivalTime | cost |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 2 | F1 | A | B | 2025-01-01 08:00:00 | 2025-01-01 10:00:00 | 2 |
| 0 | 2 | 3 | 5 | F3 | B | D | 2025-01-01 11:00:00 | 2025-01-01 12:00:00 | 3 |
Searching backward from D finds the same physical journey. The steps and cumulative totals follow the incoming search from D to A:
await scheduledFlights
.paths("origin", "destination", "flightId", "D", "A", {
direction: "incoming",
startTimeColumn: "departureTime",
endTimeColumn: "arrivalTime",
minGapMs: 60 * 60 * 1000,
weight: "cost",
})
.log();
| pathId | step | weight | total | flightId | origin | destination | departureTime | arrivalTime | cost |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 3 | 3 | F3 | B | D | 2025-01-01 11:00:00 | 2025-01-01 12:00:00 | 3 |
| 0 | 2 | 2 | 5 | F1 | A | B | 2025-01-01 08:00:00 | 2025-01-01 10:00:00 | 2 |
For the next two examples, flights contains these rows, with departure
and arrival stored as timestamps on the same day:
| flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|
| F1 | A | B | 2025-01-01 09:00 | 2025-01-01 10:00 | 40 |
| F2 | B | C | 2025-01-01 12:00 | 2025-01-01 13:00 | 40 |
| F3 | A | C | 2025-01-01 09:00 | 2025-01-01 12:00 | 120 |
Report elapsed time for every path, including layovers:
await flights
.paths("origin", "destination", "flightId", "A", "C", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
})
.log();
| pathId | step | weight | total | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 3600000 | F1 | A | B | 2025-01-01 09:00:00 | 2025-01-01 10:00:00 | 40 |
| 0 | 2 | 1 | 2 | 14400000 | F2 | B | C | 2025-01-01 12:00:00 | 2025-01-01 13:00:00 | 40 |
| 1 | 1 | 1 | 1 | 10800000 | F3 | A | C | 2025-01-01 09:00:00 | 2025-01-01 12:00:00 | 120 |
The route through B takes four hours (14,400,000 milliseconds), including its two-hour layover. The direct flight takes three hours.
Add up prices while continuing to report elapsed time for every path:
await flights
.paths("origin", "destination", "flightId", "A", "C", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
})
.log();
| pathId | step | weight | total | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 40 | 40 | 3600000 | F1 | A | B | 2025-01-01 09:00:00 | 2025-01-01 10:00:00 | 40 |
| 0 | 2 | 40 | 80 | 14400000 | F2 | B | C | 2025-01-01 12:00:00 | 2025-01-01 13:00:00 | 40 |
| 1 | 1 | 120 | 120 | 10800000 | F3 | A | C | 2025-01-01 09:00:00 | 2025-01-01 12:00:00 | 120 |
Finds all nodes you can reach from one or more starting nodes, following
one or more connections. The direction option lets you follow
connections from source to target, from target to source, or in either
direction. The result has start and node columns, sorted by start,
then node.
A starting node appears in its own results only if following connections
leads back to it, such as a self-connection or a cycle. Each reachable
node appears only once per start. With direction: "both", following
the same connection out and back also reaches the starting node.
Unknown starting IDs and starts with no connections to follow produce no rows. Empty start arrays and duplicate starting IDs throw an error.
Use the startTimeColumn or endTimeColumn options to follow connections
in chronological order. The minGapMs option sets the minimum gap between
consecutive connections, in milliseconds. With both time columns, gaps are
measured from one connection's end to the next connection's start; with one
column, between their timestamps. The strictOrdering option defaults to
true, rejecting zero-duration gaps. These options only work with
direction: "outgoing" or direction: "incoming".
The next four examples each start with this data:
| origin | destination |
|---|---|
| A | B |
| A | C |
| B | D |
| C | D |
| D | A |
By default, the method follows connections from source to target. A appears in its own results because A → B → D → A leads back to it:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
One starting node ID or an array of distinct starting node IDs.
An optional object with direction, time, and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing".
OptionalendTimeColumn?: stringThe name of the column containing each connection's end time. Use this or startTimeColumn to follow connections in chronological order.
OptionalminGapMs?: numberThe minimum gap between consecutive connections, in milliseconds. Must be a non-negative integer. Defaults to 0. Requires a time column.
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
OptionalstartTimeColumn?: stringThe name of the column containing each connection's start time. Use this or endTimeColumn to follow connections in chronological order.
OptionalstrictOrdering?: booleanWhether to reject zero-duration gaps between consecutive connections (equal timestamps). Defaults to true. Requires a time column.
The result table, so methods can be chained.
await connections
.reachable("origin", "destination", "A")
.log();
| start | node |
|---|---|
| A | A |
| A | B |
| A | C |
| A | D |
With direction: "incoming", connections are followed from target to
source. Starting at B:
await connections
.reachable("origin", "destination", "B", { direction: "incoming" })
.log();
| start | node |
|---|---|
| B | A |
| B | B |
| B | C |
| B | D |
With direction: "both", connections can be followed in either
direction:
await connections
.reachable("origin", "destination", "B", { direction: "both" })
.log();
| start | node |
|---|---|
| B | A |
| B | B |
| B | C |
| B | D |
With multiple starting nodes, each has its own results:
await connections
.reachable("origin", "destination", ["B", "A"])
.log();
| start | node |
|---|---|
| A | A |
| A | B |
| A | C |
| A | D |
| B | A |
| B | B |
| B | C |
| B | D |
Chronological options retain only journeys with valid connection times. Here, A → B arrives at 10:00, so the 09:00 connection to C is unavailable, while the 11:00 connection to D meets the one-hour minimum gap:
| origin | destination | departureTime | arrivalTime |
|---|---|---|---|
| A | B | 2025-01-01 08:00 | 2025-01-01 10:00 |
| B | C | 2025-01-01 09:00 | 2025-01-01 10:00 |
| B | D | 2025-01-01 11:00 | 2025-01-01 12:00 |
The departure and arrival columns are timestamps:
Finds the shortest route between two different nodes. If several routes
tie for shortest, all of them are returned. By default, the shortest
route uses the fewest connections. Use the weight option to find the
route with the smallest sum of values from a numeric column, such as
travel time. A route cannot visit the same node twice, to avoid loops.
Different routes can still share the same nodes.
The direction option lets you follow connections from source to target,
from target to source, or in either direction.
Each connection needs a unique, non-null ID. Pass the name of the column
containing these IDs as the edgeId argument. If your table is missing
an ID column, you can easily create one with addId().
The result starts with pathId, step, and weight, followed by the
optional metric columns and all original columns. Steps start at one and
follow the search direction. The weight column is the connection cost,
or one when no weight option is supplied. Supplying weight also adds
total, the sum of those costs up to and including the current step.
Enabling elapsedTime adds cumulative elapsedTimeMs, including gaps.
If an input column conflicts with a generated result column (regardless
of capitalization), the method throws an error. Rename it with
renameColumns() first. total is reserved only when weight is supplied,
and elapsedTimeMs only when elapsed-time reporting is enabled.
Each row is one connection along a route.
Use the startTimeColumn or endTimeColumn options to follow connections
in chronological order. The minGapMs option sets the minimum gap between
consecutive connections, in milliseconds. With both time columns, gaps are
measured from one connection's end to the next connection's start; with one
column, between their timestamps. The strictOrdering option defaults to
true, rejecting zero-duration gaps. These options only work with
direction: "outgoing" or direction: "incoming".
The minimum gap applies only between consecutive connections, not before the first connection.
Use minimize: "steps" to choose the fewest connections even when weight
or elapsed-time reporting is enabled. Without an explicit minimize, the
method minimizes weight when only weight is enabled, elapsed time when
only elapsedTime is enabled, and steps when neither is enabled. With
both enabled, specify "steps", "weight", or "elapsedTime" explicitly.
Elapsed-time reporting requires both time columns.
All routes tied on the selected metric are returned, even if their other metrics differ. Each route reports its own weights and elapsed times.
Routes are numbered from zero by comparing their sequences of connection
IDs, element by element. Rows are sorted by pathId, then step.
Unknown IDs or nodes with no route between them produce no rows. The start and end must differ. Self-connections are ignored. Weights must be non-null, finite, and non-negative.
There is no limit on route length or the number of tied routes returned. Finding many tied routes can take a long time and use substantial memory.
The first example uses this table:
| edgeId | source | target |
|---|---|---|
| E1 | A | B |
| E2 | A | C |
| E3 | B | D |
| E4 | C | D |
| E5 | D | E |
By default, the method finds routes from source to target with the fewest connections. Two routes from A to E tie at three connections each:
The name of the column containing each connection's source node ID.
The name of the column containing each connection's target node ID.
The name of the column uniquely identifying each connection (edge).
The starting node ID.
The ending node ID, which must differ from start.
An optional object with direction, time, and result configuration.
Optionaldirection?: "outgoing" | "incoming" | "both"The direction in which to follow connections. Defaults to "outgoing".
OptionalelapsedTime?: booleanWhether to add cumulative elapsedTimeMs in milliseconds, including connection gaps. Defaults to false. Requires both time columns.
OptionalendTimeColumn?: stringThe name of the column containing each connection's end time. Use this or startTimeColumn to follow connections in chronological order.
OptionalminGapMs?: numberThe minimum gap between consecutive connections, in milliseconds. Must be a non-negative integer. Defaults to 0. Requires a time column.
Optionalminimize?: "weight" | "steps" | "elapsedTime"The metric used to select optimal routes: "steps", "weight", or "elapsedTime". Defaults to "weight" when only weight is enabled, "elapsedTime" when only elapsed-time reporting is enabled, and "steps" when neither is enabled. Required when both are enabled. "steps" is always available; the other metrics require their corresponding options.
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
OptionalstartTimeColumn?: stringThe name of the column containing each connection's start time. Use this or endTimeColumn to follow connections in chronological order.
OptionalstrictOrdering?: booleanWhether to reject zero-duration gaps between consecutive connections (equal timestamps). Defaults to true. Requires a time column.
Optionalweight?: stringThe name of the numeric column used as the cost of each connection. Adds total, the cumulative sum of those costs. If omitted, no total column is generated.
The table of route connections with optional cumulative total and elapsedTimeMs metrics, so methods can be chained.
await connections
.shortestPath("source", "target", "edgeId", "A", "E")
.log();
| pathId | step | weight | edgeId | source | target |
|---|---|---|---|---|---|
| 0 | 1 | 1 | E1 | A | B |
| 0 | 2 | 1 | E3 | B | D |
| 0 | 3 | 1 | E5 | D | E |
| 1 | 1 | 1 | E2 | A | C |
| 1 | 2 | 1 | E4 | C | D |
| 1 | 3 | 1 | E5 | D | E |
For an input without connection IDs, use addId() first:
| source | target |
|---|---|
| A | B |
| B | E |
The generated IDs identify the connections in the result:
await unnumberedConnections
.addId("edgeId", { prefix: "edge-" })
.shortestPath("source", "target", "edgeId", "A", "E")
.log();
| pathId | step | weight | source | target | edgeId |
|---|---|---|---|---|---|
| 0 | 1 | 1 | A | B | edge-0 |
| 0 | 2 | 1 | B | E | edge-1 |
The next two examples each start with these flights. The direct flight takes ten minutes; the route through B and D takes three:
| flightId | origin | destination | minutes |
|---|---|---|---|
| F1 | A | E | 10 |
| F2 | A | B | 1 |
| F3 | B | D | 1 |
| F4 | D | E | 1 |
With weight: "minutes", the route with the shortest travel time is
chosen:
await flights
.shortestPath("origin", "destination", "flightId", "A", "E", {
weight: "minutes",
})
.log();
| pathId | step | weight | total | flightId | origin | destination | minutes |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1 | F2 | A | B | 1 |
| 0 | 2 | 1 | 2 | F3 | B | D | 1 |
| 0 | 3 | 1 | 3 | F4 | D | E | 1 |
Without the weight option, the direct flight is shortest because it
uses only one connection:
await flights
.shortestPath("origin", "destination", "flightId", "A", "E")
.log();
| pathId | step | weight | flightId | origin | destination | minutes |
|---|---|---|---|---|---|---|
| 0 | 1 | 1 | F1 | A | E | 10 |
With time options, the cheaper route through B is ruled out because F2 leaves before F1 arrives. The route through C meets the one-hour minimum gap:
| flightId | origin | destination | departureTime | arrivalTime | cost |
|---|---|---|---|---|---|
| F1 | A | B | 2025-01-01 08:00 | 2025-01-01 10:00 | 1 |
| F2 | B | E | 2025-01-01 09:00 | 2025-01-01 10:00 | 1 |
| F3 | A | C | 2025-01-01 08:00 | 2025-01-01 09:00 | 2 |
| F4 | C | E | 2025-01-01 10:00 | 2025-01-01 11:00 | 2 |
await scheduledFlights
.shortestPath("origin", "destination", "flightId", "A", "E", {
startTimeColumn: "departureTime",
endTimeColumn: "arrivalTime",
minGapMs: 60 * 60 * 1000,
weight: "cost",
})
.log();
| pathId | step | weight | total | flightId | origin | destination | departureTime | arrivalTime | cost |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 2 | F3 | A | C | 2025-01-01 08:00:00 | 2025-01-01 09:00:00 | 2 |
| 0 | 2 | 2 | 4 | F4 | C | E | 2025-01-01 10:00:00 | 2025-01-01 11:00:00 | 2 |
With direction: "incoming", connections are followed from target to
source. The original source and target values remain unchanged. For
this input:
| edgeId | source | target |
|---|---|---|
| F1 | A | B |
await reverseExample
.shortestPath("source", "target", "edgeId", "B", "A", {
direction: "incoming",
})
.log();
| pathId | step | weight | edgeId | source | target |
|---|---|---|---|---|---|
| 0 | 1 | 1 | F1 | A | B |
For the next three examples, flights contains these rows, with departure
and arrival stored as timestamps on the same day:
| flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|
| F1 | A | B | 2025-01-01 09:00 | 2025-01-01 10:00 | 40 |
| F2 | B | C | 2025-01-01 12:00 | 2025-01-01 13:00 | 40 |
| F3 | A | C | 2025-01-01 09:00 | 2025-01-01 12:00 | 120 |
| F4 | A | D | 2025-01-01 09:00 | 2025-01-01 09:30 | 60 |
| F5 | D | C | 2025-01-01 10:30 | 2025-01-01 11:00 | 80 |
Find the fastest journey from A to C, including layovers:
await flights
.shortestPath("origin", "destination", "flightId", "A", "C", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
})
.log();
| pathId | step | weight | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 1 | 1800000 | F4 | A | D | 2025-01-01 09:00:00 | 2025-01-01 09:30:00 | 60 |
| 0 | 2 | 1 | 7200000 | F5 | D | C | 2025-01-01 10:30:00 | 2025-01-01 11:00:00 | 80 |
The route through D takes two hours (7,200,000 milliseconds), including a one-hour layover. The direct flight takes three hours.
Find the fastest journey while also adding up its price:
await flights
.shortestPath("origin", "destination", "flightId", "A", "C", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
minimize: "elapsedTime",
})
.log();
| pathId | step | weight | total | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 60 | 60 | 1800000 | F4 | A | D | 2025-01-01 09:00:00 | 2025-01-01 09:30:00 | 60 |
| 0 | 2 | 80 | 140 | 7200000 | F5 | D | C | 2025-01-01 10:30:00 | 2025-01-01 11:00:00 | 80 |
Use minimize: "weight" to find the cheapest journey instead.
All ties on the chosen metric are returned.
Choose the fewest flights while still reporting price and elapsed time. The direct flight takes one step, although the route through D is faster:
await flights
.shortestPath("origin", "destination", "flightId", "A", "C", {
startTimeColumn: "departure",
endTimeColumn: "arrival",
minGapMs: 60 * 60 * 1000,
elapsedTime: true,
weight: "price",
minimize: "steps",
})
.log();
| pathId | step | weight | total | elapsedTimeMs | flightId | origin | destination | departure | arrival | price |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 120 | 120 | 10800000 | F3 | A | C | 2025-01-01 09:00:00 | 2025-01-01 12:00:00 | 120 |
Orders nodes so each prerequisite comes before the nodes that depend on it. Each input row means the source node must come before the target node. For example, A → B means A must come before B.
The result has node, componentId, and order columns, sorted by
componentId, then order. Nodes connected directly or through other
nodes belong to the same group, ignoring connection direction.
Groups are numbered from zero, with the group containing the smallest
node ID first.
Within each group, order starts at one. When several nodes are ready,
the method chooses the smallest node ID first: numeric order for numbers
and character order for strings. All nodes in the source and target
columns are included.
If dependencies form a loop, there is no valid order and the method throws an error. A node depending on itself also causes an error. An empty input produces no rows.
For this example, we start with these connections:
| source | target |
|---|---|
| A | B |
| B | C |
| D | C |
A must come before B, and both B and D must come before C:
The name of the column containing each prerequisite node ID.
The name of the column containing each dependent node ID.
An optional object with result configuration.
OptionaloutputTable?: string | booleanIf true, stores the result in a new table with a generated name. If a string, uses it as the new table's name. If false or omitted, overwrites the current table. Defaults to false.
The result table, so methods can be chained.
await table
.topologicalSort("source", "target")
.log();
| node | componentId | order |
|---|---|---|
| A | 0 | 1 |
| B | 0 | 2 |
| D | 0 | 3 |
| C | 0 | 4 |
C comes last because it depends on both B and D. A comes before D because both are initially ready and A has the smaller ID. After A, B is ready and comes before D for the same reason.
With two independent groups, each has its own order starting at one. This example starts with different data:
| source | target |
|---|---|
| A | D |
| B | C |
Inserts rows, provided as an array of JavaScript objects, into the table.
An array of objects, where each object represents a row to be inserted and its properties correspond to column names.
The table, so methods can be chained.
Inserts all rows from one or more other tables into this table. If tables do not have the same columns, an error will be thrown unless the unifyColumns option is set to true.
The name(s) of the table(s) or SimpleTable instance(s) from which rows will be inserted.
An optional object with configuration options:
OptionalunifyColumns?: booleanA boolean indicating whether to unify the columns of the tables. If true, missing columns in a table will be filled with NULL values. Defaults to false.
The table, so methods can be chained.
Loads an array of JavaScript objects into the table. Types are inferred for
numbers, bigints, strings, booleans, and Date values. Array and object cells
require an explicit supported type in columnTypes. Types can also be
specified for scalar columns instead of inferred from their values.
Inferred bigints use signed 64-bit BIGINT; values outside that range throw
when written instead of wrapping.
JavaScript Date values are inferred as DuckDB TIMESTAMP values. Their
instant is preserved, but JavaScript Date does not retain the timezone or
offset originally used to construct it.
Declare JSON for general nested data, FLOAT[n] for a fixed-size float
vector, or GEOMETRY('EPSG:4326') for GeoJSON geometries in WGS84.
An array of objects, where each object represents a row and its properties represent columns.
Options for loading the array, captured when called.
OptionalcolumnTypes?: {Types for specific columns. Required for array and object cells; omitted scalar columns are inferred, and all-null columns default to VARCHAR. Values must be compatible with the selected type without losing information.
The table, so methods can be chained.
// Load scalar columns without columnTypes; strings and numbers are inferred.
const data = [
{ letter: "a", number: 1 },
{ letter: "b", number: 2 }
];
await table.loadArray(data).log();
// Choose a numeric type when all-null values provide nothing to infer.
await table.loadArray(
[{ name: "A", value: null }, { name: "B", value: null }],
{ columnTypes: { value: "DOUBLE" } },
).log();
// Store the Date as a UTC timestamp with JavaScript's millisecond precision.
// The original -04:00 offset is not retained.
await table.loadArray([{
observedAt: new Date("2024-04-07T13:00:00-04:00"),
}]).log();
// Declare JSON to load nested objects and variable-length arrays directly.
await table.loadArray([{
name: "Station",
metadata: { active: true, tags: ["weather", "urban"] },
measurements: [1, 2.5, null],
}], { columnTypes: { metadata: "JSON", measurements: "JSON" } }).log();
Loads data from one or more local or remote files into the table. Supported file formats include CSV, JSON, Parquet, and Excel.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The path(s) or URL(s) of the file(s) containing the data to be loaded.
An optional object with configuration options:
OptionalallText?: booleanA boolean indicating whether all columns should be treated as text. Applicable to CSV files. Defaults to false.
OptionalautoDetect?: booleanA boolean indicating whether to automatically detect the data format. Defaults to true.
Optionalcolumns?: string[]An array of column names to load. When provided, only the specified columns are loaded, reducing memory usage and improving load times. Not supported for Excel files — combining columns with Excel files throws an error. If an invalid column name is provided, DuckDB will throw its native error. An empty array behaves the same as omitting the option (loads all columns). Defaults to loading all columns.
OptionalcolumnTypes?: { [key: string]: string }An object mapping column names to their expected data types. By default, types are inferred. Geometry types are not supported; use loadGeoData() instead.
Optionalcompression?: "none" | "gzip" | "zstd"The compression type of the file. Applicable to CSV files. Defaults to none.
Optionalconditions?: stringA SQL WHERE clause expression, without the WHERE keyword, to filter source rows before applying limit. Can reference source columns excluded from columns.
Optionaldelim?: stringThe delimiter used in the file. Applicable to CSV and DSV files. By default, the delimiter is inferred.
Optionalencoding?: stringThe encoding of the file. Applicable to CSV files. Defaults to utf-8.
OptionalfileType?: "json" | "csv" | "dsv" | "parquet" | "excel"The type of file to load ("csv", "dsv", "json", "parquet", "excel"). Defaults to being inferred from the file extension.
Optionalheader?: booleanA boolean indicating whether the file has a header row. Applicable to CSV files. Defaults to true.
OptionalignoreErrors?: booleanIf true, parsing errors encountered will be ignored, and rows with errors will be skipped. Defaults to false.
OptionalincludeFilename?: booleanA boolean indicating whether to include the filename as a new column in the loaded data. Defaults to false.
OptionaljsonFormat?: "array" | "unstructured" | "newlineDelimited"The format of JSON files ("unstructured", "newlineDelimited", "array"). By default, the format is inferred.
Optionallimit?: numberA number indicating the maximum number of matching rows to load, after applying conditions if provided. Defaults to all matching rows.
OptionalnullPadding?: booleanIf true, when a row has fewer columns than expected, the remaining columns on the right will be padded with NULL values. Defaults to false.
Optionalrecords?: booleanA boolean indicating whether each line in a newline-delimited JSON file represents a record. Applicable to JSON files. By default, it's inferred.
Optionalsheet?: stringA string indicating a specific sheet to import from an Excel file. By default, the first sheet is imported.
Optionalskip?: numberThe number of lines to skip at the beginning of the file. Applicable to CSV files. Defaults to 0.
Optionalstrict?: booleanIf true, an error will be thrown when encountering any issues. If false, structurally incorrect files will be parsed tentatively. Defaults to true.
OptionalunifyColumns?: booleanA boolean indicating whether to unify columns across multiple files when their structures differ. Missing columns will be filled with NULL values. Defaults to false.
The table, so methods can be chained.
// Load data from a remote Parquet file
await table.loadData("https://some-website.com/some-data.parquet").log();
// Load data from multiple local JSON files
await table.loadData([
"./some-data1.json",
"./some-data2.json",
"./some-data3.json"
]).log();
// Load multiple CSV files and unify columns that differ between files
await table.loadData("./data/*.csv", { unifyColumns: true }).log();
// Keep the source filename when loading multiple files
await table
.loadData("./data/*.csv", { includeFilename: true })
.log();
Fetches sample data from the simple-data-analysis-core GitHub repository.
The name of the sample to load.
Tabular data:
Geospatial data:
Downloads a complete Statistics Canada table and loads it into this table.
Results are cached as Parquet files in .sda-cache/statcan by default.
Cached data does not expire unless a TTL is provided.
The Statistics Canada Product ID. Eight-digit PIDs, ten-digit view PIDs, and hyphenated table identifiers are accepted.
Optional retrieval and cache settings.
Optionalcache?: booleanWhether to read and write the cache. Defaults to true.
Optionallang?: "en" | "fr"The language of the table data. Defaults to "en".
Optionalttl?: numberCache lifetime in seconds. Omit for no expiration, use 0 to refresh the matching cache entry immediately, or provide a positive value to refresh once the entry reaches that age.
The table, so methods can be chained.
Downloads historical market data from Yahoo Finance and loads it into this
table as datetime, open, high, low, close, adjustedClose, and
volume columns. datetime contains Yahoo's timestamps as JavaScript
Date values. Unavailable values are preserved as null.
All range boundaries are evaluated in UTC. Use ISO date-time strings ending
in Z. Yahoo trading sessions may occur on a different UTC date from their
exchange-local date, so choose the range appropriate for the market and
interval. Avoid numeric constructors such as new Date(2025, 2, 15), which
use the runtime's local timezone.
This method uses an undocumented Yahoo Finance endpoint and is not affiliated with or endorsed by Yahoo. It is provided for educational, research, and journalistic purposes. Before using it, review Yahoo's terms and any applicable data-provider restrictions.
The stock or index symbol, such as "AAPL" or "^GSPTSE".
The inclusive UTC start of the requested range.
The inclusive UTC end of the requested range. The UTC day,
hour, or minute containing this instant is included, according to
interval.
The interval between observations: daily, hourly, or every minute.
The table, so methods can be chained.
// Request daily observations using explicit UTC boundaries.
await table
.loadYahooFinanceData(
"^GSPTSE",
new Date("2025-03-01T00:00:00Z"),
new Date("2025-03-15T00:00:00Z"),
"1d",
)
.log();
Logs up to a specified number of rows from the table to the console. By default, the first 10 rows are logged.
You can optionally log the column types and filter the data based on conditions.
The footer reports the total number of matching rows and, only when some rows
are omitted, the number actually shown (for example, 100 rows in total / showing 15 rows).
SQL dates and timestamps retain their native precision for display, including
temporal infinities. Lists and objects are stringified using the same nested
representations as getData(), then truncated according to charsToLog.
Unsafe top-level large integers throw, just as with getData().
Type annotations describe the SQL type and the JavaScript extraction type
before display formatting; nulls do not determine the column's annotation.
Colors reflect the SQL value's meaning, so exact decimal strings use numeric
coloring. Column width is independent of the content truncation budget.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
Either the number of rows to log (a specific number or "all") or an object with configuration options:
Optionalconditions?: stringA SQL WHERE clause condition to filter the data before logging. Defaults to no condition.
Optionalcount?: number | "all"The maximum number of rows to log. Defaults to 10 or the value set in the SimpleDB instance. Use "all" to log all rows.
Optionaltypes?: booleanWhether to log the column types along with the data. Defaults to the value set in the SimpleDB instance.
A promise that resolves to the table, so methods can be chained.
Logs the bottom n rows of the table to the console. By default, the last row will be returned first. To preserve the original order, use the originalOrder option.
Uses the same precision-preserving temporal display, nested stringification,
numeric coloring, and charsToLog truncation as log(). Unsafe top-level
large integers throw, just as with getData().
Optionalcount: number
The number of rows to log from the bottom of the table. Defaults to the table's rowsToLog option if not specified.
An optional object with logging preferences.
OptionaloriginalOrder?: booleanIf true, the rows are displayed in their original order (top to bottom). Defaults to false.
A promise that resolves to the table, so methods can be chained.
Logs the columns in the table to the console. You can optionally include their data types.
An optional object with configuration options:
Optionaltypes?: booleanIf true, logs the column names along with their data types. Defaults to false.
A promise that resolves to the SimpleTable instance after logging the columns.
Logs descriptive information about the columns in the table to the console. This includes details such as data types, number of null values, and number of distinct values for each column.
It internally calls the getDescription method to retrieve the descriptive statistics.
A promise that resolves to the table, so methods can be chained.
Logs the extent (minimum and maximum values) of a numeric column to the console.
The name of the numeric column for which to log the extent.
A promise that resolves to the table, so methods can be chained.
Logs the projections of the geospatial data (if any) to the console.
A promise that resolves to the SimpleTable instance after logging the projections.
Logs the total number of rows in the table to the console.
A promise that resolves to the SimpleTable instance after logging the row count.
Logs the types of all columns in the table to the console.
A promise that resolves to the SimpleTable instance after logging the column types.
Logs unique values for a specified column to the console. By default, a maximum of 100 values are logged (depending on your runtime). You can optionally stringify the values to see them all.
The name of the column from which to retrieve and log unique values.
An optional object with configuration options:
Optionalstringify?: booleanIf true, converts the unique values to a JSON string before logging. Defaults to false.
A promise that resolves to the SimpleTable instance after logging the unique values.
The maximum number of characters to display for text and stringified nested cells, including the truncation marker. This does not control column width. Set to Infinity to show the entire text.
A connection to a DuckDB database.
A DuckDB database instance.
A flag indicating if the table name was assigned by default.
The number of rows to display when logging a table.
A function for running SQL queries. Subclasses can wrap this seam to observe
execution while forwarding every argument. To run custom SQL, use
SimpleDB.customQuery() instead.
The exact SQL statement to execute.
The DuckDB connection that executes the statement.
Whether to convert and return result rows.
Error-attribution, binding, and observability options.
Whether to log a supported DuckDB query plan before execution.
Whether to log the SQL immediately before execution.
The SDA method responsible for the query, when available.
The method arguments recorded for error attribution.
Optionaltable?: string | nullThe table associated with returned values and warnings.
Optionalvalues?: DuckDBValue[]Data values bound to placeholders in the SQL statement.
The converted rows when requested, otherwise null.
A flag indicating whether to include data types when logging a table.
Generates a random point within the geometries of a specified column.
The name of the new column where the random points will be stored.
The number of points to generate within the bounding box of each geometry to find one that is within the geometry itself.
An optional object with configuration options:
Optionalcolumn?: stringThe name of the column storing the geometries within which the random points will be generated. If omitted, the method will automatically attempt to find a geometry column.
Optionalstrict?: booleanIf false, the method will not throw an error if some points cannot be generated. Corresponding rows will have NULL in the new column. Defaults to true.
// Generate a random point for each geometry in the default column, trying 100 points
await table.randomPoint("randomPoint", 100).log();
The definitions of the indexes belonging to the table, if any. Do not mutate this array directly.
The SimpleDB instance that created this table.
Restructures this table by stacking (unpivoting) columns. This is useful for tidying up data from a wide format to a long format.
For example, given a table showing employee counts per department per year:
| Department | 2021 | 2022 | 2023 |
|---|---|---|---|
| Accounting | 10 | 9 | 15 |
| Sales | 52 | 75 | 98 |
We can restructure it by putting all year columns into a new column named Year and their corresponding employee counts into a new column named Employees.
An array of strings representing the names of the columns to be stacked (unpivoted).
The name of the new column that will contain the original column names (e.g., "Year").
The name of the new column that will contain the values from the stacked columns (e.g., "Employees").
The table, so methods can be chained.
// Restructure the table by stacking year columns into 'year' and 'employees'
await table.longer(["2021", "2022", "2023"], "year", "employees").log();
The table will then look like this:
| Department | Year | Employees |
|---|---|---|
| Accounting | 2021 | 10 |
| Accounting | 2022 | 9 |
| Accounting | 2023 | 15 |
| Sales | 2021 | 52 |
| Sales | 2022 | 75 |
| Sales | 2023 | 98 |
Sorts the rows of the table based on specified column(s) and order(s). If no columns are specified, all columns are sorted from left to right in ascending order.
Order-preserving transformations after a sort retain that order. Operations such as joins, grouping, aggregation, and sampling do not guarantee input order; chain sort() after them when deterministic output order matters.
An object mapping column names to their sorting order: "asc" for ascending or "desc" for descending. If null, all columns are sorted ascendingly.
An optional object with configuration options:
Optionallang?: { [key: string]: string }An object mapping column names to language codes for collation (e.g., { column1: "fr" }). See DuckDB Collations documentation for more details: https://duckdb.org/docs/sql/expressions/collations.
The table, so methods can be chained.
Restructures this table by unstacking (pivoting) values, transforming data from a long format to a wide format.
For example, given a table showing employee counts per department per year:
| Department | Year | Employees |
|---|---|---|
| Accounting | 2021 | 10 |
| Accounting | 2022 | 9 |
| Accounting | 2023 | 15 |
| Sales | 2021 | 52 |
| Sales | 2022 | 75 |
| Sales | 2023 | 98 |
We can restructure it by creating new columns for each year, with the associated employee counts as values.
The name of the column containing the values that will be transformed into new column headers (e.g., "Year").
The name of the column containing the values to be spread across the new columns (e.g., "Employees").
An optional object with configuration options:
Optionalstat?: "count" | "min" | "max" | "mean" | "median" | "sum" | "first"The stat function applied when multiple rows share the same namesFrom/grouping combination: "sum", "count", "min", "max", "mean", "median", or "first". Defaults to "sum".
The table, so methods can be chained.
// Restructure the table by pivoting 'Year' into new columns with 'Employees' as values
await table.wider("Year", "Employees").log();
The table will then look like this:
| Department | 2021 | 2022 | 2023 |
|---|---|---|---|
| Accounting | 10 | 9 | 15 |
| Sales | 52 | 75 | 98 |
When multiple rows share the same namesFrom/grouping combination, their valuesFrom values are combined with the options.stat function ("sum" by default).
Filters rows from this table based on SQL conditions. Note that it's often faster to use the removeRows method for simple removals.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The filtering conditions specified as a SQL WHERE clause (e.g., "column1 > 10 AND column2 = 'value'").
The table, so methods can be chained.
// Keep only rows where the 'fruit' column is not 'apple'
await table.filter(`fruit != 'apple'`).log();
// Keep rows where 'price' is greater than 100 AND 'quantity' is greater than 0
await table.filter(`price > 100 && quantity > 0`).log(); // Using JS syntax
Keeps rows in this table that have specific values in specified columns, removing all other rows.
An object where keys are column names and values are the specific values (or an array of values) to keep in those columns. Use null to keep rows where a column is NULL.
The table, so methods can be chained.
Removes duplicate rows from this table, keeping only unique rows. Note that the resulting data order might differ from the original.
An optional object with configuration options:
Optionalon?: string | string[]A column name or an array of column names to consider when identifying duplicates. If specified, duplicates are determined based only on the values in these columns. If omitted, all columns are considered.
The table, so methods can be chained.
Removes rows with missing values from this table.
By default, missing values include SQL NULL, as well as string representations like "NULL", "null", "NaN", "undefined", and empty strings "".
An optional object with configuration options:
Optionalcolumns?: string | string[]A string or an array of strings specifying the columns to consider for missing values. If omitted, all columns are considered.
Optionalinvert?: booleanA boolean indicating whether to invert the condition. If true, only rows containing missing values will be kept. Defaults to false.
OptionalmissingValues?: (string | number)[]An array of values to be treated as missing values instead of the default ones. Defaults to ["undefined", "NaN", "null", "NULL", ""].
The table, so methods can be chained.
Removes rows from this table based on SQL conditions. This method is similar to filter(), but removes rows instead of keeping them.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The filtering conditions specified as a SQL WHERE clause (e.g., "fruit = 'apple'").
The table, so methods can be chained.
Removes rows from this table that have specific values in specified columns.
An object where keys are column names and values are the specific values (or an array of values) to remove from those columns. Use null to remove rows where a column is NULL; otherwise, NULL rows are retained.
The table, so methods can be chained.
Selects random rows from the table, removing all others. You can optionally specify a seed to ensure repeatable sampling.
The number of rows to select (e.g., 100) or a percentage string (e.g., "10%") specifying the sampling size.
An optional object with configuration options:
Optionalseed?: numberA number specifying the seed for repeatable sampling. Using the same seed will always yield the same random rows. Defaults to a random seed.
The table, so methods can be chained.
Selects specific columns in the table, removing all others.
The name or an array of names of the columns to be selected.
The table, so methods can be chained.
Selects a specified number of rows from this table. An offset can be applied to skip initial rows, and the results can be output to a new table.
The number of rows to select.
An optional object with configuration options:
Optionaloffset?: numberThe number of rows to skip from the beginning of the table before selecting. Defaults to 0.
OptionaloutputTable?: string | booleanIf true, the selected rows will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be modified. Defaults to false.
A table instance containing the selected rows (either the current table or a new table), so methods can be chained.
Returns a new table with the same structure and data as this table. The data can be optionally filtered, limited to a specific number of rows, and offset.
With the default SimpleDB.expressionSyntax: "js", conditions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
If conditions, limit, and offset are all used, they are applied in this order: conditions (WHERE clause) first, then offset, and finally limit (LIMIT).
Note that cloning large tables can be a slow operation.
Either a string specifying the name of the new table, or an optional object with configuration options. If not provided, a default name (e.g., "table1", "table2") will be generated.
Optionalcolumns?: string | string[]An array of column names to include in the cloned table. If not provided, all columns will be included.
Optionalconditions?: stringA SQL WHERE clause condition to filter the data during cloning. Defaults to no condition (clones all rows).
Optionallimit?: numberThe number of rows to include in the cloned table. If provided, only the first X rows (potentially after filtering and offset) will be cloned.
Optionalname?: stringThe name of the new table to be created in the database. If not provided, a default name (e.g., "table1", "table2") will be generated.
Optionaloffset?: numberThe number of rows to skip before starting to clone rows.
A new table instance containing the cloned data, so methods can be chained.
// Clone tableA to a new table with a default generated name (e.g., "table1")
const tableB = await tableA.clone().log();
// Clone tableA to a new table named "my_cloned_table" using string parameter
const tableB = await tableA.clone("my_cloned_table").log();
// Clone tableA to a new table named "my_cloned_table" using options object
const tableB = await tableA.clone({ name: "my_cloned_table" }).log();
// Clone tableA, including only rows where 'column1' is greater than 10
const tableB = await tableA.clone({ conditions: `column1 > 10` }).log();
// Clone tableA with only specific columns
const tableB = await tableA.clone({ columns: ["name", "age", "city"] }).log();
// Clone only the first 10 rows of tableA
const tableB = await tableA.clone({ limit: 10 }).log();
Removes the table from the database. After this operation, invoking methods on this SimpleTable instance will result in an error.
A promise that resolves after the table is removed.
Renames the current table. The new name must not belong to another registered table, ignoring ASCII letter case.
The new name for the table.
A promise that resolves to the renamed table.
Executes all queued methods across every table in the database, not just
this table. Sync builder methods (like filter() or convert()) only
queue their operation; execution happens when an async observer method
(like getData(), log(), or writeData()) is awaited. Use run()
when a chain ends in pure mutations with nothing to observe and you want
the work done now.
Because the whole database is flushed in program order, this behaves
identically to SimpleDB.run(); call sdb.run() when your intent is to
flush the database rather than this specific table.
A promise that resolves to the table once the queued methods have been executed.
Sets the data types for columns in a new table. If the table already exists, it will be replaced.
To convert the types of an existing table, use the .convert() method instead.
An object specifying the column names and their target data types (JavaScript or SQL types), including JSON, FLOAT[n] vectors, and GEOMETRY with a CRS.
The table, so methods can be chained.
Performs a cross join operation with another table. A cross join returns the Cartesian product of the rows from both tables, meaning all possible pairs of rows will be in the resulting table.
This means that if the left table has n rows and the right table has m rows, the result will have n * m rows.
The SimpleTable instance to cross join with.
An optional object with configuration options:
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
A table instance containing the cross-joined data (either the current table or a new table), so methods can be chained.
// Perform a cross join with 'tableB', overwriting the current table (tableA)
await tableA.crossJoin(tableB).log();
Performs a fuzzy left join between this table (considered the left table) and another table (the right table) based on string similarity between two text columns. Uses the rapidfuzz DuckDB community extension.
If a similarity score column is added to the results, the rows will be ordered alphabetically by the left column, and then by descending similarity score within each group of identical left column values. Otherwise, the rows will be order alphabetically by the left column and then by the right column.
This operation might create temporary files in a .tmp folder; consider adding .tmp to your .gitignore.
The SimpleTable instance to be joined with this table.
The name of the column in this (left) table containing the text to compare.
The name of the column in the right table containing the text to compare.
The minimum similarity score (0–100) required for two rows to be joined. For method: "ratio", a length-based pre-filter is automatically applied based on the threshold to improve performance without losing accuracy.
An optional object with configuration options:
Optionalmethod?: "ratio" | "partial_ratio" | "token_sort_ratio" | "token_set_ratio"The rapidfuzz similarity algorithm to use. Defaults to "ratio".
"ratio": Overall similarity (Levenshtein-based)."partial_ratio": Best partial/substring similarity."token_sort_ratio": Similarity after sorting tokens (words), useful for reordered words."token_set_ratio": Similarity based on sets of tokens, ignoring duplicates and word order.OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
OptionalprefilterPrefixLength?: numberAn optional prefix length. Only strings sharing the same first N characters are compared. Note that prefix filtering is lossy (e.g. "John" vs. "Phon" will not match despite high similarity).
OptionalsimilarityColumn?: stringIf provided, a column with this name is added to the result containing the similarity score (0–100). If omitted, the score is not included in the output.
A table instance containing the fuzzy-joined data (either the current table or a new table), so methods can be chained.
// Fuzzy left join tableA with tableB on 'name' (left) and 'standardName' (right) with a threshold of 80
// A length-based pre-filter is automatically applied.
await tableA.fuzzyJoin(tableB, "name", "standardName", 80).log();
// Fuzzy join with a prefix-based pre-filter and a threshold of 80
await tableA.fuzzyJoin(tableB, "name", "standardName", 80, {
prefilterPrefixLength: 3, // Must share the same first 3 characters
}).log();
Merges the data of this table (considered the left table) with another table (the right table) based on a common column or multiple columns.
Note that the order of rows in the returned data is not guaranteed to be the same as in the original tables.
This operation might create temporary files in a .tmp folder; consider adding .tmp to your .gitignore.
The SimpleTable instance to be joined with this table.
An optional object with configuration options:
Optionalon?: string | string[]The column(s) to join on. If omitted, the method automatically searches for a column name that exists in both tables. Can be a single string or an array of strings for multiple join keys.
OptionaloutputTable?: string | booleanIf true, the results will be stored in a new table with a generated name. If a string, it will be used as the name for the new table. If false or omitted, the current table will be overwritten. Defaults to false.
Optionaltype?: "left" | "right" | "inner" | "full"The type of join operation to perform. Possible values are "inner", "left" (default), "right", or "full".
A table instance containing the joined data (either the current table or a new table), so methods can be chained.
// Perform a left join with 'tableB' on a common column (auto-detected), overwriting tableA
await tableA.join(tableB).log();
Adds a new column containing the number of characters in each string in the specified column.
Counts are based on Unicode code points, not grapheme clusters. A user-perceived character composed of multiple code points, such as some emoji or decomposed accented letters, counts as multiple characters.
NULL input values produce NULL counts.
The name of the column containing the strings to count.
The name of the new column where the character counts will be stored.
The table, so methods can be chained.
Adds a new column containing the word count for each string in the specified column.
A word is any contiguous sequence of non-whitespace characters. Spaces, tabs, and line breaks separate words. Punctuation is not removed, so a standalone punctuation sequence counts as a word. Text without whitespace counts as one word, regardless of language. Empty or whitespace-only strings produce 0, and NULL input values produce NULL counts.
The name of the column containing the strings to count.
The name of the new column where the word counts will be stored.
The table, so methods can be chained.
Normalizes string values in a column by:
Produces identical output to journalism-format's normalizeString() function
for all common cases including accented Latin characters.
The column containing the text to normalize
The column to store the normalized results
Configuration options
OptionalstripPunctuation?: booleanStrip punctuation and underscores (default: true)
The table, so methods can be chained.
// Normalize text column and store in new column
await table.normalizeString("recipeName", "recipeNameNormalized").log();
// "Épicerie Parisienne!" → "epicerie parisienne"
// Keep punctuation for emails and URLs
await table.normalizeString("email", "emailNormalized", { stripPunctuation: false }).log();
// "User@Example.com" → "user@example.com"
await table.normalizeString("url", "urlNormalized", { stripPunctuation: false }).log();
// "https://Example.com/path" → "https://example.com/path"
Searches a text column using DuckDB's BM25 ranking function, which scores matches using factors including term frequency and document length.
This method creates the required index with DuckDB's FTS extension.
It reuses the table's existing FTS index unless overwriteIndex is true.
DuckDB FTS indexes do not update automatically when the source table
changes; use overwriteIndex: true to rebuild the index after modifying
the table.
The search query text to match against the text column.
The name of the column containing unique identifiers for each row.
The name of the column containing the text to search.
The number of top-ranked results to return.
An optional object with configuration options:
Optionalb?: numberThe BM25 b parameter controlling document length normalization (0-1 range). Defaults to 0.75.
Optionalconjunctive?: booleanIf true, all terms in the query string must be present in order for a document to be retrieved. Defaults to false.
Optionalignore?: stringThe regular expression of patterns to be ignored. Defaults to "(\.|[^a-z])+".
Optionalk?: numberThe BM25 k parameter controlling term frequency saturation. Defaults to 1.2.
Optionallower?: booleanA boolean indicating whether to convert all text to lowercase. Defaults to true.
OptionalminScore?: numberA threshold to filter out results with a BM25 score below this value.
OptionaloutputTable?: stringThe name of a new table where the results will be stored. If not provided, the current table will be replaced with the search results.
OptionaloverwriteIndex?: booleanIf true, drops and recreates the FTS index even if it already exists. Defaults to false.
OptionalscoreColumn?: stringIf provided, the BM25 score will be included in the output table under this column name.
Optionalstemmer?: The language stemmer to apply for word normalization. Supports multiple languages or "none" to disable stemming. Defaults to 'porter'.
Optionalstopwords?: stringThe table containing the stopwords to use for the FTS index. Supports multiple languages or "none" to disable stopwords. Defaults to "english".
OptionalstripAccents?: booleanA boolean indicating whether to remove accents. Defaults to true.
Optionalverbose?: booleanIf true, logs FTS index creation status. Defaults to false.
A table instance containing the search results, ordered by relevance (best matches first), so methods can be chained.
// Load a dataset of recipes
const dishes = await table
.loadData("recipes.parquet")
.bm25("italian food", "Dish", "Recipe", 5)
.log();
// Logs the five most relevant dishes.
// Search with a specific language stemmer
await table.bm25("french food", "Dish", "Recipe", 5, {
stemmer: "french",
}).log();
// Recreate the index with different settings and perform search
await table.bm25("italian food", "Dish", "Recipe", 5, {
stemmer: "english",
overwriteIndex: true,
}).log();
// Save results to a new table without modifying the original
const italianDishes = await table.bm25("italian food", "Dish", "Recipe", 5, {
outputTable: "italian_results",
}).log();
// Original table remains unchanged
const allDishes = await table.getValues("Dish");
console.log(allDishes.length); // 336 (all dishes)
// New table contains only search results
const italianOnly = await italianDishes.getValues("Dish");
console.log(italianOnly.length); // 5 (top results)
// Multiple searches reuse the same index for better performance
// The first search creates the index
const italian = await table.bm25("italian food", "Dish", "Recipe", 5, {
outputTable: "italian",
}).log();
// The second search reuses the existing index, so it's faster
const french = await table.bm25("french food", "Dish", "Recipe", 5, {
outputTable: "french",
}).log();
Creates a full-text search (FTS) index on a specified text column using DuckDB's FTS extension.
If an FTS index already exists on the table, this method will skip creation and log a message (when verbose is enabled), unless the overwrite option is set to true.
The index definition is recorded in indexes. The bm25
method requires an FTS index and creates one automatically when needed.
DuckDB FTS indexes do not update automatically when the table changes; use
overwrite: true to rebuild the index after modifying the table.
The column containing the document identifiers.
The column containing the text to search.
An optional object with configuration options:
Optionalignore?: stringThe regular expression of patterns to be ignored. Defaults to "(\.|[^a-z])+".
Optionallower?: booleanA boolean indicating whether to convert all text to lowercase. Defaults to true.
Optionaloverwrite?: booleanA boolean indicating whether to overwrite the existing FTS index. Defaults to false.
Optionalstemmer?: The stemmer to use for the FTS index. Supports multiple languages or "none" to disable stemming. Defaults to "porter".
Optionalstopwords?: stringThe table containing the stopwords to use for the FTS index. Supports multiple languages or "none" to disable stopwords. Defaults to "english".
OptionalstripAccents?: booleanA boolean indicating whether to remove accents. Defaults to true.
Optionalverbose?: booleanIf true, logs FTS index creation status. Defaults to false.
The table, so methods can be chained.
// Load a dataset and create an FTS index for later searches
await table
.loadData("recipes.parquet")
.createFtsIndex("Dish", "Recipe").log();
// Create an index with a specific language stemmer
await table.createFtsIndex("Dish", "Recipe", {
stemmer: "french",
}).log();
Adds independently generated uniform random noise to numeric values in one or more columns.
Each changed value receives an offset between -max and max. Selected integer and decimal columns become DOUBLE columns so fractional noise is retained. This method adds random jitter; it does not provide anonymization or differential privacy guarantees.
The numeric column name or array of numeric column names to which noise will be added. When multiple columns are provided, each value receives an independent random offset.
The maximum absolute offset, expressed in each column's units. Must be a finite number greater than or equal to 0.
An optional object with configuration options:
OptionalonlyDuplicates?: booleanIf true, adds noise only to values that occur more than once in their column. Each selected column is evaluated independently, and every occurrence of a duplicated value is changed. Defaults to false.
The table, so methods can be chained.
Capitalizes the first letter of each string in the specified columns and converts the rest of the string to lowercase.
The column name or an array of column names to be capitalized.
The table, so methods can be chained.
Concatenates values from specified columns into a new column.
An array of column names whose values will be concatenated.
The name of the new column to store the concatenated values.
An optional object with configuration options:
Optionalseparator?: stringThe string used to separate concatenated values. Defaults to an empty string ("").
The table, so methods can be chained.
Converts data types of specified columns to target types (JavaScript or SQL types).
When converting non-standard timestamp, date, or time strings, provide a datetimeFormat option using DuckDB's format specifiers.
Strings converted to datetimeTz or timestamp with time zone use an
explicit Z or numeric offset when present; strings without an offset are
interpreted as UTC. Returned TIMESTAMP WITH TIME ZONE values are rendered
as UTC strings.
When converting timestamps, dates, or times to/from numbers, the numerical representation will be in milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC).
When converting strings to numbers, commas (often used as thousand separators) will be automatically removed before conversion.
An object mapping column names to their target data types for conversion, including JSON, FLOAT[n] vectors, and GEOMETRY with a CRS.
An optional object with configuration options:
OptionaldatetimeFormat?: stringA string specifying the format for date and time conversions. Uses strftime and strptime functions from DuckDB. For format specifiers, see DuckDB's documentation.
Optionalstrict?: booleanIf false, values that cannot be converted will be replaced by NULL instead of throwing an error. Defaults to true.
The table, so methods can be chained.
// Convert 'column1' to string and 'column2' to integer (JavaScript types)
await table.convert({ column1: "string", column2: "integer" }).log();
// Convert 'column1' to VARCHAR and 'column2' to BIGINT (SQL types)
await table.convert({ column1: "varchar", column2: "bigint" }).log();
// Convert strings in 'column3' to datetime using a specific format
await table.convert({ column3: "datetime" }, { datetimeFormat: "%Y-%m-%d" }).log();
// Both values identify instants and are rendered in UTC.
await table
.loadArray([
{ observedAt: "2024-04-07T13:00:00-04:00" },
{ observedAt: "2024-04-07T17:00:00Z" },
])
.convert({ observedAt: "datetimeTz" }).log();
// Convert datetime values in 'column3' to strings using a specific format
await table.convert({ column3: "string" }, { datetimeFormat: "%Y-%m-%d %H:%M:%S" }).log();
Fills NULL values in specified columns. By default, each NULL is replaced with the last non-NULL value from the preceding row. When interpolate is true, NULL values are replaced using linear interpolation (or extrapolation at the ends). Pass interpolateBy with a real numeric or date column name to use it as the X-axis, so that interpolated values are proportional to the actual distances between X-axis values rather than treating every row as equidistant. When interpolateBy is set, interpolate is automatically assumed true.
The column(s) for which to fill NULL values.
An optional object with configuration options:
Optionalby?: string | string[]A column name or an array of column names to partition by. The fill is applied independently within each group.
Optionalinterpolate?: booleanIf true, replaces NULL values with linearly interpolated values using DuckDB's fill() window function. When interpolateBy is not set, row positions are used as the X-axis, treating rows as equidistant. For NULL values at the ends, linear extrapolation is used. Both the column values and the X-axis values must support arithmetic. If false or omitted, the previous non-NULL value is used instead. Automatically assumed true when interpolateBy is set.
OptionalinterpolateBy?: stringA column name to use as the X-axis for interpolation instead of equidistant row positions. When provided, interpolate is automatically assumed true. Use this when rows are not evenly spaced (e.g., timestamps or non-uniform numeric indices) so that interpolated values are proportional to the actual distance between X-axis values.
The table, so methods can be chained.
// Fill NULL values in 'column1' with the previous non-NULL value
await table.fill("column1").log();
// Fill NULL values in 'value' independently within each 'group'
await table.fill("value", { by: "group" }).log();
// Fill NULL values in 'value' using linear interpolation
await table.fill("value", { interpolate: true }).log();
// Fill NULL values in 'value' using linear interpolation, independently within each 'group'
await table.fill("value", { by: "group", interpolate: true }).log();
Extracts a specific number of characters from the beginning (left side) of string values in the specified column.
The name of the column containing the strings to be modified.
The number of characters to extract from the left side of each string.
The table, so methods can be chained.
Normalizes string values in a column by detecting fuzzy duplicates and replacing them with a single canonical value.
Similar strings are grouped into clusters. Matching is transitive: if "New York" is similar to "New Yorke" and
"New Yorke" is similar to "New Yorkk", all three land in the same cluster even if "New York" and "New Yorkk"
would not match directly. Each cluster is then collapsed to one representative value based on the strategy option.
Similarity is computed using the rapidfuzz DuckDB community extension, which is installed and loaded automatically.
The name of the column containing the strings to normalize.
The name of the column to write the normalized values to. Use the same name as column to normalize in-place.
The minimum similarity score (0–100) for two strings to be considered duplicates. For method: "ratio", a length-based pre-filter is automatically applied based on the threshold to improve performance without losing accuracy.
An optional object with configuration options:
Optionalmethod?: "ratio" | "partial_ratio" | "token_sort_ratio" | "token_set_ratio"The rapidfuzz similarity algorithm to use. Defaults to "ratio".
"ratio": Overall similarity."partial_ratio": Best partial/substring similarity."token_sort_ratio": Similarity after sorting tokens (words), useful for reordered words."token_set_ratio": Similarity based on sets of tokens, ignoring duplicates and word order.OptionalprefilterPrefixLength?: numberAn optional prefix length. Only strings sharing the same first N characters are compared. Note that prefix filtering is lossy (e.g. "John" vs. "Phon" will not match despite high similarity).
Optionalstrategy?: "mostCommon" | "longestString" | "shortestString" | "mostCentral" | "maxScore"The strategy for choosing the canonical value within each cluster of similar strings. Defaults to "mostCommon".
"mostCommon": Keep the value that appears most frequently in the original column."longestString": Keep the longest string in the cluster."shortestString": Keep the shortest string in the cluster."mostCentral": Keep the string with the highest total similarity score to all other cluster members (the most "central" string)."maxScore": Keep the string that participates in the single highest-scoring pairwise match within the cluster.The table, so methods can be chained.
// Normalize 'city' into a new 'cityClean' column, keeping the most common string per cluster with a threshold of 80
// A length-based pre-filter is automatically applied.
await table.fuzzyClean("city", "cityClean", 80).log();
// Normalize with a prefix-based pre-filter and a threshold of 80
await table.fuzzyClean("city", "cityClean", 80, {
prefilterPrefixLength: 5, // Must share the same first 5 characters
}).log();
Extracts a specific number of characters from the end (right side) of string values in the specified column.
The name of the column containing the strings to be modified.
The number of characters to extract from the right side of each string.
The table, so methods can be chained.
Converts string values in the specified columns to lowercase.
The column name or an array of column names to be converted to lowercase.
The table, so methods can be chained.
Nests (collapses) rows by aggregating a column's values into a single string per group, separated by a delimiter.
This is the inverse operation of unnest(). Multiple rows are combined into fewer rows by grouping on specified category columns and concatenating the target column values with a separator.
The name of the column whose values will be aggregated and concatenated.
The delimiter string used to join the column values.
The column name or an array of column names to group by.
The table, so methods can be chained.
// Nest 'neighborhoods' column separated by " / " for each city
// Before: [{ city: "Montreal", neighborhoods: "Old Montreal" },
// { city: "Montreal", neighborhoods: "Chinatown" },
// { city: "Montreal", neighborhoods: "Griffintown" }]
// After: [{ city: "Montreal", neighborhoods: "Old Montreal / Chinatown / Griffintown" }]
await table.nest("neighborhoods", " / ", "city").log();
Pads the strings in the specified columns to a target length.
The columns must contain string (VARCHAR) values. An error is thrown if any
column is of a different type. null values remain null. If any string
already exceeds the target length, an error is thrown (no silent truncation).
The column name(s) containing strings to be padded.
The target length of the padded strings.
An optional object with configuration options:
Optionalcharacter?: stringThe character to use for padding. Defaults to '0'.
Optionalside?: "left" | "right"Which side to pad. 'left' (default) or 'right'.
The table, so methods can be chained.
// Left-pad 'id' column to 3 characters with zeros (default)
await table.pad("id", 3).log();
// Result: '1' -> '001', '23' -> '023', null -> null
// Right-pad 'code' column to 5 characters with spaces
await table.pad("code", 5, { side: "right", character: " " }).log();
// Result: '123' -> '123 ', '45' -> '45 ', null -> null
Repeats rows based on the values in a column.
If a row has a value of 3 in the specified column, it will be repeated 3 times. If the value is 0 or negative, the row will be removed.
The name of the column containing the number of times each row should be repeated.
An optional object with configuration options:
Optionalindex?: stringThe name of a new column to store the index of the repeated row (starting at 0).
The table, so methods can be chained.
// Before: [{ id: 1, count: 2, category: "A" }, { id: 2, count: 3, category: "B" }]
await table.repeatRows("count").log();
// After: [{ id: 1, count: 2, category: "A" }, { id: 1, count: 2, category: "A" },
// { id: 2, count: 3, category: "B" }, { id: 2, count: 3, category: "B" }, { id: 2, count: 3, category: "B" }]
// With an index column
await table.repeatRows("count", { index: "copyId" }).log();
// After: [{ id: 1, count: 2, category: "A", copyId: 0 }, { id: 1, count: 2, category: "A", copyId: 1 },
// { id: 2, count: 3, category: "B", copyId: 0 }, { id: 2, count: 3, category: "B", copyId: 1 }, { id: 2, count: 3, category: "B", copyId: 2 }]
Replaces specified strings in the selected columns.
Use ["all"] to target a column literally named all.
The column name, an array of column names, or "all" to apply the replacement to every column in the table.
An object mapping old strings to new strings (e.g., { "oldValue": "newValue" }).
An optional object with configuration options:
OptionalentireString?: booleanA boolean indicating whether the entire cell content must match the oldString for replacement to occur. Defaults to false (replaces substrings).
Optionalregex?: booleanA boolean indicating whether the oldString should be treated as a regular expression for global replacement. Cannot be used with entireString: true. Defaults to false.
The table, so methods can be chained.
// Replace all occurrences of "kilograms" with "kg" in 'column1'
await table.replace("column1", { "kilograms": "kg" }).log();
// Replace "kilograms" with "kg" and "liters" with "l" in 'column1' and 'column2'
await table.replace(["column1", "column2"], { "kilograms": "kg", "liters": "l" }).log();
// Replace only if the entire string in 'column1' is "kilograms"
await table.replace("column1", { "kilograms": "kg" }, { entireString: true }).log();
Replaces NULL values in the specified columns with a given value.
Use ["all"] to target a column literally named all.
The column name, an array of column names, or "all" to apply the replacement to every column in the table.
The value to replace NULL occurrences with.
The table, so methods can be chained.
Rounds numeric values in specified columns.
The column name or an array of column names containing numeric values to be rounded.
An optional integer specifying the number of decimal places, or an object with configuration options:
Optionaldecimals?: numberThe number of decimal places to round to. Defaults to 0 (rounds to the nearest integer).
Optionalmethod?: "round" | "ceiling" | "floor"The rounding method to use: "round" (rounds to the nearest integer, with halves rounding up), "ceiling" (rounds up to the nearest integer), or "floor" (rounds down to the nearest integer). Defaults to "round".
The table, so methods can be chained.
// Round 'column1' values to 2 decimal places
await table.round("column1", { decimals: 2 }).log();
// Round 'column1' values down to the nearest integer (floor)
await table.round("column1", { method: "floor" }).log();
Concatenates values from multiple columns into a new column with labeled rows.
This method creates a new column where each value is a concatenation of the specified columns, with each column value prefixed by its column name and a colon, followed by a newline. Column entries are separated by double newlines ("\n\n").
All values must be string, otherwise an error will be thrown. Use the convert() method first to convert non-string columns to string.
If a column value is NULL, it will be replaced by 'Unknown' in the concatenated result.
An array of column names whose values will be concatenated with labels.
The name of the new column to create with the concatenated values.
The table, so methods can be chained.
// Concatenate multiple string columns into a labeled text field
await table.rowToText(
["summary", "findings", "context", "date", "quote"],
"fullText"
).log();
// Result in "fullText" will look like:
// summary:
// [value]
//
// findings:
// [value]
//
// context:
// [value]
//
// date:
// [value]
//
// quote:
// [value]
Combines numeric scalar columns into a fixed-size vector. The input column order determines the vector dimension order, and null values remain null vector elements.
When all input columns have the same numeric type, that type is preserved.
Mixed numeric types require an explicit type option. Casting exact
decimals or large integers to FLOAT or DOUBLE can lose precision.
Numeric scalar columns to combine, in vector dimension order.
The name of the vector column to create.
Optional vector element type settings.
Optionaltype?: "float" | "double"Cast every element to "float" or "double". Required when the input column types differ.
The table, so methods can be chained.
Splits strings in a specified column by a separator and extracts a substring at a given index, storing the result in a new or existing column. If the index is out of bounds, an empty string will be returned for that row.
The name of the column containing the strings to be split.
The substring to use as a delimiter for splitting the strings.
The zero-based index of the substring to extract after splitting. For example, 0 for the first part, 1 for the second, etc.
The name of the column where the extracted substrings will be stored. To overwrite the original column, use the same name as column.
The table, so methods can be chained.
Splits strings in a specified column by a separator and spreads the resulting parts into multiple new columns.
Each part of the split string will be stored in a separate column. The number of columns created is determined by the length of the newColumns array.
If a row has fewer parts than the number of new columns, a warning will be logged and the extra columns will contain empty strings (unless strict is set to false).
If a row has more parts than the number of new columns, an error will be thrown unless strict is set to false.
The name of the column containing the strings to be split.
The substring to use as a delimiter for splitting the strings.
An array of column names for the extracted parts.
Optional configuration.
Optionalstrict?: booleanIf false, skips all validation checks (both max and min parts). Defaults to true.
The table, so methods can be chained.
// Split 'fullName' by comma and spread into 'lastName' and 'firstName'
// e.g., "Shiab, Nael" -> lastName: "Shiab", firstName: "Nael"
await table.splitSpread("fullName", ",", ["lastName", "firstName"]).log();
Trims specified characters from the beginning, end, or both sides of string values in the given columns.
Pass "all" to trim every string (VARCHAR) column, leaving other types unchanged.
If there are no string columns, "all" leaves the table unchanged.
Use ["all"] to trim a column literally named all.
The column name, an array of column names, or "all" to trim every string (VARCHAR) column.
An optional object with configuration options:
Optionalcharacter?: stringThe string to trim. Defaults to whitespace characters.
Optionalside?: "left" | "right" | "both"The side to trim: "left" (removes from the beginning), "right" (removes from the end), or "both" (removes from both sides). Defaults to "both".
The table, so methods can be chained.
Truncates string values in a specified column to a maximum number of characters.
The column name containing strings to be truncated.
The maximum number of characters to keep.
The table, so methods can be chained.
Unnests (expands) rows by splitting a column's string values into multiple rows based on a separator.
Each value in the specified column is split using the provided separator, and a new row is created for each resulting substring. All other column values are duplicated across the newly created rows.
The name of the column containing string values to be split and unnested.
The delimiter string used to split the column values.
The table, so methods can be chained.
// Unnest 'tags' column separated by commas
// Before: [{ id: 1, tags: "red,blue,green" }]
// After: [{ id: 1, tags: "red" }, { id: 1, tags: "blue" }, { id: 1, tags: "green" }]
await table.unnest("tags", ",").log();
// Unnest 'neighborhoods' column separated by " / "
// Before: [{ city: "Montreal", neighborhoods: "Old Montreal / Chinatown / Griffintown" }]
// After: [{ city: "Montreal", neighborhoods: "Old Montreal" },
// { city: "Montreal", neighborhoods: "Chinatown" },
// { city: "Montreal", neighborhoods: "Griffintown" }]
await table.unnest("neighborhoods", " / ").log();
Updates values in a specified column using a SQL expression.
With the default SimpleDB.expressionSyntax: "js", expressions support JavaScript-style operators (&&, ||, ===, !==). Set expressionSyntax: "sql" for unchanged SQL.
The name of the column to be updated.
The SQL expression used to set the new values in the column (e.g., "column1 * 2", "UPPER(column_name)").
The table, so methods can be chained.
Updates data in the table using a JavaScript function. The function receives the existing rows as an array of objects and must return the modified rows as an array of objects. This method offers high flexibility for data manipulation but can be slow for large tables as it involves transferring data between DuckDB and JavaScript. Before writing a JavaScript callback, check for an existing SDA method that performs the same operation; it will usually be faster and more efficient. Existing integer and enum columns retain their types. Fractional or out-of-range integers and unknown enum members throw when written. The original table is replaced after all callback results have been converted successfully.
If the table has geometry columns, the callback can read and modify their GeoJSON geometry objects directly. Extra properties added to these objects are ignored; create new columns on the returned rows to store additional attributes. All geometry columns must use EPSG:4326; use reproject() first if needed.
A synchronous or asynchronous function that takes the existing rows (as an array of objects) and returns the modified rows (as an array of objects).
An optional object with configuration options:
OptionalbatchSize?: numberIf provided, rows are processed in batches of this size instead of all at once, so large tables don't have to be materialized entirely in memory. This limits input rows per batch, but does not eliminate conversion work or bound geometry size or callback output expansion. The modifier function is called once per batch. Tables with a column named rowid or __sda_rowid (case-insensitive) are rejected before the modifier runs; rename the column or omit batchSize.
OptionalcolumnTypes?: { [key: string]: string }Explicit types for newly added columns, following loadArray's columnTypes contract. Declare geometry as GEOMETRY('EPSG:4326'); geometry is never inferred from object shape. Existing columns cannot be redeclared.
The table, so methods can be chained.
// Double the value in each row with a JavaScript callback.
await table.loadArray([{ value: 1 }, { value: 2 }])
.updateWithJS((rows) => rows.map((row) => ({
value: Number(row.value) * 2,
})))
.log();
// Process two rows per callback to limit how many rows are handled at once.
await table.loadArray([{ value: 1 }, { value: 2 }, { value: 3 }])
.updateWithJS((rows) => rows.map((row) => ({
value: Number(row.value) * 2,
})), { batchSize: 2 })
.log();
// Add a JSON object column to keep related attributes together.
await table.loadArray([{ name: "Station" }])
.updateWithJS((rows) => rows.map((row) => ({
...row, details: { active: true, category: "weather" },
})), { columnTypes: { details: "JSON" } })
.log();
// Add a three-element float vector, such as an embedding.
await table.loadArray([{ name: "Station" }])
.updateWithJS((rows) => rows.map((row) => ({
...row, embedding: [0.25, 0.5, 0.75],
})), { columnTypes: { embedding: "FLOAT[3]" } })
.log();
// Move each point slightly east by editing its longitude.
await sdb.newTable().loadArray([{
geom: { type: "Point", coordinates: [-73, 45] },
}], { columnTypes: { geom: "GEOMETRY('EPSG:4326')" } })
.updateWithJS((rows) => rows.map((row) => {
// This example loads only Points. Narrow the type for coordinate access.
const geom = row.geom as { type: "Point"; coordinates: number[] } | null;
if (geom !== null) geom.coordinates[0] += 0.01;
return row;
})).log();
// Create a geometry column from longitude and latitude values.
await sdb.newTable().loadArray([{ longitude: -73, latitude: 45 }])
.updateWithJS((rows) => rows.map((row) => ({
...row, geom: { type: "Point", coordinates: [row.longitude, row.latitude] },
})), { columnTypes: { geom: "GEOMETRY('EPSG:4326')" } }).log();
Converts string values in the specified columns to uppercase.
The column name or an array of column names to be converted to uppercase.
The table, so methods can be chained.
Normalizes a numeric vector column. By default, each dimension is scaled
independently to [0, 1] across rows: all first elements together, all
second elements together, and so on. With normalization: "rowL2", each
row is scaled to Euclidean unit length while preserving its direction;
zero vectors remain zero.
The input may be a numeric LIST or fixed-size ARRAY. The output is a
fixed-size DOUBLE ARRAY. Converting large integers and exact decimals to
DOUBLE can lose precision. With "dimensionMinMax", a dimension whose
converted values are all equal cannot be normalized.
The numeric vector column to normalize.
The output column. Use the source column's name to replace the source column with the normalized DOUBLE vector.
Normalization settings.
Optionalnormalization?: "dimensionMinMax" | "rowL2""dimensionMinMax" scales each dimension
across rows; "rowL2" scales each row to unit length. Defaults to
"dimensionMinMax".
The table, so methods can be chained.
Creates a vector similarity search (VSS) index on a specified column using DuckDB's VSS extension.
If a VSS index already exists on the table, this method will skip creation and log a message (when verbose is enabled), unless the overwrite option is set to true.
The index definition is recorded in indexes.
The name of the column containing vector embeddings (must be FLOAT array type).
An optional object with configuration options:
OptionalefConstruction?: numberThe number of candidate vertices to consider during index construction. Higher values result in more accurate indexes but increase build time. Defaults to 128.
OptionalefSearch?: numberThe number of candidate vertices to consider during search. Higher values result in more accurate searches but increase search time. Defaults to 64.
OptionalM?: numberThe maximum number of neighbors to keep for each vertex in the graph. Higher values result in more accurate indexes but increase build time and memory usage. Defaults to 16.
Optionaloverwrite?: booleanIf true, drops and recreates the index even if it already exists. Defaults to false.
Optionalverbose?: booleanIf true, logs VSS index creation status. Defaults to false.
The table, so methods can be chained.
// Load data that already contains an embedding column
await table
.loadData("data.csv")
.createVssIndex("embedding_column").log();
// Recreate an existing index
await table.createVssIndex("embedding_column", {
overwrite: true,
}).log();
Groups rows with similar numeric features using HDBSCAN, without requiring
a predefined number of clusters. Adds a VARCHAR column containing
"cluster-0", "cluster-1", etc., or "noise" for rows outside clusters.
Labels identify groups, not their rank.
Accepts a numeric vector column or several numeric scalar columns. Features
are used as supplied. If their scales differ, consider preparing them with
normalize(), zScore(), or normalizeVector() before clustering.
Exact clustering is the default and can be expensive for large datasets.
Set approximate: true to use approximate clustering, which can produce
different results, including between repeated runs.
A numeric vector column, or numeric scalar columns in feature-dimension order.
The cluster-label column to create.
HDBSCAN clustering and output settings.
OptionalallowSingleCluster?: booleanAllow a result with just one cluster,
possibly alongside noise. Defaults to false. Setting this to true can
favor one broad group, even when there is little meaningful structure.
Optionalapproximate?: booleanUse approximate clustering. Defaults to false.
OptionalmembershipScoreColumn?: stringOptional DOUBLE column for membership
strength from 0 to 1. Higher values indicate stronger membership in the
assigned cluster; noise scores 0.
Optionalmetric?: "euclidean" | "cosine"Use "euclidean" to compare feature values, or
"cosine" to compare vector direction regardless of overall magnitude.
Defaults to "euclidean".
OptionalminClusterSize?: numberMinimum cluster size, from 2 through the
row count. Defaults to 5.
OptionalminSamples?: numberNumber of neighbors used to estimate local
density, excluding the point itself. Higher values make clustering more
conservative, generally labeling more rows as noise. Set this separately
to adjust density sensitivity independently of minimum cluster size.
Defaults to minClusterSize.
OptionaloutlierScoreColumn?: stringOptional DOUBLE column for outlier
scores from 0 to 1. Higher values indicate more unusual rows relative to
their surroundings, including within clusters. This is not necessarily
1 - membershipScore.
The table, so methods can be chained.
Reduces numeric features, such as embeddings, to a two-dimensional UMAP
projection. The resulting coordinates are added as umapX and umapY,
while all existing columns (including the source feature columns), their
values and types, and the input row order are preserved. DuckDB computes
neighbors and the fuzzy graph; TypeScript optimizes the coordinates without
copying the input vectors into JavaScript. Neighbor search is selected
automatically.
Accepts a numeric LIST or ARRAY column, or an array of numeric scalar column
names combined internally into a vector in the supplied order. Features
must be finite and non-null; vectors must be nonempty and equally sized.
Features are converted to DOUBLE without automatic scaling. Large integers
and exact decimals can lose precision in this conversion. If feature scales
differ, consider normalize() or zScore() before projection.
The defaults are a starting point for exploration. To adjust the projection:
neighbors (default 15): How many nearby points influence the layout.
Smaller values emphasize local detail but can fragment groups. Larger
values emphasize broader structure, can hide local detail, and generally
require more time and memory.metric (default "euclidean"): How similarity is measured. Euclidean
compares distance, including differences in vector magnitude. Cosine
compares direction, ignoring magnitude. Choose according to what makes
vectors similar in your data; neither is universally better.epochs (default 200): How many passes refine the coordinates. Fewer
passes finish sooner but may leave the layout unfinished. More passes
allow further refinement and take longer, with diminishing returns.minDistance (default 0.1): How tightly points can group in the
projection. Smaller values allow tighter clumps; larger values spread
points out. This changes the layout's appearance, not an accuracy level
or the number of optimization passes.learningRate (default 1): The initial size of coordinate adjustments.
Smaller values make gentler adjustments and may need more epochs. Larger
values make bigger adjustments but can overshoot useful positions. This
does not change the number of passes.negativeSamples (default 5): How many random points are sampled for
repulsion per attractive update. Smaller values reduce work and
repulsion; larger values increase both, tending to separate unrelated
points more strongly. More samples do not guarantee a better projection.A numeric vector column name, or a nonempty array of numeric scalar column names in feature order. Use a one-element array for a single scalar feature.
Optional projection settings.
Optionalepochs?: numberInteger number of refinement passes, at least 1. Defaults to 200.
OptionallearningRate?: numberFinite, positive initial learning rate. Defaults to 1.
Optionalmetric?: "euclidean" | "cosine"Input distance metric: "euclidean" or "cosine". Defaults to "euclidean".
OptionalminDistance?: numberGrouping distance parameter between 0 and 1. Defaults to 0.1.
OptionalnegativeSamples?: numberInteger number of samples used to separate unrelated points, at least 1. Defaults to 5.
Optionalneighbors?: numberNeighborhood size, including the point itself. Integer of at least 2, clamped to row count minus one. Defaults to 15.
Optionalseed?: numberInteger used to initialize random choices. Defaults to 42.
The table, so methods can be chained.
IMPORTANT: When extending this class, always use
this.sdb.newTable()to create new tables instead ofnew SimpleTable(...)directly. This ensures subclasses that overridetableClasson their parentSimpleDBget their own table type back.Represents a table within a SimpleDB database, capable of handling tabular, geospatial, and vector data. SimpleTable instances are typically created via a SimpleDB instance.
Example
Example