How to perform the same aggregation on every column, without listing the columns?
database, postgresql, relational-database, sql
Solution
You need dynamic SQL for that, which means you have to create a function or run a `DO` command. Since you cannot return values directly from the latter, a plpgsql function it is:
CREATE OR REPLACE function f_count_all(_tbl text
, OUT columns text[]
, OUT counts bigint[])
RETURNS record LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE (
SELECT 'SELECT
ARRAY[' || string_agg('''' || quote_ident(attname) || '''', ', ') || ']
, ARRAY[' || string_agg('count(' || quote_ident(attname) || ')' , ', ') || ']
FROM ' || _tbl
FROM pg_attribute
WHERE attrelid = _tbl::regclass
AND attnum >= 1 -- exclude tableoid & friends (neg. attnum)
AND NOT attisdropped -- exclude deleted columns
GROUP BY attrelid
)
INTO columns, counts;
END
$func$;
Call:
SELECT * FROM f_count_all('myschema.mytable');
Returns:
columns | counts
--------------+--------
{c1, c2, c3} | {17, 1, 0}
More explanation and links about dynamic SQL and `EXECUTE` in this related question - or a couple more here on SO, try this search.
Related:
- Count values for every column in a table
You could even try and return a polymorphic record type to get single columns dynamically, but that's rather complex and advanced. Probably too much effort for your case. More in this related answer.
Problem
I have a table with N columns. Let's call them `c1`, `c2`, `c3`, `c4`, ... `cN`. Among multiple rows, I want to get a single row with `COUNT DISTINCT(cX)` for each X in [1, N]. ``` c1 | c2 | ... | cn 0 | 4 | ... | 1 ``` Is there a way I can do this (in a stored procedure) without writing every column name into the query manually? Why? We've had a problem where bugs in application servers mean we rewrite good column values with garbage inserted later. To solve this, I'm storing the information log-structure, where each row represents a logical `UPDATE` query. Then, when given a signal that the record is complete, I can determine if any values were (erroneously) overwritten. An example of a single correct record in multiple rows: there is at most one value for each column. ``` | id | initialize_time | start_time | end_time | | 1 | 12:00am | NULL | NULL | | 1 | 12:00am | 1:00pm | NULL | | 1 | 12:00am | NULL | 2:00pm | Reconciled row: | 1 | 12:00am | 1:00pm | 2:00pm | ``` An example of an irreconcilable record that I want to detect: ``` | id | initialize_time | start_time | end_time | | 1 | 12:00am | NULL | NULL | | 1 | 12:00am | 1:00pm | NULL | | 1 | 9:00am | 1:00pm | 2:00pm | -- New initialize time => irreconcilable! ```