Por que o hack de Rowan trabalha (principalmente)?
SELECT id, title
, CASE WHEN extra_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = 'tbl'
AND column_name = 'extra')
) AS extra(extra_exists)
Normalmente, não funcionaria de jeito nenhum. O Postgres analisa a instrução SQL e lança uma exceção se qualquer das colunas envolvidas não existe.
O truque é introduzir um nome de tabela (ou alias) com o mesmo nome do nome da coluna em questão.
extra
nesse caso. Cada nome de tabela pode ser referenciado como um todo, o que resulta no retorno de toda a linha como tipo record
. E como todos os tipos podem ser convertidos em text
, podemos converter todo esse registro em text
. Dessa forma, o Postgres aceita a consulta como válida. Como os nomes das colunas têm precedência sobre os nomes das tabelas,
extra::text
é interpretado como a coluna tbl.extra
se a coluna existir. Caso contrário, o padrão seria retornar toda a linha da tabela extra
- o que nunca acontece. Tente escolher um alias de tabela diferente para
extra
para ver por si mesmo. Este é um hack não documentado e pode quebrar se o Postgres decidir mudar a maneira como as strings SQL são analisadas e planejadas em versões futuras - mesmo que isso pareça improvável.
Sem ambiguidade
Se você decidir usar isso, pelo menos torne-o inequívoco .
Um nome de tabela sozinho não é exclusivo. Uma tabela chamada "tbl" pode existir várias vezes em vários esquemas do mesmo banco de dados, o que pode levar a resultados muito confusos e completamente falsos. Você precisa para fornecer o nome do esquema adicionalmente:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'tbl'
AND column_name = 'extra'
) AS col_exists
) extra;
Mais rápido
Como essa consulta dificilmente é portátil para outros RDBMS, sugiro usar o tabela de catálogo
pg_attribute
em vez da visualização do esquema de informações information_schema.columns
. Cerca de 10 vezes mais rápido. SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN (
SELECT EXISTS (
SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'myschema.tbl'::regclass -- schema-qualified!
AND attname = 'extra'
AND NOT attisdropped -- no dropped (dead) columns
AND attnum > 0 -- no system columns
)
) extra(col_exists);
Também usando a transmissão mais conveniente e segura para
regclass
. Ver:Você pode anexar o alias necessário para enganar o Postgres para qualquer tabela, incluindo a própria tabela primária. Você não precisa se juntar a outra relação, o que deve ser mais rápido:
SELECT id, title
, CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0)
THEN extra::text
ELSE 'default' END AS extra
FROM tbl AS extra;
Conveniência
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
LANGUAGE sql STABLE AS
$func$
SELECT EXISTS (
SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = $1
AND attname = $2
AND NOT attisdropped
AND attnum > 0
)
$func$;
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!)';
Simplifica a consulta para:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN col_exists('tbl', 'extra') AS extra(col_exists);
Usando o formulário com relação adicional aqui, já que ficou mais rápido com a função.
Ainda assim, você obtém apenas a representação de texto da coluna com qualquer uma dessas consultas. Não é tão simples obter o tipo real .
Referência
Eu executei um benchmark rápido com 100k linhas nas páginas 9.1 e 9.2 para descobrir que elas são as mais rápidas:
O mais rápido:
SELECT id, title
, CASE WHEN EXISTS (SELECT FROM pg_catalog.pg_attribute
WHERE attrelid = 'tbl'::regclass
AND attname = 'extra'
AND NOT attisdropped
AND attnum > 0)
THEN extra::text
ELSE 'default' END AS extra
FROM tbl AS extra;
2º mais rápido:
SELECT id, title
, CASE WHEN col_exists THEN extra::text ELSE 'default' END AS extra
FROM tbl
CROSS JOIN col_exists('tbl', 'extra') AS extra(col_exists);
db<>fiddle aqui
Antigo sqlfiddle