对于任何不返回行的SQL命令,例如
INSERT
如果没有RETURNING
子句,则可以在a中执行命令 PL / pgSQL函数只需编写命令即可。命令文本中出现的任何PL / pgSQL变量名称都被视为 一个参数,然后提供变量的当前值 运行时的参数值。
但是当我在查询中使用变量名时,我收到错误:
ERROR: syntax error at or near "email" LINE 16: ...d,email,password) values(identity_id,current_ts,''email'',''...
这是我的功能:
CREATE OR REPLACE FUNCTION app.create_identity(email varchar,passwd varchar)
RETURNS integer as $$
DECLARE
current_ts integer;
new_identity_id integer;
int_max integer;
int_min integer;
BEGIN
SELECT extract(epoch FROM now())::integer INTO current_ts;
int_min:=-2147483648;
int_max:= 2147483647;
LOOP
BEGIN
SELECT floor(int_min + (int_max - int_min + 1) * random()) INTO new_identity_id;
IF new_identity_id != 0 THEN
INSERT into app.identity(identity_id,date_inserted,email,password) values(identity_id,current_ts,''email'',''passwd'');
RETURN new_identity_id;
END IF;
EXCEPTION
WHEN unique_violation THEN
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
为什么当我在查询中使用变量时,Postgres会抛出错误。这应该是怎么写的?
答案 0 :(得分:3)
您不能将参数名称放在单引号(''email''
中,也不能使用参数email
"因为它与#34相同;因为它的名称与表格中的一列。此名称冲突是强烈建议不使用与其中一个表中的列名称相同的变量或参数的原因之一。您有三个选择处理这个:
重命名变量。常见的命名约定是使用p_
为参数添加前缀,例如p_email
,然后使用insert
INSERT into app.identity(identity_id,date_inserted,email,password)
values(identity_id,current_ts,p_email,p_password);
第一个参数使用$1
,第二个参数使用$2
:
INSERT into app.identity(identity_id,date_inserted,email,password)
values(identity_id,current_ts,$1,$2);
使用函数名称前缀参数名称:
INSERT into app.identity(identity_id,date_inserted,email,password)
values(identity_id,current_ts,create_identity.email,create_identity.password);
我强烈建议选择选项1
不相关,但是:如果您不从表中检索这些值,则不需要SELECT语句来分配变量值。
SELECT extract(epoch FROM now())::integer INTO current_ts;
可以简化为:
current_ts := extract(epoch FROM now())::integer;
和
SELECT floor(int_min + (int_max - int_min + 1) * random()) INTO new_identity_id;
到
new_identity_id := floor(int_min + (int_max - int_min + 1) * random());
答案 1 :(得分:1)
@a_horse answers您的实际问题,并澄清引用问题和命名冲突。
关于引用:
关于命名冲突(plpgsql的行为随时间略有变化):
我建议采用一种完全不同的方法:
CREATE OR REPLACE FUNCTION app.create_identity(_email text, _passwd text
, OUT new_identity_id int) AS
$func$
DECLARE
_current_ts int := extract(epoch FROM now());
BEGIN
LOOP
--+ Generate compeltely random int4 numbers +-----------------------------
-- integer (= int4) in Postgres is a signed integer occupying 4 bytes --
-- int4 ranges from -2147483648 to +2147483647, i.e. -2^31 to 2^31 - 1 --
-- Multiply bigint 4294967296 (= 2^32) with random() (0.0 <= x < 1.0) --
-- trunc() the resulting (positive!) float8 - cheaper than floor() --
-- add result to -2147483648 and cast the next result back to int4 --
-- The result fits the int4 range *exactly* --
--------------------------------------------------------------------------
INSERT INTO app.identity
(identity_id, date_inserted, email , password)
SELECT _random_int, _current_ts , _email , _passwd
FROM (SELECT (bigint '-2147483648' -- could be int, but sum is bigint anyway
+ bigint '4294967296' * random())::int) AS t(_random_int) -- random int
WHERE _random_int <> 0 -- exclude 0 (no insert)
ON CONFLICT (identity_id) DO NOTHING -- no exception raised!
RETURNING identity_id -- return *actually* inserted identity_id
INTO new_identity_id; -- OUT parameter, returned at end
EXIT WHEN FOUND; -- exit after success
-- maybe add counter and raise exception when exceeding n (100?) iterations
END LOOP;
END
$func$ LANGUAGE plpgsql;
您的随机整数计算会导致integer out of range
错误,因为中间词int_max - int_min + 1
与integer
一起运行,但结果赢了&# 39;适合。
我建议使用上面更便宜的正确的算法。
输入带有例外条款的块比没有条件要贵得多。幸运的是,您实际上并不需要提出异常。使用UPSERT (INSERT ... ON CONFLICT ... DO NOTHING
),便宜又优雅地解决这个问题(Postgres 9.5 + )。
The manual:
提示:包含
EXCEPTION
子句的块明显更多 进入和退出比没有一个的块贵。因此,不要 1}}无需使用。
您也不需要额外的EXCEPTION
构造。将IF
与SELECT
一起使用。
使WHERE
new_identity_id
参数简化。
使用OUT
子句直接将结果 RETURNING
插入identity_id
参数。除了更简单的代码和更快的执行之外,还有一个额外的微妙好处:您可以获得实际插入的值。如果表格中有触发器或规则,则可能与您使用OUT
发送的内容不同。
PL / pgSQL中的分配相对昂贵。为了有效的代码,将这些减少到最低限度
您也可以删除最后剩余的变量INSERT
,并在子查询中进行计算,然后根本不需要_current_ts
。我离开了那个,因为计算它一次可能是有意义的,如果函数循环多次......
剩下的就是一个 SQL命令,包含在DECLARE
中重试直到成功。
如果您的表格有可能溢出(使用全部或大多数LOOP
个数字) - 严格来说,总是 机会 - 我会添加一个计数器并在可能100次迭代后引发异常以避免无限循环。