PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

PostgreSQL, déclencheurs et concurrence pour appliquer une clé temporelle

Une solution consiste à utiliser une deuxième table pour détecter les conflits et à la remplir avec un déclencheur. En utilisant le schéma que vous avez ajouté à la question :

CREATE TABLE medicinal_product_date_map(
   aic_code char(9) NOT NULL,
   applicable_date date NOT NULL,
   UNIQUE(aic_code, applicable_date));

(Remarque :il s'agit de la deuxième tentative en raison d'une mauvaise lecture de votre exigence la première fois. J'espère que c'est correct cette fois).

Quelques fonctions pour maintenir cette table :

CREATE FUNCTION add_medicinal_product_date_range(aic_code_in char(9), start_date date, end_date date)
RETURNS void STRICT VOLATILE LANGUAGE sql AS $$
  INSERT INTO medicinal_product_date_map
  SELECT $1, $2 + offset
  FROM generate_series(0, $3 - $2)
$$;
CREATE FUNCTION clr_medicinal_product_date_range(aic_code_in char(9), start_date date, end_date date)
RETURNS void STRICT VOLATILE LANGUAGE sql AS $$
  DELETE FROM medicinal_product_date_map
  WHERE aic_code = $1 AND applicable_date BETWEEN $2 AND $3
$$;

Et remplissez le tableau la première fois avec :

SELECT count(add_medicinal_product_date_range(aic_code, vs, ve))
FROM medicinal_products;

Créez maintenant des déclencheurs pour remplir la carte de dates après les modifications apportées aux produits médicinaux :après l'insertion appelle add_, après la mise à jour appelle clr_ (anciennes valeurs) et add_ (nouvelles valeurs), après la suppression appelle clr_.

CREATE FUNCTION sync_medicinal_product_date_map()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
  IF TG_OP = 'UPDATE' OR TG_OP = 'DELETE' THEN
    PERFORM clr_medicinal_product_date_range(OLD.aic_code, OLD.vs, OLD.ve);
  END IF;
  IF TG_OP = 'UPDATE' OR TG_OP = 'INSERT' THEN
    PERFORM add_medicinal_product_date_range(NEW.aic_code, NEW.vs, NEW.ve);
  END IF;
  RETURN NULL;
END;
$$;
CREATE TRIGGER sync_date_map
  AFTER INSERT OR UPDATE OR DELETE ON medicinal_products
  FOR EACH ROW EXECUTE PROCEDURE sync_medicinal_product_date_map();

La contrainte d'unicité sur medicinal_product_date_map piégera tous les produits ajoutés avec le même code le même jour :

[email protected]@[local] =# INSERT INTO medicinal_products VALUES ('1','A','2010-01-01','2010-04-01');
INSERT 0 1
[email protected]@[local] =# INSERT INTO medicinal_products VALUES ('1','A','2010-03-01','2010-06-01');
ERROR:  duplicate key value violates unique constraint "medicinal_product_date_map_aic_code_applicable_date_key"
DETAIL:  Key (aic_code, applicable_date)=(1        , 2010-03-01) already exists.
CONTEXT:  SQL function "add_medicinal_product_date_range" statement 1
SQL statement "SELECT add_medicinal_product_date_range(NEW.aic_code, NEW.vs, NEW.ve)"
PL/pgSQL function "sync_medicinal_product_date_map" line 6 at PERFORM

Cela dépend des valeurs vérifiées pour avoir un espace discret - c'est pourquoi j'ai posé des questions sur les dates par rapport aux horodatages. Bien que les horodatages soient, techniquement, discrets puisque Postgresql ne stocke qu'une résolution en microsecondes, ajouter une entrée à la table des cartes pour chaque microseconde pour laquelle le produit est applicable n'est pas pratique.

Cela dit, vous pourriez probablement aussi vous en sortir avec quelque chose de mieux qu'une analyse complète de la table pour vérifier les intervalles d'horodatage qui se chevauchent, avec quelques astuces pour rechercher uniquement le premier intervalle pas après ou pas avant ... cependant, pour des espaces discrets faciles Je préfère cette approche, car IME peut également être utile pour d'autres choses (par exemple, les rapports qui doivent trouver rapidement quels produits sont applicables un certain jour).

J'aime aussi cette approche parce qu'il semble juste de tirer parti du mécanisme de contrainte d'unicité de la base de données de cette façon. De plus, je pense qu'il sera plus fiable dans le contexte des mises à jour simultanées de la table principale :sans verrouiller la table contre les mises à jour simultanées, il serait possible qu'un déclencheur de validation ne voie aucun conflit et autorise les insertions dans deux sessions simultanées, qui sont apparaît alors en conflit lorsque les effets des deux transactions sont visibles.