Percent to total in PostgreSQL without subquery
I have a table with users. Each user has a country. What I want is to get the list of all countries with the numbers of users and the percent/total. What I have so far is:
SELECT
country_id,
COUNT(*) AS total,
((COUNT(*) * 100) / (SELECT COUNT(*) FROM users WHERE cond1 = true AND cond2 = true AND cond3 = true)::decimal) AS percent
FROM users
WHERE cond1 = true AND cond2 = true AND cond3 = true
GROUP BY contry_id
Conditions in both of queries are the same. I tried to do this without a subquery bu开发者_开发知识库t then I can't get the total number of users but total per country. Is there a way to do this without a subquery? I'm using PostgreSQL. Any help is highly appreciated. Thanks in advance
I guess the reason you want to eliminate the subquery is to avoid scanning the users table twice. Remember the total is the sum of the counts for each country.
WITH c AS (
SELECT
country_id,
count(*) AS cnt
FROM users
WHERE cond1=...
GROUP BY country_id
)
SELECT
*,
100.0 * cnt / (SELECT sum(cnt) FROM c) AS percent
FROM c;
This query builds a small CTE with the per-country statistics. It will only scan the users table once, and generate a small result set (only one row per country).
The total (SELECT sum(cnt) FROM c) is calculated only once on this small result set, so it uses negligible time.
You could also use a window function :
SELECT
country_id,
cnt,
100.0 * cnt / (sum(cnt) OVER ()) AS percent
FROM (
SELECT country_id, count(*) as cnt from users group by country_id
) foo;
(which is the same as nightwolf's query with the errors removed lol )
Both queries take about the same time.
This is really old, but both of the select examples above either don't work, or are overly complex.
SELECT
country_id,
COUNT(*),
(COUNT(*) / (SUM(COUNT(*)) OVER() )) * 100
FROM
users
WHERE
cond1 = true AND cond2 = true AND cond3 = true
GROUP BY
country_id
The second count is not necessary, it's just for debugging to ensure you're getting the right results. The trick is the SUM on top of the COUNT over the recordset.
Hope this helps someone.
Also, if anyone wants to do this in Django, just hack up an aggregate:
class PercentageOverRecordCount(Aggregate):
function = 'OVER'
template = '(COUNT(*) / (SUM(COUNT(*)) OVER() )) * 100'
def __init__(self, expression, **extra):
super().__init__(
expression,
output_field=DecimalField(),
**extra
)
Now it can be used in annotate.
I am not a PostgreSQL user but, the general solution would be to use window functions.
Read up on how to use this at http://developer.postgresql.org/pgdocs/postgres/tutorial-window.html
Best explanation i could use to describe it is: basically it allows you to do a group by on one field without the group by clause.
I believe this might do the trick:
SELECT
country_id,
COUNT(*) OVER (country_id)
((((COUNT(*) OVER (country_id)) * 100) / COUNT(*) OVER () )::decimal) as percent
FROM
users
WHERE
cond1 = true AND cond2 = true AND cond3 = true
Using last PostgreSQL version the query can be next:
CREATE TABLE users (
id serial,
country_id int
);
INSERT INTO users (country_id) VALUES (1),(1),(1),(2),(2),(3);
select distinct
country_id,
round(
((COUNT(*) OVER (partition by country_id )) * 100)::numeric
/ COUNT(*) OVER ()
, 2) as percent
from users
order by country_id
;
Result on SQLize.online
+============+=========+
| country_id | percent |
+============+=========+
| 1 | 50.00 |
+------------+---------+
| 2 | 33.33 |
+------------+---------+
| 3 | 16.67 |
+------------+---------+
精彩评论