Update Inner Join Limit 1

Eléphanteau du PHP | 33 Messages

13 mars 2017, 10:51

Salut,

Je n'arrive pas à parvenir à mes fins pour une effectuer un update somme toute assez simple.
J'ai besoin de faire une jointure pour cette update, or il n'est pas possible d'utiliser l'option "LIMIT 1".
Du coup je ne sais pas comment m'y prendre car il y a plusieurs correspondances de possible, et je ne voudrais retenir que le premier résultat résultant de la condition de jointure, car sinon d'une part ça prend beaucoup de trop de temps, et d'une autre béh il y a plus de résultats que d'enregistrements de possible ... En plus clair :

J'ai 2 tables :
  • une contenant des codes nafs (entiers ou juste les 3/4 premiers chiffres) + leurs intitulés (métier / secteur d'activité)
  • une autre assez conséquante contenant des données d'entreprises, où figurent leurs codes NAF entier, mais pas les intitulés sus-nommés

Donc tout simplement je voudrais pour chaque enregistrement de la table 2 aller piocher le premier résultat dans la table 1 les intitulés avec la condition :

Code : Tout sélectionner

table2.code_naf like CONCAT(table1.codes_naf, '%')
Si je lance la requête suivante, ça mouline c'est interminable et j'ai trop de résultat, car plusieurs correspondances ... d'où la nécessité de n'en retenir qu'une seule :

Code : Tout sélectionner

update table2 AS table2 inner join table1 as table1 set table2.menu = table1.menu, table2.ss_menu = table1.ss_menu where table2.code_naf like CONCAT(table1.codes_naf, '%')
Comment faire ?

Merci de vos lumières !

Avatar de l’utilisateur
Modérateur PHPfrance
Modérateur PHPfrance | 8755 Messages

13 mars 2017, 12:40

salut,

As-tu essayé d'utiliser un subselect pour le prédicat tu pourras y mettre la limit ;)


@+
Il en faut peu pour être heureux ......

Eléphanteau du PHP | 33 Messages

13 mars 2017, 13:07

Merci de la réactivité !

Avant de construire mon update, si je test avec un select avec "limit 1", forcément je n'obtiens qu'un seul résultat !
Alors qu'il y en a beaucoup plus ... je dois mal raisonner.
Il me faudrait X résultats (X=nb total de lignes de table2), qui contiennent les intitulés piochés dans la table1 avec la condition :

Code : Tout sélectionner

table2.code_naf like CONCAT(table1.codes_naf, '%')
Ce Select (qui pourrait être le subselect de l'update ?) me retourne une seule ligne avec le "limit 1" :

Code : Tout sélectionner

select table1.menu, table1.ss_menu from table 1 inner join table2 where table2.code_naf like CONCAT(table1.codes_nafi, '%') limit 1;
retourne :

+-----------+----------+
| menu | ss_menu |
+-----------+----------+
| NUMÉRIQUE | Start-up |
+-----------+----------+
1 row in set (0.13 sec)

Donc si je mets ça en subselect (je ne sais pas trop comment d'ailleurs), ça va pas aller ...

Je raisonne mal ...
Je m'en sortirais avec un script PHP pour mais en SQL avec une seule requête :shock:

Eléphanteau du PHP | 33 Messages

13 mars 2017, 13:40

Quelque-chose comme ça ?

Code : Tout sélectionner

update table2 AS table2 inner join table1 as table1 set table2.menu = table1.menu, table2.ss_menu = table1.ss_menu where (select 1 from table1 where table2.code_naf like CONCAT(table1.codes_naf, '%') limit 1)

Avatar de l’utilisateur
Modérateur PHPfrance
Modérateur PHPfrance | 8755 Messages

13 mars 2017, 13:51

je pensais plus à un truc dans le style
update table2 AS table2 
inner join table1 as table1 
set table2.menu = table1.menu, table2.ss_menu = table1.ss_menu 
where table2.code_naf like CONCAT (select codes_naf from table1 t1 where t1.codes_naf =table1.codes_naf) limit 1), '%')
mais du coup c'est débile voir pire que ce que tu as

Ce que tu souhaites faire ne semble pas clair.

tu veux mettre a jour des valeurs de table2 a partir de table1 sachant que tu n'as pas un code complet ?
j'y vois un risque de mettre le souk si tu ne peux pas t'appuyer sur une donnée fiable (il y a peux être des codes : 12, 123, ou 123456 et la c'est le drame ;) ).

ce qu'il faut c'est être certain de la correspondances pour mettre les bonnes valeurs.

il y a plus grossiers mais peux être fonctionnel
update table2 t2
set 
menu = (select menu from table1 t1 where t1.code_naf like concat( t2.codes_naf,'%'))
ss_menu =  (select ss_menu from table1 t1 where t1.code_naf like concat( t2.codes_naf,'%'))
on pourrait aussi imaginer
update table2 t2
set 
menu = menu1,
ss_menu = ssmenu1
from (
    select menu from table1 t1 where t1.code_naf like concat( t2.codes_naf,'%')
)
bon a priori mysql n'aime pas la référence de la table que tu mets à jours dans le prédicat.

du coup reste la jointure ou une procédure stockée qui va parcourir la table et faire les select qui vont bien, le tout dans le même transaction qui lock la table pour éviter les soucis).



pour infos tes alias sont inutiles (dire que table1 est renommer en table1 c'est un peu redondant ;) )

@+
Il en faut peu pour être heureux ......

Eléphanteau du PHP | 33 Messages

13 mars 2017, 14:05

Effectivement il y a des cas ou il va se présenter : 12, 123, 1234

Je crois qu'il vaille mieux que je passe par un script php (qu'en penses-tu?) car il faudrait, pour être plus clair :
- dans un 1er temps insérer les intitulés de table2 lorsque les code nafs se "match" exactement en entier
- dans un 2nd temps ceux qui "match" les 4 premiers chiffres
- puis les 3
- puis le 2 !

Je pensais en pure SQL lancer plusieurs requêtes à la suite pour répondre à chacune de ces étapes, avec "=", puis utiliser SUBSTR, puis "like concat §%" ...
Comme j'ai peur du résultat, je pense passer par php et créer un fichier journal pour vérifs des étapes ...

En SQL là mmhhhh ... je vois pas ...
D'ailleurs la requête que j'ai lancé ci-dessous (local via WAMP / Mysql Console) mouline depuis 30 minutes .... ça va me foutre le bordel :
> hop CTRL + C !

Code : Tout sélectionner

update table2 AS table2 inner join table1 as table1 set table2.menu = table1.menu, table2.ss_menu = table1.ss_menu where (select 1 from table1 where table2.code_naf like CONCAT(table1.codes_naf, '%') limit 1)

Eléphanteau du PHP | 33 Messages

13 mars 2017, 16:41

Il doit bien y avoir une équivalence en une seule requête SQL pour ce code php :

Code : Tout sélectionner

$db = new PDO('mysql:host=localhost;dbname=bases;charset=utf8mb4', 'root', ''); $stmt = $db->query('SELECT * from naf_menu'); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $id_naf_menu = $row['id']; $code = $row['codes_naf_multi']; $menu = $row['menu']; $ss_menu = $row['ss_menu']; echo 'result update : '.$db->exec("UPDATE bdd set id_naf_menu = '$id_naf_menu', menu = '$menu', ss_menu='$ss_menu' where code_naf = '$code'"); }
??? car là ça mouline longtemps en lançant ce script via le navigateur (en local). (il y a 1'300'000 lignes dans 'bdd')
Ca n'irait pas plus vite via la console MySQL ?

Avatar de l’utilisateur
Modérateur PHPfrance
Modérateur PHPfrance | 8755 Messages

13 mars 2017, 18:20

ben avec un procédure stockée c'est pas trop complexe :)
delimiter |
CREATE OR REPLACE PROCEDURE testUpdate ()
BEGIN 

	DECLARE fin BOOLEAN default false;
	-- adpate les types j'ai fait au pif
	DECLARE menu,ss_menu,code varchar(20);
	DECLARE id_naf_menu int;
-- se serait pas mal de mettre les noms des colonnes pour assure le cou pet la lisibilité ;)
	DECLARE naf_cur cursor FOR SELECT id,menu,ss_menu,codes_naf_multi FROM naf_menu; 
	DECLARE CONTINUE HANDLER FOR NOT FOUND SET fin = TRUE;
		
	OPEN naf_cur;
	loop_curseur : LOOP
		FETCH naf_cur into id_naf_menu,menu,ss_menu,code;
		IF fin THEN
            LEAVE loop_curseur;
      END IF;
		UPDATE bdd SET id_naf_menu = @id_naf_menu , menu = @menu, ss_menu= @ss_menu
		WHERE code_naf = @code;
	end LOOP;
	close naf_cur;
end|
delimiter ;
pour l'utiliser
call testUpdate()
après y a plus qu'a attendre, traiter plus d'un million de ligne c'est quand même un peu long.
l'avantage de la procédure stockée c'est que t'es pas forcément obligé d'attendre la fin.


sur la forme ce que tu fait la est quand même beaucoup plus simple que ce tu indiques avant.
as tu tester la dernière forme d'update que je t'ai indiqué ?

@+
Il en faut peu pour être heureux ......

Eléphanteau du PHP | 33 Messages

13 mars 2017, 18:29

Non je n'ai pas testé ton dernier update ... désolé j'ai lancé un script php et j'attends qu'il se termine :(

Il est de la forme qui suit car je voudrais une trace écrite des résultats mais je demande si j'ai bien fait, ça mouline mouline ...

Code : Tout sélectionner

echo "Correspondances exactes !!!<br><br>"; $db = new PDO('mysql:host=localhost;dbname=bases;charset=utf8mb4', 'root', ''); $stmt = $db->query('SELECT id, menu, ss_menu, codes_naf_multi from naf_menu'); $row_count = $stmt->rowCount(); echo $row_count.' lignes dans table de référence naf_menu. <br><br>'; while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { $id_naf_menu = $row['id']; $code = $row['codes_naf_multi']; $menu = $row['menu']; $ss_menu = $row['ss_menu']; $stmt2 = $db->query("SELECT id, code_naf, activite from bdd where code_naf = '$code' and trim(menu) !='' and trim(ss_menu)!=''"); $row_count2 = $stmt2->rowCount(); echo $row_count2." correspondances pour le code $code :<br>"; while($row2 = $stmt2->fetch(PDO::FETCH_ASSOC)) { echo " - $code à insérer pour ".$row2['id']." : ".$row2['code_naf']." : ".$row2['activite']."<br>"; echo ' > result update : '.$db->exec("UPDATE bdd set id_naf_menu = '$id_naf_menu', menu = '$menu', ss_menu='$ss_menu' where code_naf = '$code'"); echo "<br>"; } echo '<br><br>'; }

Eléphanteau du PHP | 33 Messages

13 mars 2017, 19:15

arrggh forcément Fatal error: Maximum execution time of 3600 seconds exceeded

Eléphanteau du PHP | 33 Messages

13 mars 2017, 23:19

Y-a-t-il une différence entre ces 2 requêtes ?

Code : Tout sélectionner

update bdd inner join naf_menu on bdd.code_naf = naf_menu.codes_naf_multi set bdd.menu = naf_menu.menu, bdd.ss_menu = naf_menu.ss_menu where trim(bdd.menu)!="" and trim(bdd.ss_menu) !="";
et :

Code : Tout sélectionner

update bdd inner join naf_menu set bdd.menu = naf_menu.menu, bdd.ss_menu = naf_menu.ss_menu where bdd.code_naf = naf_menu.codes_naf_multi and trim(bdd.menu)!="" and trim(bdd.ss_menu) !="";
?

Car j'ai l'impression en lisant la doc que la condition de jointure "on t1.x = t2.y" doit être unique, porter sur des ids uniques ?
( = qu'on ne recontre qu'une seule fois 'x' dans 't1' et qu'une seule fois 'y' dans 't2' )
Ou je me trompe complètement !? #-o ...

Dans mon cas on trouve plusieurs fois les mêmes valeurs 'codes_naf' dans la table 'bdd' puisque c'est un listing de 1'300'000 contacts ...

Merci de m'éclairer ! :)

Avatar de l’utilisateur
Modérateur PHPfrance
Modérateur PHPfrance | 8755 Messages

14 mars 2017, 14:17

alors une jointure c'est mieux sur un index unique mais cela ne veux pas dire que les deux doivent être unique. Parce que bon sinon tu oublies les relations 1,n ou 0,n :)

Dans la table naf_menu les codes doivent être unique non ?

pour la deuxième la requête est syntaxiquement correcte ? (parce que bon la condition de jointure c'est mieux de l'indiquer ;) )

le max execution time tu devrais pourvoir l'augmenter avec ini_set sur ton script.

@+
Il en faut peu pour être heureux ......

Eléphanteau du PHP | 33 Messages

14 mars 2017, 14:38

Oui les codes naf sont uniques dans la table naf_menu, et dans la table bdd ils sont soient entiers ou sur 2,3 ou 4 chiffres (les premiers).
J'ai créé une colonne 'id' unique primary key en auto-incrément pour chaque table, mais je sais pas si cela a un intérêt du coup (?), car je l'utilise pas dans mes requêtes.
Pour optimiser faudrait-il que je mette les colonnes 'codes_naf_multi' et 'code-naf' en index ? ce sont des varchar(128) ...

La deuxième requête doit être bonne car dans la doc Mysql :

join_condition:
ON conditional_expr
| USING (column_list)

... et j'ai vu ci et là des requêtes sans condition de jointure.
En fait le inner join dans ce cas là me permet d'inclure la condition de jointure dans le 'where'.
Mais j'arrive pas dans ma ptite tête à comprendre la différence entre les 2 requêtes, s'il y en aurait une plus rapide que l'autre, si elles donneraient le même résultat ... j'arrive pas à visualiser ! oO

Pour le max_execution_time je l'ai déjà monté à 3600, mais du coup j'ai basculé sur la console MySQL de Wamp donc pas de limitation.

Merci encore,

Avatar de l’utilisateur
Modérateur PHPfrance
Modérateur PHPfrance | 8755 Messages

14 mars 2017, 15:12

pour l'index oui c'est important, mais seulement sur la table naf_menu. D'ailleurs un index unique sur cette colonne permet d'ajouter une règle métier au niveau de la base. Et du coup devrait apporter aussi un peu de perf.
Par contre si tu as une PK numérique il est préférable de l'utiliser dans les deux tables.
du coup tu éviteras aussi les problèmes de clef partielle (bon du coup faut aussi que le reste suivent et ne pas permettre l'insertion partielle). cela évite les soucis que tu rencontres maintenant ;)

pour la jointure regarde avec un explain, à la limite c'est le plan d’exécution qui change et donc les perf.

le max execution time tu peux le mettre plus, ou alors appeler ton script en ligne de commande et ne pas attendre la fin c'est à exécuter une fois de temps en temps.

@+
Il en faut peu pour être heureux ......

Eléphanteau du PHP | 33 Messages

14 mars 2017, 15:19

Merci moogli !

Pour info voici la dernière requête que j'ai lancé et son résultat elle vient juste de finir (2h22 min.) :

Code : Tout sélectionner

mysql> update bdd -> inner join naf_menu -> on left(bdd.code_naf,4) = naf_menu.codes_naf_multi -> set bdd.menu = naf_menu.menu, bdd.ss_menu = naf_menu.ss_menu -> where trim(bdd.menu) = "" or trim(bdd.ss_menu) = ""; Query OK, 171684 rows affected (2 hours 22 min 28.02 sec) Enregistrements correspondants: 171684 Modifi├®s: 171684 Warnings: 0
Maintenant je lance avec left(bdd.code_naf,4), puis après left(bdd.code_naf,3), puis left(bdd.code_naf,2)
Puis tout devrait être rempli ... normalement ! :priere: