Skip to content
200 changes: 178 additions & 22 deletions docs/sql-manual/basic-element/sql-data-types/semi-structured/VARIANT.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---

Check notice on line 1 in docs/sql-manual/basic-element/sql-data-types/semi-structured/VARIANT.md

View workflow job for this annotation

GitHub Actions / Build Check

i18n-sync-locale-candidate

Japanese docs are report-only. Generate a candidate translation from the changed files and merge it only after human review. Owner%3A @apache/doris-website-maintainers

Check notice on line 1 in docs/sql-manual/basic-element/sql-data-types/semi-structured/VARIANT.md

View workflow job for this annotation

GitHub Actions / Build Check

i18n-sync-version-candidate

A 3.x counterpart exists. Confirm whether the change is supported in 3.x before leaving it unsynced. Owner%3A @apache/doris-website-maintainers

Check warning on line 1 in docs/sql-manual/basic-element/sql-data-types/semi-structured/VARIANT.md

View workflow job for this annotation

GitHub Actions / Build Check

seo-title-duplicate

Rendered SEO title is duplicated across indexable pages%3A "VARIANT - Apache Doris". Add a version%2C locale%2C or page-specific qualifier. Owner%3A @apache/doris-website-maintainers
{
"title": "VARIANT",
"language": "en-US",
"description": "The VARIANT type stores semi-structured JSON data. It can contain different primitive types (integers, strings, booleans, etc.), one-dimensional arrays, and nested objects. On write, Doris infers the structure and type of sub-paths based on JSON paths and performs Subcolumnization on frequent paths, exposing them as independent columnar subcolumns for both flexibility and performance."
"description": "VARIANT stores semi-structured JSON data and supports typed access, casts, canonical equality, grouping, hashing, and selected SQL operations."
}
---

Expand Down Expand Up @@ -67,6 +67,162 @@
WHERE ARRAY_CONTAINS(CAST(v['tags'] AS ARRAY<TEXT>), 'Doris');
```

## Create and access values

:::info Version availability
The behavior in this section applies to Doris 4.2 and later.
:::

VARIANT values can be created from JSON text, JSON/JSONB values, or typed SQL expressions:

- Use [PARSE_TO_VARIANT](../../../sql-functions/scalar-functions/variant-functions/parse-to-variant) when a string or JSON/JSONB expression should be parsed as a structured VARIANT value.
- Use `CAST(expression AS VARIANT)` to convert a supported SQL value to VARIANT. String parsing for this CAST is controlled by `enable_variant_string_cast_parse`, as described in [CAST rules](#cast-rules).

### Parse JSON text

```sql
SELECT PARSE_TO_VARIANT('{"user": {"id": 42}, "active": true}');
SELECT PARSE_TO_VARIANT('[10, 20, 30]');
SELECT PARSE_TO_VARIANT(CAST('{"user": {"id": 42}}' AS JSON));
```

Use [PARSE_TO_VARIANT_ERROR_TO_NULL](../../../sql-functions/scalar-functions/variant-functions/parse-to-variant-error-to-null) when invalid JSON should return SQL `NULL` instead of failing the query.

### Access objects and arrays

Object fields can be accessed with a string key. In Doris 4.2 and later, VARIANT array indexes are zero-based for non-negative indexes and support negative indexes from the end. The extracted value remains `VARIANT`; CAST it before typed comparison, arithmetic, or aggregation.

```sql
SELECT CAST(PARSE_TO_VARIANT('{"user": {"id": 42}}')['user']['id'] AS BIGINT);
SELECT ELEMENT_AT(PARSE_TO_VARIANT('[10, 20, 30]'), 0); -- 10
SELECT ELEMENT_AT(PARSE_TO_VARIANT('[10, 20, 30]'), -1); -- 30
```

See [ELEMENT_AT](../../../sql-functions/scalar-functions/variant-functions/element-at) for object and array access details.

## CAST rules

CAST involving VARIANT has two directions: converting a supported SQL value to VARIANT and extracting a compatible SQL value from VARIANT.

### CAST other types to VARIANT

| Source type | Behavior |
| --- | --- |
| `CHAR`, `VARCHAR`, `STRING` | In query sessions, parses the input as JSON by default. Set `enable_variant_string_cast_parse` to `false` to preserve the input as a VARIANT string instead. |
| `JSON` / `JSONB` | Converts the structured JSON value directly to VARIANT. |
| Boolean, integer, floating-point, and Decimal types | Preserves the typed scalar value, subject to the Decimal limits below. |
| `DATE`, `DATETIME`, `TIMESTAMPTZ`, `IPV4`, `IPV6` | Preserves the typed logical value. |
| `ARRAY<T>` | Converts each supported element recursively and preserves SQL NULL elements. |
| `MAP` | Not supported. |

`enable_variant_string_cast_parse` is `true` by default in query sessions. Use `PARSE_TO_VARIANT` when the SQL text should always make the JSON-parsing intent explicit, independent of the session setting.

```sql
-- Default behavior: parse the string as JSON.
SELECT CAST('{"id": 1}' AS VARIANT) AS parsed_object;
-- {"id":1}

-- Preserve the same input as a VARIANT string root.
SET enable_variant_string_cast_parse = false;
SELECT CAST(CAST('{"id": 1}' AS VARIANT) AS STRING) AS string_value,
VARIANT_TYPE(CAST('{"id": 1}' AS VARIANT)) AS root_type;
-- string_value: {"id": 1}; root_type: string

-- JSON/JSONB input remains structured.
SET enable_variant_string_cast_parse = true;
SELECT CAST(CAST('{"id": 1}' AS JSON) AS VARIANT) AS parsed_object;
-- {"id":1}
```

When string parsing is enabled, invalid JSON causes the CAST to fail. Use `PARSE_TO_VARIANT_ERROR_TO_NULL` if malformed input should become SQL `NULL`.

### CAST VARIANT to other types

VARIANT can be cast to a compatible scalar, JSON/JSONB, or array target:

| Target type | Behavior |
| --- | --- |
| Boolean, integer, floating-point, Decimal, date/time, and IP types | Converts compatible scalar roots; incompatible shapes, invalid text, or out-of-range values return an error or SQL `NULL` according to the applicable CAST mode. |
| `STRING` | Returns scalar text for scalar roots and JSON text for objects and arrays. Variant/JSON `null` becomes the string `null`; outer SQL `NULL` remains SQL `NULL`. |
| `JSON` / `JSONB` | Converts a representable VARIANT value to structured JSON/JSONB. |
| `ARRAY<T>` | Converts a VARIANT array element by element to `T`; incompatible elements follow the target CAST rules. |
| `MAP` | Not supported. |

```sql
SELECT CAST(PARSE_TO_VARIANT('42') AS BIGINT) AS id;
-- 42

SELECT CAST(PARSE_TO_VARIANT('[1, null, 3]') AS ARRAY<INT>) AS values;
-- [1, NULL, 3]

SELECT CAST(PARSE_TO_VARIANT('{"id": 1}') AS JSON) AS json_value;
-- {"id":1}
```

### Decimal and date/time conversion limits

| Doris input type | Supported VARIANT behavior |
| --- | --- |
| Legacy `DECIMALV2` | Precision up to 27 and scale up to 9 are preserved exactly. |
| `DECIMAL(p, s)` | `1 <= p <= 38` and `0 <= s <= p` are preserved exactly. Values that require precision greater than 38 are not supported. |
| `DATE` | Preserved as a calendar date with no time or time zone. |
| Legacy `DATETIME` | Preserved with whole-second precision and no time-zone adjustment. |
| `DATETIME(p)` | Supports `0 <= p <= 6` with no time-zone adjustment. |
| `TIMESTAMPTZ(p)` | Supports `0 <= p <= 6` with time-zone-adjusted timestamp semantics. |
| `TIME` | Not supported as input to VARIANT. |

Every source value must also be valid for its Doris source type. Unsupported precision, invalid dates, or incompatible values return an error instead of being repaired.

## Equality and hash semantics

In Doris 4.2 and later, grouping, deduplication, and set operations compare logical values rather than source SQL types or physical representations:

- Equivalent integral numeric representations compare as equal.
- Decimal trailing zeros do not change the value, so `1.20` and `1.2` compare as equal.
- `+0`, `-0`, and integral zero compare as equal.
- Object key order does not affect equality, while array element order does.
- Variant/JSON `null` is distinct from SQL `NULL`.

Hash-based operators calculate their keys from the same canonical logical representation. Values that compare as equal under the rules above therefore produce the same internal hash key, regardless of whether they came from parsed JSON or a typed CAST. This hash is an implementation detail, not a stable user-facing checksum.

These rules apply to supported operations such as `GROUP BY`, `DISTINCT`, `COUNT(DISTINCT ...)`, `INTERSECT`, `EXCEPT`, and `UNION DISTINCT`. They do not enable root VARIANT comparison predicates: direct `VARIANT = VARIANT` and ordering comparisons remain unsupported.

```sql
-- 1 and 1.0 have one distinct logical value.
SELECT COUNT(DISTINCT value) AS distinct_count
FROM (
SELECT PARSE_TO_VARIANT('1') AS value
UNION ALL
SELECT PARSE_TO_VARIANT('1.0') AS value
) AS numeric_values;
-- distinct_count: 1

-- Object key order is ignored; array order is preserved.
SELECT COUNT(DISTINCT value) AS distinct_count
FROM (
SELECT PARSE_TO_VARIANT('{"a": 1, "b": 2}') AS value
UNION ALL
SELECT PARSE_TO_VARIANT('{"b": 2, "a": 1}') AS value
) AS object_values;
-- distinct_count: 1

SELECT COUNT(DISTINCT value) AS distinct_count
FROM (
SELECT PARSE_TO_VARIANT('[1, 2]') AS value
UNION ALL
SELECT PARSE_TO_VARIANT('[2, 1]') AS value
) AS array_values;
-- distinct_count: 2
```

## NULL semantics

SQL `NULL` and Variant/JSON `null` are different values:

- SQL `NULL` represents the absence of a SQL value and follows normal SQL NULL propagation.
- Variant/JSON `null` is a VARIANT value, for example the result of `PARSE_TO_VARIANT('null')`.
- `PARSE_TO_VARIANT_ERROR_TO_NULL` returns SQL `NULL` for malformed input. This differs from successfully parsing the JSON literal `null`.

## Primitive types

VARIANT infers subcolumn types automatically. Supported types include:
Expand Down Expand Up @@ -387,10 +543,26 @@

Sorting applies at every level — top-level keys become `a`, `b`, `c`, and the nested object's keys become `x`, `y`.

## Supported operations and CAST rules
## Supported operations

In Doris 4.2 and later, whole VARIANT values support hash-based grouping and deduplication, but they do not support comparison, join-key, or ordering semantics.

- VARIANT cannot be compared/operated directly with other types; comparisons between two VARIANTs are not supported either.
- For comparison, filtering, aggregation, and ordering, CAST subpaths to concrete types (explicitly or implicitly).
| Operation on a whole VARIANT value | Support | Notes |
| --- | --- | --- |
| `GROUP BY` | Supported | Uses the logical equality and canonical hash rules described above. |
| `DISTINCT`, `COUNT(DISTINCT ...)` | Supported | Logically equivalent values are deduplicated. |
| `INTERSECT`, `EXCEPT`, `UNION DISTINCT` | Supported | Uses the same logical equality and hash semantics. |
| `COUNT(*)`, `COUNT(variant)` | Supported | `COUNT(variant)` excludes outer SQL `NULL` in the normal SQL manner. |
| `IF`, `CASE`, `IFNULL`, `COALESCE` | Supported | Conditional expressions can return and consume VARIANT values. |
| VARIANT in transient `ARRAY`, `MAP`, or `STRUCT` expressions | Supported | This does not allow these nested types in persisted table schemas. |
| `EXPLODE_VARIANT_ARRAY`, `EXPLODE`/`EXPLODE_OUTER` on `ARRAY<VARIANT>` | Supported | Emits VARIANT elements and preserves SQL NULL versus Variant/JSON `null`. |
| `=`, `!=`, `<=>`, `<`, `<=`, `>`, `>=` | Not supported | Extract and CAST a comparable subpath on both sides. |
| Join key | Not supported | CAST the required subpath to the same concrete type on both inputs. |
| `ORDER BY`, Sort/TopN key | Not supported | CAST the required subpath before ordering. |
| Window partition/order key | Not supported | A whole VARIANT value cannot be a window key. |
| `MIN(variant)`, `MAX(variant)` | Not supported | CAST a scalar subpath before aggregation. |

For comparison, filtering, arithmetic, and ordering, extract the required subpath and CAST it to a concrete type explicitly or implicitly:

```sql
-- Explicit CAST
Expand All @@ -403,22 +575,6 @@
SELECT * FROM tbl WHERE v['str'] MATCH 'Doris';
```

- VARIANT itself cannot be used directly in ORDER BY, GROUP BY, as a JOIN KEY, or as an aggregate argument; CAST subpaths instead.
- Strings can be implicitly converted to VARIANT.

| VARIANT | Castable | Coercible |
| --------------- | -------- | --------- |
| `ARRAY` | ✔ | ❌ |
| `BOOLEAN` | ✔ | ✔ |
| `DATE/DATETIME` | ✔ | ✔ |
| `FLOAT` | ✔ | ✔ |
| `IPV4/IPV6` | ✔ | ✔ |
| `DECIMAL` | ✔ | ✔ |
| `MAP` | ❌ | ❌ |
| `TIMESTAMP` | ✔ | ✔ |
| `VARCHAR` | ✔ | ✔ |
| `JSON` | ✔ | ✔ |

## Wide columns

When ingested data contains many distinct JSON keys, the number of subcolumns produced by Subcolumnization can grow rapidly; at scale this may cause metadata bloat, higher write/merge cost, and query slowdowns. To address “wide columns” (too many subcolumns), VARIANT provides two mechanisms: **Sparse columns** and **DOC encoding**.
Expand Down Expand Up @@ -464,7 +620,7 @@
- **Wide tables optimization**: For wide tables with a large number of dynamic sub-columns (e.g., more than 2000 columns) generated by the `VARIANT` type, it is highly recommended to enable **Storage Format V3** by specifying `"storage_format" = "V3"` in the table `PROPERTIES`. This decouples column metadata from the Segment Footer, speeding up file opening and reducing memory overhead.
- JSON key length ≤ 255.
- Cannot be a primary key or sort key.
- Cannot be nested within other types (e.g., `Array<Variant>`, `Struct<Variant>`).
- Persisted table schemas cannot nest VARIANT within other types (for example, `ARRAY<VARIANT>` or `STRUCT<VARIANT>`). Transient expression results can use the supported nested-container operations listed above.
- Outside DOC mode, reading the entire VARIANT column scans all subpaths. For very wide columns, direct `SELECT variant_col` is generally not recommended unless DOC mode is enabled. If a column has many subpaths, consider storing the original JSON string in an extra STRING/JSONB column for whole-object searches like `LIKE`:

```sql
Expand Down Expand Up @@ -559,7 +715,7 @@
DESC variant_tbl;
```

``` sql
```sql
DESCRIBE ${table_name} PARTITION ($partition_name);
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---

Check notice on line 1 in docs/sql-manual/sql-functions/scalar-functions/variant-functions/element-at.md

View workflow job for this annotation

GitHub Actions / Build Check

i18n-sync-locale-candidate

Japanese docs are report-only. Generate a candidate translation from the changed files and merge it only after human review. Owner%3A @apache/doris-website-maintainers
{
"title": "ELEMENT_AT",
"language": "en",
Expand Down Expand Up @@ -28,7 +28,8 @@
- For `ARRAY`: An integer, with indexing starting from **1**.
- For `MAP`: The key type (`K`) of the `MAP`, which can be any supported primitive type.
- For `STRUCT`: A constant integer field position (starting from **1**) or a constant string field name (matched **case-insensitively**).
- For `VARIANT`: A string type.
- For `VARIANT` object access: A string key.
- For a VARIANT array in Doris 4.2 and later: An integer index; non-negative indexes are 0-based and negative indexes count backward from the end.

## Return Value

Expand All @@ -41,9 +42,16 @@

## Notes

1. **Array indexes start from 1**, not 0.
2. Negative indexes are supported: `-1` represents the last element, `-2` the second-to-last, and so on.
3. The `ELEMENT_AT(container, key_or_index)` function behaves the same as `container[key_or_index]` (see examples for details).
1. **ARRAY indexes start from 1**, not 0.
2. In Doris 4.2 and later, non-negative indexes into a VARIANT array start from `0`; `0` is the first element and `-1` is the last.
3. Negative indexes are supported for ARRAY and VARIANT array access: `-1` represents the last element, `-2` the second-to-last, and so on.
4. The `ELEMENT_AT(container, key_or_index)` function behaves the same as `container[key_or_index]` (see examples for details).

```sql
SELECT ELEMENT_AT(parse_to_variant('[10, 20, 30]'), 0); -- 10
SELECT ELEMENT_AT(parse_to_variant('[10, 20, 30]'), 1); -- 20
SELECT ELEMENT_AT(parse_to_variant('[10, 20, 30]'), -1); -- 30
```

## Examples

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---

Check notice on line 1 in docs/sql-manual/sql-functions/scalar-functions/variant-functions/parse-to-variant-error-to-null.md

View workflow job for this annotation

GitHub Actions / Build Check

i18n-sync-locale-candidate

Japanese docs are report-only. Generate a candidate translation from the changed files and merge it only after human review. Owner%3A @apache/doris-website-maintainers
{
"title": "PARSE_TO_VARIANT_ERROR_TO_NULL (Return NULL on Error)",
"language": "en",
"description": "Parses one complete JSON value into VARIANT and returns SQL NULL when parsing or validation fails."
}
---

## Description

`PARSE_TO_VARIANT_ERROR_TO_NULL` parses one complete JSON value into `VARIANT`. The `ERROR_TO_NULL` suffix means that a parsing or validation error returns SQL `NULL` instead of failing the query. This function is available in Doris 4.2 and later.

## Syntax

```sql
PARSE_TO_VARIANT_ERROR_TO_NULL(<json_value>)
```

## Parameters

| Parameter | Description |
| --- | --- |
| `<json_value>` | A `CHAR`, `VARCHAR`, or `STRING` expression containing one complete JSON value, or a `JSON`/`JSONB` expression. JSON/JSONB input is converted to JSON text and then parsed as VARIANT. |

## Return Value

Returns a nullable `VARIANT` value.

- Valid input returns the parsed VARIANT value.
- Invalid JSON or another parsing or validation error returns SQL `NULL`.
- SQL `NULL` input returns SQL `NULL`.
- The valid JSON literal `null` returns Variant/JSON `null`, not SQL `NULL`.

## Example

Keep valid values and convert invalid JSON to SQL `NULL`:

```sql
SELECT CAST(
PARSE_TO_VARIANT_ERROR_TO_NULL('{"id": 1}')
AS STRING
) AS valid_value,
PARSE_TO_VARIANT_ERROR_TO_NULL('{"id":') IS NULL AS invalid_is_null,
PARSE_TO_VARIANT_ERROR_TO_NULL(NULL) IS NULL AS input_is_null;
```

```text
+-------------+-----------------+---------------+
| valid_value | invalid_is_null | input_is_null |
+-------------+-----------------+---------------+
| {"id":1} | 1 | 1 |
+-------------+-----------------+---------------+
```

Parse JSON/JSONB input:

```sql
SELECT CAST(
PARSE_TO_VARIANT_ERROR_TO_NULL(CAST('[10, 20, 30]' AS JSON))
AS STRING
) AS value;
```

```text
+------------+
| value |
+------------+
| [10,20,30] |
+------------+
```

JSON `null` and SQL `NULL` remain distinct:

```sql
SELECT PARSE_TO_VARIANT_ERROR_TO_NULL('null') IS NULL AS json_null_is_sql_null,
PARSE_TO_VARIANT_ERROR_TO_NULL('{') IS NULL AS error_is_sql_null;
```

```text
+-----------------------+-------------------+
| json_null_is_sql_null | error_is_sql_null |
+-----------------------+-------------------+
| 0 | 1 |
+-----------------------+-------------------+
```

## Usage Notes

- Use [PARSE_TO_VARIANT](./parse-to-variant) when invalid input should fail the query and expose the data-quality error.
- This function converts only parsing and validation failures to SQL `NULL`; it does not change the meaning of a valid JSON `null` value.
Loading
Loading