普通插入
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
使用LEFT [OUTER] JOIN
而不是[INNER] JOIN
表示在中找不到匹配项时val
,不会删除 from 的行foo
。而是NULL
输入foo_id
。
该VALUES
子查询表达式不一样@ ypercube的 CTE。公用表表达式提供了附加功能,在大型查询中更易于阅读,但它们也构成了优化障碍。因此,当不需要上述任何一项时,子查询通常会更快一些。
id
因为列名是广泛使用的反模式。应该是foo_id
and bar_id
或任何描述性内容。当加入一堆表时,最终会得到多个列,所有列都命名为id
...
考虑使用普通text
或varchar
代替varchar(n)
。如果确实需要施加长度限制,请添加一个CHECK
约束:
您可能需要添加显式类型转换。由于VALUES
表达式未直接附加到表(如中的INSERT ... VALUES ...
),因此无法派生类型,并且在没有显式类型声明的情况下使用默认数据类型,这在所有情况下可能都不起作用。在第一行就足够了,其余的将排成一行。
同时插入缺少的FK行
如果要动态创建不存在的条目foo
,请在单个SQL语句中使用CTE:
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
注意要插入的两个新虚拟行。两者均为紫色,尚不存在foo
。两行说明了需要DISTINCT
在第一个INSERT
声明。
分步说明
第一个CTE sel
提供多行输入数据。val
带有VALUES
表达式的子查询可以替换为表或子查询作为源。马上LEFT JOIN
要foo
到追加foo_id
的预先存在的type
行。所有其他行都采用foo_id IS NULL
这种方式。
第2个CTE ins
将不同的新类型(foo_id IS NULL
)插入foo
,并返回新生成的foo_id
-和一起type
返回以插入行。
现在,最终的外部对象INSERT
可以为每行插入一个foo.id:预先存在的类型,或者它是在步骤2中插入的。
严格地说,这两个刀片发生“并联”,但由于这是一个单独的声明中,默认FOREIGN KEY
的限制不会抱怨。默认情况下,引用完整性在语句的末尾强制执行。
适用于Postgres 9.3的SQL Fiddle。(与9.1相同。)
如果您同时运行多个这些查询,则竞争条件很小。在此处,此处和此处阅读有关问题的更多信息。确实只有在繁重的并发负载下才会发生。与像在另一个答案中宣传的那样缓存解决方案相比,机会微不足道。
重复使用功能
为了重复使用,我将创建一个SQL函数,该函数将记录数组作为参数并unnest(param)
代替VALUES
表达式使用。
或者,如果记录数组的语法对您来说太麻烦了,请使用逗号分隔的字符串作为parameter _param
。例如形式:
'description1,type1;description2,type2;description3,type3'
然后使用它替换VALUES
上面的语句中的表达式:
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
在Postgres 9.5中具有UPSERT的功能
创建用于参数传递的自定义行类型。我们可以不用它,但是更简单:
CREATE TYPE foobar AS (description text, type text);
功能:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
呼叫:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
快速且坚如磐石,适用于具有并发事务的环境。
除了上面的查询之外,这...
...适用SELECT
或INSERT
上foo
:任何type
不中FK表中存在,但是,插入。假设大多数类型已经存在。为了绝对确定并排除竞争条件,我们需要的现有行被锁定(以便并发事务不会干扰)。如果这对于您的情况来说太偏执了,您可以替换:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
与
ON CONFLICT(type) DO NOTHING
...适用于INSERT
或UPDATE
(true“ UPSERT”)应用于bar
:如果description
已经存在,type
则更新:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
但仅当type
实际更改时:
...通过VARIADIC
参数传递值以及众所周知的行类型。注意默认的最大100个参数!相比:
还有许多其他方法可以传递多行...
有关: