Skip to main content

mergedJSONPatch

Introduced in: v26.8.0 Aggregates JSON values by merging them with last-write-wins semantics, implementing the core merge behavior of RFC 7396 JSON Merge Patch at the path level. The aggregate function stores state as triplets (key, value, sorting_key) where each key (JSON path) only keeps the latest effective record according to the sorting_key. Object writes are flattened into descendant paths for paths that the JSON type stores as dynamic scalar leaves. Ancestor non-object writes shadow conflicting descendants. Every explicitly typed path (declared with JSON(a Map(...)), JSON(a Tuple(...)), JSON(a Variant(...)), JSON(a Dynamic), etc.) is treated as an atomic value: the whole path value is replaced by the newer patch rather than deep-merged. Only untyped (dynamic) scalar paths follow RFC 7396 deep-merge semantics. The sort_key determines which value wins for each JSON path. The row with the largest sort_key is retained. If two conflicting patches have equal sort keys, the result is order-dependent: the patch processed later wins the tie. Users should not rely on ORDER BY to break ties deterministically. LIMITATIONS (inherited from ColumnObject):
  1. Null deletion: a patch {"key": null} does not remove the key. ColumnObject drops null-valued members on insertion, so the function cannot distinguish “key absent” from “key is null”.
  2. Empty-object replacement: a patch {"a": {}} cannot displace an older scalar or array at path a. ColumnObject silently drops paths whose value is an empty object {}, so the newer patch contributes nothing and the old value survives.
  3. Non-Nullable typed-path absence: when a JSON column declares a typed path with a non-nullable type (e.g., JSON(a UInt32)), a row that omits a is stored with the type default value (e.g., 0). The aggregate cannot tell “absent” from “explicitly written as the default”, so a newer patch that omits a silently erases an older non-zero value. To avoid this, declare typed paths as Nullable (e.g., JSON(a Nullable(UInt32))). A null in a nullable typed path is treated as “path absent” and is correctly skipped.
  4. All typed paths are atomic: every typed path (Map(K,V), JSON, Dynamic, Tuple(…), Variant(…), Array(…), or any other declared type) is stored as a single value. The aggregate replaces the entire value atomically rather than deep-merging its contents. Only dynamic (untyped, scalar) paths are deep-merged path-by-path.
  5. Dot-in-key ambiguity: the JSON type represents {"a":{"b":1}} and {"a.b":1} with the same internal path a.b. A single row can therefore expose both a and a.b as independent peers. When a newer patch writes only a, the ancestor/descendant conflict rule erases a.b; when it writes only a.b, the same rule erases a. To avoid this, set json_type_escape_dots_in_keys = 1. With this setting, literal dots in JSON keys are percent-encoded (e.g. a.b becomes a%2Eb), making them distinct from nested paths and eliminating the false conflict.
Syntax
Arguments
  • json — JSON column to aggregate. JSON
  • sort_key — Comparable column that determines which write wins for each path. The row with the largest sort_key value is retained.
Returned value Returns a single JSON object that is the result of merging all input JSON objects. JSON Examples Basic usage with sort key
Query
Response
Last modified on October 7, 2026