Синтаксическая ошибка PostgreSQL "IF"

Я новичок в PostgreSQL, и у меня уже есть первая проблема.

Я написал некоторый код, чтобы понять, как работают транзакции, следуя инструкции вручную.

Чтобы сделать это коротко, я создал 2 таблицы, пользователя и движения: в первом столбце имя, адрес электронной почты и кредит, во втором столбцы from, to, import.

Итак, я пробовал вот так:

BEGIN;
INSERT INTO movements (from, to, import) VALUES ('mary', 'steve', 600);
UPDATE users SET credit = credit - 600 WHERE name = 'mary';
UPDATE users SET credit = credit + 600 WHERE name = 'steve';
--here comes the problem!
IF (SELECT credit FROM users WHERE name = 'mary') < 0 THEN
 ROLLBACK;
END IF
COMMIT;

Я всегда получаю ошибку:

ОШИБКА: синтаксическая ошибка в или рядом с "IF"

Где я ошибаюсь?

P.S.: Не сосредотачивайтесь на функциональности примера, это просто пробная версия для меня, чтобы понять транзакции.. и теперь предложение IF...

Ответ 1

Как уже говорит Йоханнес: вы смешиваете обычный SQL с PL/pgSQL, языком хранимой процедуры. Ссылка, которую предлагает Johannes, должна объяснить вам концепцию хранимых процедур.

Я так понимаю, вы делаете это как script? Выполнение одного заявления за другим? Боюсь, вы можете делать то, что хотите делать внутри хранимой процедуры или функции, как вы могли бы ее назвать. Это происходит потому, что, когда вы выполняете инструкции таким образом, каждый оператор стоит сам по себе без каких-либо отношений или информации относительно других операторов.

Кроме того, вы можете посмотреть следующую ссылку для получения дополнительной информации о том, как использовать IF... THEN... ELSE... END IF; условные выражения внутри plpgsql: ссылка.


EDIT:

Я не знаю, разрешен ли ROLLBACK в этот момент (поскольку каждая хранимая процедура уже находится в собственной транзакции), но вы должны уметь это понять самостоятельно, используя обширную документацию @http://www.postgresql.org. Вот пример функции с вашим кодом в ней, также демонстрирующий другой синтаксис:

CREATE OR REPLACE FUNCTION public.test()
RETURNS integer AS
$$
DECLARE
tempvar integer;

BEGIN    
     tempvar := 1;

     INSERT INTO movements (from, to, import) VALUES ('mary', 'steve', 600);
     UPDATE users SET credit = credit - 600 WHERE name = 'mary';
     UPDATE users SET credit = credit + 600 WHERE name = 'steve';

     --here comes the problem!
     IF (SELECT credit FROM users WHERE name = 'mary') < 0 THEN
        ROLLBACK;
     END IF;

     RETURN tempvar;
END
$$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER;

Однако, если вы действительно идете по этому пути, я рекомендую использовать диспетчер DBI. Это легче изучить все это.

Ответ 2

Похоже, вы используете plain SQL, но оператор IF является частью процедурного языка PL/pgSQL, который является частью PostgreSQL.

Ответ 3

Если вы хотите избежать, если вы можете переписать свой код как:

BEGIN;

    INSERT INTO movements (from, to, import)    
    SELECT 'mary', 'steve', CASE credit < 600 WHEN TRUE THEN 0 ELSE 600 END;

    UPDATE users SET credit = credit - CASE credit < 600 WHEN TRUE THEN 0 ELSE 600 END    
    WHERE name = 'mary';

    UPDATE users u SET u.credit = u.credit + CASE v.credit < 600 WHEN TRUE THEN 0 ELSE 600 END    
    FROM users v    
    WHERE u.name = 'steve' and v.name = 'mary'

COMMIT;

Да, это глупо:).

Ответ 4

Вы можете попробовать изменить часть IF, от:

IF (SELECT credit FROM users WHERE name = 'mary') < 0 THEN
 ROLLBACK;
END IF

к

SELECT SUM(credit) INTO v_credit FROM users WHERE name = 'mary';
IF (v_credit) < 0 THEN
 ROLLBACK;
END IF

Предполагая, что v_credit - это переменная, которую вы определили ранее. IMHO, Postgre предполагает, что запрос SELECT возвращает более одного результата, даже если вы уверены, что он уникален. Поэтому я думаю, вы могли бы попытаться заранее присвоить значение переменной.

Ответ 5

Подобно Microsoft SQL и T/SQL, вы должны иметь возможность смешивать обычный SQL с PL/pgSQL, если они находятся в правильной последовательности. Здесь пример, где последовательность имеет значение в смешанном SQL/PL сохраненном proc:

Вы не можете обертывать условные выражения внутри курсора - вы должны поместить курсор внутрь условного оператора. Если вы выполните последовательность наоборот, вы получите ту же ошибку, что и вы, "ERROR: синтаксическая ошибка в или рядом с" IF ":

CREATE OR REPLACE FUNCTION getSubsystemFaultListCount(_bunoid integer, _subsystem text, _starttime timestamp without time zone, _stoptime timestamp without time zone)
      RETURNS refcursor AS
    $BODY$
    DECLARE mycurs refcursor;
    BEGIN 
        IF _subsystem = 'ALL' THEN
            OPEN mycurs FOR
            SELECT  count(*), fs_fault.faultcode, fs_fault.downloadtime
            FROM    fs_fault
            WHERE   fs_fault.bunoid = _bunoid
                AND fs_fault.statusid IN(2, 4)
                AND fs_fault.downloadtime BETWEEN _starttime AND _stoptime
            GROUP BY fs_fault.faultcode, fs_fault.downloadtime;
            RETURN mycurs;
        ELSE
            OPEN mycurs FOR
            SELECT  count(*), fs_fault.faultcode, fs_fault.downloadtime
            FROM    fs_fault
            WHERE   fs_fault.bunoid = _bunoid
                AND fs_fault.subsystemid 
                    IN(SELECT id FROM fs_subsystem WHERE type = _subsystem)
                AND fs_fault.statusid IN(2, 4)
                AND fs_fault.downloadtime BETWEEN _starttime AND _stoptime
            GROUP BY fs_fault.faultcode, fs_fault.downloadtime;
            RETURN mycurs;
        END IF;

    END;
    $BODY$

Я новичок в PostGresSQL; эта функция является всего лишь примером.