Semi-structured & modern warehouse SQL
Concept  ·  SQL & Querying

Array contains/overlap predicates vs join-through-unnest differ in cost and NULL semantics.

B4's membership atom chose between predicate and unnest by grain; this finishes the comparison on NULL and cost. NULL axis: predicates treat NULL elements as unknowable-therefore-no-match (three-valued logic inside the array) and a NULL array as an unknown whole; the unnest spelling materializes NULL elements as rows that never join — same net exclusion, different debuggability. Cost axis: predicates evaluate in place, no explosion, no forgotten DISTINCT.

See it break
boxes
boxings
b1['rice'
b2['rice'
The trap
SELECT
  box
FROM
  boxes
WHERE
  NOT list_contains(ings, 'nut')
  ORDER BY
  box;
The fix
SELECT
  box
FROM
  boxes
WHERE
  NOT list_contains(ings, 'nut')
  AND NOT list_contains(ings, NULL)
ORDER BY
  box;
The trap
b2
The fix

The nut-safe filter called b2 safe, but its array has a NULL (unlabeled) ingredient that could be a nut — array predicates treat a NULL element as unknowable-therefore-no-match, so a NULL-aware check is needed before declaring a box safe.

The rule

Predicates for in-place verdicts, NULL policy stated; unnest when elements (incl. NULLs) must be visible.

Shows up elsewhere
Array membership vs unnest-joinUNNEST re-introduces fan-out
Try one →Prove it in practice →