Learn SQL/Advanced/CTEs/SQL Hierarchical Queries

SQL Hierarchical Queries

Traverse parent-child nodes from roots to descendants with a recursive CTE.

What & Why

Hierarchical queries repeatedly follow a stable parent-child key. The relationship may be self-referencing, or related tables can first be normalized into one node set with compatible node_id and parent_id values.

Queryflo uses campaigns as root nodes and attaches each lead through the real marketing.leads.campaign_id relationship, then the recursive member advances one depth at a time.

See How It Works

BUSINESS QUESTION

Marketing wants a campaign-to-lead hierarchy built from the real campaigns.id to leads.campaign_id relationship.

idnamechannelspendstart_dateend_datestatustarget_segment
1Spring Launchgoogle_ads55000.002024-01-152024-03-31activesmb
2Retention Webinaremail45000.002024-02-102024-04-15activeenterprise
3Finance Retargetinglinkedin50000.002024-03-122024-05-31activeenterprise
4Enterprise Searchgoogle_ads60000.002024-04-012024-06-30activeenterprise
idcampaign_idemailcreated_atqualified_atconverted_atlead_scoresourcecountry
3011ana@example.com2024-01-21 09:10:00+002024-01-22 11:00:00+002024-02-02 10:00:00+0086google_adsUS
3021ben@example.com2024-01-24 12:40:00+00NULLNULL52google_adsCA
3032chloe@example.com2024-02-16 08:30:00+002024-02-18 14:20:00+00NULL74emailGB
3043dev@example.com2024-03-20 17:15:00+002024-03-21 09:00:00+002024-04-04 16:00:00+0091linkedinUS
EXAMPLE QUERY
WITH RECURSIVE nodes AS (
  SELECT
    'campaign:' || c.id AS node_id,
    NULL::text AS parent_id,
    c.name AS node_name,
    'campaign'::text AS node_type
  FROM marketing.campaigns c
  UNION ALL
  SELECT
    'lead:' || l.id,
    'campaign:' || l.campaign_id,
    l.email,
    'lead'::text
  FROM marketing.leads l
  WHERE l.campaign_id IS NOT NULL
),
hierarchy AS (
  SELECT node_id, parent_id, node_name, node_type, 0 AS depth
  FROM nodes
  WHERE parent_id IS NULL
  UNION ALL
  SELECT child.node_id, child.parent_id, child.node_name, child.node_type, parent.depth + 1
  FROM nodes child
  JOIN hierarchy parent ON child.parent_id = parent.node_id
)
SELECT node_id, parent_id, node_name, node_type, depth
FROM hierarchy
ORDER BY SPLIT_PART(COALESCE(parent_id, node_id), ':', 2)::int, depth, node_id;
RESULT — campaigns and their lead children
node_idparent_idnode_namenode_typedepth
campaign:1NULLSpring Launchcampaign0
lead:301campaign:1ana@example.comlead1
lead:302campaign:1ben@example.comlead1
campaign:2NULLRetention Webinarcampaign0
lead:303campaign:2chloe@example.comlead1
campaign:3NULLFinance Retargetingcampaign0
lead:304campaign:3dev@example.comlead1
campaign:4NULLEnterprise Searchcampaign0

The recursive hierarchy starts with four campaign roots and places each related lead one level beneath its campaign.

Advanced business practice

Sign up free to try it on a real business scenario