What you will be able to do
- Build OBJECT values from key-value pairs with OBJECT_CONSTRUCT and predict how SQL NULL and JSON null are handled
- Turn whole relational rows into objects with OBJECT_CONSTRUCT(*), ILIKE, EXCLUDE and object constants
- Collect rows into ordered arrays with ARRAY_AGG and WITHIN GROUP, and nest the result inside objects
1.OBJECT_CONSTRUCT: from columns to key-value pairs
Going from relational data to semi-structured data uses two building blocks: one function makes objects and another makes arrays. OBJECT_CONSTRUCT returns an OBJECT built from alternating arguments, OBJECT_CONSTRUCT(key, value, key, value, ...). Each key is a VARCHAR and each value can be any data type, including the result of an expression or a scalar subquery. That means one call can mix a column, a computed epoch value and a COUNT(*) from another table into the same JSON document. The function returns a value of type OBJECT, so everything you know about reading objects with the colon, dot and bracket notations applies to its output.
That NULL rule decides whether a downstream consumer sees a key at all. Snowflake separates three cases. A SQL NULL value drops the pair. A JSON null, written PARSE_JSON('NULL'), is kept as null. The string 'null' is just text and is kept as "null". If your target schema requires a key to always be present, pass a JSON null instead of a SQL NULL. Also, don't rely on key order: the constructed object doesn't necessarily keep the order you wrote the pairs in.
SELECT OBJECT_CONSTRUCT(
'Key_One', PARSE_JSON('NULL'),
'Key_Two', NULL,
'Key_Three', 'null') AS obj;Checkpoint 1 of 5· Check yourself
A consumer requires the key discount to appear in every JSON record, even when there is no discount. Which value keeps the key in the OBJECT_CONSTRUCT output?
A SQL NULL value drops the pair, but a JSON null created with PARSE_JSON('NULL') is kept as null.
“a JSON null as the value”Source: docs.snowflake.com
Sources1
2.Whole rows to objects: the wildcard, ILIKE, EXCLUDE and object constants
Listing every column by hand gets tedious. OBJECT_CONSTRUCT(*) turns the entire row into an object, using the column names as keys and the column values as values. You can qualify the wildcard with a table name or alias, as in mytable.*, to take only that table's columns from a join. With no column names available, as with a bare VALUES list, Snowflake uses the keys COLUMN1, COLUMN2 and so on. Unquoted identifiers are stored in uppercase, so the keys come out as PROVINCE and CREATED_DATE. Because the result is an OBJECT, you can read a field back with bracket notation, which is why the examples sort by oc['PROVINCE'].
SELECT OBJECT_CONSTRUCT(*) AS oc
FROM demo_table_1
ORDER BY oc['PROVINCE'];Two keywords narrow the wildcard. ILIKE 'prov%' keeps only columns whose names match the pattern, and only one pattern is allowed. EXCLUDE province or EXCLUDE (col1, col2) removes the named columns. You can't use both in the same call. With this function, they're valid only in a SELECT list or GROUP BY clause. An OBJECT constant does the same job in a shorter form: {* EXCLUDE province} produces the same output as the OBJECT_CONSTRUCT version.
Checkpoint 2 of 5· Fill the gap
Which keyword builds each row's object from every column except province?
SELECT OBJECT_CONSTRUCT(* ? province) AS oc
FROM demo_table_1
ORDER BY oc['PROVINCE'];EXCLUDE removes the named column or columns from the wildcard. ILIKE does the opposite and keeps only the columns that match a pattern.
Source: docs.snowflake.comCheckpoint 3 of 5· Check yourself
Which of these OBJECT_CONSTRUCT wildcard forms is NOT allowed?
Qualified wildcards, EXCLUDE lists and object constants are all valid, but ILIKE and EXCLUDE can't be combined in a single call.
“The ILIKE and EXCLUDE keywords can’t be combined in a single function call.”Source: docs.snowflake.com
Sources1
3.ARRAY_AGG: pivoting rows into arrays
Objects cover a single row. Many-to-one relationships, such as all the order keys for a status or all the clerks for a region, need arrays. ARRAY_AGG (alias ARRAYAGG) is an aggregate that collects its input values into one ARRAY per group and returns an empty array when there is no input. Combined with GROUP BY, it rebuilds the nested shape that FLATTEN takes apart. The input is typically a column name, and the function can also run as a window function when you add an OVER clause. LIMIT is not supported.
SELECT
o_orderstatus,
ARRAYAGG(o_clerk) WITHIN GROUP (ORDER BY o_totalprice DESC)
FROM orders
WHERE o_totalprice > 450000
GROUP BY o_orderstatus
ORDER BY o_orderstatus DESC;That query uses two separate ORDER BY clauses, and the difference between them is a common exam point. WITHIN GROUP (ORDER BY ...) sets the order of elements inside each array. Without it, element order is unpredictable. The outer ORDER BY sets only the order of the output rows. Write expressions inside WITHIN GROUP, not numbers: a number is read as a constant, not as a column position. A few other rules: DISTINCT removes duplicates, but when you combine it with WITHIN GROUP both must refer to the same column. NULL values are left out of the array. A single call can return at most 128 MB.
Checkpoint 4 of 5· Check yourself
A query ends with ORDER BY o_orderkey but calls ARRAY_AGG(o_orderkey) with no WITHIN GROUP clause. What can you say about the order of elements inside each array?
Only WITHIN GROUP(ORDER BY) controls the order of elements in the array. The outer ORDER BY sorts the result rows.
“applies to the order of the output rows, not to the order of the array elements within a row”Source: docs.snowflake.com
Checkpoint 5 of 5· Exam question
An `orders` table stores a VARIANT column `payload` shaped like `{"customer": {"name": "..."}, "items": [{"sku": "...", "extras": ["gift_wrap", "insurance"]}]}`. A report needs one row per extra, with the parent item's `sku` and the extra string, for every order. Which approach correctly produces that result?
Correct answer: A — Chain two `LATERAL FLATTEN` calls: first flatten `payload:items` to get each item row, then flatten `item.value:extras` from that result to get each extra, joining both flattened outputs back to the order.
- A. Chaining `LATERAL FLATTEN` calls is the documented pattern for nested arrays inside arrays: the first flatten exposes each item object as a row, and a second flatten referencing that row's `extras` path exposes each extra as its own row while both stay joined to the originating order.
- B. This is incorrect because although `RECURSIVE => TRUE` does walk the full structure, filtering by string-matching the `path` column is fragile and not the standard technique; it also mixes extras from unrelated paths and loses the clean per-item join that chained flattens provide.
- C. This is incorrect because `payload:items:extras` is not a valid path when `items` is itself an array of objects — `items` has no `extras` key directly, and `MODE => 'ARRAY'` does not automatically traverse an intervening array level for you.
- D. This is incorrect because hardcoding fixed indices assumes every item has exactly two extras and a fixed number of items, which breaks for orders with more, fewer, or zero extras per item.
Sources2
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.OBJECT_CONSTRUCT keeps every key you pass, so a SQL NULL value shows up as "key": null.Why is that wrong?
A pair with a SQL NULL key or value is dropped. Only a JSON null created with PARSE_JSON('NULL') is kept as null.
Covered in OBJECT_CONSTRUCT: from columns to key-value pairs
2.Putting ORDER BY at the end of the query sorts the elements inside each ARRAY_AGG array.Why is that wrong?
Only WITHIN GROUP(ORDER BY) sets element order. Without it, the order is unpredictable.
Covered in ARRAY_AGG: pivoting rows into arrays
Practise it for real
Turn a small relational table into JSON objects, then filter which columns go into them
1.Create demo_table_1 (province VARCHAR, created_date DATE) and insert ('Manitoba', '2024-01-18'::DATE) and ('Alberta', '2024-01-19'::DATE).
Why: This gives you a known two-row relational source to convert.
You should see: SELECT province, created_date FROM demo_table_1 returns two rows.
2.Run SELECT OBJECT_CONSTRUCT(*) AS oc FROM demo_table_1 ORDER BY oc['PROVINCE'];
Why: The wildcard turns column names into keys and values into values.
You should see: Two objects, each with the keys CREATED_DATE and PROVINCE.
3.Run the same query with OBJECT_CONSTRUCT(* ILIKE 'prov%'), then with the object constant {* EXCLUDE province}.
Why: This shows the two ways to narrow the wildcard and the shorter object-constant syntax.
You should see: The ILIKE version keeps only PROVINCE. The EXCLUDE version keeps only CREATED_DATE.
4.Run SELECT OBJECT_CONSTRUCT('Key_One', PARSE_JSON('NULL'), 'Key_Two', NULL, 'Key_Three', 'null') AS obj;
Why: This confirms the NULL-handling rules for yourself.
You should see: Key_One is null, Key_Three is "null", and Key_Two is missing.
Stuck? Get a nudge
If a key you expected is missing from the output, check whether its value was a SQL NULL.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“Returns an OBJECT constructed from the arguments.”
↩︎ OBJECT_CONSTRUCT: from columns to key-value pairs“OBJECT_CONSTRUCT supports expressions and queries to add, modify, or omit values from the JSON object.”
↩︎ OBJECT_CONSTRUCT: from columns to key-value pairs“The constructed object does not necessarily preserve the original order of the key-value pairs.”
↩︎ OBJECT_CONSTRUCT: from columns to key-value pairs“the OBJECT value is constructed from the specified data using the attribute names as keys and the associated values as values.”
↩︎ Whole rows to objects: the wildcard, ILIKE, EXCLUDE and object constants“attribute names are not specified, so Snowflake uses COLUMN1, COLUMN2, and so on”
↩︎ Whole rows to objects: the wildcard, ILIKE, EXCLUDE and object constants“In many contexts, you can use an OBJECT constant (also called an OBJECT literal) instead of the OBJECT_CONSTRUCT function.”
↩︎ Whole rows to objects: the wildcard, ILIKE, EXCLUDE and object constants“the key-value pair is omitted from the resulting object.”
↩︎ Exam trap 1“the key-value pair is omitted from the resulting object.”
↩︎ Prediction“a JSON null as the value”
↩︎ Checkpoint“The ILIKE and EXCLUDE keywords can’t be combined in a single function call.”
↩︎ Checkpoint - 2.
“Returns the input values, pivoted into an array. If the input is empty, the function returns an empty array.”
↩︎ ARRAY_AGG: pivoting rows into arrays“If you specify DISTINCT and WITHIN GROUP, both must refer to the same column.”
↩︎ ARRAY_AGG: pivoting rows into arrays“The maximum amount of data that ARRAY_AGG can return for a single call is 128 MB.”
↩︎ ARRAY_AGG: pivoting rows into arrays“do not specify numbers as WITHIN GROUP(ORDER BY) expressions.”
↩︎ ARRAY_AGG: pivoting rows into arrays“If you do not specify WITHIN GROUP(ORDER BY), the order of elements within each array is unpredictable.”
↩︎ Exam trap 2“applies to the order of the output rows, not to the order of the array elements within a row”
↩︎ Checkpoint