なぜ列型にNULLをキャストする必要があるのですか?


10

大量の更新を行うためのコードを生成し、次のようなSQLを生成するヘルパーがあります。

(アクティブフィールドとコアフィールドの両方がタイプですboolean

UPDATE fields as t set "active" = new_values."active","core" = new_values."core"
FROM (values 
(true,NULL,3419),
(false,NULL,3420)
) as new_values("active","core","id") WHERE new_values.id = t.id;

しかしそれは失敗します:

ERROR: column "core" is of type boolean but expression is of type text

::booleannullに追加することで機能させることができますが、奇妙に思えますが、なぜNULLは型と見なされますTEXTか?

また、どの型にNULLをキャストするかを知るためにコードを再調整する必要があるため、キャストするのは少し難しいです(現在、列と値のリストは、JSONオブジェクトの単純な配列から自動生成されています)。 。

なぜこれが必要なのですか?NULLのタイプを知るために生成コードを必要としない、よりエレガントなソリューションがありますか?

それが適切であれば、Node.JSでsequelizeを使用してこれを行いますが、Postgresコマンドラインクライアントでも同じ結果が得られます。

回答:


16

これは興味深い発見です。次のように、通常、NULLには想定されたデータ型はありません。

SELECT pg_typeof(NULL);

 pg_typeof 
───────────
 unknown

これは、VALUESテーブルが画像に入ると変化します。

SELECT pg_typeof(core) FROM (
    VALUES (NULL)
) new_values (core);

 pg_typeof 
───────────
 text

この動作は、https//doxygen.postgresql.org/parse__coerce_8c.html#l01373のソースコードで説明されています

 /*
  * If all the inputs were UNKNOWN type --- ie, unknown-type literals ---
  * then resolve as type TEXT.  This situation comes up with constructs
  * like SELECT (CASE WHEN foo THEN 'bar' ELSE 'baz' END); SELECT 'foo'
  * UNION SELECT 'bar'; It might seem desirable to leave the construct's
  * output type as UNKNOWN, but that really doesn't work, because we'd
  * probably end up needing a runtime coercion from UNKNOWN to something
  * else, and we usually won't have it.  We need to coerce the unknown
  * literals while they are still literals, so a decision has to be made
  * now.
  */

(はい、PostgreSQLのソースコードは、優れたコメントのおかげで、ほとんどの場所で比較的理解しやすくなっています。)

ただし、解決策は次のようになります。VALUES特定のテーブルのすべての列に一致するものを常に生成しているとしましょう(他のケースについては、下の2番目のメモを参照してください)。あなたの例から、小さなトリックがおそらく役立つかもしれません:

SELECT (x).* FROM (VALUES ((TRUE, NULL, 1234)::fields)) t(x);

 active  core   id  
────────┼──────┼──────
 t             1234

ここでは、テーブルの型にキャストされた行式を使用し、それを抽出してテーブルに戻します。

以上を踏まえ、あなたはUPDATE次のようになり

UPDATE fields AS t set active = (x).active, core = (x).core
FROM ( VALUES
           ((true, NULL, 3419)::fields),
           ((false, NULL, 3420)::fields)
     ) AS new_values(x) WHERE (x).id = t.id;

ノート:

  • 人間が読みやすいように二重引用符を削除しましたが、(列)名を生成するときに役立つので、そのままにしておくことができます。
  • 列のサブセットのみが必要な場合は、この目的でカスタムタイプ作成できます。上記と同じように使用します(テーブルで自動的に作成された型を使用し、後者の行構造を保持します)。

dbfiddleで作業している全体を見てください。


おかげで、これは興味深いですが、私にとっては、上記のコードが生成しますCannot cast type boolean to bigint in column 1(最初のフィールドステートメント間の::のエラーポイント)
ChristopherJ

1
@ChristopherJ答えは、呼び出されるテーブルfieldsに3つの列があり(active, core, id)、ブール型、ブール型、およびint / bigint型があると想定しています。テーブルに複数の列または異なるタイプがあるか、または列が異なる順序で定義されていますか?
ypercubeᵀᴹ

ああ、わかりました。ありがとう、そうです。列が多く、順序も異なります。ありがとうございました
ChristopherJ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.