sqlsqlitehaving

How is the having clause creating the correct output in this example?


How the HAVING clause works in SQLite? My code works and returns the name and division of the highest paid person in each division. However, the use of the HAVING clause does not make sense to me since it should simply filter the groups with a false value for max(salary):

CREATE TABLE salaries AS
  SELECT 'Alpha' as name, 'computer' as division, 500 as salary UNION
  SELECT 'Bravo', 'computer', 600 UNION
  SELECT 'Charlie', 'accounting', 200 UNION
  SELECT 'Delta', 'accounting', 300 UNION
  SELECT 'Echo', 'management', 800 UNION
  SELECT 'Foxtrot', 'management', 900;

SELECT name, division FROM salaries GROUP BY division HAVING max(salary);

Why does the above query produce the same output as this query:

SELECT name, division
FROM salaries AS s
WHERE salary = (
  SELECT MAX(salary)
  FROM salaries
  WHERE division = s.division
);

Solution

  • However, the use of the HAVING clause does not make much sense to me since it should simply filter out the groups with a false value for max(salary)

    It would ONLY if the max(salary) per group was 0, according to:-

    If a HAVING clause is specified, it is evaluated once for each group of rows as a boolean expression. If the result of evaluating the HAVING clause is false, the group is discarded. If the HAVING clause is an aggregate expression, it is evaluated across all rows in the group. If a HAVING clause is a non-aggregate expression, it is evaluated with respect to an arbitrarily selected row from the group. The HAVING expression may refer to values, even aggregate functions, that are not in the result. https://www.sqlite.org/lang_select.html#generation_of_the_set_of_result_rows

    So if you used, for example:-

    CREATE TABLE salaries AS
      SELECT 'Alpha' as name, 'computer' as division, 500 as salary UNION
      SELECT 'Bravo', 'computer', 600 UNION
      SELECT 'Charlie', 'accounting', 200 UNION
      SELECT 'Delta', 'accounting', 300 UNION
      SELECT 'Echo', 'management', 800 UNION
      SELECT 'Foxtrot', 'management', 900 UNION
      SELECT 'Hotel', 'other', 0; /*<<<<<<<<<< ADDED for demo */
      
    SELECT name, division, max(salary) AS msal FROM salaries GROUP BY division HAVING max(salary);
    
    
    SELECT name, division, salary AS msal
    FROM salaries AS s
    WHERE salary = (
      SELECT MAX(salary)
      FROM salaries
      WHERE division = s.division
    );
    

    enter image description here

    i.e. the group for the other division has been dropped as the max(salary) is false (0).

    While the result of the second query includes the other division:-

    enter image description here