Violation volontaire de contrainte d'unicité

Violation volontaire de contrainte d'unicité - SQL/NoSQL - Programmation

Marsh Posté le 29-05-2015 à 03:39:40    

Hello,

 

Je veux faire en sorte qu'un script insère des entrées dans ma base de données, sauf si ces entrées existent déjà (auquel cas il ne fait rien).

 

Le premier truc auquel j'ai pensé consistait à vérifier, entrée par entrée, si il n'y a pas déjà une ligne identique qui existe dans la table, si oui, ne rien faire, et si non, ajouter. Sauf qu'à chaque fois que mon script tournera, la plupart de ce que je tenterai d'insérer existera déjà (les nouveautés seront l'exception plutôt que la règle), du coup ça fait beaucoup de requêtes pour rien je trouve.

 

Le moyen le plus efficace (pour limiter le nombre de requêtes SQL) auquel j'arrive à penser consiste à mettre une contrainte d'unicité sur un ou plusieurs de mes champs, insérer tout un array de données (enfin plutôt une matrice comme ça, en fait) d'un coup dans la table, avec un IGNORE au milieu, dans l'espoir que pour les lignes qui n'existent pas déjà, elles soient ajoutés, et pour les autres qui existent déjà, rien ne se passe. (Si je dis pas de bêtises, il me semble que ça fait une seule grosse requête et que c'est mieux que plein de petites requêtes)

 

Questions :
- est-ce que ça marchera comme je le souhaite ? (autrement dit, je veux être sur que le fait qu'une ligne existe déjà n'empêchera pas les lignes qui sont nouvelles d'être ajoutées - ou encore autrement dit, que l'IGNORE ne s'applique que sur les lignes qui violent la contrainte d'unicité et pas sur toute la requête)
- est-ce que cette deuxième manière de faire est effectivement meilleure que la première ? (ou équivalente ou pire, et dans ces cas là, pourquoi ?) Et éventuellement, s'il y a une meilleure manière de faire, je prends.

 

Merci d'avance


Message édité par saint malo le 29-05-2015 à 10:22:42
Reply

Marsh Posté le 29-05-2015 à 03:39:40   

Reply

Marsh Posté le 29-05-2015 à 09:45:14    

-oui
-la seconde manière sera la plus efficace : une seule requete, tout est fait dans une seule transaction


---------------

Reply

Marsh Posté le 29-05-2015 à 10:23:21    

Taupe !
Merci :)

Reply

Marsh Posté le 09-06-2015 à 17:49:21    

Salut,
 
Sinon tu peux aussi faire ça en passant par une table temporaire, par exemple si tu as une table UTILISATEUR et que tu reçois des fichiers de nouveaux utilisateurs à insérer :  
INSERT INTO UTILISATEUR U
(
SELECT ID, NOM, PRENOM
FROM NOUVEAUX_UTILISATEURS NU
)
WHERE NOT EXISTS
(
SELECT 1
FROM UTILISATEUR U2
WHERE U2.ID = NU.ID  
-- a priori l'ID est unique
)


Message édité par Tibar le 09-06-2015 à 17:51:03
Reply

Marsh Posté le 14-06-2015 à 03:40:55    

J'ai pas bien compris ce que fait et comment fonctionne cette deuxième solution pour être honnête :)

 

Petite question sur la solution que j'ai envisagée : est-ce qu'il y a un moyen de dire à ma base "tente d'insérer ces 5 entrées, si les 5 sont insérées, très bien, mais si au moins l'une des 5 existe déjà, n'ajoute aucune des 5 entrées et dis moi laquelle était conflictuelle" ?

Message cité 1 fois
Message édité par saint malo le 14-06-2015 à 03:41:02
Reply

Marsh Posté le 14-06-2015 à 03:56:39    

Salut,

 

Tu peux regarder ici : http://stackoverflow.com/questions [...] sql-server

 

Il y les 3 méthode expliquées...

 

Ce que j'ai écrit se lit comme ça :
Insére dans la table UTILSATEUR les valeurs ID, NOM, PRENOM de la table temporaire (les donnees qui viennent d'un fichier, d'une autre table, etc.) où il n'existe pas dans la table UTILISATEUR d'identifiant identique à celui de la table temporaire.

Reply

Marsh Posté le 14-06-2015 à 04:04:36    

Sinon, pour répondre à ta demande, je ne vois pas de solution en une seule étape, le plus simple que je vois consisterait à compter si tu as des utilisateurs communs entre ta table finale et ta table temporaire, comme ceci par exemple :

 

Select count(*)
From UTILISATEUR U
where exists
(Select 1 from nouveau_utilisateur NU
Where NU.id = U.id)

 

Si tu as 0 c'est que tous tes nouveaux utilisateurs pourront être insérés sans problème, si tu plus de 0, c'est qu'au moins un utilisateur est déjà présent, et peux le trouver en faisant

 

Select *
From UTILISATEUR U
inner join NOUVEAU_UTILISATEUR NU on NU.id = U.id

 

Ça va te donner toutes les lignes de nouveaux utilisateurs que tu voudrais insérer dans utilisateur et qui existent déjà.

Reply

Marsh Posté le 16-06-2015 à 18:38:57    

Tibar a écrit :

Sinon, pour répondre à ta demande, je ne vois pas de solution en une seule étape, le plus simple que je vois consisterait à compter si tu as des utilisateurs communs entre ta table finale et ta table temporaire, comme ceci par exemple :
 
Select count(*)
From UTILISATEUR U
where exists
(Select 1 from nouveau_utilisateur NU
Where NU.id = U.id)
 
Si tu as 0 c'est que tous tes nouveaux utilisateurs pourront être insérés sans problème, si tu plus de 0, c'est qu'au moins un utilisateur est déjà présent, et peux le trouver en faisant  
 
Select *
From UTILISATEUR U
inner join NOUVEAU_UTILISATEUR NU on NU.id = U.id
 
Ça va te donner toutes les lignes de nouveaux utilisateurs que tu voudrais insérer dans utilisateur et qui existent déjà.


 
Oookay, mais là ça interdira les doublons aux IDs identiques, c'est ça ? Mettons que je veuille interdire deux utilisateurs aux noms identiques, il faut que je remplace " NU.id = U.id " par genre " NU.nom=U.nom " à chaque fois, c'est bien ça ? (vu que leurs ID respectifs dans la table U et la table temporaire NU ont pas de raison d'être les mêmes il me semble...)
Ou j'ai mal compris ?
 
Autre question, en php (avec pdo), je vais répéter plusieurs fois l'opération, je ferais mieux de :
1) recréer la table temporaire au début de chaque tour de boucle et mettre un drop temporary table à la fin à chaque fois ?
ou
2) créer la table temporaire une seule fois avant ma boucle, et à la fin de chacune de mes boucles, vider la table sans la drop ? (Et du coup, il vaudrait mieux la vider avec delete ou truncate ou une autre commande que je ne connais pas ?)
 
(A l'instinct, je dirais l'option 2 mais je demande quand même... :) )
 
Merci pour l'aide en tout cas !

Reply

Marsh Posté le 16-06-2015 à 19:02:34    

1) Il n'y a que toi qui puisses garantir qu'il n'y aura pas de doublons d'id, par construction de tes différentes tables.
2) Oui, si tu es sûr de ne pas avoir d'id en double et que tu ne veux pas de noms en double, tu peux remplacer u.id = NU.id par u.nom=NU.nom
3) Je ne comprends pas pourquoi tu parles de boucler, à moins que tu aies plusieurs fichiers avec des nouveaux utilisateurs. Dans ce cas, une bonne méthode est de créer un champ ID_FICHIER dans tes deux tables (temporaire et définitive), de créer une table FICHIER avec quelques champs ID_FICHIER, NOM_FICHIER, CHEMIN_FICHIER, DATE_RECEPTION, DATE_TRAITEMENT, de mettre toutes tes données dans ta table temporaire, ça te permettra de vérifier les doublons avant insertion définitive, de vérifier dans tes données sources que tu n'as pas d'erreur, puis de lancer l'intégration dans ta table définitive en ayant une trace de l'origine de cet utilisateur.
Les champs techniques ne sont pas assez souvent utilisés, et on en a toujours besoin un jour ou l'autre (par exemple date de création, date de mise à jour, processus de création, processus de mise à jour, et dans ton cas présent, fichier source).
Ça complexifie un tout petit peu les traitements (penser à modifier les dates, les processus etc., mais en cas de fausse manip ou de bug, la correction est souvent bien plus simple).


Message édité par Tibar le 16-06-2015 à 20:30:48
Reply

Marsh Posté le 17-06-2015 à 16:28:31    

saint malo a écrit :

Petite question sur la solution que j'ai envisagée : est-ce qu'il y a un moyen de dire à ma base "tente d'insérer ces 5 entrées, si les 5 sont insérées, très bien, mais si au moins l'une des 5 existe déjà, n'ajoute aucune des 5 entrées et dis moi laquelle était conflictuelle" ?


Ouaip, suffit de declarer une contrainte d'unicite et de laisser la base s'en occuper.
Bon ca depend p'tetre de ta base cela dit.
 
Pour ta derniere question, je dirais solution 2, mais bon a moins que tu fasse des millions de boucles, ca changera rien niveau perf je pense. La deux est p'tetre plus "propre" c'est tout.
 
Sinon +1 pour ce que dit Tibar


---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 17-06-2015 à 16:28:31   

Reply

Marsh Posté le 23-06-2015 à 12:35:40    

Okidoki, merci pour vos réponses.
 
Petite question supplémentaire :
 

Tibar a écrit :


Select *
From UTILISATEUR U
inner join NOUVEAU_UTILISATEUR NU on NU.id = U.id


 
Avec ce code, est-ce que les lignes sortent dans le même ordre que dans la table NU (s'il y a plus d'une ligne déjà présente bien sur) ?
 
Si non, est-ce que j'ai un moyen de faire en sorte que ce soit le cas ?

Reply

Marsh Posté le 23-06-2015 à 13:53:08    

Salut,

 

En sql, tu peux utiliser ORDER BY pour trier le résultat de ta requête, ici tu fais
SELECT...
INNER JOIN...
ORDER BY NU.ID

 

Sans cette clause, le résultat peut te sembler trié mais le seul moyen d'être sûr est de mettre le ORDER BY, que tu peux compléter avec ASC pour l'ordre croissant ou DESC pour l'ordre décroissant.

Reply

Marsh Posté le 25-06-2015 à 10:12:14    

Bon du coup j'ai changé la construction de mon truc pour pouvoir utiliser un order by et faire en sorte que ça fonctionne. Merci à tous pour l'aide !


Message édité par saint malo le 25-06-2015 à 10:12:29
Reply

Marsh Posté le 25-06-2015 à 18:15:42    

Tibar a écrit :

...le plus simple que je vois consisterait à compter si tu as des utilisateurs communs entre ta table finale et ta table temporaire, comme ceci par exemple :
 
Select count(*)
From UTILISATEUR U
where exists
(Select 1 from nouveau_utilisateur NU
Where NU.id = U.id)
 
Si tu as 0 c'est que tous tes nouveaux utilisateurs pourront être insérés sans problème, si tu plus de 0, c'est qu'au moins un utilisateur est déjà présent, et peux le trouver en faisant  


Désolé, je reviens à la charge, j'ai un souci. Pour la requête où je compte le nombre de mots communs entre la table temporaire et la table permanente, j'utilise ce code (nb : j'utilise PDO) :
 

Code :
  1. $qryCondition = $bdd->prepare('SELECT COUNT(*)
  2.  FROM tableA a
  3.  WHERE EXISTS
  4.   (SELECT 1 from TMP_tableB b
  5.   WHERE a.mot = b.mot)'
  6. );
  7. $qryCondition->execute();
  8. $condition = $qryCondition->fetchAll();


J'espérais que $condition me donnerait le nombre de mots communs entre les deux tables, et donc pouvoir l'utiliser comme ça par exemple :
 

Code :
  1. if ($condition == 0) {
  2. //certaines actions
  3. } elseif ($condition > 0) {
  4. //d'autres actions
  5. }


Mais ça ne fonctionne visiblement pas. var_dump($condition); me donne ça à chaque fois, quelque soit le nombre de mots communs entre les deux tables, que ce soit plusieurs, un seul, ou même s'il n'y en a aucun :
 

Code :
  1. array(1) {
  2.  [0]=> array(2) {
  3.   ["COUNT(*)"]=> string(1) "0"
  4.   [0]=> string(1) "0"
  5.  }
  6. }


 
Une idée de ce qui ne va pas ? C'est le code php ?
 
Merci d'avance :)

Reply

Marsh Posté le 26-06-2015 à 00:12:52    

Sans passer par le code PHP, ta requête te donne quoi ?
 
Pour analyser, tu peux essayer de remplacer le  
SELECT COUNT(*)
par
SELECT *
 
et si tu veux aller plus en détail, tu fais  
 
SELECT *
FROM tableA A
INNER JOIN tableB B ON B.mot = A.mot
 
ça devrait te sortir toutes les lignes où tu as un mot identique entre la table tableA et la table tableB
 
Si ça ne sort rien, c'est qu'il n'y a aucun mot de A qui a une valeur identique dans B, si visuellement tu es sûr que c'est le cas, tu peux déjà mettre un ou deux exemples du contenu de tes tables, et ensuite essayer de jouer avec UPPER(A.mot) = UPPER(B.mot) (pour s'affranchir de la casse), les TRIM(A.mot) = TRIM(B.mot) pour les espaces avant et après tes valeurs (par exemple '  test' est différent de 'test'), ensuite on pourra regarder avec COLLATE pour s'affranchir des accents, des caractères spéciaux etc.
 
Si par contre ta requête renvoie un résultat, c'est que c'est le code PHP, et là je laisse la main ;-)

Reply

Marsh Posté le 26-06-2015 à 09:39:41    

Par construction, normalement, les majuscules et accents sont tous supprimés. Pour les espaces je ne sais pas, mais à priori en regardant ce qui est dans la base permanente, le problème ne vient pas de là non plus.
 
Quoi qu'il en soit, j'ai testé avec 0, 1 et plusieurs mots clés identiques entre les deux bases (et aussi avec 0, 1 et plusieurs mots clés qui sont dans l'une sans être dans l'autre) sur phpMyAdmin, et à chaque fois la requête donnait la bonne réponse, donc à priori le souci ne vient pas de la requête. Du coup je vais reposter la question dans la partie PHP de HFR. Merci ;)

Reply

Marsh Posté le 26-06-2015 à 11:33:46    

Par sécurité, je vais quand même mettre des trim partout où ça a du sens, on verra...

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed