我想编写一个执行select查询的plpgsql过程,更新行(如果存在)然后返回结果。
为此,我希望能够像使用SELECT nickname FROM use_session_token(...)
的普通查询一样处理过程调用。
通常我会使用RETURN QUERY(...)
但我想首先更新行(它是一个或没有,因为token
是主索引)
但实际上我希望只有在满足其他条件的情况下才返回并更新行,所以我不能只对主键本身进行操作。
我有两次尝试,一次使用Refcursor
,另一次使用SELECT INTO
,但我实际上未能返回SETOF users
。
我尝试SELECT INTO
:
CREATE OR REPLACE FUNCTION use_session_token(Char(128), Inet) RETURNS SETOF users AS $$
DECLARE
row Record;
BEGIN
SELECT u.* INTO row FROM sessions AS t
INNER JOIN users AS u ON (t.user_id = u.id)
WHERE
t.token=$1 AND
t.date_last_used > NOW() - interval '30 minutes' AND
t.ip_address=$2 AND
u.is_deleted=FALSE AND
EXISTS(
SELECT 1 FROM mail AS m
WHERE m.user_id=u.id AND m.is_confirmed=TRUE AND m.is_deleted=FALSE
)
;
IF (row) THEN
UPDATE sessions SET date_last_used=NOW() WHERE token=$1;
ELSE
-- maybe do other things if there is no result
END IF;
RETURN row;
END;
$$ LANGUAGE 'plpgsql';
我尝试cursor
:
CREATE OR REPLACE FUNCTION use_session_token(Char(128), Inet) RETURNS SETOF users AS $$
DECLARE
cursor Refcursor;
row Record;
BEGIN
OPEN cursor SCROLL FOR (
SELECT u.* INTO row FROM sessions AS t
INNER JOIN users AS u ON (t.user_id = u.id)
WHERE
t.token=$1 AND
t.date_last_used > NOW() - interval '30 minutes' AND
t.ip_address=$2 AND
u.is_deleted=FALSE AND
EXISTS(
SELECT 1 FROM mail AS m
WHERE m.user_id=u.id AND m.is_confirmed=TRUE AND m.is_deleted=FALSE
)
);
FETCH cursor INTO row;
IF (FOUND) THEN
MOVE PRIOR cursor;
UPDATE sessions SET date_last_used=NOW() WHERE CURRENT OF cursor;
ELSE
-- maybe do other things if there is no result
END IF;
RETURN row;
END;
$$ LANGUAGE 'plpgsql';
但两次尝试实际上都失败了,因为我实际上无法返回正确的结果集。
实现这一目标并解决问题的最佳方法是什么?
然后,两次尝试中的哪一种更好(或者更好的是第三种解决方案)?
答案 0 :(得分:2)
您的第一次尝试是最好的(游标往往很慢),但您应该使用RETURN NEXT
从函数返回任何行。通过其他一些改进,您可以得到:
CREATE OR REPLACE FUNCTION use_session_token(Char(128), Inet) RETURNS SETOF users AS $$
DECLARE
rec users%rowtype; -- don't use reserved word as variable name, use explicit type
BEGIN
SELECT u.* INTO rec FROM sessions AS t
JOIN users AS u ON t.user_id = u.id
WHERE t.token=$1
AND t.date_last_used > now() - interval '30 minutes'
AND t.ip_address=$2
AND NOT u.is_deleted
AND EXISTS (
SELECT 1 FROM mail AS m
WHERE m.user_id=u.id AND m.is_confirmed AND NOT m.is_deleted;
IF FOUND THEN -- use built-in parameter to test for result of query
UPDATE sessions SET date_last_used = now() WHERE token=$1;
ELSE
-- maybe do other things if there is no result
END IF;
RETURN NEXT rec;
END;
$$ LANGUAGE 'plpgsql';
如果您返回所选行而不考虑SELECT
查询(IF FOUND THEN ...
部分)后发生的情况,那么您甚至可以忘记rec
变量并将第一个语句写为:
RETURN QUERY SELECT u.* ...
请注意,RETURN QUERY
实际上并不从函数返回,它只是将数据添加到结果集中。