16

特定の列が欠落している場合、基本的にフォールバック値が必要なクエリがあります。これをクエリで純粋に処理できるかどうか疑問に思っていました(最初にプローブして別のクエリを送信するのではなく)。本質的COALESCEに、欠落している列のケースを処理するのと同等のものを探しています。

次の 2 つのテーブルを想像してください。

T1
id | title | extra
1    A     | value

- and -

T2
id | title
1    A

これらのテーブルのいずれかから、WITH THE SAME QUERY を選択できるようにしたいと考えています。

たとえば、t2 に実際に「余分な」列があれば、使用できます

 SELECT id,title, COALESCE(extra, 'default') as extra

ただし、列が完全に欠落している場合ではなく、列の値が NULL の場合にのみ機能します。

私は SQL バージョンを好みますが、PLPGSQL 関数 (COALLESCE と同様の動作) も受け入れることができます。

SQL 純粋主義者への注意: アプリケーション ロジックではなく SQL でこれを行う理由 (または、列をスキーマに永続的に追加しない理由) について議論する気はないので、コメント/回答を制限してください。データベースの「正確さ」や、この質問についてあなたを怒らせる可能性のあるものについてのあなたの意見ではありません。

4

2 に答える 2

22

Rowan のハックが (ほぼ) 機能するのはなぜですか?

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)

通常、それはまったく機能しません。関連する列が存在しない場合、Postgres は SQL ステートメントを解析し、例外をスローします。

トリックは、問題の列名と同じ名前のテーブル名 (またはエイリアス) を導入することです。extraこの場合。すべてのテーブル名は全体として参照でき、その結果、行全体が type として返されrecordます。また、すべての型を にキャストできるためtext、このレコード全体を にキャストできますtext。このようにして、Postgres はクエリを有効なものとして受け入れます。

列名はテーブル名よりも優先されるため、列が存在する場合は列でextra::textあると解釈されますtbl.extra。そうしないと、デフォルトでテーブルの行全体が返されますが、extraこれは決して起こりません。

自分の目で確かめるために、別のテーブル エイリアスを選択してみてextraください。

これは文書化されていないハックであり、Postgres が将来のバージョンで SQL 文字列を解析および計画する方法を変更することを決定した場合、壊れる可能性があります。

明確な

これを使用する場合は、少なくとも明確にしてください。

テーブル名だけでは一意ではありません。「tbl」という名前のテーブルは、同じデータベースの複数のスキーマに何度でも存在する可能性があり、非常に紛らわしく完全に誤った結果になる可能性があります。さらにスキーマ名を指定する必要があります。

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;

もっと早く

このクエリは他の RDBMS にほとんど移植できないため、情報スキーマ ビューの代わりにカタログ テーブルpg_attributeinformation_schema.columnsを使用することをお勧めします。約10倍高速。

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);

また、より便利で安全な へのキャストをregclass使用します。見る:

必要なエイリアスをアタッチして、プライマリ テーブル自体を含む任意のテーブルに Postgres をだますことができます。別のリレーションに参加する必要はまったくありません。これが最も速いはずです。

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;

快適

存在するかどうかのテストを単純な SQL 関数 (1 回) にカプセル化し、求めていた関数に (ほぼ) 到達することができます。

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!)';

クエリを次のように単純化します。

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);

関数を使用するとより高速であることが判明したため、ここで追加のリレーションを使用してフォームを使用します。

それでも、これらのクエリでは、列のテキスト表現しか得られません。実際の typeを取得するのは簡単ではありません。

基準

pg 9.1 と 9.2 で 100k 行の簡単なベンチマークを実行して、これらが最速であることを確認しました。

最速:

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 番目に速い:

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
古いsqlfiddle

于 2013-09-23T16:16:03.253 に答える