使用 DEFAULT 或 INTEGER 值作为 SQL 函数的参数

Use DEFAULT or INTEGER value as a SQL function's argument

我正在制作一个 SQL 函数来执行智能插入并管理多对多 table(触发器无法工作,因为一个值是可变的)。

这是示例的最小集合:

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 关键字。我应该如何更改函数以允许这种不同的论点?使用文本类型?

在 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 类型,您应该考虑一些 ,您应该小心。

首先,始终为持久的 INSERT 语句提供 显式目标列 。如果你把它放在一个函数中,然后更改 table 的列,你的函数要么中断,要么 更糟的是 不会中断并且做意想不到的事情。

接下来,您 不应在 serial 列中输入值。这会导致键冲突。您需要使基础 SEQUENCE 恢复与 serial 列中当前最高值的同步,这对于并发写入负载来说可能很棘手。只是不要这样做。

  • Manually update the counter used for generation of primary key in Hibernate?

我怀疑你确实有 SELECT-or-INSERT 的情况。意思是,如果给定的 name 已经存在于 fooSELECT 其关联的 foo_id,否则 INSERT 一个新行并使用动态分配的新 foo_id.

问题似乎与函数参数的默认值无关,而是 SQL INSERT 语句中的关键字 DEFAULT(完全不同!)而你 可以使用一个函数,你不需要一个函数。

没有并发写入负载

您可以使用数据修改 CTE(有或没有功能):

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;

更多详情:

  • PostgreSQL multi INSERT...RETURNING with multiple columns

或者如果你真的有时必须强制手动 foo_id ... 此变体包装到 SQL 函数中:

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 并插入到行中。

并发写入负载

Postgres 9.4

您可以使用此相关答案中的函数来处理并发问题(并提供更多解释):

  • Is SELECT or INSERT in a function prone to race conditions?

INSERT INTO link_foo_to_bar(foo_id, bar_id)
VALUES (f_foo_id('given_name'), 7);

Postgres 9.5

仍在开发中,但几天前新的 UPSERT feature has been committed(耶!)将在下一个版本中发布。但请不要屏住呼吸,发布时间定于 2015 年底。

WITH val(name, bar_id) AS (SELECT 'given_name'::varchar, 7)
     -- type cast needed for varchar, but numeric constant typed automatically
   , ins AS (INSERT INTO foo(name)
             SELECT name FROM val
             ON CONFLICT (name) DO NOTHING  -- conflicting row is locked
             RETURNING foo_id)
INSERT INTO link_foo_to_bar(foo_id, bar_id)
SELECT foo_id, bar_id FROM ins, val;
UNION  ALL
SELECT f.foo_id, v.bar_id FROM foo f JOIN val v USING (name)
LIMIT  1;

开发手册中的详细信息:
http://www.postgresql.org/docs/devel/static/sql-insert.html

旁白:触发器几乎肯定也是可能的。不过不一定是更好的选择。