Access - Trucs et astuces

Requêtes d'agrégats, Alias, mise à jour, requêtes imbriquées, in, et SQL Top

Dans ce didacticiel créé sous Access 2007 (mais ça fonctionne dans les versions antérieures), nous allons pénétrer dans les requêtes SQL afin d'obtenir certains résultats difficiles à obtenir.

Très souvent, Access nous permet d'obtenir les résultats escomptés sans trop d'effort. Parfois, la tâche est nettement plus ardue, et une requête apparemment toute bête peut nous amener à effectuer de véritables prouesses.

Vous pouvez télécharger la base de données correspondante ici.

 
Sommaire

Intro

Imaginez une table T_Commentaire, garnie de la manière suivante :

Voici une liste de clients pourvus de commentaires et leur date respective.

Dans la réalité, vous n'aurez pas simplement le prénom des clients, mais dans l'exercice qui nous intéresse, cette table suffit. Supposons qu'il ne peut pas y avoir plusieurs clients avec le même prénom.

Les choses "à faire" ("lui souhaiter son anniversaire" par exemple) sont mélangées avec les "choses faites" ("facture envoyée" par exemple).

Nous aimerions la liste des commentaires
les plus récents pour chaque client.

Trions la table par ordre alphabétique des clients. Voici les enregistrements que nous aimerions extraire :

André n'a que deux commentaires, le plus récent datant du 19.1.2010. Bernard, c'est celui du 15.2.2010, et Charles a deux événements les plus récents : le 19.1.2010.

4 enregistrements nous intéressent donc (sur un total de 8).

Avant de poursuire, essayez d'imaginer quelle requête permet d'obtenir ce résultat afin de vous rendre compte de la complexité.

Requête d'agrégat, avec fonction MAX

Une requête d'agrégat permet de regrouper les données de manière judicieuse. C'est elle qui va nous permettre de grouper tous les clients avec leur date de commentaire le plus récent. Cette requête va scruter la table client par client, et déterminer quelle est à chaque fois la date la plus récente (La plus "haute" - la "MAX").

Créez une nouvelle requête, avec comme base T_Commentaire :

 

Le fait de cliquer sur le bouton "Totaux" ajoute la ligne "Opération". On demande à "regrouper" les clients, et pour chaque client, demander le "Max" de "DateCom", c'est à dire la date la plus haute (donc la plus récente).

Ca marche. Voici le résultat :

 

Ca marche... Ca marche ! Il faut le dire vite !

Pour André et Bernard, pas de souci. Pour Charles, en revanche, il devrait avoir DEUX lignes puisqu'il y a DEUX événements les plus récents (le 19.1.2010).

Bon, d'un autre côté, c'est normal qu'il n'apparaisse qu'une fois, puisqu'on a bien demandé de compresser les lignes (Regroupement).

Enregistrons cette requête - boîteuse - sous R_AgregatMax.

De plus, nous allons nous heurter à un autre problème : que va-t-il se passer si nous ajoutons le champ Commentaire ?

Il n'y aura plus aucun groupement apparent !

Regardez, le champ Client ET Commentaire sont définis sur Regroupement (DateCom est toujours définie sur Max). La requête va regrouper tous les enregistrements dont le prénom ET le commentaire sont identiques. Ainsi (à moins que - par hasard - un même client possède deux fois le même commentaire, il n'y aura aucun groupement.

 

 

Vous pouvez changer tout ce que vous voulez dans cette liste,
et même essayer d'effacer son contenu, rien ne règlera ce problème :

Requête sur une autre requête

 

 

Notre salut va consister en la création d'une nouvelle requête , basée sur notre première requête R_AgregatMax, et la table T_Commentaire, comme ceci :

Liaison double entre deux tables

Ce qui nous intéresse, ce sont ces enregistrements ci :

 

 

 

Nous devons donc lier le client ET la Date, comme ceci :

Nous obtenons alors le résultat désiré :

 

Enregistrons cette requête sous R_Agregat.

Propriété de la requête "Mise à jour globale"

Malheureusement, cette requête est en lecture seule !

Si vous essayez de modifier quoi que ce soit dans un quelconque commentaire, le message "Impossible de mettre à jour Recordset" apparaîtra en bas à gauche de votre écran.

Qu'à cela ne tienne ! Demandez à voir les propriétés de la requête en mode création, en cliquant sur le bouton droit d'une partie grise de la requête, comme ceci :

 

Et choisissez "Type Recordset" . "Feuille rép.dyn.(MAJ globale)"

Lancez la requête, et vous constatez que ... ça ne marche toujours pas !

Cette option permet de mettre à jour les données dans certaines requêtes "récalcitrantes", mais pas toutes, et notamment pas dans celle-ci !

La raison de cet échec est tout à fait logique: si les deux remarques de Charles avaient été la même (Ce qui aurait été idiot, j'en conviens, mais admettons), nous aurions obtenu une seule ligne "compressée" représentant les deux lignes à la fois. Du coup, si on modifie ce commentaire qui en représente en fait 2, que doit comprendre Access ? Qu'on veut modifier les deux commentaires à la fois ? L'un des deux ? Et lequel ? ... Si vous-même avez de la peine à répondre à cette question, imaginez Access.

Et voilà le pourquoi du comment de l'interdiction de modification.

La fonction "Top" dans les requêtes

Comme nous ne résolvons pas notre problème avec les fonctions d'agrégat, nous allons utiliser la fonction Top.

Créons une nouvelle requête,
basée sur T_Commentaire, comme ceci :

 

Cette requête va être triée par ordre inverse des DateCom (Les plus récents d'abord, donc). L'astuce consiste à écrire 3 dans la liste du ruban "Renvoyer" (On ne peut pas choisir 3, il faut l'écrire "de force"). Nous aurons donc les 3 premières lignes, soit les 3 commentaires les plus récents de toute la table, tous clients confondus.

Comme il y a 3 égalités, la fonction "Top 3" ne va pas en restreindre une au hasard, mais va nous renvoyer les 3 égalités (tant mieux, finalement).

Le mode SQL

Par curiosité, jetez un oeil sur la requête en mode SQL, avec et sans le top 3, juste pour voir ce que ça change. Pour enlever le top 3, c'est facile :

 

 

 

Et pour visiter le mode SQL :

 

 

 

SELECT T_Commentaire.Client, T_Commentaire.DateCom, T_Commentaire.Commentaire

On sélectionne les champs Client, Commentaire et DateCom
FROM T_Commentaire De la table T_Commentaire
ORDER BY T_Commentaire.DateCom DESC; Trié par DateCom inversement (DESC, comme DESCending

Pour rétablir le mode création, c'est comme ça :

Si vous mettez "Renvoyer : 3", comme avant, le code SQL est simplement changé ainsi :

SQL
SELECT TOP 3 T_Commentaire.Client, T_Commentaire.DateCom, T_Commentaire.Commentaire FROM T_Commentaire ORDER BY T_Commentaire.DateCom DESC;

Vous pourriez vous dire que non seulement nous n'obtenons pas le résultat recherché au début, mais, en plus, que ce mode SQL paraît bien inutile et moins pratique que le mode création. Pour l'instant, vous avez raison.

Pour l'instant.

Quittons cette requête. Inutile de l'enregistrer.

Le critère in

Créez une nouvelle requête, toujours basée sur T_Commenatire.

Admettons que je veuille tous les commentaires du 7 janvier, 15 janvier et 1er février. Je peux l'écrire comme ceci :

Ou comme ceci :

Et même comme ceci :

Cette dernière syntaxe est plus compliquée, moins intuitive que les deux précédentes, mais elle recèle de sérieux avantages.

Imaginons que nous voulions l'ensemble de tous les commentaires dont la date correspond à une des dates des commentaires d'André.

C'est un exemple idiot, mais c'est juste pour introduire la notion que je vais aborder un peu plus bas. Nous voudrions donc tous les commentaires de tout le monde, du moment qu'ils soient datés du 7.1.2010 ou du 19.01.2010 (puisque ce sont les dates des commentaires d'André).

En d'autres mots, nous voudrions ces enregistrements-ci

Nous récupérerons donc les deux enregistrements d'André (forcément), plus les deux enregistrements de Charles, datant du 19.1.2010... Personne d'autre n'ayant de date égale à 7.1.2010, il n'y aura donc pas d'autres réponses..

Comme critère de notre requête, nous pouvons préciser : In (#07.01.2010#;#19.01.2010#)

C'est maintenant que ça devient intéressant : le mot-clé in va développer son arôme car nous pouvons y intégrer carrément ... une requête SQL !

 

In ( On ouvre le in
SELECT DateCom On sélectionne DateCom. On doit évidemment ne sélectionner qu'un seul champ. On comprend que par exemple SELECT DateCom, Client n'a pas de sens, et va provoquer une erreur
FROM T_Commentaire Depuis la table T_Commentaire
WHERE Client="André" Le critère doit être que le client s'appelle André. On aura donc toutes ses dates à lui
) Et on referme le in

Ce n'est toujours pas ce que nous recherchons, mais nous sommes sur une bonne piste ! Comment faire pour ajouter la condition in qui précise la date la plus récente ?

Si on essaie :

SQL
in(SELECT Max(T_Commentaire.DateCom) FROM T_Commentaire)

Nous aurons tous les enregistrements correspondants à LA date la plus récente de toutes les dates de la table (En l'occurrence, 15.02.2010), ce qui n'est toujours pas ce qu'on veut.

Il faudrait, à chaque ligne affichée, qu'il nous propose effectivement la date la plus élevée, mais pas la plus élevée de toutes ! Seulement la plus élevée du client qu'on est en train de traiter !

Un peu comme ceci :

SQL
in(SELECT Max(DateCom) FROM T_Commentaire WHERE Client = Client)

Mouais ! il y a quelque chose de boîteux dans ce Client = Client !

Bon, on va essayer encore autre chose.

Alias de table

Il est possible de donner des "petits noms" à nos tables. Des "Alias".

Créons une nouvelle requête, comme ceci :

 

Rendez-vous en mode SQL, et remplacez le code :

SQL
SELECT T_Commentaire.Client, T_Commentaire.DateCom, T_Commentaire.Commentaire FROM T_Commentaire;

par

SQL
SELECT Gateau.Client, Gateau.DateCom, Gateau.Commentaire FROM T_Commentaire AS Gateau;

Repassez en mode création :

 

Si vous exécutez cette requête, elle fonctionne tout à fait normalement. Simplement, T_Commentaire se surnomme aussi "Gateau".

Solution finale

Bonne nouvelle : Nous touchons la solution du doigt, qui va inclure

Le chaînon manquant consistait à lui faire comparer le prénom du client du critère in avec le prénom du client de la requête "mère".

Créez une nouvelle requête, comme ceci :

Modifiez le code SQL :

SQL
SELECT T_Commentaire.Client, T_Commentaire.DateCom, T_Commentaire.Commentaire FROM T_Commentaire;

Comme ceci (Abandonnons "Gateau" au profit de la simple lettre "T", c'est plus sérieux ;-)

SQL
SELECT T.Client, T.DateCom, T.Commentaire FROM T_Commentaire AS T;

Revenez en mode création et modifiez le critère de DateCom de cette manière :

SQL
In (SELECT MAX(DateCom) FROM T_Commentaire WHERE Client=T.Client)

Et c'est ce Client=T.Client qui conclut l'histoire : Client est égal au Client du critère in, tandis que T.Client représente le prénom du client de la requête "mère", grâce à l'Alias T !

Et maintenant, vous pouvez accéder en lecture écriture !

Quid du Top 3 que nous avons étudié plus haut ?

Eh bien, à la place de :

SQL
In (SELECT MAX(DateCom) FROM T_Commentaire WHERE Client=T.Client)

Vous pouvez indiquer :

SQL
In (SELECT TOP 1 DateCom FROM T_Commentaire WHERE Client=T.Client ORDER BY DateCom DESC)

Ce qui revient exactement au même, puisqu'il n'y a pas de différence entre "le plus grand" (MAX) et "le premier en ordre descendant" (ORDER BY ... DESC)

Si vous pensez que cette 2ème syntaxe est plus difficile et que vous préférez la première (MAX), vous avez raison.

Sauf si au lieu de vouloir seulement LE commentaire le plus récent de chaque client, vous désiriez les 2 ou 3 ou 10 commentaires les plus récents... Auquel cas, Top 2, Top 3 ou Top 10 vous seront indispensables.