Carnet Wiki

Difficultés avec jointures et solutions

Ressources

Docs
-  spip.net : Les jointures entre tables
-  https://programmer.spip.net/-Liaisons-entre-tables-jointures-
-  b_b : Divagations sur les jointures avec SPIP
-  Wiki Contrib : Jointures.

Tickets

-  https://git.spip.net/spip/spip/issues/3894
-  https://git.spip.net/spip/spip/issues/2940
-  https://git.spip.net/spip/medias/issues/4900
-  d’autres

Accés aux champs homonymes via BOUCLE DATA SQL

Cf https://discuter.spip.net/t/jointures-et-selections-de-champs-identiques/175285

Dans une jointure sur plusieurs tables SPIP (articles, rubriques, motclés) on se retrouve avec plusieurs champs « titre » homonymes. Comment accéder à l’un ou à l’autre ?
Pour cela on peut utiliser une boucle DATA avec source sql pour boucler sur les résultats de la requête, et utiliser des alias dans le SELECT pour en interroger la valeur au moyen de la balise #VALEUR.

0) On écrit la boucle de base avec SPIP (boucle non DATA, avec jointure automatique) sans gérer les alias.
Dans le cas présent, la boucle est celle ci :

<BOUCLE_art(ARTICLES)
    {id_mot=#GET{listemc}}{id_groupe IN 2,3,9}
    {par date}{inverse}{0,8}{doublons}
    {rubriques.titre}>

1) Avec var_mode=debug on obtient le source de la requête qu’il sert de base pour la suite.
Pour simplifier son usage dans le squelette, on crée un filtre get_query_jointure dans le fichiers de _fonctions.php : cette fonction reçoit $id_mot et renvoie la query, écrite sur une seule ligne (sans retour à la ligne).
À la query récupérée, on ajoute 2 alias dans le SELECT sur les champs homonymes désirés : L3.titre as l3_titre, L4.titre as l4_titre.

2) Ensuite la boucle DATA spécifie le retour de cette fonction comme valeur de la source sql et peut utiliser les alias avec #VALEUR

Donc dans le _fonctions.php :

function get_query_jointure(?int $id_mot=0): string {
	$id_mot = ($id_mot ?: 3);
	return "SELECT articles.date, articles.id_article, articles.id_rubrique, articles.titre, articles.id_rubrique, articles.id_article, articles.lang, L3.titre as l3_titre, L4.titre as l4_titre   FROM spip_articles AS `articles` INNER JOIN spip_rubriques AS L4 ON ( L4.id_rubrique = articles.id_rubrique )  INNER JOIN spip_mots_liens AS L2 ON ( L2.id_objet = articles.id_article AND L2.objet='article') INNER JOIN spip_mots AS L3 ON ( L3.id_mot = L2.id_mot )  INNER JOIN spip_mots_liens AS L1 ON ( L1.id_objet = articles.id_article AND L1.objet='article') WHERE (articles.statut = 'publie') 	AND (L1.objet = 'article') 	AND (L1.id_mot = $id_mot) 	AND (L2.objet = 'article') GROUP BY articles.id_article ORDER BY articles.date DESC LIMIT 0,8";
}

Et ensuite la boucle

<B_ma_requete><ul>
<BOUCLE_ma_requete(DATA){source sql,#GET{q}}>
<li>CLE:#CLE l3_titre:#VALEUR{l3_titre} l4_titre:#VALEUR{l4_titre}</li>
</BOUCLE_ma_requete>
</ul></B_ma_requete>
Pas de résultats
<//B_ma_requete>

Ingrédients du Savoir

Référence : https://discuter.spip.net/t/a-laide...

Il est très simple avec ?var_mode=debug de voir comment une boucle est compilée et comment spip crée, ou pas, une jointure.

JLuc : Ce qui est automatique dans les jointures, ainsi que la doc l’indique, c’est le choix du champ sur lequel faire la jointure. Et donc pas forcément le choix des tables, qu’il faut parfois encore expliciter dans la boucle, selon les déclarations php qu’on fait ou qu’on ne fait pas. Par exemple, dans une situation où
-  il existe une table t_cccc avec une clé primaire id_cccc
-  il existe une table t_dddd qui contient un champ id_cccc
-  on ne rien déclare du tout à SPIP en PHP, ni tables, ni champs, ni jointures etc, ce qui a la vertu de la simplicité de permettre de « SPIPer direct clé en main sans prise de tête »
alors, des boucles <BOUCLE_ouafouaf(spip_cccc spip_dddd){id_cccc}> par exemple sont correctement compilées (avec, donc, les 2 tables explicitement indiquées)

Cerdic : Les jointures ne se font que via des champs qui sont aussi des clés de la table. La déclaration join est un helper pour les cas ou il faut faire la jointure entre des champs pas homonymes par exemple.

Avec un objet CHIEN et un objet RACE, et un champ id_race déclaré sur chien, pour qu’une jointure entre spip_chiens et spip_race puisse se faire, il faut que le champ id_race de la table spip_chiens soit déclaré en index de la table, comme le champ id_rubrique de la table spip_articles dans le noyau de SPIP.

Accès à champ homonyme via un critère

Thème : alias et type de join - Auteur : John Livingston et JMT19 - Source : https://code.globenet.org/attacfr/campagnodon/-/commit/d2b516b1e3e05fc4598973ae037cf868843b736d - Autre réf : ticket https://git.spip.net/spip/spip/issues/5203#issuecomment-37646 + PR https://git.spip.net/spip/spip/pulls/5205

Les 2 tables campagnodon_transactions et transactions ont toutes 2 un champ mode.
Dans une jointure <BOUCLE_liste_campagnodon_transactions(CAMPAGNODON_TRANSACTIONS transactions) on ne peut accéder qu’au #MODE de la 1re table : campagnodon_transactions

De plus, on souhaite que la jointure soit de type LEFT JOIN alors que SPIP fait par défaut des INNER JOIN.

Soluce

On y parvient au moyen du critère suivant, qui déclare la jointure au lieu que ça soit la boucle, la déclare de type LEFT JOIN et crée un alias sur le champ. La balise qui suit permet ensuite d’y accéder.

function critere_campagnodon_jointure_transactions_dist($idb, &$boucles, $crit) {
  $boucle = &$boucles[$idb];
  $boucle->from['transactions'] = 'spip_transactions';
  $boucle->from_type['transactions'] = 'LEFT';
  // le format de join est :
  // array(table depart, cle depart [,cle arrivee[,condition optionnelle and ...]])
  $boucle->join['transactions'] = array("'campagnodon_transactions'", "'id_transaction'", "'id_transaction'");
  $boucle->select[] = 'transactions.mode AS transaction_mode'; // ce champ a un homonyme dans campagnodon_transactions, on contourne.
}

function balise_TRANSACTION_MODE_dist($p) {
    return rindex_pile($p, 'transaction_mode', 'campagnodon_jointure_transactions');
}

Usage

<BOUCLE_liste_campagnodon_transactions(CAMPAGNODON_TRANSACTIONS)
    {campagnodon_jointure_transactions}
    {statut_synchronisation?=#ENV{statut_synchronisation}}
    {tri #ENV{par,id_campagnodon_transaction},#GET{defaut_tri}}
    {pagination #ENV{nb,10}}
            title="<:info_numero_abbreviation|attribut_html:> #ID_TRANSACTION">#ID_TRANSACTION</a>
        </td>
        <td class="statut">[(#STATUT|match{echec|ok|rembourse}|et{#MESSAGE|trim|strlen}|?{[<abbr title="[(#MESSAGE|attribut_html)[ - (#ERREUR|attribut_html)]]">(#STATUT)</abbr>],#STATUT})]</td>
        <td class="transaction_mode">[(#TRANSACTION_MODE|bank_afficher_mode)]</td>
        <td class="montant">[(#MONTANT|bank_affiche_montant)]</td>
        <td class="mode">#MODE</td>
...
</BOUCLE_liste_campagnodon_transactions>

Alternative

JMT19 : une simple déclaration suffit dans le pipeline table interface :
$interfaces['exceptions_des_tables']['spip_transactions']['transaction_mode'] = array('campagnodon_transaction', 'mode');

#TRANSACTION_MODE doit alors fonctionner nickel, ainsi que les critères des tris.

À tester et confirmer !

Forcer une jointure dans une boucle avec critère conditionnel

Auteur Stéphane Santon - ref https://discuter.spip.net/t/erreur-sql-sur-boucle/164214

La boucle suivante

<BOUCLE_rubs(ARTICLES){id_mot ?IN #ENV**{sections}}{0,12}>
    #SET{etabs, #GET{etabs}|push{#ID_RUBRIQUE}}
</BOUCLE_rubs>

provoque l’erreur suivante quand il y a une valeur de sections dans l’environnement :

Unknown column 'L1.id_mot' in 'order clause'
SELECT articles.id_rubrique, articles.lang, articles.titre FROM spip_articles AS articles WHERE (articles.statut = 'publie') GROUP BY articles.id_article ORDER BY FIELD(L1.id_mot,5) LIMIT 0,12

Solution
Cette erreur se produit car aucun champ de la table id_mot n’est utilisé dans le corops de la boucle Elle n’apparaît pas si le corps de la boucle utilise #ID_MOT car alors SPIP comprend bien qu’il a besoin de cette jointure.
Une soluce est donc d’y insérer [(#ID_MOT|oui)] pour forcer le compilateur SPIP à inclure la jointure même avec un critère conditionnel.

JLuc - Mise à jour :16 mars 2024 à 13h16min