From e0e0161d435479d19bab3f0746a4c558356550c6 Mon Sep 17 00:00:00 2001 From: Ryan Kuo Date: Mon, 27 Oct 2025 15:49:11 -0400 Subject: [PATCH 1/3] LTREE data type --- .../_includes/v25.4/sidebar-data/sql.json | 6 + src/current/v23.1/data-types.md | 2 +- src/current/v23.2/data-types.md | 2 +- src/current/v24.1/data-types.md | 2 +- src/current/v24.2/data-types.md | 2 +- src/current/v24.3/data-types.md | 2 +- src/current/v25.1/data-types.md | 2 +- src/current/v25.2/data-types.md | 2 +- src/current/v25.3/data-types.md | 2 +- src/current/v25.4/data-types.md | 5 +- src/current/v25.4/ltree.md | 272 ++++++++++++++++++ src/current/v25.4/string.md | 1 + 12 files changed, 290 insertions(+), 10 deletions(-) create mode 100644 src/current/v25.4/ltree.md diff --git a/src/current/_includes/v25.4/sidebar-data/sql.json b/src/current/_includes/v25.4/sidebar-data/sql.json index be1452e3715..4591796a8e6 100644 --- a/src/current/_includes/v25.4/sidebar-data/sql.json +++ b/src/current/_includes/v25.4/sidebar-data/sql.json @@ -1070,6 +1070,12 @@ "/${VERSION}/jsonb.html" ] }, + { + "title": "LTREE", + "urls": [ + "/${VERSION}/ltree.html" + ] + }, { "title": "OID", "urls": [ diff --git a/src/current/v23.1/data-types.md b/src/current/v23.1/data-types.md index b93100c2fdb..a83dfc65af3 100644 --- a/src/current/v23.1/data-types.md +++ b/src/current/v23.1/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v23.2/data-types.md b/src/current/v23.2/data-types.md index 634b247591a..041fdc3c1f3 100644 --- a/src/current/v23.2/data-types.md +++ b/src/current/v23.2/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v24.1/data-types.md b/src/current/v24.1/data-types.md index 634b247591a..041fdc3c1f3 100644 --- a/src/current/v24.1/data-types.md +++ b/src/current/v24.1/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v24.2/data-types.md b/src/current/v24.2/data-types.md index ab14f2bee51..830bd402137 100644 --- a/src/current/v24.2/data-types.md +++ b/src/current/v24.2/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v24.3/data-types.md b/src/current/v24.3/data-types.md index ab14f2bee51..830bd402137 100644 --- a/src/current/v24.3/data-types.md +++ b/src/current/v24.3/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v25.1/data-types.md b/src/current/v25.1/data-types.md index ab14f2bee51..830bd402137 100644 --- a/src/current/v25.1/data-types.md +++ b/src/current/v25.1/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v25.2/data-types.md b/src/current/v25.2/data-types.md index ab14f2bee51..830bd402137 100644 --- a/src/current/v25.2/data-types.md +++ b/src/current/v25.2/data-types.md @@ -17,8 +17,8 @@ Type | Description | Example [`BYTES`]({% link {{ page.version.version }}/bytes.md %}) | A string of binary characters. | `b'\141\061\142\062\143\063'` [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v25.3/data-types.md b/src/current/v25.3/data-types.md index b198a07c886..37ccf6238d7 100644 --- a/src/current/v25.3/data-types.md +++ b/src/current/v25.3/data-types.md @@ -18,8 +18,8 @@ Type | Description | Example [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`CITEXT`]({% link {{ page.version.version }}/citext.md %}) | Case-insensitive text. | `'Roach'` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` diff --git a/src/current/v25.4/data-types.md b/src/current/v25.4/data-types.md index b198a07c886..330dc1c17ba 100644 --- a/src/current/v25.4/data-types.md +++ b/src/current/v25.4/data-types.md @@ -18,14 +18,15 @@ Type | Description | Example [`COLLATE`]({% link {{ page.version.version }}/collate.md %}) | The `COLLATE` feature lets you sort [`STRING`]({% link {{ page.version.version }}/string.md %}) values according to language- and country-specific rules, known as collations. | `'a1b2c3' COLLATE en` [`CITEXT`]({% link {{ page.version.version }}/citext.md %}) | Case-insensitive text. | `'Roach'` [`DATE`]({% link {{ page.version.version }}/date.md %}) | A date. | `DATE '2016-01-25'` -[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`DECIMAL`]({% link {{ page.version.version }}/decimal.md %}) | An exact, fixed-point number. | `1.2345` +[`ENUM`]({% link {{ page.version.version }}/enum.md %}) | A user-defined data type comprised of a set of static values. | `ENUM ('club, 'diamond', 'heart', 'spade')` [`FLOAT`]({% link {{ page.version.version }}/float.md %}) | A 64-bit, inexact, floating-point number. | `1.2345` [`INET`]({% link {{ page.version.version }}/inet.md %}) | An IPv4 or IPv6 address. | `192.168.0.1` [`INT`]({% link {{ page.version.version }}/int.md %}) | A signed integer, up to 64 bits. | `12345` [`INTERVAL`]({% link {{ page.version.version }}/interval.md %}) | A span of time. | `INTERVAL '2h30m30s'` -[`NULL`]({% link {{ page.version.version }}/null-handling.md %}) | The undefined value. | `NULL` [`JSONB`]({% link {{ page.version.version }}/jsonb.md %}) | JSON (JavaScript Object Notation) data. | `'{"first_name": "Lola", "last_name": "Dog", "location": "NYC", "online" : true, "friends" : 547}'` +[`LTREE`]({% link {{ page.version.version }}/ltree.md %}) | A label path representing a hierarchical tree-like structure. | `'Top.Countries.Europe.France'` +[`NULL`]({% link {{ page.version.version }}/null-handling.md %}) | The undefined value. | `NULL` [`OID`]({% link {{ page.version.version }}/oid.md %}) | An unsigned 32-bit integer. | `4294967295` [`SERIAL`]({% link {{ page.version.version }}/serial.md %}) | A pseudo-type that combines an [integer type]({% link {{ page.version.version }}/int.md %}) with a [`DEFAULT` expression]({% link {{ page.version.version }}/default-value.md %}). | `148591304110702593` [`STRING`]({% link {{ page.version.version }}/string.md %}) | A string of Unicode characters. | `'a1b2c3'` diff --git a/src/current/v25.4/ltree.md b/src/current/v25.4/ltree.md new file mode 100644 index 00000000000..de768e4aef6 --- /dev/null +++ b/src/current/v25.4/ltree.md @@ -0,0 +1,272 @@ +--- +title: LTREE +summary: The LTREE data type stores hierarchical tree-like structures as label paths. +toc: true +docs_area: reference.sql +--- + +The `LTREE` [data type]({% link {{ page.version.version }}/data-types.md %}) stores hierarchical tree-like structures as a *label path*, which is a sequence of dot-separated *labels*. Labels represent positions in a tree hierarchy. `LTREE` is useful for efficiently querying and managing hierarchical data without using recursive joins. + +## Syntax + +Each label in an `LTREE` label path represents a level in the hierarchy, beginning from the root (`Animals` in the following example): + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT 'Animals.Mammals.Carnivora'::LTREE; +~~~ + +~~~ + ltree +----------------------------- + Animals.Mammals.Carnivora +~~~ + +Each label in the path must contain only alphanumeric characters (`A`-`Z`, `a`-`z`, `0`-`9`), underscores (`_`), and hyphens (`-`). + +The following are valid `LTREE` values: + +- `'Animals'` +- `'Products.Electronics.Laptops.Gaming'` +- `'project_a.phase-1.task_001'` +- `''` (empty path) + +## Size + +The size of an `LTREE` value is variable and equals the total number of characters in all labels plus the dot separators. The maximum label length is 1,000 characters, and the maximum number of labels in a path is 65,535. + +## Operators + +The following `LTREE` comparison and containment operators are valid: + +- `=` (equals). Compare paths for equality. +- `<>`, `!=` (not equal to). Compare paths for inequality. +- `<`, `<=`, `>`, `>=` (ordering). Compare paths lexicographically. +- `@>` (is ancestor of). Returns `true` if the left operand is an ancestor of (or equal to) the right operand. +- `<@` (is descendant of). Returns `true` if the left operand is a descendant of (or equal to) the right operand. +- `||` (concatenate). Concatenate two `LTREE` paths. + +For `LTREE` arrays, the following operators return the first matching element: + +- `?@>` (array contains ancestor). Returns the first array element that is an ancestor of the operand. +- `?<@` (array contains descendant). Returns the first array element that is a descendant of the operand. + +### Index acceleration + +CockroachDB supports indexing `LTREE` columns. [Indexes]({% link {{ page.version.version }}/indexes.md %}) on `LTREE` columns accelerate the following operations: + +- Comparison operators: `=`, `<>`, `!=`, `<`, `<=`, `>`, `>=` +- Containment operators: `@>`, `<@` + +## Functions + +The following [built-in functions]({% link {{ page.version.version }}/functions-and-operators.md %}#ltree-functions) are supported for `LTREE`: + +| Function | Description | +|----------|-------------| +| `index(a ltree, b ltree [, offset])` | Returns the position of the first occurrence of `b` in `a`, optionally starting at `offset`. Returns `-1` if not found. | +| `lca(ltree, ltree, ...)` | Returns the longest common ancestor (longest common prefix) of the paths. Accepts unlimited arguments or an array. | +| `ltree2text(ltree)` | Converts an `LTREE` value to `STRING`. | +| `nlevel(ltree)` | Returns the number of labels in the path. | +| `subltree(ltree, start, end)` | Extracts a subpath from position `start` to position `end-1` (zero-indexed). | +| `subpath(ltree, offset [, length])` | Extracts a subpath starting at `offset`. If `offset` is negative, starts that far from the end. Optional `length` specifies how many labels to include. | +| `text2ltree(text)` | Converts a `STRING` value to `LTREE`. | + +For more details, refer to [Functions and Operators]({% link {{ page.version.version }}/functions-and-operators.md %}). + +## Supported casting and conversion + +You can [cast]({% link {{ page.version.version }}/data-types.md %}#data-type-conversions-and-casts) `LTREE` values to the following data type: + +- [`STRING`]({% link {{ page.version.version }}/string.md %}) + +For example: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT 'Animals.Mammals.Carnivora'::LTREE::STRING; +~~~ + +~~~ + text +----------------------------- + Animals.Mammals.Carnivora +~~~ + +## Examples + +### Create a table with hierarchical data + +Create a table to store an organizational hierarchy: + +{% include_cached copy-clipboard.html %} +~~~ sql +CREATE TABLE org_structure ( + id INT PRIMARY KEY, + path LTREE NOT NULL, + name STRING, + INDEX path_idx (path) +); +~~~ + +Insert some hierarchical data (labels that represent a media production company): + +{% include_cached copy-clipboard.html %} +~~~ sql +INSERT INTO org_structure (id, path, name) VALUES + (1, 'Studio', 'Production Studio'), + (2, 'Studio.ShowA', 'Show A'), + (3, 'Studio.ShowA.Season1', 'Season 1'), + (4, 'Studio.ShowA.Season1.Episode1', 'Episode 1'), + (5, 'Studio.ShowA.Season1.Episode2', 'Episode 2'), + (6, 'Studio.ShowB', 'Show B'), + (7, 'Studio.ShowB.Season1', 'Season 1'), + (8, 'Studio.ShowB.Season1.Episode1', 'Episode 1'); +~~~ + +### Query `LTREE` with comparison operator + +Find all entries named `Episode 1`: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT name, path FROM org_structure +WHERE name = 'Episode 1'; +~~~ + +~~~ + name | path +------------+-------------------------------- + Episode 1 | Studio.ShowA.Season1.Episode1 + Episode 1 | Studio.ShowB.Season1.Episode1 +(2 rows) +~~~ + +### Query `LTREE` with containment operator + +Find all entries under `Show A` using the `<@` (is descendant of) operator: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT name, path FROM org_structure +WHERE path <@ 'Studio.ShowA' +ORDER BY path; +~~~ + +~~~ + name | path +------------+-------------------------------- + Show A | Studio.ShowA + Season 1 | Studio.ShowA.Season1 + Episode 1 | Studio.ShowA.Season1.Episode1 + Episode 2 | Studio.ShowA.Season1.Episode2 +(4 rows) +~~~ + +Find all ancestors of `Episode 1` in `Show A` using the `@>` (is ancestor of) operator: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT name, path FROM org_structure +WHERE path @> 'Studio.ShowA.Season1.Episode1' +ORDER BY path; +~~~ + +~~~ + name | path +--------------------+-------------------------------- + Production Studio | Studio + Show A | Studio.ShowA + Season 1 | Studio.ShowA.Season1 + Episode 1 | Studio.ShowA.Season1.Episode1 +(4 rows) +~~~ + +### Use `LTREE` functions + +Count the depth level of each entry using `nlevel()`: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT name, path, nlevel(path) AS depth +FROM org_structure +ORDER BY path; +~~~ + +~~~ + name | path | depth +--------------------+-------------------------------+-------- + Production Studio | Studio | 1 + Show A | Studio.ShowA | 2 + Season 1 | Studio.ShowA.Season1 | 3 + Episode 1 | Studio.ShowA.Season1.Episode1 | 4 + Episode 2 | Studio.ShowA.Season1.Episode2 | 4 + Show B | Studio.ShowB | 2 + Season 1 | Studio.ShowB.Season1 | 3 + Episode 1 | Studio.ShowB.Season1.Episode1 | 4 +(8 rows) +~~~ + +Extract a subpath using `subpath()`, with an offset of 1: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT subpath('Studio.ShowA.Season1.Episode1'::LTREE, 1) AS subpath; +~~~ + +~~~ + subpath +-------------------------- + ShowA.Season1.Episode1 +~~~ + +Extract the same subpath, but only show 2 labels: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT subpath('Studio.ShowA.Season1.Episode1'::LTREE, 1, 2) AS subpath; +~~~ + +~~~ + subpath +----------------- + ShowA.Season1 +~~~ + +Find the longest common ancestor of several `LTREE` values using `lca()`: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT lca( + 'Studio.ShowA.Season1.Episode1'::LTREE, + 'Studio.ShowA.Season1.Episode2'::LTREE, + 'Studio.ShowA.Season2.Episode1'::LTREE +) AS common_ancestor; +~~~ + +~~~ + common_ancestor +------------------- + Studio.ShowA +~~~ + +### Concatenate two `LTREE` values + +Build paths dynamically by concatenating two `LTREE` values using the `||` operator: + +{% include_cached copy-clipboard.html %} +~~~ sql +SELECT 'Animals.Mammals'::LTREE || 'Carnivora.Felidae'::LTREE AS full_path; +~~~ + +~~~ + full_path +------------------------------------- + Animals.Mammals.Carnivora.Felidae +~~~ + +## See also + +- [Data Types]({% link {{ page.version.version }}/data-types.md %}) +- [Functions and Operators]({% link {{ page.version.version }}/functions-and-operators.md %}) +- [`STRING`]({% link {{ page.version.version }}/string.md %}) \ No newline at end of file diff --git a/src/current/v25.4/string.md b/src/current/v25.4/string.md index ee7964b4723..6abc9032349 100644 --- a/src/current/v25.4/string.md +++ b/src/current/v25.4/string.md @@ -143,6 +143,7 @@ Type | Details `INT` | Requires supported [`INT`]({% link {{ page.version.version }}/int.md %}) string format, e.g., `'10'`. `INTERVAL` | Requires supported [`INTERVAL`]({% link {{ page.version.version }}/interval.md %}) string format, e.g., `'1h2m3s4ms5us6ns'`. `JSONPATH` | Requires a valid [`JSONPath`]({% link {{ page.version.version }}/jsonpath.md %}) expression string, e.g., `'$'` or `'$.players[*] ? (@.stats.ppg > 30)'`. +`LTREE` | Requires supported [`LTREE`]({% link {{ page.version.version }}/ltree.md %}) string format, e.g., `'Animals.Mammals.Carnivora'`. `TIME` | Requires supported [`TIME`]({% link {{ page.version.version }}/time.md %}) string format, e.g., `'01:22:12'` (microsecond precision). `TIMESTAMP` | Requires supported [`TIMESTAMP`]({% link {{ page.version.version }}/timestamp.md %}) string format, e.g., `'2016-01-25 10:10:10.555555'`. `TSQUERY` | Requires supported [`TSQUERY`]({% link {{ page.version.version }}/tsquery.md %}) string format, e.g., `'Requires & supported & TSQUERY & string & format'`.
Note that casting a string to a `TSQUERY` will not normalize the tokens into lexemes. To do so, [use `to_tsquery()`, `plainto_tsquery()`, or `phraseto_tsquery()`](#convert-string-to-tsquery). From d19dca0ec0e49fc8a17e264827cca7f50e3c32dc Mon Sep 17 00:00:00 2001 From: Ryan Kuo Date: Thu, 30 Oct 2025 13:58:27 -0400 Subject: [PATCH 2/3] fix LTREE sizing guidance and comparison example --- src/current/v25.4/ltree.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/current/v25.4/ltree.md b/src/current/v25.4/ltree.md index de768e4aef6..d65b988e799 100644 --- a/src/current/v25.4/ltree.md +++ b/src/current/v25.4/ltree.md @@ -22,7 +22,7 @@ SELECT 'Animals.Mammals.Carnivora'::LTREE; Animals.Mammals.Carnivora ~~~ -Each label in the path must contain only alphanumeric characters (`A`-`Z`, `a`-`z`, `0`-`9`), underscores (`_`), and hyphens (`-`). +Each label in the path must contain only alphanumeric characters (`A`-`Z`, `a`-`z`, `0`-`9`), underscores (`_`), and hyphens (`-`). The maximum label length is 1,000 characters, and the maximum number of labels in a path is 65,535. The following are valid `LTREE` values: @@ -33,7 +33,7 @@ The following are valid `LTREE` values: ## Size -The size of an `LTREE` value is variable and equals the total number of characters in all labels plus the dot separators. The maximum label length is 1,000 characters, and the maximum number of labels in a path is 65,535. +The size of an `LTREE` value is variable and equals the total number of characters in all labels plus the dot separators. Cockroach Labs recommends keeping values below 64 kilobytes. Above that threshold, [write amplification]({% link {{ page.version.version }}/architecture/storage-layer.md %}#write-amplification) and other considerations may cause significant performance degradation. ## Operators @@ -126,20 +126,24 @@ INSERT INTO org_structure (id, path, name) VALUES ### Query `LTREE` with comparison operator -Find all entries named `Episode 1`: +Find all entries that come before `Studio.ShowB` using lexicographic ordering. The `<` operator returns entries where the path is lexicographically less than `Studio.ShowB`: {% include_cached copy-clipboard.html %} ~~~ sql SELECT name, path FROM org_structure -WHERE name = 'Episode 1'; +WHERE path < 'Studio.ShowB' +ORDER BY path; ~~~ ~~~ - name | path -------------+-------------------------------- - Episode 1 | Studio.ShowA.Season1.Episode1 - Episode 1 | Studio.ShowB.Season1.Episode1 -(2 rows) + name | path +--------------------+-------------------------------- + Production Studio | Studio + Show A | Studio.ShowA + Season 1 | Studio.ShowA.Season1 + Episode 1 | Studio.ShowA.Season1.Episode1 + Episode 2 | Studio.ShowA.Season1.Episode2 +(5 rows) ~~~ ### Query `LTREE` with containment operator From 8d060261a3faae9182117e232b95dbc5372423e8 Mon Sep 17 00:00:00 2001 From: Ryan Kuo <8740013+taroface@users.noreply.github.com> Date: Fri, 31 Oct 2025 15:05:04 -0400 Subject: [PATCH 3/3] docs review feedback --- src/current/v25.4/ltree.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/current/v25.4/ltree.md b/src/current/v25.4/ltree.md index d65b988e799..e7fb1ca3aa7 100644 --- a/src/current/v25.4/ltree.md +++ b/src/current/v25.4/ltree.md @@ -33,7 +33,7 @@ The following are valid `LTREE` values: ## Size -The size of an `LTREE` value is variable and equals the total number of characters in all labels plus the dot separators. Cockroach Labs recommends keeping values below 64 kilobytes. Above that threshold, [write amplification]({% link {{ page.version.version }}/architecture/storage-layer.md %}#write-amplification) and other considerations may cause significant performance degradation. +The size of an `LTREE` value is variable and equals the total number of characters in all labels plus the total number of dot separators. Cockroach Labs recommends keeping values below 64 kilobytes. Above that threshold, [write amplification]({% link {{ page.version.version }}/architecture/storage-layer.md %}#write-amplification) and other considerations may cause significant performance degradation. ## Operators @@ -273,4 +273,4 @@ SELECT 'Animals.Mammals'::LTREE || 'Carnivora.Felidae'::LTREE AS full_path; - [Data Types]({% link {{ page.version.version }}/data-types.md %}) - [Functions and Operators]({% link {{ page.version.version }}/functions-and-operators.md %}) -- [`STRING`]({% link {{ page.version.version }}/string.md %}) \ No newline at end of file +- [`STRING`]({% link {{ page.version.version }}/string.md %})