2016-08-23 98 views
2

我想編寫一個執行select查詢的plpgsql過程,更新行(如果存在),然後返回結果。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'; 

但兩者實際上嘗試失敗的事實是,我真的不能夠返回正確的結果集。

什麼是最好的方法來解決我的問題?

然後,這兩種嘗試中的哪一種是更好的(或者是更好的第三種解決方案)?

回答

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實際上並不從函數返回,它只是將數據添加到結果集。

+0

謝謝你,你的回答真的幫了我很多。我實際上用'RETURN QUERY SELECT ...'解決了它,因爲我不需要函數中的記錄。我不知道RETURN不會結束程序。儘管如此:它是'RETURN QUERY ...',而不是'RETURN NEXT QUERY ...'。後者導致語法錯誤 – h345k34cr

+0

很高興幫助和感謝指出錯誤。答案已更正。 'RETURN'實際上完成了這個功能,它只是不返回任何數據,你需要'RETURN NEXT'或'RETURN QUERY'。 – Patrick