Why does Rowan 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
Usually this will 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 enter a table name (or an alias) with the same name as the column name in the question. extra in this case. Each table name can be referenced as a whole, as a result of which the entire row is returned as a record type. And since each type can be cast to text , we can cast the whole record to text . Therefore, Postgres accepts the request as valid.
Because column names take precedence over table names, extra::text interpreted as the column mytable.extra if the column exists. Otherwise, by default the entire row of the extra table will be returned - which never happens.
Try choosing a different alias table for extra to see for yourself.
This is an undocumented hack, and it can break if Postgres decides to change the way it parses SQL strings and plans them in future versions β even if this seems unlikely.
unambiguous
If you decide to use it, at least make it unambiguous .
The table name alone is not unique. A table named "mytable" can exist any number of times in several schemas of the same database, which can lead to very confusing and completely false results. You need to specify the schema name in addition:
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 DBMSs, I suggest using the pg_attribute catalog pg_attribute instead of presenting the information_schema.columns information schema. 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
A more convenient and safer cast to regclass is also used - explained in detail here:
What does regclass mean in Postgresql
You can attach the required alias to trick Postgres into any table, including the main table itself. You donβt need to join another relation, which should be the 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 can enclose the existence test in a simple SQL function (once) by arriving (almost) at the function you requested:
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 request 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);
Here we use a form with an additional relation, since it turned out to be faster with a function.
However, you only get a textual representation of the column with any of these queries. It is not so simple to get the actual type .
benchmark
I ran a quick test with 100k lines on pages 9.1 and 9.2 to find them the 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.