2

I'm trying to create a "Percentage of Total" column and currently using a subquery with no issues:

SELECT ID, COUNT(*), COUNT(*) / (SELECT COUNT(*)
FROM DATA) AS % OF TOTAL FROM DATA GROUP BY ID;

|  ID  | COUNT | % OF TOTAL |
|  1   |  100  |    0.10    |
|  2   |  800  |    0.80    |
|  3   |  100  |    0.10    |

However, for reasons outside the scope of this question, I'm looking to see if there is any way to accomplish this without using a subquery. Essentially, the application uses logic outside of the SQL query to determine what the WHERE clause is and injects it into the query. That logic does not account for the existence of subqueries like the above, so before going back and rebuilding all of the existing logic to account for this scenario, I figured I'd see if there's another solution first.

I've tried accomplishing this effect with a window function, but to no avail.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
AvocadoRivalry
  • 411
  • 1
  • 7
  • 16
  • You can use windowing like `count(*) over ()` - assuming you are actually working on grouped data. However, if this is a performance critical query, you probably want to go with the subquery anyway. – Luaan Mar 10 '15 at 15:10

2 Answers2

5

Use window functions:

SELECT ID, COUNT(*),
       COUNT(*) / SUM(COUNT(*)) OVER () AS "% OF TOTAL"
FROM DATA
GROUP BY ID;
Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • Great, the outer-wrapper on the `COUNT(*)` was exactly what I needed. Would you mind explaining why exactly that wrapper is necessary? – AvocadoRivalry Mar 10 '15 at 15:11
  • In an aggregation query, you can sum the results of aggregations. This syntax looks awkward at first, but it really makes sense. The `count(*)` is defined for each row. The `sum( . . .) over ()` sums the argument over all the rows. – Gordon Linoff Mar 10 '15 at 15:14
2
SELECT id, count(*) AS ct
     , round(count(*)::numeric
           / sum(count(*)) OVER (ORDER BY id), 2) AS pct_of_running_total
FROM   data
GROUP  BY id;

You must add ORDER BY to the window function or the order of rows is arbitrary. I may seem correct at first, but that can change any time and without warning. It seems you want to order rows by id.

And you obviously don't want integer division, which would truncate fractional digits. I cast to numeric and round the result to two fractional digits like in your result.

Related answer:

Key to understanding why this works is the sequence of evens in a SELECT query:

Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228