Dynamic fields
Understand mixed types, missing values, conversion, and query semantics.
Dynamic fields
A JSON leaf path in a raw table normally has the Dynamic type. Each row
retains a concrete value type at that path. One row can contain a number,
another a string, and another no value.
This flexibility removes the need to choose one column type before ingestion.
It also makes the intended meaning of a field part of query design. The number
42 and the string "42" need not mean the same thing.
Stored types and missing values
dynamicType identifies the concrete type for a value.
SELECT value, dynamicType(value) AS value_type FROM events ORDER BY value_typeA known path absent from a row and an explicit JSON null both read as NULL.
An entirely unknown path produces UNKNOWN_IDENTIFIER. These are different
situations: missing data in a known field is not a missing field definition.
A Dynamic wrapper is not an ordinary nullable column. For presence checks,
countIf(value IS NOT NULL) expresses the intended count. count(value) can
count the wrapper even when its stored value is null. count() counts rows.
Conversion and exact extraction
A cast converts compatible values to a chosen type. For example, a numeric string can participate in a numeric calculation after conversion.
SELECT value::Int64 AS value_number FROM eventsA strict cast can fail for incompatible values. It can also convert a missing value to a target default, such as zero or an empty string. Those defaults can create false matches or combine unrelated rows in a group.
Exact extraction selects one stored type instead of converting other types. A stored numeric string does not become an integer through this expression:
SELECT __raw_data.value.:Int64 AS exact_integer FROM eventsA nonmatching type returns NULL when the target supports nulls. Some targets
that cannot be nullable return their default instead. Exact extraction and
conversion therefore answer different questions.
accurateCastOrNull expresses tolerant conversion. It returns NULL for
incompatible or out-of-range values, so the query can handle them explicitly.
SELECT accurateCastOrNull(value, 'Int64') AS value_number FROM eventsComparisons, grouping, and ordering
Operations on dynamic fields can dispatch by concrete type. A comparison that works for numeric variants need not interpret string variants numerically. A successful comparison is not proof that every type participated as intended.
Grouping and distinct operations do not supply a universal conversion rule. Compatible numeric variants can normalize, while a numeric value and its string representation remain different values. A shared business identity requires an explicit common type.
Ordering also depends on the stored types and engine behavior. A mixed field does not automatically define a useful business order. Numeric scores, dates, and identifiers need the appropriate conversion before sorting or aggregation. Null placement is a separate choice.
Joins and arrays
Dynamic join keys are disabled by default. Converting both sides to the same concrete type makes their intended relationship explicit. Missing or invalid keys need separate handling, since converting both to an empty string can create false matches.
Array values retain element types. A field can contain arrays in some rows and scalars or nulls in others.
SELECT dynamicType(tags) AS tags_type, tags FROM eventsArray operations can omit incompatible variants. An empty array also produces
no rows in an ordinary ARRAY JOIN. These outcomes affect row counts, not just
formatting.
Runtime type-compatibility warnings
Query hints are runtime type-compatibility warnings. They report cases where
RawTree skips incompatible Dynamic values or substitutes NULL or default
results while continuing execution.
Scalar functions emit hints when they handle an incompatible concrete type.
For example, length(value) can return NULL for integer values while
processing strings. With dynamic_throw_on_type_mismatch = 0, the default,
the function uses NULL or its defined default for supported mismatch cases.
Set dynamic_throw_on_type_mismatch = 1 to raise an exception for those scalar
mismatches. This setting does not control aggregate skipping.
Numeric/string comparisons can also produce hints. With the default setting,
comparing a string value to a number returns false for equality and ordering
comparisons, but true for !=. A message about skipped rows does not always
mean the result excludes those rows. A SELECT expression can retain the row
with a default result, while a WHERE condition can exclude it.
Aggregates can skip types that their signatures do not support. For values
1, 2, 3, "hello", and "world", sum(value) returns 6 and avg(value)
returns 2. Each emits a hint about incompatible String values.
count(value) accepts those types and returns 5 without a hint.
Missing values and ordinary NULL handling do not themselves produce these warnings.
Some aggregate paths with multiple arguments can skip incompatible types without a hint. An absent hint does not prove that every value contributed to the result. Inspect the stored types and choose whether to convert values, select one type, or count excluded values explicitly.
The JSON result exposes warnings in its optional hints field.
Query logging also records them in system.query_log.hints.
See Query response format for the
field contract and Query fields with mixed types
for examples.
Type conversion and performance
Conversion can also affect index use. The correct type semantics come first. The query plan and execution statistics then show whether a predicate can skip data. No general rule says that every cast disables optimization.