当我尝试同时更新多行时遇到问题。
以下是我使用的表格和查询(为了更好的阅读而简化):
表格
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的查询时,出现错误:
使用空值查询
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)
中的至少一个值更改为(null:int, 50, 2)
,但我不能这样做,因为我有一个生成这些“更新多行”的函数查询,它对列类型一无所知。
这里最好的解决方案是什么?是否有更好的多行更新查询?是否有任何函数或语法,如AS t(x:gettype(foo.x), y:gettype(foo.y), pkid:gettype(foo.pkid))
?
答案 0 :(得分:10)
使用独立的VALUES
表达式PostgreSQL不知道数据类型应该是什么。使用简单的数字文字,系统很乐意假设匹配类型。但是对于其他输入(如NULL
),您需要明确地进行投射 - 正如您已经发现的那样。
您可以查询pg_catalog
(快速但PostgreSQL特定的)或information_schema
(慢速但标准的SQL)来查找并准备适当类型的语句。
或者你可以使用其中一个简单的“技巧”(我为最后保存了最好的):
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
子查询的第一个子选择:
(SELECT x, y, pkid FROM foo LIMIT 0)
获取列的名称和类型,但LIMIT 0
阻止它添加实际行。后续行被强制转换为现在明确定义的行类型 - 如果它们与类型匹配,则立即检查。应该比原始形式有一个微妙的额外改进。
主要限制:使用单独的SELECT
行,Postgres会立即将输入文字转换为“尽力而为”类型。当它稍后尝试转换为第一个SELECT
的给定类型时,如果在假定类型和目标类型之间没有注册的赋值转换,则某些类型可能已经太晚了。示例text
- > timestamp
。
临:
- 最低开销
- 可读,简单,快速,几行
- 您只需要知道表的相关列名。
缺点:
- 某些类型的类型解析可能会失败。
- 正如您在测试中找到的那样,UNION ALL SELECT
对于长行列表的VALUES
表达式要慢一些
- 每行详细语法。
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.
...
VALUES
表达式中的第一行是一行NULL
值,用于定义所有后续行的类型。
临:
- 比 1快。
- 具有多列且只有少数几个相关的表的最短语法
- 您只需要知道表的相关列名。
缺点:
- 只有几行的详细语法
- 不太可读(IMO)。
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
值。为每个不相关的尾随列添加逗号
在下一步中,您可以使用演示的语法访问各个列。有关Field Selection in the manual的更多信息。
或者您可以 添加 一行NULL值,并对实际数据使用统一语法:
...
VALUES
((NULL::foo)) -- row of NULL values
, ('(1,20,)') -- uniform ROW value syntax for all
, ('(2,50,)')
...
添加的行被WHERE
中的UPDATE
子句排除
出于其他目的,您可以在子查询中删除添加了OFFSET 1
的第一行。
临:
- 最快(至少在我的测试中有少量行和列)
- 需要所有列的少数行或表的最短语法。
- 您不必拼写表格的列 - 所有列都自动具有匹配的名称。
缺点:
- 从记录/行/复合类型中选择字段的语法不是很清楚
- 您需要按默认顺序了解相关列的数量和位置。
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;
或者,再次使用前导行的NULL值:
...
VALUES
((NULL::foo).*) -- row of NULL values
, (1, 20, NULL) -- uniform syntax for all
, (2, 50, NULL)
...
优点和缺点,例如 3。 ,但使用更常见的语法。
你需要拼出列名(如果你需要的话)。
VALUES
表达式类型
与Unril commented一样,我们可以将 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.是我的最爱。
答案 1 :(得分:2)
如果您有一个生成查询的脚本,您可以提取并缓存每列的数据类型,并相应地创建类型转换。 E.g:
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;
答案 2 :(得分: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