Affichage des articles dont le libellé est SOQL. Afficher tous les articles
Affichage des articles dont le libellé est SOQL. Afficher tous les articles

mardi 27 mars 2012

La puissance des requêtes SOQL

Le langage SOQL ou Salesforce Object Querying Language permet d'interroger la base de données de Force.com via des requêtes qui ne sont pas sans rappeler les requêtes SQL.

Le petit frère de SQL ?

Nous allons noter quelques différences majeures entre les langages SOQL et SQL :

Tout d'abord, il est impossible de sélectionner tous les champs d'une table en utilisant l'opérateur " * ". Vous ne verrez donc jamais cette requête :

[SELECT * FROM Account];

Ensuite, le langage SOQL ne peut être utilisé que pour extraire des données de la base de données, soit avec le mot-clé "SELECT". Pour tout ce qui est CRUD, il faudra alors se tourner vers les instructions DML que nous aborderons dans un prochain article.

Adieu CONCAT, UPPER, DATEDIF ou autre opérateur "+"... Hélas, SOQL est très pauvre en fonction et opérateurs. Et ne comptez pas utiliser des alias non plus.
On peut toutefois bénéficier des opérateurs suivants :
  • =, <, , > et pour comparer nombres et dates
  • =, LIKE (et son meilleur ami "%") pour comparer des chaînes de caractère
Il est important de noter qu'il est impossible de comparer deux champs entre eux dans une requête. La comparaison doit obligatoirement se faire entre un champ et une constante. Vous ne verrez donc jamais une requête de ce genre :

[SELECT Subject 
 FROM Event 
 WHERE EndDatetime > StartDatetime + 10];

Attendez ! Je vous vois venir... Vous allez me dire que cet article s'intitule "La puissance des requêtes SOQL" alors que jusqu'à maintenant ce que nous avons vu fait plus penser à de l'impuissance. Mais SOQL c'est aussi la capacité à extraire des données des tables parents et enfants en toute simplicité.

Un langage [très] relationnel

Les champs de types Lookup ou Master-detail permettent d'avoir des relations 1-n entre les tables de la base de données. Un exemple est le lien entre les tables Compte et Opportunité.
Comme c'est la référence du compte qui est stockée dans un champ de l'opportunité, nous disons que la table Compte est une table parent de la table Opportunité et, inversement, la table Opportunité est une table enfant de la table Compte.
En SQL, nous parlons de jointure droite ou jointure gauche.

Relation avec une table parent

Jointure externe droite
Ce type de jointure permet d'extraire des données d'une table parent.
Exemple : 
[SELECT Name, Position__r.Department__c FROM Job_Application__c];

Jointure interne droite
Cette jointure rajoute un filtre supplémentaire sur le résultat de la requête. Il faut que chaque enregistrement soit rattaché à un enregistrement parent.

Exemple :
[SELECT Name
 FROM Job_Application__c
 WHERE Position__r.Department__c=‘Sales’];

Anti-jointure droite
L'anti-jointure permet de ne pas extraire les éléments pour lesquels un enregistrement parent existe.

Exemple :
[SELECT Name FROM Job_Application__c WHERE Position__c = null];

Attention : Salesforce impose une limite de 25 relations vers des champs de tables parent.

Relation avec une table enfant

Jointure externe gauche
Ce type de jointure permet d'extraire des données d'une table enfant.

Exemple :
[SELECT Name, (SELECT Name FROM Job_Applications__r)
 FROM Position__c];

Jointure interne gauche
Cette jointure rajoute un filtre supplémentaire sur le résultat de la requête. Il faut que chaque enregistrement possède des enregistrements enfant

Exemple :
[SELECT Name
 FROM Position__c
 WHERE Id IN (SELECT Position__c FROM Job_Application__c)];

 
Anti-jointure gauche
L'anti-jointure permet de ne pas extraire les éléments pour lesquels il n'y a pas d'enregistrement enfant.

Exemple : 
[SELECT Name 
 FROM Position__c
 WHERE Id NOT IN (SELECT Position__c FROM Job_Application__c)];
Attention : Le nombre de tables enfant extraites est de 20 maximum par requête. Il est possible d'utiliser d'autres requêtes SOQL pour extraire les tables enfant restante, en faisant attention à la limite « Too many SOQL Queries ».


Le langage SOQL c'est tout ça, et bien plus encore grâce aux résultats globaux offrant COUNT, GROUP BY et autre HAVING, mais aussi grâce aux requêtes dynamiques et la généricité qu'elles apportent.

source: A Deeper look at SOQL and Relationship Queries on Force.com

mercredi 21 mars 2012

Les requêtes bulkées ou comment échapper au "Too many SOQL queries"

Elle laisse les novices dubitatifs et fait pleurer les plus expérimentés... Il s'agit de l'exception : Too many SOQL queries !

Qu'est ce c'est ?
Tout développeur Force.com qui a quelques heures de travail derrière lui, a déjà dû être confronté à ce fléau cette erreur au moins une fois.
Elle fait référence à la limite  "Total number of SOQL queries issued" fixée à 100 par Salesforce (voir Apex Governor Limits) qui empêche l'exécution d'un trop grand nombre de requêtes SOQL.

Pourquoi ?
Comptent pour une requête SOQL :
  • Chaque appel via l'instruction SELECT et ce peu importe sa complexité (champs enfants ou parents). Exemple :
Account myAccount = [SELECT Name FROM Account LIMIT 1];

  • Chaque appel dynamique via la méthode Database.query(). Exemple :
Account myAccount = (Account) Database.query('SELECT Name FROM Account LIMIT 1');
L'enjeu est donc de limiter ces appels dans le code. C'est d'autant plus vrai pour les triggers que pour les classes APEX car les requêtes contenues dans un trigger seront appelées autant de fois que celui-ci est déclenché.

Comment résoudre le problème ?
Il n'y a pas de solution miracle. Seul un code plus propre et plus respectueux des bonnes pratiques prônées par Salesforce permettra d'éviter d'atteindre la limite.

Parmi les bonnes pratiques, on peut retenir celle-ci (qui dépannera dans 99% des cas). Cette « Best Practice » conseille de ne pas envoyer de requêtes SOQL à l'intérieur d'une boucle (for ou while) mais plutôt de « bulker » (grouper) les requêtes en une seule.

Exemple :
Nous voulons lister les noms des contacts qui sont rattachés à un compte.

List<String> contactNames = new List<String>();
for (Account a : [SELECT Id FROM Account]) {
    for (Contact c : [SELECT Name FROM Contact WHERE AccountId=:a.Id]) {
        contactNames.add(c.Name);
    }
}

Le code ci-dessus soulèvera une exception SOQL si nous avons plus de 100 comptes dans le CRM.
Ceci peut être évité si nous utilisons plutôt le code qui suit.

Set<Id> accountIds = new Set<Id>(); 
for (Account a : [SELECT Id FROM Account]) {
     accountIds.add(a.Id);
}

List<String> contactNames = new List<String>(); 
for (Contact c : [SELECT Name FROM Contact WHERE AccountId IN :accountIds]) {
     contactNames.add(c.Name);
}

Si on gagne en lignes de codes, on gagne surtout en requêtes SOQL car on comptabilise maintenant 2 requêtes seulement et ce, indépendamment du nombre de comptes récupérés par la première requête.
Le code peut être encore amélioré en exploitant entièrement la puissance des requêtes SOQL, cela n'est pas le but de cet article mais fera sûrement l'objet d'un nouveau.