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

Membership predicates (list_contains/list_has_any-class) test without exploding — an unnest-join answers the same question at a different grain and must be re-deduped.

The predicate shape stays at parent grain — one verdict per row, duplicates in the array irrelevant, intent readable. The unnest shape moves to element grain — which you want when the question is 'which element matched' — but every duplicate element is now a row, and forgetting the DISTINCT re-aggregation hands you the double-count. Predicate for yes/no, explode for which/how-many, and a grain sentence before any count.

See it break
jars
jaringredients
stew['nut'
jam['fruit']
The trap
SELECT
  COUNT(*) AS jars
FROM
  (
    SELECT
      jar,
      UNNEST(ingredients) AS ing
    FROM
      jars
  )
WHERE
  ing = 'nut';
The fix
SELECT
  COUNT(*) AS jars
FROM
  jars
WHERE
  list_contains(ingredients, 'nut');
The trap
2
The fix
1

The unnest-count read 2 jars for 'nut', not 1 — stew's array lists the element 'nut' twice, so exploding the duplicate double-counted the jar; a membership predicate stays at jar grain and answers once.

The rule

list_has_any / list_contains for membership (dialect spellings vary); unnest + join + DISTINCT re-agg when element detail is the question.

Shows up elsewhere
UNNEST re-introduces fan-outDetecting fan-out
Try one →Prove it in practice →