Un site e-commerce d'articles de sport ajoute à son email mensuel un encart qui affiche les trois dernières commandes du client. Le développeur lit la table des commandes avec LookupRows sur l'identifiant client, puis affiche la première, la deuxième et la troisième ligne du résultat. Les trois profils de test ont chacun cinq commandes, l'aperçu est parfait, la recette valide.
Le jour de l'envoi, deux tickets arrivent. L'équipe CRM constate un écart entre le volume ciblé et le volume envoyé, avec des erreurs signalées sur une partie de l'audience. Ce sont les clients qui n'ont qu'une ou deux commandes, pour lesquels le code réclame une troisième ligne qui n'existe pas.
Le second ticket vient du service client. Une cliente fidèle voit dans son email des commandes de 2021 alors qu'elle a commandé la semaine précédente. Deux fonctions ont été mal comprises, et des données de test trop régulières ont masqué les deux défauts.
Toutes prennent le nom de la data extension, puis une ou plusieurs paires composées d'une colonne de recherche et d'une valeur recherchée. Plusieurs paires se combinent comme un ET. Ce qui les sépare, c'est ce qu'elles renvoient et ce qu'elles promettent.
| Fonction | Renvoie | Ordre | Casse | Volume |
|---|---|---|---|---|
Lookup | Une valeur | Première correspondance trouvée | Sensible selon la documentation | Une seule valeur |
LookupRows | Un rowset | Aucun ordre garanti | Insensible | Jusqu'à 2 000 lignes |
LookupRowsCS | Un rowset | Aucun ordre garanti | Sensible | Jusqu'à 2 000 lignes |
LookupOrderedRows | Un rowset trié | Le tri que vous indiquez | Insensible | Le nombre demandé, 2 000 au plus |
LookupOrderedRowsCS | Un rowset trié | Le tri que vous indiquez | Sensible | Le nombre demandé, 2 000 au plus |
Lookup(dataExt, colonneRetour, colonneRecherche1, valeur1 [, colonneRecherche2, valeur2 ])
LookupRows(dataExt, colonneRecherche1, valeur1 [, ])
LookupOrderedRows(dataExt, nbLignes, tri, colonneRecherche1, valeur1 [, ])
Le paramètre de tri s'écrit comme une clause SQL, un nom de colonne suivi d'un espace et de ASC ou DESC, plusieurs colonnes séparées par des virgules. Si nbLignes est inférieur à 1, la fonction renvoie toutes les lignes dans la limite de 2 000. Si rien ne correspond, elle renvoie un rowset vide.
La documentation décrit Lookup comme renvoyant la première valeur trouvée quand plusieurs lignes correspondent. Première trouvée ne veut dire ni la plus récente ni la plus ancienne, c'est une ligne quelconque parmi celles qui correspondent. Cette fonction ne s'utilise donc que sur une recherche qui identifie une ligne unique, typiquement la clé primaire.
Le bon usage tient en deux lignes : lire le champ de la data extension d'envoi avec AttributeValue, vérifier qu'il n'est pas vide, puis chercher sur la clé primaire de la table de référence. Chaque lecture évitée pour un client qui n'a pas la donnée est du temps d'envoi gagné, et sur quelques centaines de milliers de destinataires la différence se voit dans la durée du job.
⚠️ Le cas le plus sournois : lire le statut de la commande d'un client avec Lookup sur son identifiant. Le code semble dire « le statut de sa commande », il renvoie en réalité celui d'une commande au hasard parmi toutes les siennes. Le résultat est plausible, donc personne ne le conteste.
Le choix se règle en trois questions, dans cet ordre.
Lookup convient. Plusieurs, il faut une fonction qui renvoie un rowset.LookupOrderedRows avec un tri explicite et le nombre de lignes voulu. Sinon, LookupRows suffit.CS. Sinon, la version insensible tolère les écarts de saisie.Sur la sensibilité à la casse de Lookup, la documentation et les retours terrain ne concordent pas toujours. Testez le comportement sur votre compte avant de faire reposer une règle métier dessus, plutôt que de recopier ce qu'affirme un billet de blog.
Deux considérations viennent ensuite. Le volume, d'abord : si le besoin porte sur plus de 2 000 lignes ou sur un total calculé, la lecture n'a pas sa place dans l'email et se prépare en SQL. La performance, ensuite : Salesforce indique que ces appels ralentissent l'envoi et conseille de lire plusieurs données en une fois avec LookupRows plutôt que d'enchaîner les Lookup. Si l'email affiche trois colonnes d'une même ligne, un seul rowset vaut mieux que trois appels.
Un rowset est une collection de lignes qui ne s'affiche pas directement. On compte ses lignes avec RowCount, on extrait une ligne avec Row, puis une valeur avec Field. La première ligne porte la position 1, pas 0.
%%[
VAR @clientId, @commandes, @nb
SET @clientId = AttributeValue("ClientId")
SET @commandes = LookupOrderedRows("Commandes", 3, "DateCommande DESC, NumCommande DESC", "ClientId", @clientId)
SET @nb = RowCount(@commandes)
]%%
%%[ IF @nb > 0 THEN ]%%
%%[
VAR @i, @ligne, @num, @date
FOR @i = 1 TO @nb DO
SET @ligne = Row(@commandes, @i)
SET @num = Field(@ligne, "NumCommande")
SET @date = Field(@ligne, "DateCommande")
]%%
<p>Commande %%=v(@num)=%% du %%=Format(@date, "dd/MM/yyyy", "Date")=%%</p>
%%[ NEXT @i ]%%
%%[ ENDIF ]%%
La variable @nb vaut 0, 1, 2 ou 3, et c'est elle qui pilote la boucle, jamais la constante 3. Le second critère de tri sur le numéro de commande départage deux commandes passées le même jour et rend le résultat stable d'un envoi à l'autre. Salesforce recommande d'ailleurs de vérifier qu'un rowset contient des données avant de le parcourir.
Chaque ligne n'est extraite qu'une fois, puis ses champs sont lus depuis la variable. Le troisième paramètre de Field mérite une décision consciente : par défaut, un nom de champ inexistant provoque une erreur, ce qui est souvent le bon choix. Une faute de frappe dans un nom de colonne doit casser l'aperçu, pas produire en silence un email vide. Passer false se justifie quand la structure de la table varie légitimement.
Cette limite ne gêne pas un encart qui affiche trois commandes. Elle devient un piège dès qu'on veut afficher un compteur.
Vous avez passé %%=RowCount(LookupRows("Commandes", "ClientId", _subscriberkey))=%% commandes.
Un compte professionnel à 3 500 commandes n'en verra jamais plus de 2 000 affichées, et la lecture de tout l'historique se paie sur chaque destinataire. Ce type d'indicateur se calcule en SQL avant l'envoi et s'ajoute comme colonne à la data extension d'envoi.
SELECT c.ClientId, COUNT(o.NumCommande) AS NbCommandes
FROM Clients AS c
LEFT JOIN Commandes AS o
ON o.ClientId = c.ClientId
GROUP BY c.ClientId
Le LEFT JOIN associé à un COUNT portant sur une colonne de la table des commandes donne bien 0 aux clients qui n'ont jamais commandé, là où un comptage sur toutes les colonnes leur donnerait 1. L'email se contente ensuite d'afficher une colonne, sans aucune lecture pendant le rendu.
💡 Le jeu de test qui manque toujours : un client sans commande, un client à une seule commande, un client à deux commandes datées du même jour, un client sans magasin de rattachement. Ces quatre lignes coûtent dix minutes à créer et révèlent la majorité des défauts de ce type d'encart.
Depuis une business unit enfant, Lookup, LookupRows et LookupOrderedRows acceptent le préfixe ENT. devant le nom de la data extension pour lire une table du niveau entreprise.
Dans Adobe Campaign, on évite en général de lire la base pendant la personnalisation. Les données additionnelles sont ajoutées en amont, dans le ciblage ou par une activité d'enrichissement du workflow, puis lues via les données de la cible. Une requête dans un script de personnalisation existe, mais reste l'exception qu'on justifie en revue de code.
Marketing Cloud rend la lecture en cours d'envoi tellement facile qu'elle devient le réflexe par défaut, y compris là où elle n'a rien à faire. Le réflexe Adobe Campaign reste le bon sur les gros volumes : préparez les données avant, et gardez les fonctions de lecture pour ce qui ne peut pas l'être.
Quand le rang d'une ligne a un sens dans votre email, demandez le tri explicitement. Quand une boucle parcourt un rowset, bornez-la par RowCount et jamais par le nombre de lignes que vous espérez. Ces deux règles auraient évité les deux tickets de l'incident, et elles se vérifient en relisant le code, sans ouvrir la plateforme.
LookupRows renvoie un rowset sans aucun ordre garanti. LookupOrderedRows renvoie un rowset trié selon le tri que vous indiquez, limité au nombre de lignes demandé et à 2 000 lignes au plus. Dès que le rang d'une ligne a un sens dans l'email, il faut LookupOrderedRows avec un tri explicite, écrit comme une clause SQL : un nom de colonne suivi de ASC ou DESC.
La documentation décrit Lookup comme renvoyant la première valeur trouvée. Première trouvée ne veut dire ni la plus récente ni la plus ancienne : c'est une ligne quelconque parmi celles qui correspondent. Cette fonction ne s'utilise donc que sur une recherche qui identifie une ligne unique, typiquement la clé primaire. Lire le statut d'une commande avec Lookup sur l'identifiant client renvoie le statut d'une commande au hasard, avec un résultat plausible que personne ne conteste.
Un rowset ne s'affiche pas directement : on compte ses lignes avec RowCount, on extrait une ligne avec Row, puis une valeur avec Field. La première ligne porte la position 1, pas 0. La boucle se borne par la valeur de RowCount, jamais par le nombre de lignes que vous espérez. Salesforce recommande aussi de vérifier qu'un rowset contient des données avant de le parcourir.
Les fonctions de lecture plafonnent à 2 000 lignes, donc un compte à 3 500 commandes n'en verra jamais plus de 2 000 comptées, et la lecture de tout l'historique se paie sur chaque destinataire. Ce type d'indicateur se calcule en SQL avant l'envoi et s'ajoute comme colonne à la data extension d'envoi. Un LEFT JOIN avec un COUNT portant sur une colonne de la table des commandes donne bien 0 aux clients qui n'ont jamais commandé. L'email se contente alors d'afficher une colonne, sans aucune lecture pendant le rendu.
Je peux relire vos blocs AMPscript, vos lectures de data extensions et vos jeux de test, et vous rendre un diagnostic écrit avec les corrections à appliquer.
Me décrire votre contexte →