J'ai créé le tableau suivant:
CREATE TABLE MMCompany (
CompanyUniqueID BIGSERIAL PRIMARY KEY NOT NULL,
Name VARCHAR (150) NOT NULL,
PhoneNumber VARCHAR(20) NOT NULL UNIQUE,
Email VARCHAR(75) UNIQUE,
CompanyLogo BYTEA
);
La colonne e-mail est unique et elle provoque un "bug" dans mon scénario car il ne peut y avoir qu'un seul enregistrement avec null. J'essaie de réaliser des enregistrements d'entreprises sans le même e-mail, mais en même temps, permettre à une entreprise de ne pas avoir d'e-mail.
Comment puis-je y parvenir?
C'est un malentendu.
La contrainte UNIQUE
fait exactement ce que vous voulez. Plusieurs valeurs NULL
peuvent coexister dans une colonne définie UNIQUE
.
Citant le manuel sur les contraintes UNIQUES :
En général, une contrainte unique est violée lorsqu'il existe plusieurs lignes dans le tableau où les valeurs de toutes les colonnes incluses dans la contrainte sont égales. Cependant, deux valeurs nulles ne sont pas considérées comme égales dans cette comparaison. Cela signifie même en présence d'une contrainte unique, il est possible de stocker des lignes en double qui contiennent une valeur nulle dans au moins une des colonnes contraintes. Ce comportement est conforme à la norme SQL, mais nous avons entendu que d'autres bases de données SQL pourraient ne pas suivre cette règle. Soyez donc prudent lorsque vous développez des applications destinées à être portables.
Accentuation sur moi.
Sachez que types de caractères autorise une chaîne vide (''
), qui est pas une valeur NULL
et déclencherait une violation unique comme toute autre valeur non nulle lorsqu'elle est entrée dans plusieurs lignes.
Dans la bonne réponse d'Erwin Brandstetter réponse , il explique que vous devriez en effet voir le comportement que vous souhaitez (plusieurs NULL autorisés dans une contrainte Unique). Vous devriez voir ce comportement dans Postgres en particulier, ainsi que dans toute base de données conforme aux normes SQL en général.
Cependant, le document Postgres met en garde contre la portabilité car certaines bases de données sont réputées enfreindre cette fonctionnalité. Pour un tel système non conforme, je suggère de remplacer l'utilisation d'une valeur NULL dans ces champs par une valeur fausse. La valeur fausse serait une chaîne telle que "unknown_" plus une valeur arbitraire qui est pratiquement certaine d'être unique. Cette valeur arbitraire pourrait être quelque chose comme la date-heure actuelle plus un nombre aléatoire.
Mais, plutôt que de rouler votre propre valeur arbitraire, générez un UUID . L'UUID original de la version 1 est en effet une combinaison de la date-heure actuelle, d'un nombre aléatoire et de l'ordinateur pratiquement unique adresse MAC .
Un UUID présenté comme une chaîne hexadécimale avec une mise en forme canonique utilisant des tirets ressemble à ceci:
93e6f268-5c2d-4c63-9d9c-40e6ac034f88
Donc, ma suggestion est de combiner une chaîne arbitraire telle que "unknown_" plus un UUID, pour ressembler à ceci:
unknown_93e6f268-5c2d-4c63-9d9c-40e6ac034f88
Donc, ma suggestion pour les bases de données non conformes est de générer une telle valeur et de l'utiliser à la place de NULL, utilisez-la là où vous n'avez pas encore de valeur connue dans cette colonne pour une ligne particulière. Au lieu d'écrire des requêtes qui recherchent des lignes qui ont (ou n'ont pas) une valeur NULL dans cette colonne, écrivez des requêtes qui recherchent des lignes qui ont (ou n'ont pas) une valeur commençant par la chaîne arbitraire, "unknown_" dans ce Exemple. Chaque ligne satisferait alors la contrainte d'avoir une valeur unique.
En effet, j'attribuerais cette valeur "unknown_" + UUID comme valeur par défaut pour cette colonne.
Vous pouvez également ajouter une contrainte NOT NULL à cette colonne.
Postgres a une prise en charge intégrée pour le type de données UUID, mais ce n'est pas pertinent dans cette réponse ici. Ce dont vous avez besoin est de générer un UUID value.
Pour générer des UUID, vous avez besoin d'une extension (plugin) qui ajoute cette fonctionnalité à Postgres. La plupart des installateurs Postgres incluent une telle extension. Cette extension est appelée id-ossp . En général, l'extension n'est pas activée par défaut. Pour ce faire, dans les versions récentes de Postgres, utilisez la commande CREATE EXTENSION . Pour obtenir des instructions, consultez mon article de blog sur installation dans Postgres 9.1 et versions ultérieures ou mon autre article sur Postgres 9.0 et versions antérieures . La nouvelle et l'ancienne méthode d'installation est facile à condition que l'extension/plugin soit compilé et fourni avec votre installation Postgres.
Permettez-moi d'être clair que pour Postgres seul, il n'y a pas cette solution de contournement car Postgres est conforme à la norme SQL. Mais si:
… Alors une solution de contournement comme celle-ci est nécessaire.
Certaines bases de données n'autorisent pas plusieurs valeurs nulles, par exemple le documentation SQL Server indique que "plusieurs valeurs nulles sont considérées comme des doublons". Sur les bases de données qui n'autorisent pas les contraintes UNIQUES nullables, vous pouvez essayer ceci (de réponse de GuidoG à une autre question):
CREATE UNIQUE NONCLUSTERED INDEX IDX_Email
ON MMCompany (Email)
WHERE Email IS NOT NULL;
Déposez la colonne des e-mails du tableau. Mettez-le dans une nouvelle table où il peut être NON NUL et UNIQUE:
CREATE TABLE CompanyEmail
(
CompanyUniqueID INT NOT NULL PRIMARY KEY
REFERENCES MMCompany (CompanyUniqueID),
Email VARCHAR(75) NOT NULL UNIQUE
);
Évitez les contraintes UNIQUES nullables.
Unique et null ne s'entendent pas beaucoup, puisque null n'est pas défini par définition - vous ne pouvez pas savoir si deux null sont la même inconnue.
En ce sens, votre contrainte unique actuelle sur le courrier électronique est la bonne chose à faire et devrait fonctionner telle quelle.
Dans le cas où vous auriez besoin de le faire autrement, cependant, un index partiel fonctionne:
create unique index on MMCompany((email is null)) where (email is null);
Une autre approche consiste à définir un déclencheur de contrainte. Quelque chose comme:
create function email_chk() returns trigger as $$
begin
if exists (
select 1 from mmcompany where email is null and companyuniqueid <> new.id
) then
raise 'dup null found';
end if;
return null;
end;
$$ language plpgsql;
create constraint trigger after insert or update on mmcompany
for each row when (new.email is null)
execute procedure email_chk();