plpgsql在返回之前对查询进行操作

时间:2016-08-23 13:39:35

标签: sql postgresql plpgsql

我想编写一个执行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';

但两次尝试实际上都失败了,因为我实际上无法返回正确的结果集。

实现这一目标并解决问题的最佳方法是什么?

然后,两次尝试中的哪一种更好(或者更好的是第三种解决方案)?

1 个答案:

答案 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实际上并不从函数返回,它只是将数据添加到结果集中。

相关问题