使用 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
已经存在于 foo
,SELECT
其关联的 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
旁白:触发器几乎肯定也是可能的。不过不一定是更好的选择。
我正在制作一个 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
已经存在于 foo
,SELECT
其关联的 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
旁白:触发器几乎肯定也是可能的。不过不一定是更好的选择。