2011-07-02 10 views
3

J'ai une table 'users' avec deux colonnes, 'email' et 'new_email'. J'ai besoin:Inclure plusieurs colonnes dans un seul index dans Postgres

  • Une contrainte d'unicité insensible à la casse qui couvre les deux colonnes - à savoir, si « [email protected] » apparaît dans la colonne « e-mail » d'une ligne, insérer « [email protected] » dans un autre La colonne 'new_email' de la ligne (ou même celle de la même ligne) devrait échouer.

  • Recherche rapide dans la casse d'une adresse e-mail donnée dans les champs 'email' ou 'nouveau_email' - c'est-à-dire que vous trouvez la ligne où le nouvel email OR est "[email protected]", insensible à la casse.

Je sais que je pourrais le faire plus facilement en créant une table reliée « e-mails », mais je prévois être à la recherche des utilisateurs dans ce tableau (par clé primaire) de plusieurs applications, et je J'aime éviter de dupliquer la logique de jointure à différents endroits pour récupérer également leurs emails. Je pense donc qu'un index d'expression serait préférable, si c'est possible. Si ce n'est pas possible, je suppose que ma meilleure option serait de créer une vue que les autres applications pourraient utiliser pour récupérer facilement les emails d'un utilisateur avec leurs autres informations, mais je ne sais pas comment faire ça non plus. J'utilise Postgres 8.4. Je vous remercie!

Répondre

4

Je pense que vous devrez utiliser un déclencheur pour appliquer votre contrainte d'unicité de colonne croisée. Si vous ajoutez des index uniques sur chaque colonne, puis un quelque chose de déclencheur comme celui-ci (non testé sur le haut de mon code de tête):

CREATE FUNCTION no_dups_allowed() RETURNS trigger AS $$ 
DECLARE 
    r ROW; 
BEGIN 
    SELECT 1 INTO r 
    FROM users 
    WHERE LOWER(email)  = LOWER(NEW.email_new) 
     OR LOWER(email_new) = LOWER(NEW.email); 
    IF FOUND THEN 
     -- Found a duplicate so it is time for a hissy fit! 
     RAISE 'Duplicate email address found' USING ERRCODE = 'unique_violation'; 
    END; 
    RETURN NEW; 
END; 
$$ LANGUAGE plpgsql; 

Vous voudriez quelque chose comme ça comme INSERT AVANT et AVANT déclencheur UPDATE. Ce déclencheur prendrait soin d'intercepter les doublons inter-colonnes et les index uniques prendraient en charge les doublons en colonne.

Quelques références utiles:

Vous voulez que les indices individuels pour vos requêtes de toute façon et en utilisant la moitié de l'unicité les index simplifie y notre déclencheur en le laissant uniquement traiter de la partie de la colonne transversale; Si vous essayez de tout faire dans le trigger, alors vous devrez faire attention à la mise à jour d'une ligne sans vraiment changer les colonnes email ou email_new.

Pour la moitié d'interrogation, vous pouvez create a view qui a utilisé un UNION pour combiner les deux colonnes. Vous pouvez également créer une fonction pour fusionner les adresses e-mail de l'utilisateur en une seule liste. Difficile de dire lequel serait le mieux sans connaître plus de détails de ces autres requêtes mais je soupçonne que la fixation de toutes les autres requêtes à savoir sur email et email_new serait la meilleure approche; vous devrez mettre à jour toutes les autres requêtes pour utiliser la vue ou la fonction de toute façon alors pourquoi construire une vue ou une fonction?

+0

Deux minuscules fonctions 'UNIQUE INDEX' avec un contrôle de table est votre meilleur choix. Il n'est pas nécessaire d'invoquer un déclencheur pour résoudre ce problème. Utilisez une dérivation de ce que @Scott suggère ci-dessous. – Sean

+0

@Sean: La solution de Scott est sympa mais la COALESCE laisse un trou. –

+0

D'accord, je n'utiliserais pas COALESCE ou deux colonnes pour stocker les valeurs anciennes et actuelles dans une seule table. – Sean

0

Pas besoin de déclencheurs.Essayez ceci:

create table et (email text, email2 text); 
create unique index et_u on et (coalesce(lower(email),lower(email2))); 
insert into et (email,email2) values ('[email protected]',NULL); 
insert into et (email,email2) values ('[email protected]',NULL); 
ERROR: duplicate key value violates unique constraint "et_u" 
insert into et (email,email2) values (NULL,'[email protected]'); 
ERROR: duplicate key value violates unique constraint "et_u" 
insert into et (email,email2) values (NULL,'[email protected]'); 
ERROR: duplicate key value violates unique constraint "et_u" 
+0

Eh bien, ce n'est pas parfait, je peux entrer le même courriel dans les deux champs en même temps. Vous aurez donc également besoin d'une contrainte de vérification par email <> email2 ici. alter table et ajouter la contrainte et_nn check (email <> email2); –

+0

C'est intéressant. Postgres serait-il assez intelligent pour utiliser cet index sur les requêtes d'une adresse e-mail? Comme 'WHERE lower (email) = 'scott @ gmail.com''? Ou y a-t-il une autre façon de formuler la requête pour l'obtenir? – PreciousBodilyFluids

+0

Cela échoue si vous insérez '(NULL, 'xx @ example.com')' et ensuite '('[email protected]', '[email protected]')' quand il n'y a pas de "yy @ example" .com "adresse encore, le COALESCE cache efficacement le' email2' des contrôles d'unicité quand 'email' n'est pas NULL. –

Questions connexes