Checking NULL Rates - SQL
- Which classification logic produces incomplete keys and orphan? Answer: Incomplete: either component is NULL; orphan: both are non-NULL and no parent row matches both components When working with composite foreign keys, you need to track two separate concepts: incomplete keys (where NULL makes a key unknowable) and orphan keys (where a real, complete key has no matching parent). Getting the counts right depends entirely on how precisely each term is defined. Under option D's definitions, a key is incomplete if either component is NULL. Scanning the five pairs: , , and each contain at least one NULL — that's exactly 3 incomplete keys. The remaining pairs are and , both fully non-NULL. Of those, has a matching parent row, but does not — giving exactly 1 orphan. D is the only classification that produces . Option A fails immediately because it defines incomplete as both components being NULL — only qualifies, giving just 1 incomplete key, not 3. Option B makes the same "both NULL" mistake for incomplete keys, and compounds it with a broken orphan definition (matching on individual components rather than the full pair). Option C gets the incomplete definition right ("either component is NULL"), but defines orphans using left-join NULL output, which would catch incomplete rows too — conflating the two categories and producing the wrong orphan count. A useful rule of thumb: in composite key problems, NULL in any component poisons the entire key (making it incomplete), while orphan status only applies when the key is fully known but unmatched. Keeping these two ideas cleanly separate will help you navigate any foreign key classification question.
- Which modification preserves the intended denominator and correctly exposes the NULL statuses? Answer: Change the filter to
WHERE status <> 'cancelled' OR status IS NULLand keep the same average Whenever SQL filters out rows, those rows vanish from both the numerator and denominator of any aggregate — includingAVG. The core issue here is how<>interacts with NULL values in SQL's three-valued logic. In SQL,NULL <> 'cancelled'evaluates to UNKNOWN, not TRUE. This means theWHERE status <> 'cancelled'clause silently eliminates all NULL rows — exactly the rows you're trying to measure. That's why the result is : no NULL rows survive to be counted. The fix is answer C: changing the filter toWHERE status <> 'cancelled' OR status IS NULL. This explicitly rescues NULL rows by adding a second condition. Sincestatus IS NULLevaluates to TRUE for those rows, theORensures they pass the filter regardless of the first condition's UNKNOWN result. Your denominator now includes all non-cancelled rows plus NULL rows, and theAVGcorrectly exposes the NULL rate. A is wrong becauseCOUNT(status)skips NULLs by definition — it counts only non-NULL values — so the ratio still hides NULL rows rather than measuring them. B is wrong becauseWHERE status <> 'cancelled' AND status IS NULLis logically contradictory: it keeps only NULL rows, making the denominator just the NULL rows themselves and destroying the meaningful rate calculation. D is wrong becauseWHERE status = 'cancelled' OR status IS NOT NULLdoes the opposite — it includes cancelled rows and excludes NULLs, completely inverting the intended population. Study tip: Always remember that<> valuesilently drops NULLs in SQL. Whenever your filter might interact with NULLs, explicitly handle them withIS NULLorIS NOT NULL. - What does the query report, and how should the customer-level NULL rate be obtained? Answer: It reports about ; calculate the rate from
customersbefore performing the one-to-many join Whenever you join a one-to-many relationship and then aggregate, you must think carefully about what each row in the result set represents — not each original entity. Here's the math. After joining, the result has rows. Of those, rows come from the NULL-region customers (each contributing 10 orders), and rows come from the non-NULL customers. SoAVG(CASE WHEN c.region IS NULL THEN 1.0 ELSE 0 END)computes . Answer A is correct — it reports roughly , and to get the true customer-level rate you should calculate the NULL proportion directly from thecustomerstable before joining. Answer B is wrong because the join does not give each customer equal weight. Customers with more orders appear in more rows, so high-order customers are over-represented in the average. Answer C is wrong in its reasoning. While is indeed the joined denominator, the NULL rows total (not ), so , not . That figure would arise from mistakenly counting the non-NULL rows as the numerator. Answer D is wrong becauseCOUNT(DISTINCT c.customer_id)would count distinct customers in each group, not compute a rate, and doesn't follow from any coherent calculation here. Study tip: Any time you compute a rate or average on a column from the "one" side of a one-to-many join, pre-aggregate on that table first. Otherwise, entities with more related rows silently inflate their weight. - Which conclusion is supported by these results? Answer: The missing-key rate is because transaction rows have
customer_id IS NULLWhen working withCOUNT()in SQL, the most important distinction to internalize is thatCOUNT(column_name)counts only non-NULL values, whileCOUNT(*)counts every row regardless of NULLs. This question tests whether you understand what each aggregate function actually measures. Here's the key logic: the extract has total rows, andCOUNT(customer_id)returns . BecauseCOUNT(customer_id)skips NULL values, it tells you directly that rows have a valid customer key. That means rows havecustomer_id IS NULL. The missing-key rate is therefore , confirming C as the correct answer. A is wrong because the distinct customer IDs describe uniqueness among valid keys — not missingness. The gap between and reflects both NULLs and repeat customers, so you cannot isolate missing keys this way. B makes a similar error by subtracting (non-NULL count) from and calling the -row gap a missing rate — but , not . This is a simple arithmetic mistake in the final step. D is a trap for students who distrustCOUNT(column_name)in the presence of duplicates. Duplicates are irrelevant here —COUNT(customer_id)ignores NULLs and counts duplicates normally, which is exactly the behavior you need. Study tip: Whenever you need to count missing values, remember thatCOUNT(*) - COUNT(column)isolates NULLs precisely —DISTINCTis a separate concern about uniqueness, not completeness. - Which result and predicate correctly implement the business-defined missing-value rate? Answer: using
value IS NULL OR TRIM(value) = '' OR TRIM(value) = 'N/A'When a business defines "missing" more broadly than SQL does natively, your predicate must capture every form the business considers missing — not just what the database considers NULL. Here, four categories are defined as missing: SQL NULLs (30), empty strings (20), space-only strings (10), and'N/A'sentinel values (5), totaling missing values out of 500. That gives a rate of , achieved withvalue IS NULL OR TRIM(value) = '' OR TRIM(value) = 'N/A'. TheTRIM()call collapses space-only strings to empty strings, catching both empty and whitespace-only entries in a single condition, and the final clause catches the documented sentinel. That makes D the correct answer. Choice A only flags SQL NULLs — that's — completely ignoring the three other business-defined missing forms. Choice B usesvalue IS NULL OR TRIM(value) = '', which catches NULLs, empty strings, and space-only strings , reaching , but it deliberately excludes'N/A', contradicting the stated data-quality rule. Choice C appliesNULLIF(TRIM(value), '') IS NULL, which converts empty-after-trim values to NULL and then checks for NULL — but'N/A'trimmed is still'N/A', not an empty string, so it is not caught by this predicate. C reaches the same as B, not with'N/A'included as the answer claims. When you see a missing-value rate problem, always inventory all exceptional forms the business defines, then verify your predicate covers each one individually. A single overlooked sentinel can shift your rate — and your answer. - Which assessment correctly recalculates the company-wide row-level NULL rate? Answer: The correct rate is about because the regional missing counts must be divided by all records When aggregating data quality metrics across groups of different sizes, you must always weight by record count — never simply average the percentages. This question tests whether you understand the difference between a simple average of rates and a true row-level NULL rate. The correct approach is to count total NULL records across all regions, then divide by total records. Region East contributes NULL values; Region West contributes NULL values. Combined: . This is exactly what B describes — the correct company-wide NULL rate is approximately . A is wrong because averaging two independently calculated percentages ignores the fact that East has ten times as many records as West. When group sizes differ, simple averaging introduces a mathematical distortion called Simpson's paradox-adjacent bias — the smaller, high-NULL region gets equal weight when it shouldn't. C is wrong because "twice the weight" is an arbitrary rule. Proper weighting isn't based on a fixed multiplier; it's based on actual record counts, which in this case give East a weight, not simply . D is wrong because adding rate differences has no statistical basis — it's a fabricated operation that produces a meaningless result. A useful rule of thumb: whenever you see percentages calculated from groups of unequal size, always go back to raw counts before combining. The formula is , not .
- Which SQL expression correctly calculates the NULL rate for
email?Answer:1.0 * (COUNT(*) - COUNT(email)) / COUNT(*), which returns When profiling data quality, the NULL rate measures what fraction of rows are missing a value — not what fraction have one. To find it, you need to count the gap between total rows and non-NULL values, then express that gap as a share of total rows.COUNT(*)counts every row regardless of NULLs, whileCOUNT(column)counts only non-NULL values. So the number of NULL rows is simply . Dividing by the total gives the NULL rate: , or . The1.0 *prefix forces floating-point division so you don't get integer truncation. That's exactly what A computes, making it correct. B inverts the question entirely — it calculates the fill rate (how often email is present, ), not the NULL rate. It's measuring the complement of what you want. C uses the right numerator ( missing rows) but divides byCOUNT(email)() instead ofCOUNT(*)(). That denominator represents only the non-NULL rows, so the result — about — has no meaningful real-world interpretation as a rate. D introducesCOUNT(DISTINCT email), which removes duplicates. Since the passage explicitly allows duplicate emails, this count is unknown from the given information, so the calculation can't even be completed. A reliable memory anchor: NULL rate = missing ÷ total, and "total" always meansCOUNT(*). Any formula usingCOUNT(column)in the denominator is almost certainly a trap. - Which query returns exactly the intended set? Answer:
SELECT o.* FROM orders o LEFT JOIN customers c ON c.customer_id = o.customer_id WHERE c.customer_id IS NULL;When working with "missing relationship" problems in SQL, your first instinct should be to reach for a LEFT JOIN. The core idea is: a LEFT JOIN preserves every row from the left table, filling the right table's columns with NULL whenever no match exists. That NULL is your signal that the foreign key relationship broke down. Option B is correct because it covers both failure cases in one clean pattern. The LEFT JOIN keeps all orders, andWHERE c.customer_id IS NULLthen filters to only the rows where no matching customer was found — which happens both wheno.customer_id IS NULL(no join is possible) and wheno.customer_idhas a value that simply doesn't exist incustomers(the join attempted but failed). Both of your target rows satisfy this condition. Option A uses an INNER JOIN, which only returns rows where a match exists on both sides. Since it requires a valid customer match,c.customer_idcan never be NULL after an inner join — so this query returns zero rows. Option C appliesIS NULL AND NOT INsimultaneously, which is logically contradictory: ifo.customer_id IS NULL, theNOT INsubquery behaves unpredictably (NULL comparisons inNOT INcan suppress all results), and more importantly the two conditions can never both be true for the same row. Option C captures neither group. Option D only retrieves the rows with non-NULL unmatched IDs — it explicitly excludes NULL customer IDs witho.customer_id IS NOT NULL, missing the NULL rows entirely. Your takeaway: whenever you need to find "unmatched or absent" foreign keys, LEFT JOIN + WHERE right-side key IS NULL is the reliable, all-in-one pattern. - Which query reliably identifies the missing reference keys despite the NULL in
valid_codes?Answer:SELECT s.* FROM incoming_codes s WHERE s.code IS NOT NULL AND NOT EXISTS (SELECT 1 FROM valid_codes v WHERE v.code = s.code);Whenever you seeNOT INwith a subquery that might return NULLs, stop and think carefully — this is one of SQL's most common and dangerous traps. Here's why it matters: SQL uses three-valued logic (TRUE, FALSE, UNKNOWN). When you compare any value to NULL using=, the result is UNKNOWN, not FALSE. TheNOT INoperator internally checks whether your value equals any value in the list. If that list contains even one NULL, every comparison produces UNKNOWN, andNOT INreturns UNKNOWN rather than TRUE — meaning no rows are returned at all, regardless of what's actually invalid_codes. Option B is correct becauseNOT EXISTSsidesteps this entirely. It checks for the presence of a matching row, returning FALSE when no match is found and ignoring NULLs that don't participate in the join conditionv.code = s.code. Thes.code IS NOT NULLguard ensures you're only checking meaningful incoming values, giving you exactly the rows that are non-NULL and absent from valid references. Option A fails precisely because of the NULL trap described above — theNOT INsubquery pulls the NULL fromvalid_codes, poisoning every comparison and returning zero rows. Option C inverts the logic entirely: it returns rows whose code is found invalid_codes, which is the opposite of what's needed. Option D returns rows that are NULL or already matched — essentially identifying rows that should probably be excluded, not flagged as missing. Your takeaway: **never useNOT INwith a subquery unless you can guarantee the subquery returns no NULLs**. When in doubt, reach forNOT EXISTSinstead — it's NULL-safe by design.