Joins
Combine data from multiple sources with SQL joins.
Cross-Source Joins
The power of rawquery: join tables from different data sources in one query.
sql
-- Join Stripe customers with HubSpot contactsSELECT s.email, s.name AS stripe_name, h.firstname || ' ' || h.lastname AS hubspot_name, h.lifecyclestageFROM my_stripe.customers sJOIN crm.contacts h ON s.email = h.email;-- Join Stripe customers with HubSpot contactsSELECT s.email, s.name AS stripe_name, h.firstname || ' ' || h.lastname AS hubspot_name, h.lifecyclestageFROM my_stripe.customers sJOIN crm.contacts h ON s.email = h.email;Join Types
INNER JOIN
Returns only rows that match in both tables.
sql
SELECT c.email, ch.amountFROM my_stripe.customers cINNER JOIN my_stripe.charges ch ON ch.customer = c.idWHERE ch.status = 'succeeded';SELECT c.email, ch.amountFROM my_stripe.customers cINNER JOIN my_stripe.charges ch ON ch.customer = c.idWHERE ch.status = 'succeeded';LEFT JOIN
Returns all rows from the left table, with matching rows from the right (or NULL).
sql
-- All customers, with their charges if anySELECT c.email, COUNT(ch.id) AS charge_count, COALESCE(SUM(ch.amount), 0) AS totalFROM my_stripe.customers cLEFT JOIN my_stripe.charges ch ON ch.customer = c.idGROUP BY c.email;-- All customers, with their charges if anySELECT c.email, COUNT(ch.id) AS charge_count, COALESCE(SUM(ch.amount), 0) AS totalFROM my_stripe.customers cLEFT JOIN my_stripe.charges ch ON ch.customer = c.idGROUP BY c.email;FULL OUTER JOIN
Returns all rows from both tables, matching where possible.
sql
-- All emails from both Stripe and HubSpotSELECT COALESCE(s.email, h.email) AS email, s.id IS NOT NULL AS in_stripe, h.id IS NOT NULL AS in_hubspotFROM my_stripe.customers sFULL OUTER JOIN crm.contacts h ON s.email = h.email;-- All emails from both Stripe and HubSpotSELECT COALESCE(s.email, h.email) AS email, s.id IS NOT NULL AS in_stripe, h.id IS NOT NULL AS in_hubspotFROM my_stripe.customers sFULL OUTER JOIN crm.contacts h ON s.email = h.email;Common Patterns
Revenue by CRM Lifecycle Stage
sql
SELECT h.lifecyclestage, COUNT(DISTINCT s.id) AS customer_count, SUM(ch.amount) / 100.0 AS revenueFROM crm.contacts hJOIN my_stripe.customers s ON h.email = s.emailJOIN my_stripe.charges ch ON ch.customer = s.idWHERE ch.status = 'succeeded'GROUP BY h.lifecyclestageORDER BY revenue DESC;SELECT h.lifecyclestage, COUNT(DISTINCT s.id) AS customer_count, SUM(ch.amount) / 100.0 AS revenueFROM crm.contacts hJOIN my_stripe.customers s ON h.email = s.emailJOIN my_stripe.charges ch ON ch.customer = s.idWHERE ch.status = 'succeeded'GROUP BY h.lifecyclestageORDER BY revenue DESC;Customers Missing from CRM
sql
-- Paying Stripe customers not in HubSpotSELECT DISTINCT s.email, s.nameFROM my_stripe.customers sJOIN my_stripe.charges ch ON ch.customer = s.idLEFT JOIN crm.contacts h ON s.email = h.emailWHERE ch.status = 'succeeded' AND h.id IS NULL;-- Paying Stripe customers not in HubSpotSELECT DISTINCT s.email, s.nameFROM my_stripe.customers sJOIN my_stripe.charges ch ON ch.customer = s.idLEFT JOIN crm.contacts h ON s.email = h.emailWHERE ch.status = 'succeeded' AND h.id IS NULL;Self-Join for Comparison
sql
-- Compare this month vs last monthWITH monthly AS ( SELECT DATE_TRUNC('month', created) AS month, SUM(amount) AS revenue FROM my_stripe.charges WHERE status = 'succeeded' GROUP BY 1)SELECT curr.month, curr.revenue AS current_revenue, prev.revenue AS previous_revenue, curr.revenue - prev.revenue AS changeFROM monthly currLEFT JOIN monthly prev ON prev.month = curr.month - INTERVAL '1 month'ORDER BY curr.month DESC;-- Compare this month vs last monthWITH monthly AS ( SELECT DATE_TRUNC('month', created) AS month, SUM(amount) AS revenue FROM my_stripe.charges WHERE status = 'succeeded' GROUP BY 1)SELECT curr.month, curr.revenue AS current_revenue, prev.revenue AS previous_revenue, curr.revenue - prev.revenue AS changeFROM monthly currLEFT JOIN monthly prev ON prev.month = curr.month - INTERVAL '1 month'ORDER BY curr.month DESC;