使用独立VALUES
表达式 PostgreSQL 不知道数据类型应该是什么。使用简单的数字文字,系统很乐意假设匹配类型。但是对于其他输入(如NULL
),您需要显式转换 - 正如您已经发现的那样。
您可以查询pg_catalog
(快速,但特定于 PostgreSQL)或information_schema
(慢,但标准 SQL)来找出并准备具有适当类型的语句。
或者你可以使用这些简单的“技巧”之一(我把最好的留到最后):
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)
主要限制:PostgresVALUES
立即将独立表达式的输入文字转换为“尽力而为”类型。当它稍后尝试转换为 first 的给定类型时,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;
临:
缺点:
UNION ALL SELECT
VALUES
正如您在测试中发现的那样,它比长行列表的表达式慢。
- 每行的详细语法。
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.)快
- 具有许多列且只有少数列相关的表的简短语法。
- 您只需要知道表的相关列名。
缺点:
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<>fiddle here - 演示所有