Analyse croisée dynamique
Il s'agit d'un tableau interactif qui contient des données de synthèse constituées à partir d'une BDD interne (Access) ou externe (SQL Server, etc.) que l'on peut manipuler à l'aide de fonctions statistiques pour les analyser sous divers angles.
Le vocable dynamique découle du fait que l'on peut faire pivoter les titres des colonnes et des lignes pour obtenir différentes présentations analytiques des données. Par exemple, on récapitule les ventes par service, par mois ou par vendeur. On peut ensuite subdiviser ces catégories par produit. On utilise également les tableaux croisés pour comparer les ventes réalisées et les dépenses avec les montants budgétés par mois, par trimestre ou par année. Par rapport à une requête Sélection, les informations obtenues sont plus compactes et se prêtent donc mieux à une analyse.
Etude de cas
On veut connaître le nombre de commandes par mois passées par chaque client. On désire donc effectuer une synthèse au niveau des clients et comparer le nombre de commandes pour chaque mois, présenté sous forme d'une entête de colonne affichant le nom complet du mois.
Remarques:
Calcul du total de chaque ligne de synthèse
On va compléter le résultat précédent en calculant le total des commandes par client. A cet effet, la marche à suivre est la suivante:
| Haut du document | Trucs et Astuces MS-Access |
Combiner des données de rubriques de deux ou plusieurs tables à l'aide d'une requête UNION
Les requêtes UNION combinent les rubriques correspondantes de deux ou plusieurs tables ou requêtes dans une seule rubrique. Par exemple une requête UNION des tables Clients et Fournisseurs génère un instantané qui contient tous les enregistrements spécifiés de ces deux tables
Exemple:
SELECT [Société],[Ville] FROM [Fournisseurs] WHERE [Pays]="France"
UNION SELECT [Société],[Ville] FROM [Clients] WHERE [Pays]="France";
Chaque instruction SELECT doit retourner le même nombre de rubriques dans le même ordre. Le type de données des rubriques correspondantes doit être compatible, mais les noms n'ont pas besoin d'être identiques.
F
Si la 2ème table ne comporte pas toutes les rubriques, il faut insérer une expression pour générer une valeur null. Par exemple, Titre: NullExemple:
SELECT [Société],[Ville] FROM [Fournisseurs] WHERE [Pays]="Italie"
UNION SELECT [Société],[Ville] FROM [Clients] WHERE [Pays]="Italie"
ORDER BY [Ville];
F
Si vous convertissez une requête Union en une requête d'un autre type, telle qu'une requête Sélection, vous perdez l'instruction SQL que vous venez d'entrer.Les noms des colonnes d'une requête Union correspondent aux noms des colonnes de la première table ou instruction SELECT. Pour renommer une rubrique dans les résultats, utilisez la clause AS pour créer un alias du nom. Par exemple:
SELECT [Société] AS [Nom Client/Fournisseur], [Ville] FROM [Fournisseurs]
UNION SELECT [Société] AS [Nom Client/Fournisseur] ,[Ville] FROM [Clients];
Dans la fenêtre Bdd, les requêtes Union sont symbolisées par deux anneaux. Leur résultat étant un instantané (snapshot), on ne peut pas le modifier.
Idées pratiques d'emploi de requêtes UNION
| Haut du document | Trucs et Astuces MS-Access |
Utiliser une sous-requête pour définir une rubrique ou les critères d'une rubrique
Une sous-requête est une instruction SQL SELECT située en qualité d'expression dans la cellule champ si on veut définir une rubrique ou située dans la cellule critères pour définir un critère de sélection.
Vous pouvez utiliser des sous-requêtes pour entre autres vérifier si des résultats de la sous-requête existent, rechercher les valeurs de la requête principale égales, supérieures ou inférieures aux valeurs retournées par la sous -requête ou créer des sous-requêtes au sein d'autres sous-requêtes.
Exemples de l'emploi du résultat d'une sous-requête comme critère
|
Champ |
Expression dans Critères |
Affiche |
|
PrixUnitaire |
(SELECT [PrixUnitaire] FROM [Produits] WHERE [NomProduit] = "Sirop d'anis") |
Produits dont le prix est identique au prix de Sirop d'anis. |
|
PrixUnitaire |
>(SELECT AVG([Prix unitaire]) FROM [Produits]) |
Produits dont le prix unitaire est supérieur à la moyenne. |
|
Salaire |
>ALL (SELECT [Salaire] FROM [Employés] WHERE ([Fonction] COMME "*Responsable*") OU ([Fonction] COMME "*Directeur*")) |
Salaire des représentants dont le salaire est supérieur à celui des employés dont la fonction contient "Responsable" ou "Directeur". |
|
Total:[PrixUnitaire] * [Quantité] |
> ALL (SELECT AVG([PrixUnitaire] * [Quantité]) FROM [DétailCommande]) |
Commandes dont le montant total est supérieur au montant moyen des commandes. |
Remarques:
| Haut du document | Trucs et Astuces MS-Access |
Distribution d'un objet en valeur et pourcentage
La direction d'une société demande souvent d'établir un état montrant la distribution (répartition) d'un actif en valeur et en pourcentage. Par exemple on veut connaître le nombre des clients par pays et le pourcentage que ce nombre représente par rapport au nombre total des clients. A cet effet, il suffit de créer une requête de sélection comportant la rubrique Pays, une opération de comptage sur la clé primaire, et une expression pour calculer le pourcentage. L'expression SQL suivante est alors générée ou écrite:
SELECT Clients.Pays, Count(Clients.[Code client]) AS [Nombre de Clients], (Count(*)/DCount("[Code client]","Clients")) * 100 AS Pourcentage
FROM Clients
GROUP BY Clients.Pays;
| Haut du document | Trucs et Astuces MS-Access |
Comportement dangereux d'Accès 97 pour les requêtes action
Par rapport aux versions précédentes. les requêtes action incluent une nouvelle propriété Utiliser une transaction, dont la valeur par défaut est Oui. Dans ce cas, Access envoie un message de demande de confirmation:
"Vous allez exécuter une requête Mise à jour qui modifiera les données de votre table".
L'usager a la possibilité de répondre Non et les modifications sont annulées en mémoire par une opération Rollback.
Par contre, si vous changez la valeur de la propriété à Non, la boîte de dialogue de confirmation est aussi affichée alors que les modifications ont été exécutées sur le disque. L'usager peut ainsi croire qu'il est en mesure d'annuler les changements, alors qu'il n'en est rien.
Pour toutes les requêtes action converties de versions Access 95 ou Access 2.0, la propriété Utiliser une transaction est réglée sur Non par défaut! Cela signifie que toutes ces requêtes action seront exécutées malgré l'affichage du message de demande de confirmation. Vous pouvez imaginer ce qui arrive quand vous exécutez une requête action sur une base de données SQL Server. Il faut donc régler cette propriété sur Oui.
| Haut du document | Trucs et Astuces MS-Access |
Suppression d'une requête
Dans plusieurs applications on crée / supprime les requêtes dynamiquement. Il s'agit alors d'éviter le message d'erreur "La requête existe déjà". La fonction ci-après permet de supprimer la requête dont le nom est spécifié.
Public Function SuppressionRequête(NomRequête As String)
Dim Bdd As Database, qd As QueryDef
Set Bdd = DBEngine.Workspaces(0)(0)
With Bdd
| Haut du document | Trucs et Astuces MS-Access |
Obtenir un échantillon aléatoire des enregistrements
A cet effet, il suffit de soumettre la clé primaire de la table à la fonction Rnd() afin d'obtenir un nombre aléatoire pour chaque enregistrement de la table. On trie alors le recordset sur ce nombre aléatoire et on choisit le nombre d'enregistrements désirés à l'aide de la clause TOP ou de la clause PERCENT (premières valeurs).
Exemple:
SELECT TOP 50 Clients.*
FROM Clients
ORDER BY Rnd([CodeClient]);
| Haut du document | Trucs et Astuces MS-Access |
Sélection par paramètres stockés dans une table
Quand on a plusieurs paramètres d'exécution, il est plus convivial d'utiliser un formulaire permettant la mise à jour d'une table tblParamètres et le lancement de la requête par un clic sur un bouton de commande. La table comporte un seul enregistrement contenant tous les paramètres d'exécution de la requête.
Exemple:
Dans la bdd exemple Comptoir.mdb, on désire obtenir le total des ventes mensuelles pour n'importe quel employé, durant une période à choix. Cela suppose donc la saisie de trois paramètres: Le nom de l'employé analysé (prénom et nom), une date de début et une date de fin.
On crée la tblParamètres dont la structure sera par exemple la suivante:
|
Nom Rubrique |
Type |
Propriétés |
|
CléPrimaire |
Numérique |
Taille: Octet, Valeur par défaut: 1, Valide si = 1, Indexé: Oui sans doublons |
|
DateDébut |
Date/Heure |
Format: abrégé |
|
DateFin |
Date/Heure |
Format abrégé |
|
Employé |
Texte |
Taille: 30 |
Le formulaire de saisie des paramètres aura l'aspect suivant:

La liste déroulante sera fondée sur une requête qui va concaténer le prénom et le nom de l'employé de la table Employés. SELECT DISTINCTROW [Prénom] & " " & [Nom] AS Employés FROM Employés;
Il ne faut pas oublier de mettre à jour la table après chaque mise à jour d'un paramètre. Par exemple, pour la liste déroulante on aura:
Private Sub cboEmployés_AfterUpdate()
On Error Resume Next
If Not IsNull(Me!cboEmployés) Then
Le bouton de commande va lancer la requête désirée dont la grille d'interrogation sera la suivante:
|
Rubrique |
Table |
Opération |
Tri |
Afficher |
Critère |
|
Mois: Format([Date commande];"mmm aaaa") |
|
Regroupement |
|
Oui |
|
|
Ventes: Somme(([Quantité]*[Prix unitaire])*(1-[Remise (%)])) |
|
Expression |
|
Oui |
|
|
Unités Vendues: Quantité |
Détailscommandes |
Somme |
|
Oui |
|
|
(Année([Date commande])*12+Mois([Date commande])-1) |
|
Regroupement |
Asc |
Non |
|
|
Date commande |
Commandes |
Où |
|
Non |
Entre [DateDébut] Et [DateFin] |
|
[Prénom] & " " & [Nom] |
|
Où |
|
Non |
[tblParamètres]![Employé] |
La procédure sur clic du bouton de commande sera:
Private Sub cmdReqVentesMensuellesEmployé_Click()
On Error GoTo TraitementErreur
If IsNull(Me.cboEmployés) Then
| Haut du document | Trucs et Astuces MS-Access |
Lancer une requête action paramétrée depuis VBA
A cet effet, on lancera une fonction, qui référencera la collection QueryDefs pour rechercher la requête à exécuter. Ensuite on entrera les invites et les valeurs désirées dans la collection Parameters de l'objet QueryDef représentant la requête à exécuter à l'aide de la méthode Execute. Il faut noter que celle-ci ne fonctionne qu'avec des requêtes action.
Exemple:
Function LancerReqParam()
' Objectif: Lancer une requête action paramétrée depuis VBA.
' Va créer une nouvelle table
Dim Req As QueryDef
Set Req = CurrentDb.QueryDefs("reqInfoBoîtesParam")
Req.Parameters("Entrez la taille") = "500"
' On répétera l'entrée des invites et des valeurs dans la collection Parameters
' pour chaque paramètre prévu.
Req.Execute
End Function
| Haut du document | Trucs et Astuces MS-Access |
Requête paramétrée avec valeur par défaut
Quand on doit entrer certains paramètres bien plus souvent que d'autres, il est judicieux de prévoir une valeur par défaut, qui est elle-même susceptible d'être modifiée une fois ou l'autre. Pour y parvenir, il existe deux solutions. La première consiste à placer la ou les valeurs par défaut dans la base registre du système d'exploitation via la fonction SaveSetting, qui permet de créer des sous-clés et d'enregistrer une valeur sous forme de chaîne à partir de zones de texte d'un formulaire. Par la suite, on les lit via la fonction GetSetting pour peupler la zone Critère d'une requête paramétrée.
La seconde, beaucoup plus simple, consiste à ouvrir une boîte de dialogue InputBox permettant de spécifier une valeur par défaut, depuis la zone Critère d'une requête paramétrée. C'est cette solution que nous expliquons ici.
Dans la zone Critère de la rubrique à paramétrer dans une requête, on entre une expression du type suivant:
Eval("InputBox(""Invite"",""Titre"",""ValeurParDéfaut"")")Par exemple:
C'est la fonction Eval(), qui va exécuter l'ouverture de la boîte de dialogue avec la valeur par défaut présentée à l'usager. Celui-ci pourra toujours la modifier si nécessaire.
| Haut du document | Trucs et Astuces MS-Access |
Trier des enregistrements selon la casse
Par défaut, Access n'effectue pas la distinction entre majuscules et minuscules. Il est alors nécessaire de créer une fonction VBA qui sera utilisée dans une requête de sélection de tous les enregistrements.
La table suivante montre comment l'ordre croissant par défaut diffère bien de l'ordonnancement selon la casse.
| Ordre Croissant Par défaut | Ordre sensible à la casse |
| a | A |
| A | B |
| b | C |
| B | D |
| c | a |
| C | b |
| d | c |
| D | d |
On crée une procédure de conversion dans un module standard:
:
Function ConvStrHexadecimal (Car As Variant) As VariantOn crée la requête de sélection fondée sur la table à trier, en utilisant la rubrique servant de clé de tri et un champ calculé, non affiché, qui fait appel à la fonction de conversion pour trier sur la casse.
Champ: Expr1: ConvStrHexadecimal([Nom])
Tri: Croissant
Afficher: Non
| Haut du document | Trucs et Astuces MS-Access |
Centraliser les critères de recherche
Pour faciliter la création des requêtes de sélection implémentant la logique OU à l'aide de la clause IN, il est avantageux de stocker les critères dans une table "tblCritèresCentralisés" créée à cet effet. Pour des opérations financières on aura par exemple une rubrique Devise peuplée comme suit:
CHF, FRF, GBP, DEM, LIT, USD, CAD, AUD..
Un appel à la fonction fournie InstructionIn("tblCtritèresCentralisés", "Devise") retourne la chaîne In ("CHF", "FRF", "GBP", "DEM", "LIT", "USD", "CAD", "AUD")
Public strVAR As String '*** stockage de la clause IN générée
Public Const Quote = """"
Function InstructionIn(NomTable As String, NomRubrique As String)
Ci-après vous avez une procédure de test permettant de passer le contenu de la chaîne strVar à une instruction SQL de sélection.
Sub TestCritères()
Dim Bdd As Database
Dim rs As Recordset
Dim i As Integer
Dim strSQL As String
Set Bdd = CurrentDb()
' Génération de la clause IN stockée dans la variable globale strVAR.
i = InstructionIn("tblCritèresCentralisés", "Devise")
' Création de la requête avec les critères générés
strSQL = "SELECT * FROM tblCritèresCentralisés " _
& "WHERE Devise " & strVAR & ""
Set rs = Bdd.OpenRecordset(strSQL)
rs.MoveLast
Debug.Print "Nombre d'enregistrements: " & rs.RecordCount
Debug.Print strSQL
rs.Close
Bdd.Close
End Sub
| Haut du document | Trucs et Astuces MS-Access |
Requêtes sélection fondées sur une période de temps
Une période est délimitée par une date de début et une date de fin. Par exemple, on gère les demandes de vacances des employés et on désire obtenir la liste de tous les employés en vacances quelques jours durant la période du 15/07/2001 au 15/08/2001. La table aura au minimum trois rubriques: NomEmployé, DébutVac, FinVac.
Ceci signifie que nous devons rechercher les intervalles de dates suivants:
La grille de la requête aura donc l'aspect suivant:

Nous avons donc quatre critères de sélection. Chaque critère répond à l'une des questions.
Le code SQL exécuté est le suivant:
SELECT tblVacances.NomEmployé, tblVacances.DébutVac, tblVacances.FinVac FROM tblVacances
GROUP BY tblVacances.NomEmployé, tblVacances.DébutVac, tblVacances.FinVac
HAVING (((tblVacances.DébutVac)<#7/16/2001#) AND ((tblVacances.FinVac)<#8/16/2001# And (tblVacances.FinVac)>#7/14/2001#)) OR (((tblVacances.DébutVac)<#7/16/2001#) AND ((tblVacances.FinVac)>#8/14/2001#)) OR (((tblVacances.DébutVac)>#7/14/2001#) AND ((tblVacances.FinVac)<#8/16/2001#)) OR (((tblVacances.DébutVac)>#7/14/2001# And (tblVacances.DébutVac)<#8/16/2001#) AND ((tblVacances.FinVac)>#8/14/2001#))
ORDER BY tblVacances.DébutVac, tblVacances.FinVac;
On notera la conversion en format américain des dates entrées en format européen..Généralement une requête de ce type sera la source d'un état.
| Haut du document | Trucs et Astuces MS-Access |
Sélection par comparaison de valeurs entre enregistrements
On veut par exemple obtenir la liste de toutes les commandes effectuées dans un intervalle de 5 jours par chaque client. Donc la différence entre deux dates de commande pour un client déterminé ne doit excéder -5 ou 5 jours.
Pour comparer les valeurs d'une rubrique entre plusieurs enregistrements, il faut inclure la table Commandes deux fois en qualité de source des données dans la partie supérieure de la fenêtre par l'exemple graphique. Ainsi, on peut comparer la valeur d'une rubrique de la première occurrence de la table au contenu de la même rubrique dans la seconde occurrence de la table,
La 2ème occurrence de la table reçoit automatiquement le nom de Commandes_1.
Il faut alors établir une jointure entre les deux rubriques NoClient puisqu'on désire effectuer les comparaisons entre dates client par client. Donc on a une liaison entre Commandes.NoClient et Commandes_1.NoClient.
Dans la grille de la partie inférieure de la fenêtre on aura
Champ: Commandes.*
Table: Commandes
Champ: Expr1: DiffDate("j";[Commandes].[Date commande];[Commandes_1].[Date commande])
Critère: Entre -5 Et 5
Champ: NoCommande
Table: Commandes
Critère: <>[Commandes_1].[NoCommande]
Explications:
L'objectif étant de lister les enregistrements de chaque client avec des NoCommande différents et dont les dates de commande sont dans un intervalle de 5 jours au maximum les unes par rapport aux autres, il faut effectuer trois comparaisons.
La 1ère est effectuée au niveau des rubriques NoClient à l'aide de la jointure.
La 2ème doit tester l'intervalle entre deux dates de commande successives pour un client déterminé. Ceci est réalisé par la fonction DiffDate() qui calcule la différence en jours entre la date de commande de la 1ère occurrence de la table et celle de la 2ème occurrence de la table. Le critère limite le résultat pris en considération à un écart de 5 jours.
La 3ème permet d'éviter les doublets au niveau du NoCommande. On limite donc les enregistrements sélectionnés à ceux qui ont des NoCommande différents.
Si on veut lister les commandes effectuées par chaque client dans l'intervalle spécifié et gérées par le même employé, il suffit d'ajouter une liaison entre les rubriques NoEmployé des deux occurrences de la table.
| Haut du document | Trucs et Astuces MS-Access |
Gestion des messages de confirmation pour les requêtes action
Au niveau de l'exécution d'une requête déterminée on encadre l'appel de la requête avec la méthode SetWarnings de l'objet DoCmd de la manière suivante:
Avant l'exécution, pour désactiver le message système de confirmation: DoCmd.SetWarnings
FalseAprès l'exécution de la requête pour réactiver l'affichage des messages système: DoCmd.SetWarnings
TrueSi on doit exécuter une série de requêtes action à partir du code Visual Basic, il peut être avantageux de créer une fonction dans un module standard vous permettant d'intervenir dans les options d'exécution accessibles à partir de la commande Option du menu Outils.
Public Function ConfirmerRequêtesAction(bOption As Boolean) As Variant
| Haut du document | Trucs et Astuces MS-Access |
Déterminer le nombre d'enregistrements affectés par une requête action
Quand on exécute une requête de suppression, de mise à jour ou d'ajout d'enregistrements, il est parfois utile de connaître le nombre d'enregistrements affectés par l'action. A cet effet, la technologie DAO nous offre la propriété de bdd RecordsAffected, qu'il suffit de consulter après l'exécution de l'opération.
Sub ExecuterReqAction_DAO()
| Haut du document | Trucs et Astuces MS-Access |
Ecriture d'une requête d'interrogation en langage SQL
Les requêtes SQL pour interroger la Bdd peuvent être très gourmandes en ressources système et en temps processeur. Alors autant les formuler correctement afin de ne pas surcharger le serveur et donc d'afficher les résultats rapidement. Vous pensez connaître la fonction "SELECT", la plus simple et la plus utilisée ? Bien, alors examinons cela...
Syntaxe d'extraction des données
Il est plus performant de définir les requêtes de sélection dans la propriété Source d'un objet de formulaire ou d'état au lieu de les stocker séparément dans la bdd. De même il est performant de créer les requêtes dynamiquement dans des procédures ou des fonctions VBA. Il faut savoir que la syntaxe présente des différences dans ces deux cas de figure.
Syntaxe de la commande SELECT dans la propriété Source d'un objet:
SELECT [ALL | DISTINCT | DISTINCTROW | [ TOP n [PERCENT]]] liste de rubriques
FROM Table_1, Table_2, ... , Table_n
WHERE ......
GROUP BY ......
HAVING ........
ORDER BY .......
Exemples simples:
SELECT Nom, Prénom FROM Employés
Détail des prédicats de la clause SELECT:
Un prédicat spécifie les enregistrements qui vont être sélectionnés
ALL
Si vous n'incluez aucun prédicat, tous les enregistrements qui remplissent les conditions de l'instruction SELECT sont sélectionnés. Les deux exemples suivants sont équivalents et renvoient tous les enregistrements de la table Clients:
SELECT ALL FROM Clients ORDER BY [CodeClient];
SELECT * FROM Clients ORDER BY [CodeClient];
DISTINCT
Omet tous les enregistrements pour lesquels les rubriques sélectionnées contiennent des données en double. Ainsi, pour être incluses dans les résultats de la requête, les valeurs de chaque rubrique répertoriée dans l'instruction SELECT doivent être uniques. Par exemple, plusieurs employés répertoriés dans une table Employés peuvent avoir le même nom. Si deux enregistrements contiennent Barthez dans la rubrique "Nom", l'instruction SQL suivante ne renvoie alors qu'un seul de ces enregistrements:
SELECT DISTINCT Nom FROM Employés;
Si on omet DISTINCT, cette requête renvoie les deux enregistrements Barthez. Si la clause SELECT contient plusieurs rubriques, la combinaison des valeurs de toutes ces rubriques doit être unique, pour un enregistrement donné, pour que ces valeurs apparaissent dans le résultat. Le résultat d'une requête qui utilise DISTINCT ne peut pas être mis à jour et ne reflète pas les modifications ultérieures effectuées par d'autres utilisateurs.
DISTINCTROW
(Valeur par défaut sous Access). Omet les données sur la base des enregistrements complets en double, et pas seulement de rubriques en double. Par exemple, vous pouvez créer une requête qui joint les tables Clients et Commandes à l'aide de la rubrique CodeClient. La table Clients ne contient aucun doublon dans la rubrique CodeClient (clé primaire), mais la table Commandes en contient car chaque client peut passer plusieurs commandes. L'instruction suivante montre comment utiliser DISTINCTROW pour produire une liste de sociétés qui ont passé au moins une commande:
SELECT DISTINCTROW Société FROM Clients INNER JOIN Commandes ON Clients.[CodeClient] = Commandes.[CodeClient] ORDER BY Société;
Si on omet DISTINCTROW, cette requête produit plusieurs lignes pour chaque société ayant passé plusieurs commandes. DISTINCTROW n'a d'effet que si vous sélectionnez des rubriques dans seulement quelques-unes des tables utilisées dans la requête. DISTINCTROW est ignoré si votre requête n'inclut qu'une seule table ou si vous sélectionnez les rubriques de toutes les tables.
Remarque:
La clause FROM nomTableMaîtresse INNER JOIN nomTableSecondaire ON rubriqueTableMaîtresse OPERATEUR rubriqueTableSecondaire permet de spécifier le type de jointure (équi-jointure) et le sens de la liaison entre les deux tables.
TOP n [PERCENT]
Renvoie un certain nombre d'enregistrements situés au début ou à la fin d'une plage spécifiée par une clause ORDER BY. Supposons que vous souhaitiez obtenir les noms des 10 premiers étudiants de la promotion 2001:
SELECT TOP 10 Nom, Prénom FROM Etudiants WHERE Promotion = 2001 ORDER BY Moyenne DESC;
Si vous n'incluez pas la clause ORDER BY, la requête renverra une série de 10 enregistrements choisis arbitrairement parmi ceux de la table Etudiants qui remplissent les conditions de la clause WHERE. Le prédicat TOP n'effectue pas de choix entre des valeurs égales. Dans l'exemple précédent, si, parmi les meilleurs résultats obtenus, le dixième et le onzième ont obtenu la même moyenne, la requête renvoie 11 enregistrements. Vous pouvez également utiliser le mot réservé PERCENT pour renvoyer un certain pourcentage des premiers ou derniers enregistrements d'une plage spécifiée par la clause ORDER BY. Supposons qu'au lieu des 10 meilleurs étudiants, vous souhaitiez sélectionner 10 pour cent de la promotion:
SELECT TOP 10 PERCENT Nom, Prénom FROM Etudiants WHERE Promotion = 2001 ORDER BY Moyenne DESC;
Détail des autres clauses optionnelles:
La clause WHERE exprime les conditions de recherches.
La clause GROUP BY groupe les résultats par critères.
La clause HAVING permet de restreindre les résultats en imposant une propriété.
La clause ORDER BY trie les résultats suivant un critère de qualification.
La clause WHERE
Les conditions de recherche peuvent faire référence à des rubriques qui ne sont pas incluses dans la liste de sélection. Elle est très utilisée pour définir des jointures:
SELECT Clients.Société, Factures.Date FROM Clients, Factures
WHERE Clients.CodeClient = Factures.CodeClient
renverra le nom des clients ayant des factures, ainsi que la/les date des factures correspondantes. Il est important de faire une jointure sur CodeClient dans la clause Where, pour indiquer au moteur SQL que la liaison entre les deux tables s'effectue sur cette colonne "commune".
Nous avons plusieurs outils pour les types de condition :
Opérateur de comparaison: = , > , < , >= , <=, <>
Intervalles: BETWEEN , NOT BETWEEN
WHERE PrixUnitaire BETWEEN 100 AND 200
Listes: IN , NOT IN
Remarque:
En SQL, NULL ne signifie pas 0 ,il représente une valeur indéterminée, il signifie "rien", comme l'ensemble vide en Mathématique.
SELECT Libellé, Quantité, Date FROM Produits , DétailFacture, Factures
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture = Factures.CodeFacture
AND PrixUnitaire NOT BETWEEN 100 AND 200 OR PrixUnitaire = 120
Les agrégats
Les fonctions d'agrégats calculent des valeurs à partir des données d'une colonne particulière. Les opérateurs d'agrégat sont : sum (somme), avg (moyenne), max (le plus grand), min (le plus petit), et count (le nombre d'enregistrements retournés).
SELECT sum(PrixUnitaire * Quantité) As [Total Facture] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture =123456
SELECT avg(PrixUnitaire) As [Prix Unitaire Moyen] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture= 123456
SELECT max(PrixUnitaire) AS [Prix Unitaire Maximum] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture =123456
Si on doit comparer des littéraux dates, il faudra les spécifier en format américain et les encadrer avec le signe #.
SELECT ALL FROM Commandes WHERE DateEnvoi = #3/10/2001#;
Si on doit comparer des valeurs non numériques, il faudra les encadrer avec le signe '
SELECT NoCommande, PaysLivraison FROM Commandes
WHERE Pays IN ('France', 'Canada', 'Suisse');
La clause GROUP BY
Cette clause, utilisée dans une instruction SELECT, divise le résultat d'une requête en groupes. Actuellement le regroupement peut s'effectuer sur un maximum de 10 rubriques. La clause GROUP BY est présente dans les requêtes qui comportent également des fonctions d'agrégats, ces dernières produisant alors une valeur de synthèse pour chaque groupe, appelée parfois agrégat vectoriel.
SELECT Nom, avg(PrixUnitaire) As [Prix Unitaire Moyen], sum(Quantité) As [Quantité des Produits], CodeFacture
FROM Produits , DétailFacture, Factures, Clients
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture = Factures.CodeFacture
AND Facture.CodeClient = Clients.CodeClient GROUP BY CodeFacture
SELECT NoDept, min(Salaire) AS [Salaire Minimum], max(Salaire) AS [Salaire Maximum] FROM Employés WHERE Fonction = 'Ingénieur' GROUP BY NoDept
La clause HAVING
Cette clause permet de limiter les enregistrements regroupés par la clause GROUP BY. Le moteur de la Bdd traite une requête comportant les clauses WHERE, GROUP BY et HAVING comme suit:
SELECT NoDept, min(Salaire) As [Salaire Minimum], max(Salaire) AS [Salaire Maximum] FROM Employés
WHERE Fonction = 'Ingénieur' GROUP BY NoDept HAVING min(Salaire) < 9000
Une clause HAVING peut contenir jusqu'à 40 expressions liées par des opérateurs logiques comme AND et OR.
SELECT NoEmployé, Count(NoCommande) AS [Nombre de Commandes]
FROM Commandes GROUP BY NoEmployé
HAVING Count(NoCommande) > 99 AND < 201;
Attention à la performance:
L'avantage de la clause HAVING est qu'elle autorise l'emploi de fonctions d'agrégat, ce qui n'est pas possible avec la clause WHERE. Mais c'est aussi sa seule justification, car autrement on va faire exécuter des calculs inutiles puisqu'une partie des résultats sera finalement éliminée par cette clause.
On se rend alors compte que la clause HAVING ci-dessus peut-être remplacée par une clause WHERE, et de manière plus pertinente puisque nous éliminons des lignes avant de les regrouper.
La clause HAVING est intéressante quand elle porte directement sur une valeur issue du regroupement.
La clause ORDER BY
Trie, par ordre croissant ou décroissant, les enregistrements résultants d'une requête en fonction de la ou des rubriques spécifiées. Une rubrique de type Mémo ou OLE ne peut pas être triée. Cette clause est généralement la dernière d'une instruction SELECT.
SELECT Nom, Salaire FROM Employés ORDER BY Salaire DESC, Nom;
Combinaison avec d'autres fonctions
SELECT Left([RéfProduit],9) AS categorie, Max(Right([RéfProduit],4)) AS numeroSyntaxe de la commande SELECT dans le code VBA
Dans la pratique, on peut distinguer deux méthodes de création des chaînes SQL. La méthode standard et la méthode utilisée quand les chaînes SQL contiennent des variables
Exemple de la 1ère méthode utilisée quand il n'y a pas de variables, mais seulement des rubriques des tables. L'objectif est la lisibilité et la standardisation de l'écriture.
Dim strSQL As String
strSQL= "SELECT DISTINCTROW Catégories.NomCatégorie, " & _
"Produits.NomProduit, " & _
"Catégories.Description, Categories.ImageCateg, " & _
"Produits.CodeProduit, Produits.QuantitéParUnité," & _
"Produits.PrixUnitaire, Produits.Abandonné " & _
"FROM Catégories " & _
"INNER JOIN Produits ON Catégories.CodeCatégorie = Produits.CodeCatégorie " & _
"WHERE ((Catégories="'Livres')) " & _
"ORDER BY Catégories.NomCatégorie, Produits.NomProduit;"
Règles d'écriture suggérées
Exemple de la seconde méthode utilisée notamment quand un critère de recherche peut varier et doit être stocké dans une variable (ClauseWhere)..
Dim strSQL As String
Dim ClauseWhere As String
Select Case Me!cmdSelectionPar.Caption
strSQL = "SELECT DISTINCTROW '' As extraspace, NomRestaurant, id "
strSQL = strSQL & "FROM tblRestaurant " & ClauseWhere
strSQL = strSQL & " ORDER BY NomRestaurant ;"
Set rec = Bdd.OpenRecordset(strSQL)
On peut aussi définir le code de terminaison de la chaîne SQL sous forme d'une constante:
Public Const TerminaisonSql = ";"
Dans ce cas la chaîne sera terminée comme suit:
strSQL = strSQL & TerminaisonSql
Remarque:
Ecrire les instructions SQL dans le code VBA présente l'avantage de pouvoir supprimer les messages système d'avertissement affichés avant les mutations (insertion, suppression, mise à jour). A cet effet on utilise la méthode SetWarnings de l'objet DoCmd.
Dim strSql As String
strSql = "UPDATE tblAppels SET tblAppels.DernierAppel = Now(), ............."
' Désactiver les messages système d'avertissement
DoCmd.SetWarnings False
DoCmd.RunSQL strSql
' Rétablir les messages système d'avertissement
DoCmd.SetWarnings True
Une solution encore plus avantageuse est d'utiliser la méthode Execute de l'objet CurrentDb, car elle permet d'éviter de devoir désactiver / réactiver les avertissements.
Ainsi pour les ajouts on aura: CurrentDb.Execute "SELECT INTO..."
Pour les mises à jour on aura: CurrentDb.Execute "UPDATE..."
Pour les suppressions on aura: CurrentDb.Execute "DELETE..."
| Haut du document | Trucs et Astuces MS-Access |
Requêtes de regroupement
Les requêtes de regroupement permettent de faire une analyse statistique de la bdd
L'objectif est de regrouper les données, chaque groupe étant l'ensemble des lignes ayant une valeur commune. Nous devons donc indiquer à SQL comment il doit constituer les groupes et ce que l'on veut faire comme opération. C'est la clause GROUP BY qui nous permet de donner le regroupement à réaliser :
Nous pouvons ensuite étudier les caractéristiques de ces groupes: notamment en calculer l'effectif. Par exemple, à partir d'une base de suivi de visites d'un site on peut regrouper les données par serveur d'origine, par moteur de recherche, par date de visite.
Exemples
Prenons une table d'individus dont nous connaissons les dates de naissance, il peut être intéressant de connaître le nombre de personnes par année de naissance.
SELECT year(dateDeNaissance) AS Année, count(*) As Nombre FROM tblHabitants GROUP BY Année
Voilà un extrait du résultat :
|
Année |
Nombre |
|
1969 |
39 |
|
1970 |
31 |
|
1971 |
28 |
|
1972 |
22 |
|
1973 |
19 |
|
1974 |
32 |
Le résultat nous est donné trié par année, nous pouvons l'obtenir trié sur le nombre :
SELECT year(dateDeNaissance) As Année, count(*) AS Nombre FROM tblHabitants GROUP BY Année ORDER BY count(*)
Sélectionner les groupes
SELECT year(dateDeNaissance) AS Année, count(idElement) AS Nombre, CodePostal FROM tblHabitants GROUP BY Année HAVING CodePostal = '1000'
WHERE ou HAVING ?Par exemple, nous pourrions très bien n'étudier que les groupes d'année de naissance pour les femmes :
SELECT year( dateDeNaissance) AS adn, count(idElement) AS cpt FROM tblHabitants WHERE sexe = 'F' GROUP BY dateDeNaissance
On se rend alors compte que la clause HAVING ci-dessus peut-être remplacé par une clause WHERE, et de façon plus pertinente puisque nous éliminons des lignes avant de les regrouper. L'exécution sera donc plus rapide.
La clause HAVING est intéressante quand elle porte directement sur une valeur issue du regroupement. Intéressons-nous par exemple aux groupes dont les effectifs sont supérieurs à 10 :
SELECT year( dateDeNaissance) AS adn, count(idElement) AS cpt FROM tblHabitants GROUP BY adn HAVING cpt >10 ORDER BY cpt
| Haut du document | Trucs et Astuces MS-Access |
Faciliter l'entretien des requêtes
Parfois l'évolution des besoins des utilisateurs nécessite de changer les noms de quelques tables. Si des requêtes existantes sont fondées sur ces tables, il faut modifier le nom des tables pour toutes les rubriques affichées, ce qui n'est pas un travail négligeable. On peut réduire largement le temps nécessaire par l'emploi systématique de pseudonymes (Alias) pour les noms des tables dans la clause FROM du langage SQL.. Ainsi, les références à la table au niveau des rubriques n'ont pas besoin d'être changées, il suffira de modifier le nom de la table associé à la clause FROM.
Pour attribuer un pseudonyme à la table lors de la création de la requête, il suffit de procéder comme suit:
Au cours de son existence une application subit des changements plus ou moins nombreux pour faire face aux exigences de l'économie. Il est donc judicieux d'anticiper pour réduire le temps consacré à l'entretien des applications, et par suite d'augmenter les possibilités de création de nouvelles applications en fonction des besoins en évolution permanente des utilisateurs de votre organisation.
| Haut du document | Trucs et Astuces MS-Access |
Emploi de variables pour construire les instructions SQL
Si vous créez une procédure ou une fonction comportant plusieurs instructions SQL presque identiques, vous pouvez faciliter l'entretien du code en stockant les différents éléments des instructions SQL dans des variables. Ainsi vous pourrez combiner les variables selon les besoins en réutilisant les éléments invariables.
Exemple sous ADO:
Plus les requêtes sont complexes, plus ce système de construction devient avantageux.
| Haut du document | Trucs et Astuces MS-Access |
Effectuer un cumul
Problème:
On veut obtenir un état mensuel présentant un cumul des ventes réalisées tel que l'extrait suivant:
| No Mois | Montant | Cumul |
| 01 | 1000 | 1000 |
| 02 | 1500 | 2500 |
| 03 | 1400 | 3900 |
Solution:
Une possibilité est de fonder l'état sur une requête de totalisation utilisant la fonction domaine DSum. Dans notre exemple, la requête est basée sur une table dénommée tblVentes contenant les rubriques NoMois et MontantMensuel .auxquelles on adjoindra un champ calculé indépendant dénommé CumulVentes.
Ainsi, on aura le code SQL suivant qui balaye la table enregistrement par enregistrement: et calcule le cumul tant que le contenu de la rubrique NoMois est inférieur ou égal à celui de l'enregistrement courant.
SELECT tblVentes.NoMois, tblVentes.MontantMensuel,
DSum("MontantMensuel","tblVentes","NoMois <=" & [NoMois]) AS CumulVentes
FROM tblVentes
ORDER BY tblVentes.NoMois;
| Haut du document | Trucs et Astuces MS-Access |
Totaliser dans un formulaire par encapsulation de la requête
Il arrive souvent qu'un total ou un soustotal doive être calculé à partir d'une source différente du recordset sous-jacent d'un formulaire. Par exemple, dans un formulaire affichant les données clients, si on veut présenter le total du chiffre d'affaires réalisé avec chacun. En effet, on devra notamment balayer les enregistrements de la table Détails commandes, à l'aide d'une requête de totalisation. Malheureusement cette approche rend le formulaire dépendant d'un objet supplémentaire. Si on veut l'utiliser dans une autre application, il faudra aussi y importer la requête appropriée.
Pour éviter ceci et augmenter la performance, il est avantageux de recourir au concept d'encapsulation de la technologie orientée objets. Il s'agit tout simplement d'encapsuler la logique de la requête de totalisation dans le formulaire. A cet effet, le module intégré au formulaire clients, contiendra la fonction calculant le total, par exemple:
Private Function CalculTotalVenteClient()
Dim rs As Recordset, strSQL As String
strSQL = "SELECT tblClients.CodeClient, " & _
Pour afficher le résultat, on crée une zone de texte indépendante, dont la propriété Source contrôle sera réglée comme suit: =CalculTotalVenteClient()
Ainsi, plus besoin de documenter ou de se rappeler les concordances entre requêtes et formulaires.
| Haut du document | Trucs et Astuces MS-Access |
Tester l'existence d'une requête (DAO, ADO)
Comme toujours, il y a plusieurs solutions en programmation. La plus rapide est la suivante, qui intercepte le numéro de l'erreur obtenu quand l'objet invoqué n'existe pas dans la bdd active.
Sous DAO on va simplement tester si le nom de la requête existe dans la collection QueryDefs de la bdd. Si le nom n'existe pas on obtient l'erreur 3265. On va donc comparer ce code erreur à celui obtenu, en principe zéro. S'il survient le résultat de l'expression booléenne (Err.Number <> 3265) sera zéro. Dans ce cas la fonction CBool renvoie la valeur zéro, soit False, dans le cas contraire elle renvoie True.
Public Function ExisteRequete(NomRequete As String) As Boolean
Sous ADO on va ouvrir un recordset de test pour la requête concernée. Si la requête existe la valeur de Err.Number sera zéro. Dans ce cas, CBool retourne la valeur zéro ou False et par conséquent l'opérateur Not va inverser la valeur booléenne et on obtient -1, soit True (Vrai). Le code suivant, exécuté en cliquant sur un bouton de commande, illustre cette logique de programmation. A noter qu'on désactive momentanément le traitement normal des erreurs et qu'on le réactive à la fin de l'exécution.
Private Sub cmdExisteRequete_Click()
Private Function ExisteRequeteADO(NomRequete As String) As Boolean
| Haut du document | Trucs et Astuces MS-Access |
Sauvegarder une requête dynamique dans le container
Parfois il peut être utile de sauvegarder une requête dynamique, créée par code, dans le container des requêtes enregistrées. A cet effet, on crée un nouvel objet QueryDef et on renseigne sa propriété SQL avec la chaîne dynamique
Public Function SauverRequeteDynamique()
Les deux lignes de code définissant l'objet Qd et le peuplant peuvent être placées dans n'importe quel module approprié.
| Haut du document | Trucs et Astuces MS-Access |