Skip to content

SQLite - GROUP By Clause

The SQLite GROUP BY clause is a powerful feature used with the SELECT statement to collect and arrange multiple rows with identical values in one or more columns into a single summary row. It is fundamental for data aggregation and reporting.

GROUP BY is typically used with aggregate functions like COUNT(), SUM(), AVG(), MAX(), and MIN() to perform calculations on each group of rows.

The GROUP BY clause follows the WHERE clause (if present) and precedes the HAVING and ORDER BY clauses. The standard order of clauses is crucial for the query to be valid:

SELECT
column_1,
aggregate_function(column_2)
FROM
table_name
WHERE
[ conditions ]
GROUP BY
column_1
HAVING
[ group_conditions ]
ORDER BY
column_1;

Any column in the SELECT list that is not encapsulated in an aggregate function must be included in the GROUP BY clause. This is a standard SQL rule that ensures deterministic results.

Practical Example: Analyzing Employee Data

Section titled “Practical Example: Analyzing Employee Data”

Let’s consider a more modern employees table for a tech company. This table stores employee details, including their department and salary.

-- First, let's create the table and insert some data.
CREATE TABLE employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
department TEXT NOT NULL,
salary INTEGER NOT NULL
);
INSERT INTO employees (name, department, salary)
VALUES
('Alice', 'Engineering', 90000),
('Bob', 'Engineering', 85000),
('Charlie', 'Marketing', 70000),
('David', 'Engineering', 120000),
('Eve', 'Sales', 75000),
('Frank', 'Marketing', 68000),
('Grace', 'Sales', 82000);

Now, let’s use GROUP BY to find out the number of employees in each department and the total salary expense per department.

SELECT
department,
COUNT(id) AS number_of_employees,
SUM(salary) AS total_salary_expense
FROM
employees
GROUP BY
department;

This query produces a clear, aggregated summary:

department number_of_employees total_salary_expense
----------- ------------------- --------------------
Engineering 3 295000
Marketing 2 138000
Sales 2 157000

What if you only want to see departments where the total salary expense is greater than $150,000? You can’t use the WHERE clause for this, because WHERE filters rows before they are grouped. Instead, you use the HAVING clause, which filters the groups after aggregation.

SELECT
department,
SUM(salary) AS total_salary_expense
FROM
employees
GROUP BY
department
HAVING
SUM(salary) > 150000;

The result now only includes the groups that meet the HAVING condition:

department total_salary_expense
----------- --------------------
Engineering 295000
Sales 157000

A frequent point of confusion. Remember this rule: WHERE filters individual rows, HAVING filters aggregated groups. Use WHERE for conditions on the original table columns and HAVING for conditions on aggregate function results.

2. Selecting a Non-Aggregated, Non-Grouped Column

Section titled “2. Selecting a Non-Aggregated, Non-Grouped Column”

Standard SQL strictly forbids selecting a column that is not in an aggregate function and not in the GROUP BY list. However, SQLite has a unique feature allowing this. For example:

-- This query is valid in SQLite but NOT in most other SQL databases.
SELECT department, name FROM employees GROUP BY department;

In this case, SQLite will return a name from an arbitrary row within each department group. This can be unpredictable and is generally considered bad practice unless you are intentionally fetching a sample value. For predictable results, always aggregate or group by the columns you select.

Using the AS keyword to create aliases for your aggregated columns (like number_of_employees) makes your query and its results much easier to read and understand.