2016-08-23 2 views
2

Я хотел бы написать процедуру 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' фактически завершает функцию, она просто не возвращает никаких данных, для этого вам нужны« ВОЗВРАТ СЛЕДУЮЩИЙ »или« ВОЗВРАТ ЗАПРОС ». – Patrick