更新多行时转换NULL类型

Alb*_*ras 8 sql postgresql types casting sql-update

当我尝试同时更新多行时遇到问题.

这是我使用的表和查询(为了更好的阅读简化):

CREATE TABLE foo
(
    pkid integer,
    x integer,
    y integer
)
Run Code Online (Sandbox Code Playgroud)

询问

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
Run Code Online (Sandbox Code Playgroud)

此查询工作正常,但是当我尝试执行所有xy值为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
Run Code Online (Sandbox Code Playgroud)

错误

ERROR:  column "x" is of type integer but expression is of type text
LINE 1: UPDATE foo SET x=t.x FROM
Run Code Online (Sandbox Code Playgroud)

解决这个问题的唯一方法是将至少一个值更改(null, 20, 1)(null:int, 50, 2)但我不能这样做,因为我有一个生成这些"更新多行"查询的函数,并且它对列类型一无所知.

什么是最好的解决方案?是否有更好的多行更新查询?是否有任何功能或语法AS t(x:gettype(foo.x), y:gettype(foo.y), pkid:gettype(foo.pkid))

Erw*_*ter 11

使用独立VALUES表达式PostgreSQL不知道数据类型应该是什么.使用简单的数字文字,系统很乐意假设匹配类型.但是对于其他输入(比如NULL),您需要明确地进行投射 - 正如您已经发现的那样.

您可以查询pg_catalog(快,但PostgreSQL特有的)或information_schema(慢,但标准SQL),找出并相应品种准备发言.

或者你可以使用这些简单的"技巧"之一(我最后保存了最好的):

1.选择行,用LIMIT 0,追加行 UNION ALL

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
Run Code Online (Sandbox Code Playgroud)

子查询的第一个子选择:

(SELECT x, y, pkid  FROM foo LIMIT 0)
Run Code Online (Sandbox Code Playgroud)

获取列的名称和类型,但LIMIT 0阻止它添加实际行.后续行被强制转换为现在明确定义的行类型 - 如果它们与类型匹配,则立即检查.应该是对原始形式的微妙的额外改进.

主要限制:有独立的SELECT线路,Postgres的铸输入文字为"尽力而为"立即类型.当稍后尝试投给定类型第一SELECT,它可能已经成为某些类型的太晚了,如果有假定类型和目标类型之间没有注册的赋值转换.示例text- > timestamp.

亲:
- 最小开销.
- 可读,简单,快速,几行.
- 您只需要知道表的相关列名.

Con:
- 某些类型的类型解析可能会失败.
- 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.
...
Run Code Online (Sandbox Code Playgroud)

VALUES表达式中的第一行是一行NULL值,用于定义所有后续行的类型.

Pro:
- 比1.
- 具有多列且只有少数几个相关的表的最短语法.
- 您只需要知道表的相关列名.

Con:
- 只有几行的详细语法
- 可读性较低(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
Run Code Online (Sandbox Code Playgroud)

你显然知道表名.如果您也知道列数及其顺序,则可以使用它.

对于PostgreSQL中的每个表,都会自动注册行类型.如果您在表达式匹配的列数,你可以投行类型的表(中'(1,50,)'::foo),从而赋予列类型含蓄.不要在逗号后面输入NULL值.为每个不相关的尾随列添加逗号.
在下一步中,您可以使用演示的语法访问各个列.有关手册中字段选择的更多信息.

或者您可以添加一行NULL值并对实际数据使用统一语法:

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

添加的行被WHERE您的子句排除在外UPDATE.
出于其他目的,您可以OFFSET 1在子查询中消除添加的第一行.

亲:
- 最快(至少在我的测试中有少量行和列).
- 需要所有列的少数行或表的最短语法. - 您不必拼写表格的列 - 所有列都自动具有匹配的名称.

Con:
- 从记录/行/复合类型中选择字段的语法不是很清楚.
- 您需要按默认顺序了解相关列的数量和位置.

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;
Run Code Online (Sandbox Code Playgroud)

或者,再次使用前导行的NULL值:

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

优点和缺点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;
Run Code Online (Sandbox Code Playgroud)

优点和缺点4.,但我们可以使用任何列的子集,而不必知道完整列表.

同时显示UPDATE自身的短语法,方便多列的情况.有关:

4.和5.是我的最爱.