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.

This lesson's practice is part of Pro.

Advanced business practice

Sign up free to try it on a real business scenario