Deux colonnes nullables dont une doit avoir une valeur

10

Question sans explication:

Existe-t-il de toute façon une contrainte de 2 valeurs nulles qui nécessite toujours 1 pour avoir de la valeur? Par exemple, deux colonnes de date nulles mais ayant au moins 1 qui nécessite d'avoir une valeur

Description du problème:

Disons que j'ai une table appelée Dépenses

et ont 2 dates:

prevision_expense_expiration_date DATE NULLABLE dépenses_payment_date DATE NULLABLE

la logique de ces 2 colonnes est la suivante:

J'ai acheté quelque chose et je sais que je dois payer pour cela, une date, comme une facture de téléphone. Je vais saisir ceci comme une dépense avec une date de dépense_paiement. Cette date est la date supposée que je devrais payer mais pas la date réelle du paiement, comme la date d'expiration de la facture.

Dans d'autres situations, je vends une carte-cadeau d'un fournisseur pour son service. Je peux avoir à payer pour acheter à mon fournisseur le service transféré à mon client uniquement si le client utilise la carte. Par conséquent, la carte-cadeau a une date d'expiration, je veux faire une prévision pour cette «dépense» sans insérer comme dépense pour la durée de validité de la carte-cadeau, si la carte-cadeau expire, cette «dépense» ne devrait pas entrer dans le compte système.

Je sais que je peux avoir 2 tables égales appelées prevision_expense et

Il y a une autre stratégie possible:

payment_date DATE NOT NULL is_prevision_date BOOL NOT NULL

Donc, dans ce cas, si la date est une valeur booléenne de prévision serait 1, sinon sera 0. Aucune valeur nulle, tout est bon. sauf que je veux l'option de stocker les DEUX valeurs lorsque j'ai d'abord une date de prévision et ALORS (disons deux jours plus tard) avoir une date confirmée pour cette dépense, auquel cas avec la stratégie 2 je n'aurai pas cette option.

Suis-je en train de tout faire mal dans la conception de la base de données? :RÉ

Bart Calixto
la source

Réponses:

10

Une version de la réponse de JD Schmidt, mais sans la maladresse d'une colonne supplémentaire:

CREATE TABLE foo (
  FieldA INT,
  FieldB INT
);

DELIMITER //
CREATE TRIGGER InsertFieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
  IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
  END IF;
END//
CREATE TRIGGER UpdateFieldABNotNull BEFORE UPDATE ON foo
FOR EACH ROW BEGIN
  IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
  END IF;
END//
DELIMITER ;

INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
UPDATE foo SET FieldA = NULL; -- gives error
Michael Platings
la source
2

Si vous utilisez SQL Server, vous pouvez éviter l'utilisation d'un déclencheur en utilisant une colonne calculée persistante dans votre table:

CREATE TABLE Test_Constraint
(
    A DateTime Null,
    B DateTime Null,
    A_and_B AS (CASE WHEN A IS Null AND B IS Null THEN Null ELSE Convert(Binary(1), 1) END) PERSISTED Not Null 
);

L'instruction case dans la colonne calculée A_and_B renverra une valeur nulle si les deux colonnes A et B sont nulles, mais la contrainte Not Null sur la colonne calculée soulèverait alors une erreur empêchant l'insertion. Sinon, il renvoie un 1.

Étant donné que la colonne calculée est persistante, elle sera physiquement stockée dans la table. La conversion en binaire minimise l'impact de cela, faisant de la colonne un type de données binaire de longueur 1.

Shane Estelle
la source
1
Dans SQL-Server, vous pouvez également le faire avec une CHECKcontrainte. Pas besoin de colonne persistante.
ypercubeᵀᴹ
1
Génial, cela semble un peu plus propre. CREATE TABLE Test_Constraint2 ( A DateTime Null, B DateTime Null, CONSTRAINT A_or_B_Not_Null CHECK (CASE WHEN A IS Null AND B IS Null THEN 0 ELSE 1 END = 1) )
Shane Estelle
très bonne réponse! Celui-ci est plus approprié que l'autre mais puisque j'utilise MySQL, CHECK CLAUSE est analysé mais ignoré sur MySQL, donc je marque l'autre réponse comme acceptée. +1
Bart Calixto
1

J'ai trouvé un article qui ressemble à la même chose ici

CREATE TABLE foo (
  FieldA INT,
  FieldB INT,
  FieldA_or_FieldB TINYINT NOT NULL;
);

DELIMITER //
CREATE TRIGGER FieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
  IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
    SET NEW.FieldA_or_FieldB = NULL;
  ELSE
    SET NEW.FieldA_or_FieldB = 1;
  END IF;
END//
DELIMITER ;

INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
JD Schmidt
la source
merci, j'utilise MySQL et cela fonctionne parfaitement avec. spécialement à cause du lancement d'une valeur nulle sur une erreur de colonne non nulle.
Bart Calixto