What you will be able to do
- Navigate VARIANT values with the :, ., [key], and [index] path operators, including field names that contain special characters
- Extract typed values with variant_get, try_variant_get, ::, and ?::, and predict when each returns NULL or raises an error
- Tell a variant NULL from a SQL NULL with is_variant_null
- Flatten arrays and objects with variant_explode and inspect structure with schema_of_variant
1.Navigating a VARIANT with path operators
The examples on this page query a table store_data that has one VARIANT column, raw. It holds a nested store document with top-level fields such as owner, zip code, and fb:testid, and an object store that contains arrays fruit and book and an object bicycle. You navigate VARIANT with the same SQL operators used for JSON strings: : selects a top-level field, . or [<key>] selects a nested field, and [<index>] selects an array element. Array indices are 0-based.
-- Use dot notation
SELECT raw:store.bicycle FROM store_data
-- Use brackets
SELECT raw:store['bicycle'] FROM store_dataThis catches teams migrating from JSON strings: JSON string paths are case-insensitive, but every VARIANT path element is matched case-sensitively. A path that does not exist, or an array index that is out of bounds, returns NULL rather than an error, so a wrong case gives you a column of NULLs, not a failure. A second migration difference: the [*] wildcard for selecting or unpacking every array element is not supported on VARIANT.
Field names with special characters need escaping. The ['<field>'] form escapes any special character: spaces, periods, colons, and square brackets. Backticks escape only spaces and colons. If a field name contains a period or brackets and you escape it with backticks, you get NULL.
-- Enclose a field name that contains special characters in single quotes inside square brackets.
SELECT raw:['zip code'], raw:['fb:testid'] FROM store_dataCheckpoint 1 of 5· Check yourself
A VARIANT column raw contains a top-level field literally named zip.code. Which expression returns its value?
raw:zip.code reads a nested field named code inside zip. Backticks do not escape periods and return NULL, and raw:zip_code looks for a differently named field. Only the ['<field>'] form escapes the period.
“For example, use raw:['zip.code'] to select a field named zip.code”Source: docs.databricks.com
2.Typed extraction with variant_get and try_variant_get
The : operator returns a VARIANT. That is why raw:owner displays as "amy", quotes included. To get a typed SQL value, use variant_get(variantExpr, path, type). The path is a JSON path string literal starting at $, and the type is a string literal such as 'string', 'int', or 'double'. The same function exists in PySpark, e.g. variant_get(col("raw"), "$.owner", "string").
-- Simple example
> SELECT variant_get(parse_json('{"key": 123, "data": [4, {"a": "hello"}, "str"]}'), '$.data[1].a', 'string')
hello
-- missing path
> SELECT variant_get(parse_json('{"key": 123, "data": [4, {"a": "hello"}, "str"]}'), '$.missing', 'int')
null
-- Invalid cast
> SELECT variant_get(parse_json('{"key": 123, "data": [4, {"a": "hello"}, "str"]}'), '$.key', 'array<int>')
Error: INVALID_VARIANT_CAST.variant_get treats the two failure modes differently. A missing path is not an error: you get NULL. A value that is found but cannot be cast to the requested type raises INVALID_VARIANT_CAST. try_variant_get takes the same arguments but returns NULL in both cases, which makes it the safe option for messy data. The SQL shortcuts work the same way: :: (or cast) casts a path result and behaves like variant_get, while ?:: is the error-tolerant form that matches try_variant_get. :: can also cast a nested object straight into a STRUCT, for example raw:store.bicycle::STRUCT<price DOUBLE, color STRING>.
-- These are synonymous to:
> SELECT '{"key": 123, "data": [4, {"a": "hello"}, "str"]}':data[1].a ?::STRING;
helloCheckpoint 2 of 5· Fill the gap
Some rows hold a non-numeric price. This query must return price as a DOUBLE and give NULL instead of failing on those rows. Which function fills the blank?
SELECT ? (raw, '$.store.bicycle.price', 'double') as price FROM store_datatry_variant_get returns NULL when the value cannot be cast. variant_get would raise INVALID_VARIANT_CAST on those rows.
Source: docs.databricks.comCheckpoint 3 of 5· Exam question
A Lakeflow Spark Declarative Pipeline reads a `VARIANT` column named `payload` where a small fraction of upstream events have a `price` field that is sometimes a string like `"N/A"` instead of a number. The engineer wants to extract `price` as a `DOUBLE` and have any row that fails the cast produce a `NULL` for that column instead of failing the entire pipeline run. Which expression should be used?
Correct answer: A — `try_variant_get(payload, '$.price', 'double')`, which returns `NULL` for values that cannot be cast to `DOUBLE` instead of raising an error for the whole batch.
- A. `try_variant_get` is the safe-casting variant function: it attempts the extraction and type coercion in one step and falls back to `NULL` on failure rather than raising an error, which is exactly the row-level fault tolerance the pipeline needs.
- B. Plain `variant_get` does not substitute a default numeric value on a failed cast; it raises an error rather than silently inventing `0.0`, so this expression does not deliver the described behavior and would still risk failing the batch.
- C. A failed cast does not cause Delta or Spark to drop the offending row on its own; casting failures either raise an error or, depending on ANSI mode, return `NULL` for that expression, but they do not silently delete rows from the batch.
- D. `parse_json` expects a JSON-formatted string as input, not an already-extracted variant value, so applying it to `payload:price` is the wrong composition and does not provide the described selective type-mismatch handling.
3.Variant NULLs, exploding, and schema inspection
Each VARIANT function has a JSON-string counterpart, but VARIANT handles casting and NULLs differently from that counterpart. Learn this mapping and you can translate existing JSON-string queries:
| Variant function | JSON string function | Behavior to remember |
|---|---|---|
| variant_get | cast and get_json_object | Takes expression, path, and type; errors on a failed cast |
| try_variant_get | try_cast and get_json_object | Same arguments; NULL on a failed cast |
| is_variant_null | is null | True only for a VARIANT-encoded NULL |
| schema_of_variant / schema_of_variant_agg | schema_of_json / schema_of_json_agg | Fall back to VARIANT when types conflict |
| variant_explode / variant_explode_outer | explode / explode_outer | Output pos, key, value; key is null for arrays |
A variant can hold two kinds of NULL. A SQL NULL means the value is missing. A variant NULL means the document explicitly contains null. is_variant_null returns true only for a variant NULL, so a missing path, which is a SQL NULL, gives false. The reverse is also true: IS NULL is false for an explicit JSON null, because the VARIANT value itself exists.
-- difference between is_variant_null and is null
> SELECT is_variant_null(v:key), v:key IS NULL
FROM VALUES(parse_json('{"key": null}')) AS T(v)
true falseBecause [*] is unavailable, you flatten with variant_explode. It is a table-valued generator, so it goes in the FROM clause with LATERAL, not in the SELECT list. It returns pos, key, and value columns, and key is NULL when you explode an array. If the input is NULL or is not an array or object, variant_explode produces no rows, while variant_explode_outer produces a single row of NULLs. To find out what a document contains, schema_of_variant returns the schema of one value. schema_of_variant_agg merges the schemas across rows and falls back to VARIANT when it finds no common type.
SELECT key, value
FROM store_data,
LATERAL variant_explode(store_data.raw);
SELECT pos, value
FROM store_data,
LATERAL variant_explode(store_data.raw:store.basket[0]);Checkpoint 4 of 5· Match them up
Match each function to what it does
Tap a term, then the definition that fits it.
Each VARIANT function replaces a JSON-string counterpart: is null, schema_of_json_agg, explode, and try_cast with get_json_object.
“Checks whether the expression is storing a VARIANT encoded NULL. Use is null to check if the input expression is NULL.”Source: docs.databricks.com
Checkpoint 5 of 5· Exam question
A team is designing a Delta table with a `VARIANT` column to store nested event attributes. During the design review, several proposals are made about how the `VARIANT` column can be used elsewhere in the table definition and in queries. Which of the following statements are correct limitations of the `VARIANT` data type? (Select all that apply.)(Select 3)
Correct answers: A, B, C — A `VARIANT` column cannot be used as a Z-order key.; A `VARIANT` column cannot be used as a table partition column.; Rows cannot be directly compared or sorted with `ORDER BY` on a raw `VARIANT` column.
- A. Databricks documentation explicitly states that `VARIANT` columns cannot be used for Z-order keys, since the type does not support the ordering comparisons that Z-ordering relies on.
- B. `VARIANT` columns cannot be used as partition columns; the type is intended for flexible in-row storage of semi-structured values, not for defining the physical partition layout of a table.
- C. Direct comparisons, grouping, ordering, and set operations are all unsupported on raw `VARIANT` values, so sorting on the column with `ORDER BY` is not possible without first extracting a typed scalar.
- D. Grouping on a `VARIANT` column is unsupported regardless of what else the query selects; extracting a scalar field elsewhere in the query does not make the raw variant expression itself groupable in `GROUP BY`.
- E. Equality comparisons and joins on raw `VARIANT` values are not supported, so two variant expressions cannot be joined directly the way two STRUCT values might be compared field by field.
- F. Clustering keys, including Liquid Clustering keys, cannot be defined on a `VARIANT` column; enabling Liquid Clustering on the table does not lift this restriction for the variant-typed column itself.
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.variant_get returns NULL whenever it cannot produce a value, including when the cast to the requested type fails.Why is that wrong?
variant_get returns NULL only for a missing path. A value that is found but cannot be cast raises INVALID_VARIANT_CAST. Use try_variant_get or ?:: to get NULL instead.
Covered in Typed extraction with variant_get and try_variant_get
2.JSON-string queries carry over to VARIANT unchanged, including case-insensitive field names and the [*] wildcard for arrays.Why is that wrong?
VARIANT path elements are matched case-sensitively, and [*] is not supported. Use variant_explode to unpack arrays.
Covered in Navigating a VARIANT with path operators
3.is_variant_null returns true for any NULL, including a missing path or a SQL NULL input.Why is that wrong?
is_variant_null is true only when the variant explicitly stores a null. A missing value is a SQL NULL, which you test with IS NULL.
Practise it for real
Build a small VARIANT table and see each extraction and NULL behavior for yourself
1.Run SELECT parse_json('{"key": 123, "data": [4, {"a": "hello"}, "str"]}') AS v and save it as a temp view or CTAS table.
Why: parse_json is how JSON text becomes VARIANT.
You should see: One row containing {"data":[4,{"a":"hello"},"str"],"key":123}. Key order may differ from your input.
2.Query v:data[1].a, then v:DATA[1].a.
Why: Shows 0-based indexing and case-sensitive path matching.
You should see: The first returns "hello" as a VARIANT; the second returns NULL.
3.Run variant_get(v, '$.key', 'array<int>'), then try_variant_get(v, '$.key', 'boolean').
Why: Compares the strict and error-tolerant extractors on a bad cast.
You should see: variant_get raises INVALID_VARIANT_CAST; try_variant_get returns NULL.
4.Run SELECT * FROM variant_explode(parse_json('[1, "a", {"b": "hello"}]')).
Why: Shows how to flatten arrays now that [*] is not available.
You should see: Three rows with pos 0–2, key NULL, and the element values.
Stuck? Get a nudge
If step 2 returns NULL for both paths, check the case of the field names first.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“All variant path elements are matched in a case-sensitive way. JSON strings are case-insensitive.”
↩︎ Navigating a VARIANT with path operators“When no least common type is identified, the type is derived as VARIANT.”
↩︎ Variant NULLs, exploding, and schema inspection“The [*] syntax is not support for identifying or unpacking all elements in an array.”
↩︎ Exam trap 2“column_name:FIELD_NAME and column_name:field_name look for different fields in the stored data.”
↩︎ Prediction“Checks whether the expression is storing a VARIANT encoded NULL. Use is null to check if the input expression is NULL.”
↩︎ Checkpoint - 2.
“If the path cannot be found, or if the array-index is out of bounds, the result is null.”
↩︎ Navigating a VARIANT with path operators“A field name that contains a period or square brackets returns NULL when escaped with backticks”
↩︎ Navigating a VARIANT with path operators“You can also use :: or cast to cast values to supported data types:”
↩︎ Typed extraction with variant_get and try_variant_get“Because variant_explode is a generator function, you use it as part of the FROM clause rather than in the SELECT list”
↩︎ Variant NULLs, exploding, and schema inspection“Variant NULLs indicate that the variant explicitly contains a NULL value.”
↩︎ Exam trap 3“For example, use raw:['zip.code'] to select a field named zip.code”
↩︎ Checkpoint - 3.
“If the object cannot be found or it cannot be cast to type, NULL is returned.”
↩︎ Typed extraction with variant_get and try_variant_get - 4.
“If input is NULL, or is not a VARIANT ARRAY or VARIANT object, no rows are produced.”
↩︎ Variant NULLs, exploding, and schema inspection
Also cited
“If the object is found but cannot be cast to the desired type, Databricks raises INVALID_VARIANT_CAST.”
↩︎ Exam trap 1