Postgres return a default value when a column doesn't exist
Why does Rowan's hack work (mostly)?
SELECT id, title, CASE WHEN extra_exists THEN extra::text
ELSE 'default'::text END AS extra
FROM mytable
CROSS JOIN (
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_name = 'mytable'
AND column_name = 'extra') AS extra_exists
) AS extra
Normally, it would not work at all. Postgres parses the SQL statement and throws an exception if any of the involved columns does not exist.
The trick is to introduce a table name (or alias) with the same name as the column name in question. extra
in this case. Every table name can be referenced as a whole, which results in the whole row being returned as type record
. And since every type can be cast to text
, we can cast this whole record to text
. This way, Postgres accepts the query as valid.
Since column names take precedence over table names, extra::text
is interpreted to be the column mytable.extra
if the column exists. Otherwise, it would default to returning the whole row of the table extra
- which never happens.
Try to pick a different table alias for extra
to see for yourself.
This is an undocumented hack and might break if Postgres decides to change the way SQL strings are parsed abd planned in future versions - even though this seems unlikely.
Unambiguous
If you decide to use this, at least make it unambiguous.
A table name alone is not unique. A table named "mytable" can exist any number of times in multiple schemas of the same database, which could lead to very confusing and completely false results. You need to supply the schema name additionally:
SELECT id, title, CASE WHEN col_exists THEN extra::text
ELSE 'default'::text END AS extra
FROM mytable
CROSS JOIN (
SELECT EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'mytable'
AND column_name = 'extra'
) AS col_exists
) extra
Faster
Since this query is hardly portable to other RDBMS, I suggest to use the catalog table pg_attribute
instead of the information schema view information_schema.columns
. About 10 times faster.
SELECT id, title, CASE WHEN col_exists THEN extra::text
ELSE 'default'::text END AS extra
FROM mytable
CROSS JOIN (
SELECT EXISTS (
SELECT 1
FROM pg_catalog.pg_attribute
WHERE attrelid = 'myschema.mytable'::regclass -- schema-qualified!
AND attname = 'extra'
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
) AS col_exists
) extra;
Also using the more convenient and secure cast to regclass
- explained in detail here:
What does regclass signify in Postgresql
You can attach the needed alias to fool Postgres to any table, including the primary table itself. You don't need to join to another relation at all, which should be fastest:
SELECT id, title, CASE WHEN EXISTS (
SELECT 1
FROM pg_catalog.pg_attribute
WHERE attrelid = 'mytable'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0
) THEN extra::text ELSE 'default'::text END AS extra
FROM mytable AS extra;
Convenience
You could encapsulate the test for existence in a simple SQL function (once), arriving (almost) at the function you have been asking for:
CREATE OR REPLACE FUNCTION col_exists(_tbl regclass, _col text)
RETURNS bool AS
$func$
SELECT EXISTS (
SELECT 1
FROM pg_catalog.pg_attribute
WHERE attrelid = $1
AND attname = $2
AND NOT attisdropped
AND attnum > 0
)
$func$
LANGUAGE sql STABLE;
COMMENT ON FUNCTION col_exists(regclass, text) IS
'Test for existence of a column. Returns TRUE / FALSE.
$1 .. exact table name (case sensitive!), optionally schema-qualified
$2 .. exact column name (case sensitive!)';
Simplifies the query to:
SELECT id, title, CASE WHEN col_exists THEN extra::text
ELSE 'default'::text END AS extra
FROM mytable
CROSS JOIN col_exists('mytable', 'extra') AS extra(col_exists);
Using the form with additional relation here, since it turned out to be faster with the function.
Still, you only get the text representation of the column with any of these queries. It's not as simple to get the actual type.
Benchmark
I ran a quick benchmark with 100k rows on pg 9.1 and 9.2 to find these to be fastest:
-- fastest
SELECT id, title, CASE WHEN EXISTS (
SELECT 1
FROM pg_catalog.pg_attribute
WHERE attrelid = 'mytable'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0
) THEN extra::text ELSE 'default'::text END AS extra
FROM mytable AS extra;
-- 2nd fastest
SELECT id, title, CASE WHEN col_exists THEN extra::text
ELSE 'default'::text END AS extra
FROM mytable
CROSS JOIN col_exists('mytable', 'extra') AS extra(col_exists);
-> SQLfiddle demo.
One way is to look up the information schema table and do a little magic with it.
Something like:
SELECT id, title, CASE WHEN extra_exists THEN extra ELSE 'default' END AS extra
FROM mytable
CROSS JOIN (
SELECT EXISTS (SELECT 1
FROM information_schema.columns
WHERE table_name='mytable' AND column_name='extra') AS extra_exists) extra
Edit: Where 'mytable' needs to be passed in for the table you want to query.