Как получить доступ к новому или старому полю, указав только имя поля?


8

Я пишу триггер проверки. Триггер должен проверить, что сумма массива равна другому полю. Поскольку у меня много экземпляров этой проверки, я хочу написать одну процедуру и создать несколько триггеров, каждый из которых должен иметь свой набор полей для проверки.

Например, у меня есть следующая схема:

CREATE TABLE daily_reports(
     start_on date
   , show_id uuid
   , primary key(start_on, show_id)

     -- _graph are hourly values, while _count is total for the report
   , impressions_count bigint not null
   , impressions_graph bigint[] not null

   -- interactions_count, interactions_graph
   -- twitter_interactions_count, twitter_interactions_graph
);

Проверка должна подтвердить это impressions_count = sum(impressions_graph).

Я застрял, потому что я не знаю, как динамически получить доступ к полю NEWизнутри plpgsql:

CREATE FUNCTION validate_sum_of_array_equals_other() RETURNS TRIGGER AS $$
DECLARE
  total bigint;
  array_sum bigint;
BEGIN
  -- TG_NARGS = 2
  -- TG_ARGV[0] = 'impressions_count'
  -- TG_ARGV[1] = 'impressions_graph'

  -- How to access impressions_count and impressions_graph from NEW?
  RETURN NEW;
END
$$ LANGUAGE plpgsql;

CREATE TRIGGER validate_daily_reports_impressions
ON daily_reports BEFORE INSERT OR UPDATE
FOR EACH ROW EXECUTE
  validate_sum_of_array_equals_other('impressions_count', 'impressions_graph');

Я пытался выполнить динамические командыEXECUTE 'SELECT $1 FROM NEW' INTO total USING TG_ARGV[0] , но PL / PGsql жалуется, что NEW - неизвестное отношение.

Я специально ориентируюсь на PostgreSQL 9.1.


Крест отправил в общий список рассылки PostgreSQL postgresql.org/message-id/…
Франсуа Босолей

AFAIK единственный способ динамического доступа к полям внутри NEWв данный момент состоит в том, чтобы использовать hstore(NEW)и затем обращаться к полям как hstoreзначениям, указанным в имени столбца. Который отстой, потому что тогда они все брошены, textи если вы хотите работать с ними в их первоначальном виде, вы должны отбросить их обратно. Кроме того, вы можете написать триггер на другом процедурном языке, таком как PL / Python, который лучше поддерживает динамический доступ к записи.
Крейг Рингер

@CraigRinger: ну нет. Вы слишком пессимистичны сегодня. Есть способ с динамическим SQL.
Эрвин Брандштеттер

Ответы:


14

На самом деле, поскольку NEWэто хорошо определенный составной тип, вы можете просто получить доступ к любому столбцу с простой и простой нотацией атрибутов. Сам по себе SQL не допускает динамические идентификаторы (имена таблиц или столбцов и т. Д.). Но вы можете использовать динамический SQL сEXECUTE функцией PL / pgSQL.

демонстрация

CREATE OR REPLACE FUNCTION trg_demo1()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text;
   _col_name  text := quote_ident(TG_ARGV[0]);  -- escape identifier
BEGIN
   EXECUTE format('SELECT ($1).%s::text', _col_name)
   USING NEW
   INTO  _col_value;

   -- do something with _col_value ...

   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', _col_name, _col_value;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

Приведение к не textявляется обязательным. Используя это, потому что это работает универсально. Если вы знаете тип, вы можете работать без кастинга ...

Использование format()с %s, потому что идентификатор уже экранирован в этой точке.
В противном случае, использование format()с %Iдля защиты от SQL инъекций.

В качестве альтернативы , в Postgres 9.3 или новее вы можете конвертировать NEWв JSON с to_json()ключами и получать к ним доступ:

CREATE OR REPLACE FUNCTION trg_demo2()
  RETURNS TRIGGER AS
$func$
DECLARE
   _col_value text := to_json(NEW) ->> TG_ARGV[0];  -- no need to escape identifier
BEGIN
   RAISE NOTICE 'It works. The value of NEW.% is >>%<<.', TG_ARGV[0], _col_value;
   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

Поскольку имя столбца не объединяется в строку SQL, внедрение SQL невозможно, и имя не нужно экранировать.

db <> fiddle здесь ( EXCEPTIONвместо того, NOTICEчтобы сделать эффект видимым).

Связанные с:

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.