PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Как мога да задействам тригер в края на верига от актуализации?

Вместо да използвате флаг в report_subscriber мисля, че ще е по-добре с отделна опашка с чакащи промени. Това има няколко предимства:

  • Без рекурсия на задействане
  • Под капака, UPDATE е просто DELETE + повторно INSERT , така че вмъкването в опашка всъщност ще бъде по-евтино от обръщането на флаг
  • Вероятно доста по-евтино, тъй като трябва само да поставите на опашка отделния report_id s, вместо да клонира целия report_subscriber записи и можете да го направите във временна таблица, така че съхранението е непрекъснато и нищо не трябва да се синхронизира на диск
  • Няма условия за състезание, за които да се притеснявате, когато обръщате флаговете, тъй като опашката е локална за текущата транзакция (във вашето внедряване, записите, засегнати от UPDATE report_subscriber не са непременно същите записи, които сте избрали в SELECT ...)

И така, инициализирайте таблицата на опашката:

CREATE FUNCTION create_queue_table() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  CREATE TEMP TABLE pending_subscriber_changes(report_id INT UNIQUE) ON COMMIT DROP;
  RETURN NULL;
END
$$;

CREATE TRIGGER create_queue_table_if_not_exists
  BEFORE INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH STATEMENT
  WHEN (to_regclass('pending_subscriber_changes') IS NULL)
  EXECUTE PROCEDURE create_queue_table();

...поставете промените на опашка, когато пристигнат, като игнорирате всичко, което вече е на опашка:

CREATE FUNCTION queue_subscriber_change() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  IF TG_OP IN ('DELETE', 'UPDATE') THEN
    INSERT INTO pending_subscriber_changes (report_id) VALUES (old.report_id)
    ON CONFLICT DO NOTHING;
  END IF;

  IF TG_OP IN ('INSERT', 'UPDATE') THEN
    INSERT INTO pending_subscriber_changes (report_id) VALUES (new.report_id)
    ON CONFLICT DO NOTHING;
  END IF;
  RETURN NULL;
END
$$;

CREATE TRIGGER queue_subscriber_change
  AFTER INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH ROW
  EXECUTE PROCEDURE queue_subscriber_change();

...и обработва опашката в края на оператора:

CREATE FUNCTION process_pending_changes() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  UPDATE report
  SET report_subscribers = ARRAY(
    SELECT DISTINCT subscriber_name
    FROM report_subscriber s
    WHERE s.report_id = report.report_id
    ORDER BY subscriber_name
  )
  FROM pending_subscriber_changes c
  WHERE report.report_id = c.report_id;

  DROP TABLE pending_subscriber_changes;
  RETURN NULL;
END
$$;

CREATE TRIGGER process_pending_changes
  AFTER INSERT OR UPDATE OF report_id, subscriber_name OR DELETE
  ON report_subscriber
  FOR EACH STATEMENT
  EXECUTE PROCEDURE process_pending_changes();

Има малък проблем с това:UPDATE не предлага никакви гаранции относно реда на актуализиране. Това означава, че ако тези два израза се изпълняват едновременно:

INSERT INTO report_subscriber (report_id, subscriber_name) VALUES (1, 'a'), (2, 'b');
INSERT INTO report_subscriber (report_id, subscriber_name) VALUES (2, 'x'), (1, 'y');

...тогава има шанс за блокиране, ако се опитат да актуализират report записи в обратен ред. Можете да избегнете това, като наложите последователно подреждане за всички актуализации, но за съжаление няма начин да прикачите ORDER BY до UPDATE изявление; Мисля, че трябва да прибегнете до курсори:

CREATE FUNCTION process_pending_changes() RETURNS TRIGGER LANGUAGE plpgsql AS $$
DECLARE
  target_report CURSOR FOR
    SELECT report_id
    FROM report
    WHERE report_id IN (TABLE pending_subscriber_changes)
    ORDER BY report_id
    FOR NO KEY UPDATE;
BEGIN
  FOR target_record IN target_report LOOP
    UPDATE report
    SET report_subscribers = ARRAY(
        SELECT DISTINCT subscriber_name
        FROM report_subscriber
        WHERE report_id = target_record.report_id
        ORDER BY subscriber_name
      )
    WHERE CURRENT OF target_report;
  END LOOP;

  DROP TABLE pending_subscriber_changes;
  RETURN NULL;
END
$$;

Това все още има потенциал да блокира, ако клиентът се опита да изпълни множество изрази в рамките на една и съща транзакция (тъй като редът на актуализиране се прилага само във всеки оператор, но блокировките за актуализиране се задържат до извършване). Можете да заобиколите това (донякъде), като задействате process_pending_changes() само веднъж в края на транзакцията (недостатъкът е, че в рамките на тази транзакция няма да видите вашите собствени промени, отразени в report_subscribers масив).

Ето общ план за тригер „при извършване“, ако смятате, че си струва труда да го попълните:

CREATE FUNCTION run_on_commit() RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
  <your code goes here>
  RETURN NULL;
END
$$;

CREATE FUNCTION trigger_already_fired() RETURNS BOOLEAN LANGUAGE plpgsql VOLATILE AS $$
DECLARE
  already_fired BOOLEAN;
BEGIN
  already_fired := NULLIF(current_setting('my_vars.trigger_already_fired', TRUE), '');
  IF already_fired IS TRUE THEN
    RETURN TRUE;
  ELSE
    SET LOCAL my_vars.trigger_already_fired = TRUE;
    RETURN FALSE;
  END IF;
END
$$;

CREATE CONSTRAINT TRIGGER my_trigger
  AFTER INSERT OR UPDATE OR DELETE ON my_table
  DEFERRABLE INITIALLY DEFERRED
  FOR EACH ROW
  WHEN (NOT trigger_already_fired())
  EXECUTE PROCEDURE run_on_commit();



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Изчакване на PostgreSQL връзката

  2. как автоматично да създавате таблица на базата на CSV в postgres с помощта на python

  3. Двама собственици на една и съща база данни PostgreSQL

  4. Как да конкатенираме низове в PostgreSQL

  5. Как да търсите конкретна стойност във всички таблици (PostgreSQL)?