Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rmorenobello/c94e28818bd7cb6930e71380c7f915ea to your computer and use it in GitHub Desktop.
Save rmorenobello/c94e28818bd7cb6930e71380c7f915ea to your computer and use it in GitHub Desktop.
Calculate Multiple Aggregate Functions (with diff combinations of fixed WHERE conditions) in a Single Query
-- https://dzone.com/articles/how-to-calculate-multiple-aggregate-functions-in-a?edition=297992
-- Calculate Multiple Aggregate Functions (diff WHERE conditions) in a Single Query
-- ***************** PROBLEM to solve:
-- Number of films with a given length / language_id
SELECT count(*)
FROM film
WHERE length BETWEEN 120 AND 150
AND language_id = 1;
-- Number of films with a given length / rating
SELECT count(*)
FROM film
WHERE length BETWEEN 120 AND 150
AND rating = 'PG';
-- Number of films with a given language_id / rating
SELECT count(*)
FROM film
WHERE language_id = 1
AND rating = 'PG';
-- Number of films with a given length / language_id / rating
SELECT count(*)
FROM film
WHERE length BETWEEN 120 AND 150
AND language_id = 1
AND rating = 'PG';
-- we observe always combinations of 3 basic conditions:
length BETWEEN 120 AND 150
language_id = 1
rating = 'PG';
-- ************** SOLUTION 1
-- With DERIVED TABLE with nulls for false
/*
Create a derived table with the different predicates to combine, which should return null when false and 1 when true.
Then we can count each case as nulls won't sum.
The trick to combine cases is the fact that
1+null=null
so we can also do count(length + language_id) and it will only count when both conditions are true.
*/
SELECT
count(*),
count(length),
count(language_id),
count(rating),
-- 1+null=null trick
count(length + language_id),
count(length + rating),
count(language_id + rating),
count(length + language_id + rating)
FROM (
-- Heart of the trick (notice no use of ELSE so we get those usefull NULLs):
SELECT
CASE WHEN length BETWEEN 120 AND 150 THEN 1 END length,
CASE WHEN language_id = 1 THEN 1 END language_id,
CASE WHEN rating = 'PG' THEN 1 END rating
FROM film
) film
;
/* result:
col1 col2 col3 col4 col5 col6 col7 col8
1000 224 1000 194 224 43 194 43
*/
-- ************** SOLUTION 2
-- With FILTER clause syntax (PostgreSQL and HSQLDB and in the SQL standard)
SELECT
count(*),
count(*) FILTER (WHERE length BETWEEN 120 AND 150),
count(*) FILTER (WHERE language_id = 1),
count(*) FILTER (WHERE rating = 'PG'),
count(*) FILTER (
WHERE length BETWEEN 120 AND 150 AND language_id = 1),
count(*) FILTER (
WHERE length BETWEEN 120 AND 150 AND rating = 'PG'),
count(*) FILTER (
WHERE language_id = 1 AND rating = 'PG'),
count(*) FILTER (
WHERE length BETWEEN 120 AND 150
AND language_id = 1 AND rating = 'PG')
FROM film
;
-- *************** How the derived table variant works:
-- the derived table:
SELECT
CASE WHEN length BETWEEN 120 AND 150 THEN 1 END length,
CASE WHEN language_id = 1 THEN 1 END language_id,
CASE WHEN rating = 'PG' THEN 1 END rating
FROM film
;
/*
length language_id rating
---------------------------
NULL 1 1
NULL 1 NULL
NULL 1 NULL
NULL 1 NULL
1 1 NULL
NULL 1 1
NULL 1 NULL
...
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment