Postgres функция для отображения новых значений столбца после обновления как JSON - PullRequest
1 голос
/ 05 января 2020

Я пытаюсь написать функцию postgres для триггера AFTER UPDATE, который создает объект JSON с именами столбцов и их обновленными значениями только для измененных столбцов. Я пытаюсь создать обобщенную функцию, которую я могу использовать для любой таблицы, следовательно, используя Record в качестве аргумента функции. old и new происходят из переменных OLD и NEW триггера. Конечная цель - сохранить возвращенное значение JSON в поле JSON в таблице аудита.

CREATE OR REPLACE FUNCTION row_updates(old RECORD, new RECORD) RETURNS JSON AS
$$
DECLARE
    updates JSON;
BEGIN
    WITH columns AS (
        SELECT json_object_keys(row_to_json(new)) "column"
    )
    SELECT
        json_object_agg("column", new_value) INTO updates
    FROM (
        SELECT 
            "column",
            (row_to_json(new)->"column" #>> '{}') as new_value,
            (row_to_json(old)->"column" #>> '{}') as old_value
        FROM
            columns
    ) changes
    WHERE 
        new_value IS DISTINCT FROM old_value;

    RETURN updates;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION audit_change() RETURNS TRIGGER AS
$$
DECLARE
    updates JSON;
BEGIN
    IF (TG_OP = 'INSERT') THEN
        raise NOTICE 'Logging insert on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME;

        RETURN NEW;
    ELSIF (TG_OP = 'UPDATE') THEN
        updates := row_updates(OLD, NEW)
        raise NOTICE 'Logging update on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME, updates;
        RETURN NEW;
    ELSIF (TG_OP = 'DELETE') THEN
        raise NOTICE 'Logging delete on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME;
        RETURN OLD;
    END IF;
END;
$$ LANGUAGE plpgsql;

Я получаю эту ошибку: ERROR: PL/pgSQL functions cannot accept type record.

Есть ли лучший способ сделать это? Очевидно, что я хочу сделать это в не триггерной функции, чтобы я мог использовать его в разных триггерах для разных таблиц. Также я могу захотеть адаптировать его для триггеров AFTER INSERT и AFTER DELETE, если это возможно, поэтому я не хочу отключать TG_OP в функции триггера и повторять логи c, показанные в SQL выше.

Конечная цель:

=> select * from org_user;

 id | org_id | user_id 
----+--------+---------
  1 |      1 |       1
 23 |      1 |       3

=> update org_user set org_id=3, user_id = 4 where id = 23; 

-- trigger creates following row in audit table

 id | relation  | record_id | updates 
----+-----------+-----------+--------------
  1 |  org_user |     23    | {'org_id': 3, 'user_id': 4} 

1 Ответ

2 голосов
/ 05 января 2020

Создайте функцию, которая сравнивает два JSONB значения:

create or replace function jsonb_diff(jsonb, jsonb)
returns jsonb language sql immutable as $$
    select jsonb_object_agg(n.key, n.value)
    from jsonb_each($1) o
    join jsonb_each($2) n on o.key = n.key
    where o.value <> n.value;
$$;

и используйте ее в своей функции триггера:

    updates := jsonb_diff(to_jsonb(OLD), to_jsonb(NEW));
    raise NOTICE 'Logging update on relation (%.%) %', TG_TABLE_SCHEMA, TG_TABLE_NAME, updates;
    RETURN NEW;

Кстати, в Postgres 11+ Вы можете использовать аргументы функции типа record.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...