对于任何不返回行的 SQL 命令,例如
如果没有INSERT
子句,您可以在 只需编写命令即可实现 PL/pgSQL 功能。RETURNING
命令文本中出现的任何 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 会抛出错误。这应该怎么写?
您不能将参数名称放在单引号中 (
''email''
,并且您不能“按原样”使用参数 email
,因为它与表中的列具有相同的名称。此名称冲突是其中之一强烈建议不要使用与其中一个表中的列同名的变量或参数。您有三种选择来处理此问题:
重命名变量。常见的命名约定是在参数前面加上
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());
关于引用:
关于命名冲突:
我建议采用完全不同的方法:
CREATE OR REPLACE FUNCTION app.create_identity(_email text, _passwd text
, OUT new_identity_id int)
LANGUAGE plpgsql AS
$func$
/* + Generate completely 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*
*/
DECLARE
_current_ts int := extract(epoch FROM now());
BEGIN
LOOP
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 warning/exception when exceeding 5/10 (?) iterations
END LOOP;
END
$func$;
您的随机整数计算将导致
integer out of range
错误,因为中间项 int_max - int_min + 1
与 integer
一起运算,但结果不合适。以上更便宜且正确。
输入带有异常子句的块比没有异常子句要昂贵得多。幸运的是,您实际上不需要一开始就引发异常。使用 UPSERT (
INSERT ... ON CONFLICT ... DO NOTHING
),可以廉价而优雅地解决这个问题(Postgres 9.5+)。提示: 包含
子句的块明显更多 进入和退出比没有进入和退出的街区昂贵。因此,不要 无需使用EXCEPTION
。EXCEPTION
您也不需要额外的
IF
。将 SELECT
与 WHERE
一起使用。
将
new_identity_id
设为 OUT
参数以简化。
使用
RETURNING
子句并将 结果 identity_id
直接插入到 OUT
参数中。更简单、更快。还有一个额外的微妙好处:您可以获得“实际”插入的值。如果表中有触发器或规则,这可能与输入不同。
_current_ts
,并在子查询中进行计算,那么您根本不需要
DECLARE
。我留下了那个,因为计算它一次可能是有意义的,如果函数循环多次......
SQL 命令,包装到 LOOP
中以重试直至成功。
int4
数字) - 严格来说,存在
alwayschance - 我会添加一个计数器并在 10 次迭代后引发异常以避免无限循环。尝试一半后就已经发出警告了。