Typorithm · medium

EXISTS, NOT EXISTS and nullable anti-joins

sqlrelational-datasqlitepostgresqlindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

EXISTS, NOT EXISTS and nullable anti-joins

0%

Concept and contract

Ask whether a related match exists without accidentally multiplying or poisoning the parent result.

EXISTS tests whether a subquery produces any row; NOT EXISTS tests the opposite. The worked query uses a small staging set of observed company keys, including one unknown key. It asks which complete company identities have no matching observed key and correctly returns D.

The unknown staging row is deliberate external-data evidence, not a change to the roles table's non-null foreign key. Keeping that distinction explicit lets the lesson demonstrate nullable anti-joins without pretending the constrained source schema permits an impossible role record.

Mechanism and worked example

The correlated predicate compares a staging key with the outer company identity.

The correlated predicate compares a staging key with the outer company identity. A null key does not produce a true equality match. EXISTS only cares whether any matching row remains, so unrelated unknown keys do not remove the unmatched company D.

The selected expression inside EXISTS is conventionally one; its value is irrelevant to this existence question. Duplicating matching staging rows would not duplicate a company in the outer output. This is a useful alternative to a join when only eligibility evidence is needed.

Complete worked example

sql

WITH observed AS (SELECT company_id FROM roles UNION ALL SELECT CAST(NULL AS TEXT))
SELECT c.id FROM companies c WHERE NOT EXISTS
 (SELECT 1 FROM observed o WHERE o.company_id=c.id) ORDER BY c.id;
Reasoning
The null staging member does not count as an equality match.

Counterexamples and common mistakes

The counterexample uses NOT IN against the nullable staging set.

The counterexample uses NOT IN against the nullable staging set. The unknown member can make the negative membership result unknown rather than true, so D disappears. This is a semantic trap, not a general statement that every NOT IN query is wrong.

When the subquery values are proved non-null and the intended missingness policy is clear, NOT IN can be valid. Do not cargo-cult a replacement. Trace the exact nullable input and use an explicit anti-match contract that remains correct for that input.

Counterexample: a plausible but wrong query

sql

WITH observed AS (SELECT company_id FROM roles UNION ALL SELECT CAST(NULL AS TEXT))
SELECT id FROM companies WHERE id NOT IN (SELECT company_id FROM observed) ORDER BY id;
Reasoning
NOT IN can become unknown because its input contains NULL.

Independent practice: small query

Find companies that have at least one active Ukrainian role.

Find companies that have at least one active Ukrainian role. Use EXISTS over roles with the company relationship, status and vacancy-country predicates. Predict only A. You need not select or count every matching role to answer this parent-eligibility question.

Keep all three predicates inside the correlated subquery. The task requires one output row per company, so a raw role join would need additional reasoning about repeated companies. EXISTS expresses the requested yes-or-no relationship directly.

Step 1: Correlate the role with its company. Step 2: Require active status and UA inside EXISTS.

Practice starter: solve before reading the solution

sql

SELECT c.id FROM companies c WHERE EXISTS
 (SELECT 1 FROM roles r WHERE /* same company and active UA */) ORDER BY c.id;
Reasoning
Use a correlated eligibility test rather than a multiplying join.
  • Hint 1: You only need evidence of one matching child.
  • Hint 2: No DISTINCT is needed on the parent output.

Solution, tests and trade-offs

The solution returns A exactly once even though two active Ukrainian roles match.

The solution returns A exactly once even though two active Ukrainian roles match. Archived role 7 does not change eligibility. If a new matching role were added in a disposable fixture, A should remain one parent row rather than appearing again.

The acceptance check should include a company with unrelated roles and a company with no roles. Both must be excluded from this positive existence result for different reasons. The original all-company table remains unchanged and available to other reports.

Practice solution and acceptance checks

sql

SELECT c.id FROM companies c WHERE EXISTS
 (SELECT 1 FROM roles r WHERE r.company_id=c.id AND r.active=1 AND r.country='UA')
ORDER BY c.id;
Reasoning
Company A appears once despite two qualifying role records.

Interview reasoning and next step

In interviews, explain the difference between scalar value, row multiplication and existence.

In interviews, explain the difference between scalar value, row multiplication and existence. For negative matching, draw the effect of a null subquery value instead of claiming that a keyword is universally safer or faster. Correctness assumptions should precede plan comparisons.

These patterns recur in permissions, skill evidence, missing applications and incomplete imports. The next lesson combines populations with set operators, where preserving repetitions versus removing them is another deliberate semantic choice.

Optional stretch — after the core task works: Verify both a non-null NOT IN case and the nullable staging case before comparing their meanings.

Computer practice: https://applydjinn.com/typorithm/sql-lab-20261105.zip. Load the shared setup.sql fixture before running a snippet. The local verifier compares exact ordered row arrays in SQLite and PostgreSQL, keeps SQL values separate from driver bindings and never connects to the production website database.

Ask whether a related match exists without accidentally multiplying or poisoning the parent result.