Learn SQL/Intermediate/Joins/SQL ON vs WHERE

SQL ON vs WHERE

For an INNER JOIN, putting a filter in ON or WHERE gives the same answer. For an OUTER JOIN, it absolutely does not — one of the most consequential gotchas in this entire series.

What & Why

With a plain INNER JOIN, it genuinely doesn't matter whether an extra filtering condition lives inside ON or inside WHERE. The moment an outer join is involved, that stops being true. ON decides which rows count as a match while the join is happening. WHERE filters the result after the join has already completed — including any NULL-filled rows the outer join added to preserve unmatched rows. If a WHERE condition checks a column that's NULL for one of those rows, the comparison evaluates to NULL (not true), and WHERE quietly throws that row away — cancelling out the entire reason the outer join was used.

See How It Works

BUSINESS QUESTION

Marketing wants every campaign retained while attaching only leads that have been qualified.

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
Trace the relationship row by row1× speed
marketing.campaigns
idname
1Spring Launch
2Retention Webinar
3Finance Retargeting
4Enterprise Search
FILTER IN ON
marketing.leads
idcampaign_idqualified_at
30112024-01-22 11:00:00+00
3021NULL
30322024-02-18 14:20:00+00
30432024-03-21 09:00:00+00
Result set · 4 rows
campaign_namequalified_lead_id
Spring Launch301
Retention Webinar303
Finance Retargeting304
Enterprise SearchNULL

The ON condition limits matching leads, while LEFT JOIN still preserves Enterprise Search with a NULL lead.

EXAMPLE QUERY
SELECT
  c.name AS campaign_name,
  l.id AS qualified_lead_id
FROM marketing.campaigns c
LEFT JOIN marketing.leads l
  ON l.campaign_id = c.id
 AND l.qualified_at IS NOT NULL
ORDER BY c.id, qualified_lead_id;
Now You Try

Practice this concept

Marketing wants every campaign retained while attaching only leads that have been qualified.

Available schema
marketing

Prefix tables with marketing.table_name.

campaign_namequalified_lead_id
marketing.campaigns
ColumnType
idinteger
nametext
channeltext
spendnumeric
start_datedate
end_datedate
statustext
target_segmenttext
legacy_idtext
marketing.leads
ColumnType
idinteger
campaign_idinteger
emailtext
created_attimestamp with time zone
qualified_attimestamp with time zone
converted_attimestamp with time zone
lead_scoreinteger
sourcetext
countrytext
archive_statustext
query.sql
Intermediate business practice

Sign up free to try it on a real business scenario