我正在创建一个SQL函数来执行智能插入并管理多对多的表(触发器不起作用,因为一个值是可变的)。
以下是示例的最小集:
link_foo_to_bar (foo_id int, bar_id int)
foo (id serial, name character varying)
这是我的sql函数:
CREATE FUNCTION smart_insert (bar_id integer, foo_id integer, name character varying name)
RETURNS void AS $body$
INSERT INTO foo VALUES (foo_id, name);
INSERT INTO link_foo_to_bar (CURRVAL('foo_id_seq'), bar_id);
$body$ LANGUAGE sql;
现在问题是foo_id
是serial
,因此它可以获得DEFAULT
或INTEGER
作为传入值。该函数不接受DEFAULT
关键字。我应该如何更改函数以允许这种变化的参数?使用文字类型?
答案 0 :(得分:3)
以下是在PostgreSQL中指定DEFAULT
函数参数的方法:
CREATE FUNCTION smart_insert (
name character varying,
bar_id integer,
foo_id integer = -1
)
RETURNS void AS $body$
INSERT INTO foo VALUES (
CASE foo_id WHEN -1 THEN NEXTVAL('foo_id_seq') ELSE foo_id END,
name
);
INSERT INTO link_foo_to_bar (
CASE foo_id WHEN -1 THEN CURRVAL('foo_id_seq') ELSE foo_id END,
bar_id
);
$body$ LANGUAGE sql;
以下是您可以调用的方式:
-- with an explicit foo_id:
smart_insert('abc', 1, 2);
-- applying the default value:
smart_insert('abc', 1);
...但是,关于插入serial
类型,你应该考虑一些useful feedback from Erwin,你应该小心。
答案 1 :(得分:3)
首先,始终为持久性INSERT
语句提供显式目标列。如果你把它放在一个函数中,然后更改表的列,你的函数就会中断,或者 更糟 不会中断并做出意想不到的事情。
接下来,您不应该在serial
列中输入值。这导致了关键的冲突。您需要使基础SEQUENCE
与serial
列中当前最高的值同步,这对于并发写入加载可能很棘手。不要这样做。
我怀疑你确实有 SELECT
- 或 - INSERT
的案例。这意味着,如果给定name
已存在于foo
,SELECT
已关联foo_id
,则INSERT
为新行,并使用动态分配的新{{1} }}
问题似乎与函数参数的默认值无关,就像SQL foo_id
语句中的关键字DEFAULT
一样(完全不同的东西!)当你可以使用时一个功能,你不需要一个功能。
您可以使用数据修改CTE(带或不带功能):
INSERT
更多详情:
或者,如果你真的必须强行手册WITH val(name, bar_id) AS (SELECT 'given_name'::varchar, 7)
-- type cast needed for varchar, but numeric constant typed automatically
, sel AS (SELECT f.foo_id, v.bar_id FROM foo f JOIN val v USING (name)
, ins AS (INSERT INTO foo(name)
SELECT name FROM val
WHERE NOT EXISTS (SELECT 1 FROM sel) -- only if not found
RETURNING foo_id)
INSERT INTO link_foo_to_bar(foo_id, bar_id)
SELECT foo_id, bar_id FROM sel
UNION ALL
SELECT foo_id, bar_id FROM ins, val;
......
此变体包含在SQL函数中:
foo_id
致电... like Lukas instructed.
关键元素是CREATE FUNCTION smart_insert(
_name varchar
, _bar_id integer
, _foo_id integer = NULL) -- no default necessary, but NULL for convenience
RETURNS void AS
$func$
WITH sel AS (SELECT f.foo_id -- existing foo_id overrules
FROM foo f WHERE f.name = _name)
, ins AS (INSERT INTO foo(foo_id, name)
SELECT COALESCE(_foo_id, nextval('foo_id_seq')), _name
WHERE NOT EXISTS (SELECT 1 FROM sel) -- only if not found
RETURNING foo_id)
INSERT INTO link_foo_to_bar(foo_id, bar_id)
SELECT foo_id, _bar_id FROM sel
UNION ALL
SELECT foo_id, _bar_id FROM ins;
$func$ LANGUAGE sql;
:如果你传递COALESCE(_foo_id, nextval('foo_id_seq')
它会被使用,否则默认是从序列中提取的。无论您选择什么,它都会随_foo_id
一起返回并插入该行。
您可以使用此相关答案中的函数来处理并发问题(并提供更多解释):
RETURNING foo_id
仍处于开发阶段,但几天前新的UPSERT
feature has been committed(耶!)并将在下一个版本中发布。但请不要屏住呼吸,计划在2015年底发布。
INSERT INTO link_foo_to_bar(foo_id, bar_id)
VALUES (f_foo_id('given_name'), 7);
devel手册中的详细信息:
http://www.postgresql.org/docs/devel/static/sql-insert.html
除此之外:几乎可以肯定一个触发器也是可能的。但不一定是更好的选择。