CertSafari
    Databricks Certified Data Analyst Associate· Lessons

    Domain 5 · Lesson 23/39

    Fixing SQL Queries That Miscount or Fail: Aggregates, Casts and Diagnosis

    Fix a query to achieve the desired results.

    10 min read
    2.56% of exam
    5 sources
    Published 3 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Pick between COUNT(*) and COUNT(column) and predict how aggregates treat NULL and empty input
    • Resolve GROUP BY errors and NULL results from null-intolerant expressions
    • Explain how ANSI mode changes failing casts from silent NULLs into errors
    • Use the query profile and Genie Code /fix or Diagnose Error to find and repair a broken query

    1.Aggregates that miscount

    A query can return the right rows and still report the wrong totals. All aggregate functions skip NULL values, except COUNT(*). So COUNT(*) and COUNT(column) answer different questions. COUNT(*) counts rows. COUNT(column) counts the non-NULL values in that column.

    COUNT(*) counts rows; COUNT(age) skips NULL agessql
    -- `count(*)` does not skip `NULL` values.
    > SELECT count(*) FROM person;
     count(1)
     --------
            7
    
    -- `NULL` values in column `age` are skipped from processing.
    > SELECT count(age) FROM person;
     count(age)
     ----------
              5

    The same NULL-skipping explains a subtler bug. AVG(age) divides by the 5 known ages, not by all 7 people. Empty input also behaves differently depending on the function. COUNT(*) on an empty set returns 0, but MAX, MIN, SUM, AVG, EVERY, ANY and SOME return NULL when every input is NULL or there are no rows. If a dashboard tile goes blank instead of showing 0, the cause is often a SUM over a filter that matched nothing.

    Scalar expressions cause the same kind of surprise. Most functions are *null-intolerant*: concat('John', null) returns NULL, not 'John'. The fix is a function designed for NULL input, such as COALESCE, IFNULL or NVL. COALESCE returns its first non-NULL argument. It returns NULL only when every argument is NULL.

    COALESCE substitutes the first non-NULL valuesql
    -- Returns the first occurrence of non `NULL` value.
    > SELECT coalesce(null, null, 3, null) AS expression_output;
     expression_output
     -----------------
                     3

    Checkpoint 1 of 6· Check yourself

    SELECT max(age) FROM person WHERE 1 = 0 and SELECT count(*) FROM person WHERE 1 = 0 are run. What do they return?

    Checkpoint 2 of 6· Exam question

    An analyst wants one row per order with its total quantity, but this query returns multiple duplicated rows per order and inflated sums after joining to a table of order line notes: ``` SELECT o.order_id, SUM(oi.quantity) AS total_qty FROM orders o JOIN order_items oi ON o.order_id = oi.order_id JOIN order_notes n ON o.order_id = n.order_id GROUP BY o.order_id ``` Each order has many `order_items` rows and many `order_notes` rows. What is the correct fix so `total_qty` reflects the true quantity per order?

    Sources1

    2.GROUP BY errors and conditional aggregates

    Grouping mistakes usually show up as named errors, which makes them easy to read once you know the names. An aggregate inside the GROUP BY list, such as GROUP BY sum(x), raises GROUP_BY_AGGREGATE. A column position that does not exist in the SELECT list raises GROUP_BY_POS_OUT_OF_RANGE, and a position that points to an aggregate raises GROUP_BY_POS_AGGREGATE. The fix is to group only by the non-aggregated expressions.

    GROUP BY ALL (Databricks Runtime 12.2 LTS and above) builds that list for you. It adds every SELECT-list expression that does not contain an aggregate. It is not guaranteed to produce a valid clause, though. If it can't, Databricks raises UNRESOLVED_ALL_IN_GROUP_BY or MISSING_AGGREGATION.

    Another common logic error is filtering with WHERE when only one aggregate should be restricted. WHERE removes rows from every aggregate in the query. A FILTER (WHERE ...) clause attached to a single aggregate restricts only that one, so the other totals in the same SELECT still see every row.

    Checkpoint 3 of 6· Check yourself

    A query must return, per region, both the total order count and the count of returned orders. Adding WHERE status = 'returned' makes the total count wrong. What fixes it?

    Sources2

    3.Casts and overflow: errors versus silent NULLs

    Whether a bad type conversion fails loudly or quietly depends on ANSI mode. When spark.sql.ansi.enabled is true, an illegal cast such as string to integer, or an arithmetic overflow, throws an error at runtime. When it is false, cast('a' AS INT) returns NULL, 2147483647 + 1 wraps around to a negative number, and a decimal overflow returns NULL. In other words, the same query can give wrong answers without complaint under one setting and fail under the other.

    With ANSI mode on, malformed or overflowing casts raise named errorssql
    -- `spark.sql.ansi.enabled=true`
    > SELECT CAST('a' AS INT);
      ERROR: [CAST_INVALID_INPUT] The value 'a' of the type "STRING" cannot be cast to "INT" because it is malformed.
    
    > SELECT CAST(2147483648L AS INT);
      ERROR: [CAST_OVERFLOW] The value 2147483648L of the type "BIGINT" cannot be cast to "INT" due to an overflow.

    Some casts are invalid in either mode. In the cast table, Date to Numeric is marked N, so CAST(DATE'2020-01-01' AS INT) fails with DATATYPE_MISMATCH under ANSI and returns NULL without it. The error name tells you which fix is needed. CAST_INVALID_INPUT means the data contains values that don't parse. CAST_OVERFLOW means the target type is too narrow. A mismatch means the conversion itself is not allowed. Inserts behave the same way: spark.sql.storeAssignmentPolicy defaults to ANSI, which rejects a mismatched INSERT outright. Under LEGACY, the same INSERT writes NULLs or wrong values instead.

    Checkpoint 4 of 6· Check yourself

    With ANSI mode on, SELECT CAST('a' AS INT) fails. Which error name points to the cause, and what does it tell you to fix?

    Sources3

    4.Finding the faulty step: query profile and Genie Code

    When a query is wrong in a way that only shows up at scale, such as an exploding join, the query profile makes the mistake visible. It shows each operator with its time spent and rows processed, so a join that outputs far more rows than it reads stands out. After you fix the query, the profile also lets you check the effect of the change. To open it, you must own the query or have at least CAN MONITOR on the SQL warehouse.

    Checkpoint 5 of 6· Put it in order

    Put the steps for opening a query profile from the query history in order

    1. 1.Click the name of a query to open the query details panel
    2. 2.Click See query profile
    3. 3.Click Query History in the sidebar

    For queries that fail outright, Genie Code can propose the fix. Quick Fix suggests a correction for basic errors that need only a one-line change. Diagnose Error appears in the cell results when an error occurs, and clicking it runs /fix. /fix shows the proposed change in a diff view, where you choose Accept or Reject. Accepting does not run the code, so you can review it before running it.

    Checkpoint 6 of 6· Check yourself

    You click Diagnose error on a failing SQL cell and accept the suggested change. What happens next?

    Sources45

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.COUNT(column) and COUNT(*) return the same number, so they are interchangeable.Why is that wrong?

      Every aggregate except COUNT(*) skips NULLs, so COUNT(column) counts only the non-NULL values in that column.

      Covered in Aggregates that miscount

    2. 2.If a CAST is invalid, the query always fails, so a query that runs has clean conversions.Why is that wrong?

      With ANSI mode off, an invalid cast such as cast('a' AS INT) returns NULL, and integer overflow wraps around, with no error raised.

      Covered in Casts and overflow: errors versus silent NULLs

    Practise it for real

    Reproduce and fix the common join mistakes on the reference employee/department views

    1. 1.Create the employee (6 rows) and department (3 rows) temp views from the JOIN reference, then run the INNER JOIN on employee.deptno = department.deptno.

      Why: This gives you a baseline count for matching rows only.

      You should see: 3 rows: Paul, John and Lisa.

    2. 2.Change INNER JOIN to LEFT JOIN and run it again.

      Why: This shows how to keep unmatched rows when the question asks for every employee.

      You should see: 6 rows, with NULL deptname for Chloe, Evan and Amy.

    3. 3.Remove the ON clause from the join and run it.

      Why: This reproduces the missing-condition bug.

      You should see: 18 rows: every employee paired with every department.

    4. 4.Run SELECT * FROM employee JOIN department USING (name);

      Why: This reproduces a USING column that exists in only one table.

      You should see: Error UNRESOLVED_USING_COLUMN_FOR_JOIN; switch to USING (deptno) to fix it.

    Stuck? Get a nudge

    If your row count is the product of the two table sizes, look for the missing join condition before anything else.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Some aggregate functions return NULL when all input values are NULL or the input data set is empty.”
      ↩︎ Aggregates that miscount
      “Null intolerant expressions return NULL when one or more arguments of expression are NULL”
      ↩︎ Aggregates that miscount
      “NULL values are ignored from processing by all the aggregate functions. Only exception to this rule is COUNT(*) function.”
      ↩︎ Exam trap 1
      “NULL values are ignored from processing by all the aggregate functions. Only exception to this rule is COUNT(*) function.”
      ↩︎ Prediction
      “`count(*)` on an empty input set returns 0. This is unlike the other”
      ↩︎ Checkpoint
    2. 2.
      “If group_expression contains an aggregate function Databricks raises a GROUP_BY_AGGREGATE error.”
      ↩︎ GROUP BY errors and conditional aggregates
      “A shorthand notation to add all SELECT-list expressions not containing aggregate functions as group_expressions.”
      ↩︎ GROUP BY errors and conditional aggregates
      “When a FILTER clause is attached to an aggregate function, only the matching rows are passed to that function.”
      ↩︎ Checkpoint
    3. 3.
      “throws a runtime exception for illegal cast patterns defined in the standard, such as casts from a string to an integer”
      ↩︎ Casts and overflow: errors versus silent NULLs
      “Spark SQL returns null for decimal overflows.”
      ↩︎ Casts and overflow: errors versus silent NULLs
      “invalid casts during table insertions result in either NULL values or incorrect values being inserted, rather than throwing an exception.”
      ↩︎ Exam trap 2
      “ERROR: [CAST_INVALID_INPUT] The value 'a' of the type "STRING" cannot be cast to "INT" because it is malformed.”
      ↩︎ Checkpoint
    4. 4.
      “You can discover and fix common mistakes in SQL statements, such as exploding joins or full table scans.”
      ↩︎ Finding the faulty step: query profile and Genie Code
      “You can view the query profile from the query history using the following steps:”
      ↩︎ Checkpoint
    5. 5.
      “When code returns errors, Quick Fix automatically recommends fixes for basic errors that can be fixed in a single-line change.”
      ↩︎ Finding the faulty step: query profile and Genie Code
      “When you click Diagnose error, Genie Code automatically runs /fix.”
      ↩︎ Finding the faulty step: query profile and Genie Code
      “If you accept the proposed code, the code does not automatically run.”
      ↩︎ Checkpoint

    Ready to test yourself?

    Practise the 10 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.