10

同時に多くの行を更新しようとすると問題が発生します。

これが私が使用するテーブルとクエリです(読みやすくするために簡略化されています):

テーブル

CREATE TABLE foo
(
    pkid integer,
    x integer,
    y integer
)

クエリ

UPDATE foo SET x=t.x, y=t.y FROM
(VALUES (50, 50, 1),
        (100, 120, 2))
AS t(x, y, pkid) WHERE foo.pkid=t.pkid

このクエリは完全に機能しますが、xまたはyの値がすべて null であるクエリを実行しようとすると、エラーが発生します。

null を使用したクエリ

UPDATE foo SET x=t.x, y=t.y FROM
(VALUES (null, 20, 1),
        (null, 50, 2))
AS t(x, y, pkid) WHERE foo.pkid=t.pkid

エラー

ERROR:  column "x" is of type integer but expression is of type text
LINE 1: UPDATE foo SET x=t.x FROM

(null, 20, 1)これを修正する唯一の方法は、値の少なくとも 1 つを変更することですが(null:int, 50, 2)、これらの「複数行の更新」クエリを生成する関数があり、列の種類について何も知らないため、それはできません。

ここで最善の解決策は何ですか?複数の行に対するより良い更新クエリはありますか? のような関数や構文はありますAS t(x:gettype(foo.x), y:gettype(foo.y), pkid:gettype(foo.pkid))か?

4

3 に答える 3

16

スタンドアロンVALUES式の場合、PostgreSQLはデータ型がどうあるべきかわかりません。単純な数値リテラルを使用すると、システムは一致する型を想定できます。しかし、他の入力(のようなNULL)では、明示的にキャストする必要があります-すでに知っているように。

クエリpg_catalog(高速ですがPostgreSQL固有)またはinformation_schema(低速ですが標準SQL)を使用して、適切なタイプのステートメントを見つけて準備できます。

または、これらの単純な「トリック」の1つを使用できます(私は最後に最善を保存しました):

0.で行を選択しLIMIT 0、で行を追加します UNION ALL VALUES

UPDATE foo f
SET    x = t.x
     , y = t.y
FROM  (
  (SELECT pkid, x, y FROM foo LIMIT 0) -- parenthesis needed with LIMIT
   UNION ALL
   VALUES
      (1, 20, NULL)  -- no type casts here
    , (2, 50, NULL)
   ) t               -- column names and types are already defined
WHERE  f.pkid = t.pkid;

サブクエリの最初のサブ選択:

(SELECT x, y, pkid  FROM foo LIMIT 0)

列の名前とタイプを取得しますがLIMIT 0、実際の行を追加することはできません。後続の行は、現在明確に定義されている行タイプに強制変換され、タイプと一致するかどうかをすぐにチェックします。元のフォームよりも微妙に改善されているはずです。

テーブルのすべての列に値を提供する一方で、この短い構文を最初の行に使用できます。

(TABLE foo LIMIT 0)

主な制限:Postgresは、独立VALUESした式の入力リテラルをすぐに「ベストエフォート」タイプにキャストします。後で最初のタイプの指定されたタイプにキャストしようとしたときにSELECT、想定されるタイプとターゲットタイプの間にキャストされた割り当てが登録されていない場合、一部のタイプにはすでに遅すぎる可能性があります。例:text->timestampまたはtext-> json

プロ:

  • 最小オーバーヘッド。
  • 読みやすく、シンプルで高速です。
  • テーブルの関連する列名を知っているだけで済みます。

短所:

  • 一部のタイプでは、タイプ解決が失敗する可能性があります。

1.で行を選択しLIMIT 0、で行を追加しますUNION ALL SELECT

UPDATE foo f
SET    x = t.x
     , y = t.y
FROM  (
  (SELECT pkid, x, y FROM foo LIMIT 0) -- parenthesis needed with LIMIT
   UNION ALL SELECT 1, 20, NULL
   UNION ALL SELECT 2, 50, NULL
   ) t               -- column names and types are already defined
WHERE  f.pkid = t.pkid;

プロ:

  • 0と同様ですが、タイプ解決の失敗を回避します。

短所:

  • UNION ALL SELECTVALUESテストで見つかったように、行の長いリストの式よりも低速です。
  • 行ごとの詳細な構文。

2.VALUES列ごとのタイプの式

...
FROM  (
   VALUES 
     ((SELECT pkid FROM foo LIMIT 0)
    , (SELECT x    FROM foo LIMIT 0)
    , (SELECT y    FROM foo LIMIT 0))  -- get type for each col individually
   , (1, 20, NULL)
   , (2, 50, NULL)
   ) t (pkid, x, y)  -- columns names not defined yet, only types.
...

0とは異なり、これにより、型の早期解決が回避されます。

VALUES式の最初の行は、NULL後続のすべての行のタイプを定義する値の行です。この主要なノイズ行はWHERE f.pkid = t.pkid後でフィルタリングされるため、日の目を見ることはありません。他の目的でOFFSET 1は、サブクエリで追加された最初の行を削除できます。

プロ:

  • 通常、1よりも高速です(または0よりも高速です。 )
  • 多くの列があり、少数の列しかないテーブルの短い構文が関係します。
  • テーブルの関連する列名を知っているだけで済みます。

短所:

  • 数行のみの冗長構文
  • 読みにくい(IMO)。

3.VALUES行タイプの式

UPDATE foo f
SET x = (t.r).x         -- parenthesis needed to make syntax unambiguous
  , y = (t.r).y
FROM (
   VALUES
      ('(1,20,)'::foo)  -- columns need to be in default order of table
     ,('(2,50,)')       -- nothing after the last comma for NULL
   ) t (r)              -- column name for row type
WHERE  f.pkid = (t.r).pkid;

あなたは明らかにテーブル名を知っています。列の数とその順序もわかっている場合は、これを使用できます。

PostgreSQLのすべてのテーブルについて、行タイプが自動的に登録されます。式の列数と一致する場合は、テーブルの行タイプ('(1,50,)'::foo)にキャストして、列タイプを暗黙的に割り当てることができます。NULL値を入力するには、コンマの後ろに何も入れないでください。無関係な末尾の列ごとにコンマを追加します。
次のステップでは、デモンストレーションされた構文で個々の列にアクセスできます。マニュアルのフィールド選択の詳細。

または、NULL値の行を追加して、実際のデータに統一構文を使用することもできます。

...
  VALUES
      ((NULL::foo))  -- row of NULL values
    , ('(1,20,)')    -- uniform ROW value syntax for all
    , ('(2,50,)')
...

プロ:

  • 最速(少なくとも行と列が少ない私のテストでは)。
  • すべての列が必要ないくつかの行またはテーブルの最短構文。
  • テーブルの列を詳しく説明する必要はありません。すべての列に自動的に一致する名前が付けられます。

短所:

  • レコード/行/複合型からのフィールド選択の構文はあまり知られていません。
  • デフォルトの順序で関連する列の数と位置を知る必要があります。

4.分解された行タイプのVALUES

3と同様ですが、標準構文で行が分解されています。

UPDATE foo f
SET    x = t.x
     , y = t.y
FROM (
   VALUES
      (('(1,20,)'::foo).*)  -- decomposed row of values
    , (2, 50, NULL)
   ) t(pkid, x, y)  -- arbitrary column names (I made them match)
WHERE  f.pkid = t.pkid;     -- eliminates 1st row with NULL values

または、NULL値の先頭行を再度使用します。

...
   VALUES
      ((NULL::foo).*)  -- row of NULL values
    , (1, 20, NULL)    -- uniform syntax for all
    , (2, 50, NULL)
...

長所と短所は3に似ていますが、より一般的に知られている構文を使用します。
また、列名を詳しく説明する必要があります(必要な場合)。

5.VALUES行タイプからフェッチされたタイプの式

Unrilがコメントしたように、 2。4.の長所を組み合わせて、列のサブセットのみを提供できます。

UPDATE foo f
SET   (  x,   y)
    = (t.x, t.y)  -- short notation, see below
FROM (
   VALUES
      ((NULL::foo).pkid, (NULL::foo).x, (NULL::foo).y)  -- subset of columns
    , (1, 20, NULL)
    , (2, 50, NULL)
   ) t(pkid, x, y)       -- arbitrary column names (I made them match)
WHERE  f.pkid = t.pkid;

4のような長所と短所がありますが、列の任意のサブセットを処理でき、完全なリストを知る必要はありません。

UPDATEまた、列が多い場合に便利な、それ自体の短い構文も表示します。関連している:

4.と5.が私のお気に入りです。

db<>ここでフィドル-すべてを示します

于 2012-09-14T15:19:53.547 に答える
2

クエリを生成するスクリプトがある場合は、各列のデータ型を抽出してキャッシュし、それに応じて型キャストを作成できます。例えば:

SELECT column_name,data_type,udt_name 
FROM information_schema.columns 
WHERE table_name = 'foo';

最後の段落で説明したように、この udt_name から必要なキャストを取得します。さらに、これを行うことができます:

UPDATE foo
SET x = t.x
FROM (VALUES(null::int4,756),(null::int4,6300))
AS t(x,pkid)
WHERE foo.pkid = t.pkid;
于 2012-09-14T14:53:08.737 に答える
0

スクリプトは、foo から一時テーブルを作成します。foo と同じデータ型になります。空になるように不可能な条件を使用します。

select x, y, pkid
into temp t
from foo
where pkid = -1

それに挿入するスクリプトを作成します。

insert into t (x, y, pkid) values
(null, 20, 1),
(null, 50, 2)

それから更新します:

update foo 
set x=t.x, y=t.y 
from t
where foo.pkid=t.pkid

最後にドロップします:

drop table t
于 2012-09-14T15:14:10.337 に答える