Вернуть значение, измененное обновлением без триггера - PullRequest
2 голосов
/ 13 октября 2019

У Postgres есть отличное предложение ВОЗВРАТ для INSERT, DELETE и UPDATE ... и это делает меня немного жадным. В некоторых случаях я хотел бы получить не только текущее значение, но и предыдущее значение:

UPDATE analytic_productivity

  SET points = 1000
WHERE points > 1000

RETURNING id, points, OLD.points;

Я не верю, что есть какой-либо способ получить доступ к предыдущим значениям за пределами срока службы и контекста триггера. Итак, я думаю, что я хотел бы, невозможно как таковое. Если это правильно, кто-нибудь может предложить альтернативу? Я перезаписываю выбросы с некоторыми установленными значениями и хотел бы записать измененные значения в другую таблицу. Вот почему я не знаю текущее значение заранее. Это редкая (и явно подозрительная) операция, и я не хочу записывать изменения при обычных вставках и обновлениях.

В качестве альтернативы я думаю, что могу выбрать выбросы, пересмотреть их, а затем записать изменения. Итак, сделайте большую часть работы на стороне клиента с парой запросов к Postgres. Если да, может ли кто-нибудь предложить правильный уровень блокировки, который можно применить между моим начальным SELECT и моим следующим UPDATE? Я считаю, что блокировка FOR UPDATE правильна.

Любые предложения по интеллектуальному способу захвата предыдущих значений во время обновления без триггера было бы здорово услышать.

Follow-up

Благодаря комментариям я немного поэкспериментировал и нашел решение, которое работает в моем случае. Чтобы сделать мои цели более понятными:

  • У меня есть таблица с именем outlier_rule, которая определяет значения, которые слишком велики для определенного столбца.
  • Цель состоит в том, чтобы выполнить цикл потаблицу, и применять правила, чтобы установить выбросы на фиксированное значение.
  • Стучать по таким выбросам ... сомнительно. В пользовательском интерфейсе приложения должны быть утечки, которые допускают необоснованные значения. Чтобы отследить их, я записываю большие значения в таблицу с именем outlier_change.
  • . Я бы хотел перенести это поведение в функцию на стороне сервера, чтобы любой из наших серверов, независимо от ихверсия кодовой базы, может вызывать текущую логику.
  • Клиентские серверы составляют и отправляют электронную почту со сводкой результатов, когда найдены и исправлены выбросы.

Итак, на стороне сервераФункция делать все, регистрировать некоторые данные и возвращать результат. У меня это работает, но у него запах "Ты не знаешь, что делаешь", так что продолжай добавлять код, пока он не заработает. По крайней мере, я лучше справляюсь с использованием FORMAT и думаю, что теперь я понимаю, что одна функция может делать много вещей, и что вы можете выбирать, что возвращать, с помощью предложения RETURN. Для справки, различные биты кода:

    CREATE TABLE IF NOT EXISTS data.outlier_rule (
        id uuid NOT NULL DEFAULT extensions.gen_random_uuid(),
        schema_name text NOT NULL DEFAULT NULL,
        table_name text NOT NULL DEFAULT NULL,
        column_name text NOT NULL DEFAULT NULL,
        threshold integer,
        set_to integer,

    CONSTRAINT outlier_rule_id_pkey
        PRIMARY KEY (schema_name,table_name,column_name)
    );

For tracking the modifications, I've got a second table named outlier_change:

    ------------------------------
    -- Table
    ------------------------------
    DROP TABLE IF EXISTS data.outlier_change CASCADE;

    CREATE TABLE IF NOT EXISTS data.outlier_change (
        id uuid NOT NULL DEFAULT NULL,
        outlier_rule_id uuid NOT NULL DEFAULT NULL,
        value_was integer NOT NULL DEFAULT NULL,
        set_to integer NOT NULL DEFAULT NULL,
        change_count integer NOT NULL DEFAULT 0,
        last_changed_dts timestamptz NOT NULL DEFAULT NOW(),

    CONSTRAINT outlier_change_id_pkey
        PRIMARY KEY (id,outlier_rule_id)
    );

    ALTER TABLE data.outlier_change OWNER TO user_change_structure;

    ------------------------------
    -- Trigger Function
    ------------------------------
    CREATE OR REPLACE FUNCTION data.on_outlier_change_upsert()
      RETURNS pg_catalog.trigger AS $BODY$
    BEGIN

        NEW.last_changed_dts := NOW();
        NEW.change_count     := OLD.change_count + 1;
        RETURN NEW;          -- important!

    END;

    $BODY$
      LANGUAGE plpgsql VOLATILE
      COST 100;

    ------------------------------
    -- Trigger
    ------------------------------
    CREATE TRIGGER outlier_change_upsert BEFORE INSERT OR UPDATE ON data.outlier_change
    FOR EACH ROW
    EXECUTE PROCEDURE data.on_outlier_change_upsert();

DROP FUNCTION IF EXISTS data.outlier_fix ();
CREATE OR REPLACE FUNCTION data.outlier_fix ()

RETURNS TABLE (
   schema_name text,
   table_name  text,
   column_name text,
   id          uuid,
   value_was   integer,
   set_to      integer,
   change_count integer
)

AS $$

DECLARE
    rule record;
    now_ timestamptz = NOW();

BEGIN

    FOR rule IN SELECT * FROM data.outlier_rule LOOP

        EXECUTE FORMAT (
       'INSERT INTO outlier_change (
                        outlier_rule_id,
                        set_to,
                        id,
                        value_was)

               SELECT %6$L,
                        %5$s,
                        %2$I.id,
                        %2$I.%3$I

                 FROM %1$I.%2$I

                WHERE %3$I > %4$s

                   ON CONFLICT(id,outlier_rule_id) DO UPDATE SET
                       value_was = EXCLUDED.value_was,
                       set_to    = EXCLUDED.set_to

          RETURNING outlier_rule_id,
                      id,
                      value_was,
                      set_to
                      change_count;

             UPDATE %1$I.%2$I 
                SET %3$I = %5$s
              WHERE %3$I > %4$s;',

                rule.schema_name,
                rule.table_name,
                rule.column_name,
                rule.threshold,
                rule.set_to,
                rule.id);

 END LOOP;

  RETURN QUERY EXECUTE ('
        SELECT outlier_rule.schema_name,
              outlier_rule.table_name,
              outlier_rule.column_name,
              outlier_change.id,
              outlier_change.value_was,
              outlier_change.set_to,
              outlier_change.change_count 

              FROM outlier_change 
              JOIN outlier_rule ON (outlier_rule.id = outlier_change.outlier_rule_id)

             WHERE last_changed_dts = $1')
       USING now_;


    END;
    $$ LANGUAGE plpgsql;

ALTER FUNCTION data.outlier_fix() OWNER TO user_bender;

1 Ответ

1 голос
/ 13 октября 2019

Вы могли бы достичь этого с небольшим количеством взлома. Вы можете самостоятельно присоединиться к таблице в запросе на обновление следующим образом:

UPDATE analytic_productivity NEW
  SET points = 1000
  FROM analytic_productivity OLD
  WHERE NEW.points > 1000
  and NEW.id = OLD.id
  RETURNING NEW.id,
      NEW.points,
      OLD.points as old_points;
...