sql >> Databáze >  >> RDS >> PostgreSQL

Jak mohu spustit spouštěč na konci řetězce aktualizací?

Namísto použití příznaku v report_subscriber Myslím, že by pro vás bylo lepší vytvořit samostatnou frontu nevyřízených změn. Má to několik výhod:

  • Žádná rekurze spouštěče
  • Pod pokličkou UPDATE je pouze DELETE + znovu INSERT , takže vložení do fronty bude ve skutečnosti levnější než přehození vlajky
  • Možná o něco levnější, protože stačí do fronty zařadit odlišné report_id s namísto klonování celého report_subscriber záznamy a můžete to udělat v dočasné tabulce, takže úložiště je souvislé a není třeba nic synchronizovat na disk
  • Při přepínání příznaků si nemusíte dělat starosti, protože fronta je místní vzhledem k aktuální transakci (ve vaší implementaci záznamy ovlivněné UPDATE report_subscriber nemusí být nutně stejné záznamy, které jste vybrali v SELECT ...)

Inicializujte tedy tabulku fronty:

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();

...zařaďte změny, jakmile dorazí, ignorujte vše, co již bylo ve frontě:

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();

...a zpracujte frontu na konci příkazu:

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();

S tím je malý problém:UPDATE neposkytuje žádné záruky ohledně objednávky aktualizace. To znamená, že pokud byly tyto dva příkazy spuštěny současně:

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');

...potom existuje možnost uváznutí, pokud se pokusí aktualizovat report záznamy v opačném pořadí. Tomu se můžete vyhnout vynucením jednotného řazení všech aktualizací, ale bohužel neexistuje způsob, jak připojit ORDER BY do UPDATE tvrzení; Myslím, že se musíte uchýlit ke kurzorům:

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
$$;

Stále může dojít k uváznutí, pokud se klient pokusí spustit více příkazů v rámci stejné transakce (protože pořadí aktualizací se použije pouze v rámci každého příkazu, ale zámky aktualizace jsou drženy až do potvrzení). Můžete to obejít (tak nějak) spuštěním process_pending_changes() pouze jednou na konci transakce (nevýhoda je, že v rámci této transakce neuvidíte své vlastní změny, které se projeví v report_subscribers pole).

Zde je obecný přehled pro spouštěč „při odevzdání“, pokud si myslíte, že stojí za námahu jej vyplnit:

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. Jak zkontrolovat, zda je pole prázdné v Postgres

  2. Jedinečné omezení ORA-00001 porušeno

  3. Více UNION dotazů nefunguje

  4. Jak shromáždit datum narození z formuláře php a vložit do mysql?