Akt*_*tau 10 sql postgresql merge upsert sql-insert
我发现使用可写CTE模拟PostgreSQL中的upsert是一个非常优雅的解决方案,直到我们在Postgres中获得实际的upsert/merge.(见:https://stackoverflow.com/a/8702291/558819)
但是,有一个问题:如何插入默认值?NULL当然,使用将无助于NULL显式插入NULL,与MySQL不同.一个例子:
WITH new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
, (NULL::int, 21, 1, 'b', 34, 2, NULL::boolean)
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
), upsert AS (
UPDATE playlist_items m
SET (playlist, item, group_name, duration, sort, legacy)
= (nv.playlist, nv.item, nv.group_name, nv.duration, nv.sort, nv.legacy)
FROM new_values nv
WHERE nv.id = m.id
RETURNING m.id
)
INSERT INTO playlist_items (playlist, item, group_name, duration, sort, legacy)
SELECT playlist, item, group_name, duration, sort, legacy
FROM new_values nv
WHERE NOT EXISTS (SELECT 1
FROM upsert m
WHERE nv.id = m.id)
RETURNING id
Run Code Online (Sandbox Code Playgroud)
所以我希望例如legacy列可以采用第二VALUES行的默认值.
我已经尝试了一些东西,比如DEFAULT在VALUES列表中明确使用,这不起作用,因为CTE不知道它插入了什么.我也尝试coalesce(col, DEFAULT)过插入语句似乎无法工作.那么,有可能做我想要的吗?
Erw*_*ter 15
Postgres 9.5已实施UPSERT.见下文.
这是一个棘手的问题.您遇到此限制(每个文档):
在
VALUES出现在a 的顶层的列表中INSERT,表达式可以替换DEFAULT为表示应插入目标列的默认值.在其他情境中出现DEFAULT时无法使用VALUES.
大胆强调我的.如果没有要插入的表,则不会定义缺省值.因此,您的问题没有直接解决方案,但根据具体要求,有许多可能的替代路线.
您可以从@Patrick评论的系统目录中获取或从中获取.完整说明如下:pg_attrdef information_schema.columns
但是,您仍然只有一个行列表,其中包含表达式的文本表示以烹饪默认值.您必须动态构建和执行语句以获取要使用的值.单调乏味.相反,我们可以让内置的Postgres功能为我们做到这一点:
插入一个虚拟行并让它返回使用生成的默认值:
INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
Run Code Online (Sandbox Code Playgroud)
STABLE或IMMUTABLE默认表情.大多数VOLATILE功能都可以正常工作,但没有任何保证.该current_timestamp系列函数都是稳定的,因为它们的值不会在事务中改变.serial列(或从序列中绘制的任何其他默认值)产生副作用.但这应该不是问题,因为您通常不serial直接写入列.这些不应该在INSERT声明中列出.列的serial:序列仍然通过单次调用来提前获取默认行,从而在编号中产生间隙.同样,这应该不是问题,因为通常在serial列中预期存在差距.还有两个问题可以解决:
如果已定义列NOT NULL,则必须插入虚拟值并NULL在结果中替换.
我们实际上并不想插入虚拟行.我们可以稍后删除(在同一个事务中),但可能会有更多的副作用,比如触发器ON DELETE.有一个更好的方法:
克隆一个临时表,包括列缺省值,并插入到该:
BEGIN;
CREATE TEMP TABLE tmp_playlist_items (LIKE playlist_items INCLUDING DEFAULTS)
ON COMMIT DROP; -- drop at end of transaction
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *;
...
Run Code Online (Sandbox Code Playgroud)
结果相同,副作用更少.由于默认表达式是逐字复制的,因此克隆从相同的序列中提取(如果有的话).但完全避免了不需要的行或触发器的其他副作用.
感谢Igor的想法:
NOT NULL约束您必须为NOT NULL列提供虚拟值,因为(每个文档):
始终将非空约束复制到新表.
要么适应INSERT声明中的那些,要么(更好地)消除约束:
ALTER TABLE tmp_playlist_items
ALTER COLUMN foo DROP NOT NULL
, ALTER COLUMN bar DROP NOT NULL;
Run Code Online (Sandbox Code Playgroud)
有超级用户权限的快速和脏的方式:
UPDATE pg_attribute
SET attnotnull = FALSE
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0;
Run Code Online (Sandbox Code Playgroud)
它只是一个没有数据而没有其他目的的临时表,它在事务结束时被删除.所以快捷方式很诱人.不过,基本规则是:永远不要直接篡改系统目录.
所以,让我们看一个干净的方法:在DO语句中自动化动态SQL .您只需要保证您拥有的常规权限,因为相同的角色创建了临时表.
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$
Run Code Online (Sandbox Code Playgroud)
更清洁,仍然非常快.使用动态命令执行注意,并警惕SQL注入.这句话很安全.我已经发布了几个相关的答案和更多的解释.
BEGIN;
CREATE TEMP TABLE tmp_playlist_items
(LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$;
LOCK TABLE playlist_items IN EXCLUSIVE MODE; -- forbid concurrent writes
WITH default_row AS (
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
)
, new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
VALUES
(651, 21, 30012, 'a', 30, 1, FALSE)
, (NULL, 21, 1, 'b', 34, 2, NULL)
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
)
, upsert AS ( -- *not* replacing existing values in UPDATE (?)
UPDATE playlist_items m
SET ( playlist, item, group_name, duration, sort, legacy)
= (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
-- ..., COALESCE(n.legacy, m.legacy) -- see below
FROM new_values n
WHERE n.id = m.id
RETURNING m.id
)
INSERT INTO playlist_items
(playlist, item, group_name, duration, sort, legacy)
SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
, COALESCE(n.legacy, d.legacy)
FROM new_values n, default_row d -- single row can be cross-joined
WHERE NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
RETURNING id;
COMMIT;Run Code Online (Sandbox Code Playgroud)
LOCK如果您有并发事务尝试写入同一个表,则只需要.
根据请求,这仅替换案例legacy的输入行的列中的NULL值INSERT.可以很容易地扩展到其他列或在这种UPDATE情况下工作.例如,您也可以UPDATE有条件地:仅当输入值为时NOT NULL.我在UPDATE上面添加了注释行.
旁白:你并不需要投在任何行,但在第一个值VALUES表达式,因为类型从派生的第一排.
实现UPSERT用INSERT .. ON CONFLICT .. DO NOTHING | UPDATE.这大大简化了操作:
INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
, (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT) -- !
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
ON CONFLICT (id) DO UPDATE
SET (playlist, item, group_name, duration, sort, legacy)
= (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
, EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
-- (..., COALESCE(l.legacy, EXCLUDED.legacy)) -- see below
RETURNING m.id;
Run Code Online (Sandbox Code Playgroud)
我们可以直接附加VALUES子句INSERT,它允许DEFAULT关键字.在发生独特违规的情况下(id),Postgres会更新.我们可以使用排除的行UPDATE.手册:
在
SET和WHERE条款在ON CONFLICT DO UPDATE访问使用表的名称(或别名)现有行,并提出了使用特殊插入行excluded表.
和:
请注意,所有每行
BEFORE INSERT触发器的效果都会反映在排除值中,因为这些效果可能会导致排除插入行.
你有各种各样的选择UPDATE:你可以......
WHERE子句UPDATE只能写入选定的行.COALESCE(l.legacy, EXCLUDED.legacy)NOT NULL:COALESCE(EXCLUDED.legacy, l.legacy)但是没有办法辨别DEFAULT实际提供的价值观和价值观INSERT.只有结果EXCLUDED行可见.如果您需要区分,请回到之前的解决方案,您可以随意使用.
| 归档时间: |
|
| 查看次数: |
1787 次 |
| 最近记录: |