在 PL/pgSQL 函数中使用变量
Using variables in a PL/pgSQL function
For any SQL command that does not return rows, for example INSERT
without a RETURNING
clause, you can execute the command within a
PL/pgSQL function just by writing the command.
Any PL/pgSQL variable name appearing in the command text is treated as
a parameter, and then the current value of the variable is provided as
the parameter value at run time.
但是当我在查询中使用变量名时出现错误:
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
"as is" 因为它与table。这种名称冲突是强烈建议 不要 使用与 [=55= 之一中的列同名的变量或参数的原因之一]s. 你有三种选择来处理这个问题:
重命名变量。常见的命名约定是在参数前加上 p_
前缀,例如p_email
,然后在 insert
中使用明确的名称
INSERT into app.identity(identity_id,date_inserted,email,password)
values(identity_id,current_ts,p_email,p_password);
第一个参数使用</code>,第二个参数使用<code>
:
INSERT into app.identity(identity_id,date_inserted,email,password)
values(identity_id,current_ts,,);
在参数名前加上函数名:
INSERT into app.identity(identity_id,date_inserted,email,password)
values(identity_id,current_ts,create_identity.email,create_identity.password);
我强烈建议选择选项 1
无关,但是:如果您不从 table 中检索这些值,则不需要 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());
您的实际问题并澄清引用问题和命名冲突。
关于引用:
- Insert text with single quotes in PostgreSQL
关于命名冲突(plpgsql 的行为随时间略有变化):
- Set a default return value for a Postgres function
更好的解决方案
我建议开始使用完全不同的方法:
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
与 [=13= 运算],但结果不合适。
我建议使用上面更便宜的正确算法。
进入带有例外条款的区块比没有例外条款要昂贵得多。幸运的是,您实际上不需要一开始就引发异常。使用 UPSERT (INSERT ... ON CONFLICT ... DO NOTHING
),以廉价而优雅的方式解决这个问题(Postgres 9.5+)。
The manual:
Tip: A block containing an EXCEPTION
clause is significantly more
expensive to enter and exit than a block without one. Therefore, don't
use EXCEPTION
without need.
您也不需要额外的 IF
结构。使用 SELECT
和 WHERE
.
使 new_identity_id
成为 OUT
参数以简化。
使用RETURNING
子句并将结果identity_id
直接插入到OUT
参数中。除了更简单的代码和更快的执行之外,还有一个额外的、微妙的好处:您获得 实际 插入的值。如果 table 上有触发器或规则,这可能与您使用 INSERT
发送的内容不同。
PL/pgSQL 中的分配相对昂贵。将这些减少到最低限度以实现高效代码。
您也可以删除最后一个剩余的变量 _current_ts
,并在子查询中进行计算,那么您根本不需要 DECLARE
。我留下了那个,因为计算它 一次 可能有意义,如果函数循环多次...
剩下的就是一个SQL命令,包装成LOOP
重试直到成功。
如果您的 table 有可能溢出(使用全部或大部分 int4
数字)- 严格来说,总是 机会 - 我会添加一个计数器并在大约 100 次迭代后引发异常以避免无限循环。
For any SQL command that does not return rows, for example
INSERT
without aRETURNING
clause, you can execute the command within a PL/pgSQL function just by writing the command.Any PL/pgSQL variable name appearing in the command text is treated as a parameter, and then the current value of the variable is provided as the parameter value at run time.
但是当我在查询中使用变量名时出现错误:
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
"as is" 因为它与table。这种名称冲突是强烈建议 不要 使用与 [=55= 之一中的列同名的变量或参数的原因之一]s. 你有三种选择来处理这个问题:
重命名变量。常见的命名约定是在参数前加上
中使用明确的名称p_
前缀,例如p_email
,然后在insert
INSERT into app.identity(identity_id,date_inserted,email,password) values(identity_id,current_ts,p_email,p_password);
第一个参数使用
</code>,第二个参数使用<code>
:INSERT into app.identity(identity_id,date_inserted,email,password) values(identity_id,current_ts,,);
在参数名前加上函数名:
INSERT into app.identity(identity_id,date_inserted,email,password) values(identity_id,current_ts,create_identity.email,create_identity.password);
我强烈建议选择选项 1
无关,但是:如果您不从 table 中检索这些值,则不需要 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());
关于引用:
- Insert text with single quotes in PostgreSQL
关于命名冲突(plpgsql 的行为随时间略有变化):
- Set a default return value for a Postgres function
更好的解决方案
我建议开始使用完全不同的方法:
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
与 [=13= 运算],但结果不合适。 我建议使用上面更便宜的正确算法。进入带有例外条款的区块比没有例外条款要昂贵得多。幸运的是,您实际上不需要一开始就引发异常。使用 UPSERT (
INSERT ... ON CONFLICT ... DO NOTHING
),以廉价而优雅的方式解决这个问题(Postgres 9.5+)。
The manual:Tip: A block containing an
EXCEPTION
clause is significantly more expensive to enter and exit than a block without one. Therefore, don't useEXCEPTION
without need.您也不需要额外的
IF
结构。使用SELECT
和WHERE
.使
new_identity_id
成为OUT
参数以简化。使用
RETURNING
子句并将结果identity_id
直接插入到OUT
参数中。除了更简单的代码和更快的执行之外,还有一个额外的、微妙的好处:您获得 实际 插入的值。如果 table 上有触发器或规则,这可能与您使用INSERT
发送的内容不同。PL/pgSQL 中的分配相对昂贵。将这些减少到最低限度以实现高效代码。
您也可以删除最后一个剩余的变量_current_ts
,并在子查询中进行计算,那么您根本不需要DECLARE
。我留下了那个,因为计算它 一次 可能有意义,如果函数循环多次...剩下的就是一个SQL命令,包装成
LOOP
重试直到成功。如果您的 table 有可能溢出(使用全部或大部分
int4
数字)- 严格来说,总是 机会 - 我会添加一个计数器并在大约 100 次迭代后引发异常以避免无限循环。