You can have your pudding and eat it, too. Meaning, you can have the result just naming the table, and still get optimal performance, building the command dynamically.
This builds your command:
SELECT format('SELECT %1$s AS my_sum FROM %2$s'
, string_agg(quote_ident(a.attname), ' + ')
, min(attrelid)::regclass
)
FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
WHERE a.attrelid = 'my_schema.my_table'::regclass -- provide table name here, optionally schema-qualified!
AND a.attnum > 0 -- only user columns
AND NOT a.attisdropped -- no dropped columns
AND t.typcategory = 'N'; -- all "numeric" types
Notably safe against SQL injection. See:
Here I use the psql meta-command \gexec to immediately execute the built command:
test=> SELECT format('SELECT %1$s AS result FROM %2$s'
test(> , string_agg(quote_ident(a.attname), ' + ')
test(> , min(a.attrelid)::regclass
test(> )
test-> FROM pg_catalog.pg_attribute a
test-> JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
test-> WHERE a.attrelid = 'my_table'::regclass
test-> AND a.attnum > 0
test-> AND NOT a.attisdropped
test-> AND t.typcategory = 'N'\gexec
result
--------
21
23
12
(4 rows)
See:
Alternatively, create a function that executes dynamic SQL.
You may want to wrap columns that can be null into COALESCE to not nullify the sum (like demonstrated in row 3 of above example). And you may want to cast to float8 (which can hold the sum of any numeric types) to get a consistent result type:
SELECT format('SELECT (%1$s)::float8 AS my_sum FROM %2$s'
, string_agg(CASE WHEN a.attnotnull THEN quote_ident(a.attname) ELSE 'COALESCE(' || quote_ident(a.attname) || $$, '0')$$ END, ' + ')
, min(a.attrelid)::regclass
)
FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
WHERE a.attrelid = 'my_table'::regclass -- provide table name here!
AND a.attnum > 0
AND NOT a.attisdropped
AND t.typcategory = 'N'\gexec
The manual about pg_attribute and pg_type.