Méthode publique · ORM_CSHX2 · Chargement

mth_ChargerSelonClauseWhere

Charge en mémoire un ou plusieurs enregistrements depuis la base via une clause WHERE SQL, avec options avancées : ORDER BY, verrouillage pessimiste, LIMIT / OFFSET. Hydrate l'objet courant et le tableau interne m_tabResults.

PUBLIQUE SELECT MySQL · MariaDB · PostgreSQL
01

📋 Description

mth_ChargerSelonClauseWhere est le point d'entrée principal pour tout chargement conditionnel d'objets ORM_CSHX2. La méthode construit dynamiquement la requête SELECT à partir des clauses positionnées sur l'objet, exécute la requête, puis hydrate les résultats dans le tableau interne m_tabResults (et l'objet courant pour le premier tuple).

Tous les paramètres sont optionnels et suivent un mécanisme de résolution en deux temps :

• Si un paramètre est laissé à sa valeur par défaut ("" pour les chaînes, 0 pour nLimit/nOFfSet, NoLock pour nTypeLock) → la méthode n'écrase pas le membre correspondant déjà positionné sur l'objet.

• Si un paramètre est renseigné → il est appliqué et écrit dans le membre.

Cela permet deux styles d'appel équivalents : tout passer en paramètres, ou pré-positionner les membres puis appeler la méthode sans argument.

🧹 RAZ post-succès des clauses

En sortie de méthode, et uniquement en cas de succès, les 9 clauses SELECT (p_sClauseWhere, p_sClauseOrderBy, p_nClauseLock, p_nClauseLimit, p_nClauseOffset, p_sClauseSelect, p_sClauseNotSelect, p_sClauseWith, p_sClauseJoin) sont remises à zéro pour garantir qu'aucun état ne fuit entre deux appels successifs.

En cas d'échec, les clauses sont préservées pour permettre le diagnostic côté appelant (logs, retry…).

🔗 CTE & Common Table Expressions

La méthode supporte les CTE (WITH … AS) via le membre p_sClauseWith, à pré-positionner avant l'appel. La jointure sur la CTE se fait via p_sClauseJoin (clause JOIN brute).

📖 MySQL 8.4 — WITH (Common Table Expressions) · PostgreSQL — WITH Queries

02

🔑 Signature

Déclaration
PROCÉDURE mth_ChargerSelonClauseWhere(
  LOCAL sClauseWhere est une chaîne = "",
  LOCAL sClauseOrderBy est une chaîne = "",
  LOCAL nTypeLock est un entier = NoLock,
  LOCAL nLimitNombreTuples est un entier = 0,
  LOCAL nOFfSet est un entier = 0
) : (booléen, entier, chaîne)
ÉlémentValeur
VisibilitéPUBLIQUE

Paramètres directs — chaque paramètre, quand il est non-défaut, écrase le membre correspondant.

ParamètreTypeDéfautMembre associéRôle
sClauseWherechaîne""p_sClauseWhereCondition SQL de filtrage (sans le mot WHERE). Vide → réutilise p_sClauseWhere.
sClauseOrderBychaîne""p_sClauseOrderByTri des résultats (sans le mot ORDER BY). Vide → réutilise p_sClauseOrderBy, puis tri par défaut sur la clé primaire ASC.
nTypeLockentierNoLockp_nClauseLockVerrouillage pessimiste : NoLock · LockInShareMode · LockForUpdate. Une transaction doit être ouverte par l'appelant pour tout verrou ≠ NoLock.
nLimitNombreTuplesentier0p_nClauseLimitNombre maximum de lignes (LIMIT SQL). 0 → pas de limite.
nOFfSetentier0p_nClauseOffsetDécalage de pagination (OFFSET SQL). 0 → pas de décalage.

Membres pré-positionnables — sans paramètre direct, à renseigner avant l'appel.

MembreTypeDéfautRôle
p_bClauseRAZbooléenVraiVide m_tabResults et réinitialise les membres mappés avant le chargement. À laisser à Vrai dans la quasi-totalité des cas.
p_sClauseSelectchaîne""Liste explicite des colonnes à projeter au format TABLE.COLONNE séparées par ,. Vide → toutes les colonnes mappées.
p_sClauseNotSelectchaîne""Liste de colonnes à exclure du SELECT — complémentaire de p_sClauseSelect.
p_sClauseWithchaîne""Définition CTE complète (WITH nom AS (SELECT …)), préfixée au SELECT.
p_sClauseJoinchaîne""Jointure SQL brute supplémentaire — utile pour joindre une CTE.
✅ Bonne pratique — pré-positionner les membres

Préférer la forme sans paramètre, plus lisible et cohérente avec les membres qui ne disposent pas de paramètre direct (p_sClauseSelect, p_sClauseWith, p_sClauseJoin…) :

:p_sClauseWhere = "..."
(bProcessing, nErrorCode, sErrorMessage) = :mth_ChargerSelonClauseWhere()

⚠️ Impact de p_sClauseSelect sur les UPDATE

Si p_sClauseSelect est utilisé pour ne charger qu'un sous-ensemble de colonnes, un appel ultérieur à mth_Enregistrer() sur cet objet ne mettra à jour que les colonnes effectivement chargées — les autres restent inchangées en base.

C'est le comportement attendu : il évite d'écraser silencieusement avec des valeurs vides les colonnes qui n'ont pas été lues. Pour modifier d'autres colonnes, recharger d'abord l'objet sans p_sClauseSelect (ou avec une liste élargie).

🌍 Traductions multilingues

Les colonnes listées dans m_sListeRubriquesTraduites (déclaré dans le constructeur de la classe métier, format TABLE.COLONNE,TABLE.COLONNE) sont automatiquement traduites via la table système cshx2_traductions si la langue affichée diffère de la langue par défaut.

L'ID de la langue affichée est alimenté automatiquement depuis la session ORM_CSHX2 — aucune action côté appelant.

03

📦 Résultats hydratés — m_tabResults & objet courant

Les résultats du SELECT sont restitués directement sous forme d'objets WLangage, prêts à être consommés sans intermédiaire. L'appelant n'a jamais à manipuler de source de données SQL ni à recopier des champs un à un.

La structure exacte d'une classe métier (mapping, colonnes framework, déclaration de m_tabResults, constructeur, jointures) est détaillée dans la section 📐 Anatomie d'une classe métier ci-dessous.

Règle d'hydratation

Après un appel réussi à mth_ChargerSelonClauseWhere() :

CibleContenu
m_tabResultsTableau d'instances de la classe métier — une instance par tuple SQL retourné. Indexé à partir de 1.
Objet courant (:m_xxx)Hydraté avec les valeurs du premier tuple (équivalent à m_tabResults[1]). Pratique pour les recherches mono-résultat.
p_nOccurrencesTrouvéesPropriété renvoyant le nombre d'éléments dans m_tabResults.

Trois cas d'usage selon le nombre de résultats

clClient est un Client clClient:p_sClauseWhere = "clients.REGION = 'SUD'" (bProcessing, nErrorCode, sErrorMessage) = clClient:mth_ChargerSelonClauseWhere() SI bProcessing ALORS SELON Vrai CAS clClient:p_nOccurrencesTrouvées = 0 // ── Aucun résultat ────────────────────────────────────────── // L'objet courant n'est pas hydraté. m_tabResults est vide. Info("Aucun client trouvé") CAS clClient:p_nOccurrencesTrouvées = 1 // ── Cas mono-résultat ─────────────────────────────────────── // L'objet courant EST le résultat — accès direct, pas besoin du tableau Info("Client trouvé : " + clClient:m_DENOMINATION) CAS clClient:p_nOccurrencesTrouvées > 1 // ── Liste à itérer ────────────────────────────────────────── // L'objet courant contient le 1er tuple ; les autres sont dans m_tabResults POUR TOUT UnClient DE clClient:m_tabResults Trace(UnClient:m_ID_CLIENT, UnClient:m_DENOMINATION) FIN FIN FIN
ℹ️ Pourquoi l'objet courant est aussi hydraté

Cette double hydratation (objet courant + tableau) permet d'écrire un code uniforme : pour les recherches qui ramènent typiquement zéro ou un résultat (recherche par code unique, par GUID…), l'appelant peut utiliser directement :m_xxx sans avoir à écrire :m_tabResults[1]:m_xxx. Pour les listes, on itère sur m_tabResults.

⚠️ Accès direct vs itération

Si p_nOccurrencesTrouvées > 1 et que vous lisez :m_xxx directement (l'objet courant), vous n'obtenez que les valeurs du premier tuple. Pour traiter tous les résultats d'une requête multi-lignes, itérer obligatoirement sur m_tabResults.

04

📐 Anatomie d'une classe métier

Toute classe métier accessible via l'ORM doit hériter de la classe de base xFrameWork_CSHX2. Cet héritage fournit l'ensemble des méthodes mth_* et les membres internes nécessaires au mapping objet ↔ SQL.

Une classe métier complète comprend trois parties : la déclaration (mapping de table, colonnes métier et framework, objets liés, tableau de résultats), un constructeur (initialisation, jointures, traductions), et éventuellement un destructeur. La déclaration et le constructeur sont décrits ci-dessous ; la liste détaillée des colonnes framework et des conventions de nommage fera l'objet d'une page dédiée.

Déclaration de classe

Exemple basé sur la table niveau_1 avec deux objets liés (niveau_1_bis et niveau_2) :

Mniveau_1 est une Classe <MAPPING=niveau_1> hérite de xFrameWork_CSHX2 // ── Objets liés (jointures déclarées dans le constructeur) ───────── m_clNiveau1Bis est un objet Mniveau_1_bis m_clNiveau2 est un objet Mniveau_2 // ─────────────────────────────────────────────────────────────────── // Le code se trouvant entre <MAPPING> et <FIN> est généré automatiquement. // Il sera effacé et recréé entièrement à chaque génération. // ─────────────────────────────────────────────────────────────────── <MAPPING> // 🔑 Clé primaire m_ID_NIVEAU_1 est un entier sur 8 octets <MAPPING=ID_NIVEAU_1, clé unique> // 🏷️ Colonnes métier m_DENOMINATION est une chaîne <MAPPING=DENOMINATION> // 🔧 Colonnes framework — à déclarer par le développeur (table + classe). // Noms personnalisables via stConfig.MetadataColumns (valeurs par défaut ci-dessous). // Le framework les valorise automatiquement lors des INSERT / UPDATE. m_SQL_UUID est une chaîne <MAPPING=SQL_UUID> m_SQL_IP_USER est une chaîne <MAPPING=SQL_IP_USER> m_SQL_LOCKED est un booléen <MAPPING=SQL_LOCKED> m_SQL_ID_USER_INSERT est un entier <MAPPING=SQL_ID_USER_INSERT> m_SQL_INSERTED est une DateHeure <MAPPING=SQL_INSERTED> m_SQL_PROCEDURE_INSERT est une chaîne <MAPPING=SQL_PROCEDURE_INSERT> m_SQL_ID_USER_UPDATE est un entier <MAPPING=SQL_ID_USER_UPDATE> m_SQL_UPDATED est une DateHeure <MAPPING=SQL_UPDATED> m_SQL_PROCEDURE_UPDATE est une chaîne <MAPPING=SQL_PROCEDURE_UPDATE> m_SQL_EXE est une chaîne <MAPPING=SQL_EXE> m_SQL_STATUS est un entier <MAPPING=SQL_STATUS> <FIN> // ── Tableau des résultats — typé sur la classe elle-même ────────── m_tabResults est un tableau de 0 Mniveau_1 FIN
📌 Points clés de la déclaration

1. Mapping de table — l'attribut <MAPPING=niveau_1> sur la déclaration de classe lie cette classe à la table SQL niveau_1.

2. Bloc <MAPPING> ... <FIN> — délimite le code entièrement régénéré par le framework lors de la synchronisation avec l'analyse. Ne rien écrire à la main dans ce bloc, tout serait perdu à la prochaine génération.

3. Clé primaire — annotée clé unique. Une et une seule colonne porte cette annotation.

4. Colonnes framework (SQL_*) — à déclarer par le développeur dans la table SQL et dans la classe. Leurs noms sont personnalisables au démarrage du framework ; les valeurs SQL_* montrées ici ne sont que les valeurs par défaut. Le framework valorise automatiquement leur contenu lors des INSERT / UPDATE.

5. Objets liés — déclarés hors du bloc auto-généré. Leur nom doit correspondre exactement à la valeur passée à sObjectName dans le constructeur (voir ci-dessous).

6. m_tabResults — tableau typé sur la classe elle-même, dimensionné à 0. Hydraté par les méthodes de chargement.

🏷️ Personnalisation des noms de colonnes framework

Les noms par défaut (SQL_UUID, SQL_INSERTED, SQL_LOCKED…) peuvent être remplacés par les conventions de nommage maison via la structure stConfig.MetadataColumns, fournie au démarrage du framework. Si un nom personnalisé y est fourni, il remplace le nom par défaut ; sinon, le défaut est conservé.

Champs disponibles : sUUID · sID_USER_INSERT · sID_USER_UPDATE · sIP_USER · sLOCKED · sINSERTED · sUPDATED · sEXE · sPROCEDURE_INSERT · sPROCEDURE_UPDATE · sSTATUS.

Détails complets dans la page dédiée aux colonnes framework.

Constructeur type

Le constructeur initialise l'objet, déclare les colonnes traduites et définit les jointures via m_TabJoinDefinitions. Le pattern recommandé expose un paramètre booléen par jointure pour permettre à l'appelant de choisir lesquelles activer :

PROCÉDURE Constructeur(LOCAL bJointureNiveau2 est un booléen = Faux, LOCAL bJointureNiveau1Bis est un booléen = Vrai) LOCAL nIndiceJointure est un entier // 🆔 GUID temporaire — utile pour identifier l'instance avant insertion en base :p_sGUIDTemp = DonneGUID(guidBrut256) // 🌍 Liste des colonnes traduites (séparées par virgule, format TABLE.COLONNE) :p_sListeRubriquesTraduites = [ niveau_1.DENOMINATION ] // 🔍 Introspection unique de la classe (auto-skippée si déjà faite) SI :pub_NeedsIntrospection() ALORS xFrameWork_CSHX2:pro_CLASS_Introspection(RécupèreDéfinition(objet)) FIN // ────────────────────────────────────────────────────────────────────── // 🔗 Jointure niveau_2 (table enfant) — désactivée par défaut // ────────────────────────────────────────────────────────────────────── SI bJointureNiveau2 = Vrai ALORS nIndiceJointure = TableauAjouteLigne(:m_TabJoinDefinitions) SI nIndiceJointure > 0 ALORS :m_TabJoinDefinitions[nIndiceJointure].sLeftTable = "niveau_2" :m_TabJoinDefinitions[nIndiceJointure].sLeftField = "ID_NIVEAU_1" :m_TabJoinDefinitions[nIndiceJointure].sRightTable = "niveau_1" :m_TabJoinDefinitions[nIndiceJointure].sRightField = "ID_NIVEAU_1" :m_TabJoinDefinitions[nIndiceJointure].sJoinType = "LEFT" :m_TabJoinDefinitions[nIndiceJointure].sObjectName = "m_clNiveau2" FIN FIN // ────────────────────────────────────────────────────────────────────── // 🔗 Jointure niveau_1_bis (table sœur) — activée par défaut // ────────────────────────────────────────────────────────────────────── SI bJointureNiveau1Bis = Vrai ALORS nIndiceJointure = TableauAjouteLigne(:m_TabJoinDefinitions) SI nIndiceJointure > 0 ALORS :m_TabJoinDefinitions[nIndiceJointure].sLeftTable = "niveau_1_bis" :m_TabJoinDefinitions[nIndiceJointure].sLeftField = "ID_NIVEAU_1" :m_TabJoinDefinitions[nIndiceJointure].sRightTable = "niveau_1" :m_TabJoinDefinitions[nIndiceJointure].sRightField = "ID_NIVEAU_1" :m_TabJoinDefinitions[nIndiceJointure].sJoinType = "LEFT" :m_TabJoinDefinitions[nIndiceJointure].sObjectName = "m_clNiveau1Bis" FIN FIN

Champs de m_TabJoinDefinitions

Chaque ligne du tableau décrit une jointure entre la table parente et une table liée :

ChampTypeRôle
sLeftTablechaîneTable jointe (côté gauche du JOIN, généralement la table enfant qui porte la clé étrangère).
sLeftFieldchaîneColonne de jointure côté gauche (FK vers la table parente).
sRightTablechaîneTable parente (côté droit du JOIN, en général la table de la classe courante).
sRightFieldchaîneColonne de jointure côté droit (typiquement la clé primaire de la table parente).
sJoinTypechaîneType SQL de la jointure : "LEFT" · "INNER" · "RIGHT". "LEFT" est le défaut le plus sûr (retourne l'objet parent même sans enfant).
sObjectNamechaîneNom exact du membre objet déclaré dans la classe qui recevra l'objet joint. Sert à la récursion automatique de l'analyseur de jointures.
⚠️ Cohérence sObjectName ↔ déclaration de classe

La valeur de sObjectName doit correspondre caractère pour caractère au nom du membre objet déclaré dans la classe. Une faute de frappe casse silencieusement la récursion sur les jointures (l'objet imbriqué ne sera pas hydraté).

Dans l'exemple : "m_clNiveau2" ↔ membre m_clNiveau2 est un objet Mniveau_2.

✅ Pattern recommandé — paramètres booléens par jointure

Le constructeur expose un paramètre par jointure, avec une valeur par défaut adaptée à l'usage le plus courant. L'appelant active uniquement les jointures dont il a besoin pour la requête en cours :

clObjet est un Mniveau_1(bJointureNiveau2 = Vrai)
// → niveau_2 ET niveau_1_bis seront jointes

05

📤 Valeur de retour

Triplet standard ORM (bProcessing, nErrorCode, sErrorMessage) :

RetourCondition
(Vrai, 0, "")La requête SQL s'est exécutée sans erreur. Ne garantit pas qu'un enregistrement a été trouvé — consulter p_nOccurrencesTrouvées pour le nombre de lignes chargées.
(Faux, <code>, sMsg)Échec de validation ou erreur SQL — voir la section ⚠️ Gestion des erreurs.
⚠️ p_nOccurrencesTrouvées — nombre de lignes chargées

bProcessing = Vrai confirme uniquement que la requête s'est exécutée sans erreur SQL — un SELECT retournant zéro ligne reste un succès.

C'est p_nOccurrencesTrouvées qui valide la présence effective de données. À tester systématiquement après un appel réussi pour distinguer : 0 (introuvable) · 1 (cas nominal) · N (liste à itérer).

06

⚠️ Gestion des erreurs

Les contrôles de validation sont exécutés après résolution des paramètres, avant tout accès SQL.

CodeConstanteCondition
-21099ERR_ORM_SELECT_LOCK_INVALIDEp_nClauseLock ne vaut ni NoLock, ni LockInShareMode, ni LockForUpdate.
-21098ERR_ORM_SELECT_LOCK_SANS_TRANSACTIONVerrou demandé (p_nClauseLock ≠ NoLock) mais aucune transaction active. La transaction doit être ouverte par l'appelant via ORM_TransactionBegin().
-21097ERR_ORM_SELECT_LIMIT_INVALIDEp_nClauseLimit < 0
-21096ERR_ORM_SELECT_OFFSET_INVALIDEp_nClauseOffset < 0
-21095ERR_ORM_SELECT_ENREG_VERROUILLEUn enregistrement chargé en mode LockForUpdate est déjà verrouillé par un autre poste / utilisateur.
-21094ERR_ORM_SELECT_EXCEPTIONException non gérée pendant la construction ou l'exécution de la requête.
-999ERR_SQL_INVALID_REQUESTErreur SQL générique propagée par le moteur (syntaxe, contrainte, etc.). Le détail est dans sErrorMessage.
⚠️ Verrou et transaction — obligation de l'appelant

Si un verrou est demandé (LockForUpdate ou LockInShareMode), une transaction doit être préalablement ouverte via ORM_TransactionBegin(). Si aucune transaction n'est active au moment de l'appel, la méthode retourne immédiatement (Faux, -21098, "...") sans exécuter le SELECT.

ℹ️ Conservation des clauses en cas d'échec

En cas d'échec, les 9 clauses SELECT positionnées sur l'objet sont préservées (la RAZ post-succès n'est pas exécutée). L'appelant peut donc inspecter :p_sClauseWhere, :p_nClauseLock, etc. pour logger ou tenter un retry sans avoir à tout reconstruire.

07

💡 Exemples

Mode 1 — pré-positionnement des membres (style recommandé)

// ── Charger les clients actifs de la région SUD ───────────────── clClient est un Client clClient:p_sClauseWhere = "clients.REGION = 'SUD' AND clients.ACTIF = 1" clClient:p_sClauseOrderBy = "clients.NOM ASC" clClient:p_nClauseLimit = 50 (bProcessing, nErrorCode, sErrorMessage) = clClient:mth_ChargerSelonClauseWhere() SI bProcessing ALORS SELON Vrai CAS clClient:p_nOccurrencesTrouvées = 0 // Aucun résultat CAS clClient:p_nOccurrencesTrouvées = 1 // Cas nominal — clClient est hydraté avec le seul résultat CAS clClient:p_nOccurrencesTrouvées > 1 // Plusieurs résultats — itérer sur clClient:m_tabResults POUR TOUT UnClient DE clClient:m_tabResults // ... FIN FIN SINON Erreur(sErrorMessage) FIN

Mode 2 — paramètres directs (style hérité)

(bProcessing, nErrorCode, sErrorMessage) = clClient:mth_ChargerSelonClauseWhere( "clients.REGION = 'SUD'", // sClauseWhere "clients.NOM ASC", // sClauseOrderBy NoLock, // nTypeLock 50, // nLimitNombreTuples 0 // nOFfSet )

Mode 3 — projection partielle avec p_sClauseSelect

// ── Ne charger que l'ID et le nom — utile pour des listes ────── clClient:p_sClauseSelect = "clients.ID_CLIENT,clients.NOM" clClient:p_sClauseWhere = "clients.ACTIF = 1" (bProcessing, nErrorCode, sErrorMessage) = clClient:mth_ChargerSelonClauseWhere() // ⚠️ Si on appelle clClient:mth_Enregistrer() ensuite, // seules ID_CLIENT et NOM seront mis à jour en base.

Mode 4 — verrou pessimiste avec transaction

// ── Ouverture de la transaction — verrou + modification atomique ── (bProcessing, nErrorCode, sErrorMessage) = ORM_TransactionBegin() SI bProcessing ALORS clClient:p_sClauseWhere = "clients.ID_CLIENT = 42" clClient:p_nClauseLock = LockForUpdate (bProcessing, nErrorCode, sErrorMessage) = clClient:mth_ChargerSelonClauseWhere() SI bProcessing ALORS // ... modifications sur clClient ... (bProcessing, nErrorCode, sErrorMessage,) = clClient:mth_Enregistrer() FIN SI bProcessing ALORS // ✅ Tout est OK — on valide en base (bProcessing, nErrorCode, sErrorMessage) = ORM_TransactionCommit() SI bProcessing = Faux ALORS Erreur(sErrorMessage) FIN SINON // ❌ Échec — annulation complète, aucune modification en base ORM_TransactionRollBack() FIN FIN

Mode 5 — CTE simple

// ── Charger les clients VIP (CA > 10 000) via CTE ─────────────── clClient:p_sClauseWith = "WITH cte_vip AS (SELECT ID_CLIENT FROM commandes GROUP BY ID_CLIENT HAVING SUM(MONTANT_HT) > 10000)" clClient:p_sClauseWhere = "clients.ID_CLIENT IN (SELECT ID_CLIENT FROM cte_vip)" (bProcessing, nErrorCode, sErrorMessage) = clClient:mth_ChargerSelonClauseWhere()

Mode 6 — cas avancé : CTE d'agrégation + multi-jointures + tri sur agrégat + LIMIT

Cas réaliste combinant tous les leviers de la méthode. Objectif : récupérer les 2 villes les plus polyvalentes (mesurées par le nombre de catégories distinctes qu'elles offrent) parmi celles qui contiennent au moins un Monument. La CTE calcule l'indicateur, les jointures filtrent sur la catégorie, le tri exploite l'agrégat exposé par la CTE.

LOCAL // 📦 Variables du patron de retour standard ORM_CSHX2 bProcessing est un booléen = Vrai nErrorCode est un entier sErrorMessage est une chaîne // ────────────────────────────────────────────────────────────────── // 📊 CTE : polyvalence = nombre de catégories distinctes par ville // → expose nbCategories pour le critère de tri (agrégat) // ────────────────────────────────────────────────────────────────── clObjNiveau1:p_sClauseWith = [ WITH cte_Polyvalence AS ( SELECT niveau_2.ID_NIVEAU_1 AS ID_NIVEAU_1, COUNT(DISTINCT niveau_2.ID_NIVEAU_3) AS nbCategories FROM niveau_2 GROUP BY niveau_2.ID_NIVEAU_1 ) ] // ────────────────────────────────────────────────────────────────── // 🔗 Trois jointures combinées : // - cte_Polyvalence : expose nbCategories (pour le ORDER BY) // - niveau_2 + niveau_3 : permet le filtre sur la catégorie // ────────────────────────────────────────────────────────────────── clObjNiveau1:p_sClauseJoin = [ INNER JOIN cte_Polyvalence ON cte_Polyvalence.ID_NIVEAU_1 = niveau_1.ID_NIVEAU_1 INNER JOIN niveau_2 ON niveau_2.ID_NIVEAU_1 = niveau_1.ID_NIVEAU_1 INNER JOIN niveau_3 ON niveau_3.ID_NIVEAU_3 = niveau_2.ID_NIVEAU_3 ] // 🎯 Filtre : doit contenir au moins un Monument // ORM_Quote() échappe la valeur SQL (anti-injection) clObjNiveau1:p_sClauseWhere = [ niveau_3.DENOMINATION = [%ORM_Quote("Monument")%] ] // 📈 Tri par polyvalence décroissante clObjNiveau1:p_sClauseOrderBy = "cte_Polyvalence.nbCategories DESC" // 🥇 Top 2 clObjNiveau1:p_nClauseLimit = 2 // 🚀 Exécution de la requête SELECT (bProcessing, nErrorCode, sErrorMessage) = clObjNiveau1:mth_ChargerSelonClauseWhere() SI bProcessing = Faux ALORS ToastAffiche(sErrorMessage, toastCourt, cvMilieu, chCentre, RougeFoncé) FIN
💡 Pourquoi cet exemple est représentatif

Il combine en un seul appel les cinq leviers de la méthode : p_sClauseWith (CTE d'agrégation), p_sClauseJoin (jointures multiples sur la CTE et les tables métier), p_sClauseWhere (filtre métier sécurisé via ORM_Quote), p_sClauseOrderBy (tri sur l'agrégat de la CTE) et p_nClauseLimit (Top N).

Le résultat est un objet hydraté (clObjNiveau1 + son m_tabResults) prêt à être consommé sans avoir à manipuler de source de données SQL côté appelant.