Search code examples
sqllogicarithmetic-expressions

How do you filter mathematically calculated data in SQL?


This is probably easier than I think it is, but I am stumped. I wrote the following query which sums up all the purchases made by a customer at a particular store, as well as the total amount of returns made by the same customer at the same store, and lastly, stores the result of subtracting total returns from total purchases in a calculated column called TOTAL_SPENT. Here is my query:

SELECT Account_Id,
  SUM(CASE WHEN Transaction_Code = 'Purchase' THEN Transaction_Amount ELSE 0 END) AS TOTAL_PURCHASED,
  SUM(CASE WHEN Transaction_Code = 'Return' THEN Transaction_Amount ELSE 0 END) AS TOTAL_RETURNED,
  (TOTAL_PURCHASED - TOTAL_RETURNED) AS TOTAL_SPENT
FROM Transactions_Table
WHERE Store = 'Store XYZ'
GROUP BY Account_Id
ORDER BY TOTAL_SPENT DESC

My problem is that this query returns hundreds of thousands of records, but I only care about approximately 10% of the data that's returned. So, say you only want to see data returned where TOTAL_SPENT >= 500.00. But for the life of me, I cannot figure out how to do this! hanging head in shame


Solution

  • In SQL, the WHERE clause is processed before the SELECT clause. So, when SQL begins filtering the rows from your table that are relevent to your query, it doesn't yet know about the column TOTAL_SPENT.

    There's a few ways to fix that.

    1) Repeat the expressions in your WHERE clause, rather than referring to a column name in your SELECT clause:

    SELECT Account_Id,
      SUM(CASE WHEN Transaction_Code = 'Purchase' THEN Transaction_Amount ELSE 0 END) AS TOTAL_PURCHASED,
      SUM(CASE WHEN Transaction_Code = 'Return' THEN Transaction_Amount ELSE 0 END) AS TOTAL_RETURNED,
      (TOTAL_PURCHASED - TOTAL_RETURNED) AS TOTAL_SPENT
    FROM Transactions_Table
    WHERE Store = 'Store XYZ'
    GROUP BY Account_Id
    ORDER BY 
      (SUM(CASE WHEN Transaction_Code = 'Purchase' THEN Transaction_Amount ELSE 0 END) - SUM(CASE WHEN Transaction_Code = 'Return' THEN Transaction_Amount ELSE 0 END)) DESC
    

    Note: If you're looking to find groups with a TOTAL_SPENT above 500, use the HAVING clause instead of WHERE.

    2) Use a view. Remove your WHERE clause and create a VIEW with your query. Then, select from that view and add your WHERE clause.

    CREATE VIEW Whatever AS
       SELECT Account_Id,
          SUM(CASE WHEN Transaction_Code = 'Purchase' THEN Transaction_Amount ELSE 0 END) AS    TOTAL_PURCHASED,
          SUM(CASE WHEN Transaction_Code = 'Return' THEN Transaction_Amount ELSE 0 END) AS TOTAL_RETURNED,
          (TOTAL_PURCHASED - TOTAL_RETURNED) AS TOTAL_SPENT
       FROM Transactions_Table
       GROUP BY Account_Id;
    
    SELECT * From Whatever WHERE TOTAL_SPENT >= 500
    

    3) Use a nested SELECT:

    select * from (
      SELECT
        Account_Id,
        SUM(CASE WHEN Transaction_Code = 'Purchase' THEN Transaction_Amount ELSE 0 END) AS TOTAL_PURCHASED,
        SUM(CASE WHEN Transaction_Code = 'Return' THEN Transaction_Amount ELSE 0 END) AS TOTAL_RETURNED,
        (TOTAL_PURCHASED - TOTAL_RETURNED) AS TOTAL_SPENT
      FROM Transactions_Table
      WHERE Store = 'Store XYZ'
      GROUP BY Account_Id
    ) where TOTAL_SPENT > 500 ORDER BY TOTAL_SPENT DESC;
    

    4) If your database supports it, use a CTE:

    WITH MyQuery AS (
      SELECT
        Account_Id,
        SUM(CASE WHEN Transaction_Code = 'Purchase' THEN Transaction_Amount ELSE 0 END) AS TOTAL_PURCHASED,
        SUM(CASE WHEN Transaction_Code = 'Return' THEN Transaction_Amount ELSE 0 END) AS TOTAL_RETURNED,
        (TOTAL_PURCHASED - TOTAL_RETURNED) AS TOTAL_SPENT
      FROM Transactions_Table
      WHERE Store = 'Store XYZ'
      GROUP BY Account_Id
    )
    SELECT * FROM MyQuery WHERE TOTAL_SPENT > 500 ORDER BY TOTAL_SPENT DESC;