web-dev-qa-db-fra.com

MYSQL Mettre à jour la gâchette qui met à jour plusieurs colonnes des valeurs d'une autre table

J'ai une table PRODUCTS avec des colonnes id, name, sku, packaging_type, width, height, weight, case_count.

Je veux qu'un déclencheur se déclenche quand packaging_type Est mis à jour. packaging_type sera une valeur entière.

Il y a une deuxième table PACK_TYPES avec des colonnes id, width, height, weight, case_count. C'est là que j'ai besoin d'obtenir les valeurs.

Alors PRODUCTS.packaging_type commence à mettre à jour, comment mettre à jour PRODUCTS.width, PRODUCTS.height, PRODUCTS.weight, et PRODUCTS.case_count avec les valeurs correspondantes où PRODUCTS.packaging_type = PACK_TYPES.id?

1
ndcisiv

Si vous voulez y avoir raison, ce n'est pas la meilleure approche d'une perspective théorique, car vous copiez des données dans votre base de données que vous devriez vraiment dériver.

CREATE ALGORITHM=MERGE VIEW products_with_packaging_info AS
SELECT p.*, 
       pt.width as packaging_width, 
       pt.height as packaging_height,
       pt.weight as packaging_weight,
       pt.case_count AS packaging_case_count
  FROM PRODUCTS p
  JOIN PACK_TYPES pt ON pt.id = p.packaging_type;

Terminé. SELECT Les requêtes contre ce point de vue fonctionnent exactement la même chose que les requêtes contre l'une ou l'autre table individuellement, tant que chaque produit a un type de pack. Les requêtes contre cette vue peuvent toujours tirer parti des index sur les tables de base et il n'y a pas de frais générale impliquée dans la copie des attributs d'une table à une autre, ce qui a toujours le potentiel pour mise à jour des anomalies .

Vous pourriez même être surpris de constater que les colonnes de la vue peuvent réellement être mises à jour comme s'il s'agissait d'une table, les mises à jour se propagent dans les tables de base.

J'offre cette suggestion car une base de données bien conçue devrait être telle qu'il est impossible d'obtenir deux réponses différentes à la même question. Par exemple, si une ligne PACK_TYPES est modifiée car une erreur est trouvée, comment s'approprient ses nouvelles valeurs en arrière dans les produits?

Mais si vous voulez vraiment prendre l'approche de déclenchement, cela ressemble à quelque chose comme ça:

DELIMITER $$

DROP TRIGGER IF EXISTS PRODUCTS_bu $$
CREATE TRIGGER PRODUCTS_bu BEFORE UPDATE ON PRODUCTS FOR EACH ROW
BEGIN

  IF NOT (NEW.packaging_type <=> OLD.packaging_type) THEN
    BEGIN
      DECLARE my_width INT DEFAULT NULL;      -- using
      DECLARE my_height INT DEFAULT NULL;     -- the
      DECLARE my_weight INT DEFAULT NULL;     -- appropriate
      DECLARE my_case_count INT DEFAULT NULL; -- data types here
      SELECT width, height, weight, case_count
        FROM PACK_TYPES
       WHERE id = NEW.packaging_type
        INTO my_width, my_height, my_weight, my_case_count;
      SET NEW.width = my_width, NEW.height = my_height, NEW.weight = my_weight, NEW.case_count = my_case_count;
    END;
  END IF;

END $$
DELIMITER ;

Le <=> "spatialhip" est l'opérateur " Opérateur d'égalité null-sécurité " qui limite "non [éventuellement null] = [éventuellement nul]" "" pour toujours être TRUE ou FALSE; Ceci est nécessaire car [éventuellement nul]! = [éventuellement null] ne sera jamais vrai si l'expression est NULL. C'est le cas car, logiquement, "non (faux)" est "vrai" alors que "non (null)" est "null".

J'aurais pu déclarer les variables au début et évité l'intérieur BEGIN/END, mais il semble optimal d'éviter ce travail jusqu'à ce que nous sachions que nous devons réellement exécuter la logique intérieure en premier lieu, Ce qui est évité chaque fois que 'emballage_type' n'a pas réellement changé d'une ligne pour une requête de mise à jour donnée. Dans un bloc, les déclarations doivent précéder d'autres déclarations, de sorte que le retardement des déclarations nécessite l'addition du BEGIN/END.

Vous voulez également un déclencheur similaire pour BEFORE INSERT qui serait identique sauf que vous supprimiez les 4 lignes commençant par IF ... BEGIN ... END ... END IF À partir du corps de la procédure, utilisez un nouveau nom de déclenchement et changez BEFORE UPDATE à BEFORE INSERT.

C'est BEFORE - pas AFTER - dans les deux cas, car la gâchette déclenche BEFORE la ligne nouvellement insérée ou nouvelle mise à jour est écrite dans la base de données.

3
Michael - sqlbot

Quelque chose de similaire à cela peut fonctionner:

DROP TRIGGER IF EXISTS BI_PRODUCTS;
DELIMITER //
CREATE TRIGGER BI_PRODUCTS BEFORE INSERT ON PRODUCTS FOR EACH ROW 
BEGIN 
  SET NEW.width := (SELECT width FROM PACK_TYPES WHERE id = NEW.packaging_type);
END //

DELIMITER ;

Cependant, pourquoi stocker les enregistrements de manière double? Ce n'est pas très normalisé. Sauf si vous avez une très bonne raison, une meilleure façon de le faire serait de convertir packaging_type dans une FOREIGN KEY de PACK_TYPES et effectuer un JOIN Chaque fois que les colonnes des deux tables sont nécessaires.

0
jynus