Requêtes Analyse croisée Requêtes Union, Sous-Requêtes et Requêtes complexes

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.

  1. Créer une nouvelle requête sans l'assistant en ajoutant les tables Clients et Commandes.
  2. Glisser-déposer successivement les rubriques NomClient, Date commande et NoCommande.
  3. Pour obtenir les entêtes de colonnes mensuels, on utilise la fonction Format comme indiqué dans l'illustration suivante.
  4. Choisir la commande Requête Analyse croisée. On obtient alors l'affichage des lignes Opération (avec la fonction Regroupement) et Analyse dans la grille d'interrogation.
  5. Cliquer sur la cellule Analyse de la rubrique NomClient et choisir Ligne dans la liste déroulante car les entêtes de lignes indiquent les éléments que l'on veut synthétiser.
  6. Cliquer sur la cellule Analyse de la rubrique Date commande et choisir Colonne dans la liste déroulante car les entêtes de colonnes indiquent les éléments que l'on veut comparer.
  7. Cliquer sur la cellule Analyse de la rubrique NoCommande et choisir Valeur dans la liste déroulante, puis cliquer sur la cellule Opération et choisir la fonction statistique Compte car on veut compter le nombre de commandes.
  8. Cliquer sur le bouton Feuille de données de la barre d'outils pour exécuter la requête.

Remarques:

  • On peut avoir plusieurs entêtes de lignes (par exemple nom et prénom du client) mais normalement un seul entête de colonne et une seule rubrique dont on traite les valeurs à l'aide d'une fonction statistique.
  • On peut permuter les entêtes de ligne et de colonne.

  • Ce type de requête ne permet pas de connaître à l'avance le nom des rubriques incluses dans le résultat. Si on veut fonder un état sur une telle requête, on devra écrire un module en VBA.
  • Les entêtes de colonne sont triées par ordre alphabétique. Si cet ordre ne convient pas, on renseigne la propriété Entête des colonnes. Par exemple: "Nov";"Déc".

 

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:

  1. Répéter l'expression ou le champ utilisé en qualité de Valeur.
  2. Dans la cellule Opération, choisir Somme, sauf si une Somme est déjà calculée au niveau de la cellule champ. Dans ce cas on choisira Expression.
  3. Dans la cellule Analyse, choisir Entête de Ligne.et exécuter la requête.

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

  1. Dans la fenêtre Bdd, cliquez sur l'onglet Requêtes, puis sur Nouveau.
  2. Dans la boîte de dialogue Nouvelle requête, cliquez sur Mode Création, puis sur OK.
  3. Sans ajouter de tables ou de requêtes, cliquez sur Fermer dans la boîte de dialogue Ajouter une table.
  4. Dans le menu Requête, pointez sur Spécifique SQL, puis cliquez sur Union.
  5. Entrez des instructions SQL SELECT combinées soit avec l'opération UNION pour empêcher que des enregistrements dupliqués soient retournés, soit avec l'opération UNION ALL pour retourner des enregistrements dupliqués.
  6. 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: Null

  7. Pour spécifier le tri dans une requête Union, ajoutez une seule clause ORDER BY à la fin de la dernière instruction SELECT. Dans cette clause, spécifiez le nom de la rubrique à trier, qui doit provenir de la première instruction SELECT.
  8. Exemple:

    SELECT [Société],[Ville] FROM [Fournisseurs] WHERE [Pays]="Italie"

    UNION SELECT [Société],[Ville] FROM [Clients] WHERE [Pays]="Italie"

    ORDER BY [Ville];

  9. Pour afficher les résultats de la requête, cliquez sur Mode d'affichage dans la barre d'outils.

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.

  1. En mode Création de requête, ajoutez les rubriques désirées dans la grille de création, y compris celles pour lesquelles vous voulez utiliser la sous-requête.
  2. Si vous utilisez une sous-requête pour définir les critères d'un champ, tapez une instruction SELECT dans la cellule Critères du champ dont les critères doivent être définis. Placez l'instruction SELECT entre parenthèses.
  3. Si vous utilisez une sous-requête pour définir une cellule Champ, tapez une instruction SELECT entourée de parenthèses dans cette cellule. Lorsque vous quittez la cellule, Access insère automatiquement " Expr1 :", " Expr2 :", etc. devant l'instruction SELECT.
  4. Pour afficher les résultats, cliquez sur Mode d'affichage dans la barre d'outils.

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:

  • Vous ne pouvez pas utiliser un champ que vous avez défini à l'aide d'une sous-requête pour regrouper des enregistrements.
  • L'instruction SELECT d'une sous-requête ne permet pas de définir une requête Union ou Analyse croisée.

 

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

For Each qd In .QueryDefs
If qd.Name = NomRequête Then
DoCmd.DeleteObject acQuery, NomRequête End If
Next qd
End With
End Function

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

DoCmd.RunCommand acCmdSaveRecord Else
End If
End Sub

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

 

Non

Entre [DateDébut] Et [DateFin]

[Prénom] & " " & [Nom]

 

 

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

MsgBox "Vous devez sélectionner un employé " _
& "dans la liste déroulante!", _
vbOKOnly, "Pas de sélection effectuée"
Else DoCmd.OpenQuery ("reqVentesMensuellesEmployé") End If
Sortie: Exit Sub TraitementErreur: MsgBox Err.Description
Resume Sortie
End Sub

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:
Eval("InputBox(""Entrez un pays"",""reqPays"",""Suisse"")")

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 Variant
' Convertit une chaîne de caractères en une suite de
' nombres hexadécimaux (code interne).
' Par exemple, ConvStrHexadecimal(Chr(7) & "Cu")
' produit 074375.
Dim Temp As String, I As Integer
' Si la rubrique n'est pas une suite de caractères de type
' Variant, pas de conversion.
If VarType(Car) <> 8 Then

ConvHexadecimal = Car Else Temp = ""
For I = 1 To Len(Car)
' La conversion est effectuée Temp = Temp & Format(Hex(Asc(Mid(Car, I, 1))), "00") Next I
ConvStrHexadecimal = Temp
End If
End Function

On 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)
' Objectif: génération d'une liste de critères (logique OU), stockés de manière centralisée
' dans une table et qui sont utilisés dans des requêtes ou des modules VBA.

Dim Bdd As Database
Dim rs As Recordset
Dim i As Integer
Dim ctr As Integer
Dim strCritère As String
Dim strSQL As String
Set Bdd = CurrentDb()
strSQL = "SELECT " & NomRubrique _
& " FROM " & NomTable _
& " WHERE " & NomRubrique & " Is Not Null;"
Set rs = Bdd.OpenRecordset(strSQL)
rs.MoveLast
' comptage du nombre de critères sélectionnés.
ctr = rs.RecordCount
rs.MoveFirst
strCritère = "In ("
For i = 1 To ctr

strCritère = strCritère & Quote _
& rs.Fields(NomRubrique).Value & Quote
If i < ctr Then strCritère = strCritère & ","
rs.MoveNext
End If
Next i
strCritère = strCritère & ")"
strVAR = strCritère
rs.Close
End Function

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:

  1. Ceux qui débutent avant le 16 juillet, mais qui terminent avant le 16 août et après le 14 juillet. La dernière condition de terminaison exclut les employés dont les vacances complètes ont lieu avant la période indiquée.
  2. Ceux qui débutent avant le 16 juillet, et qui terminent après le 14 août.
  3. Ceux qui débutent après le 14 juillet et qui terminent avant le 16 août.
  4. Ceux qui débutent après le 14 juillet et avant le 16 août et qui terminent après le 16 août. La seconde condition de début exclut les employés dont les vacances complètes ont lieu après la période indiquée

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 False

Après l'exécution de la requête pour réactiver l'affichage des messages système: DoCmd.SetWarnings True

Si 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
'Objectif: Dans le menu Outils Option, désactiver la case à cocher Requêtes action de l'onglet
'Modifier/Rechercher si l'argument bOption est False. Autrement, activer cette case à cocher pour obtenir les
'dialogues de confirmation des modifications apportées par la requête action.
'-----------------------------------------------------------------------------------------------------------
On Error GoTo Err_ConfirmerRequêtesAction
'Lecture de la valeur actuelle de l'option avec la méthode GetOption
ConfirmerRequêtesAction = Application.GetOption("Confirmer Requêtes action")
If bOption = False Then

Application.SetOption "Confirmer Requêtes action", False Else Application.SetOption "Confirmer Requêtes action", True End If
Exit_ConfirmerRequêtesAction: Exit Function Err_ConfirmerRequêtesAction: MsgBox Err.Description
Resume Exit_ConfirmerRequêtesAction
End Function

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()

Dim Bdd As DAO.Database
Dim strSQL As String
' Ouverture de la Bdd
Set Bdd = DBEngine.OpenDatabase("D:\Comptoir.mdb")
' Création du texte de la requête en langage SQL
strSQL = "UPDATE tblClients SET Pays = 'Suisse';"
' Exécution de la requête action
Bdd.Execute strSQL
' Montrer le nombre d'enregistrements affectés par la mise à jour
Debug.Print "Nombre d'enregistrements affectés: " & Bdd.RecordsAffected
Bdd.Close
End Sub

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
renvoie le contenu des colonnes NOM et PRENOM de la table CLIENT.

SELECT Nom As Nom de famille, Prénom FROM Employés
renverra le même contenu, mais en renommant le nom de la 1ère rubrique du résultat.

SELECT Libellé, PrixUnitaire * 0.80 As PrixPromo FROM Produits
affichera le libellé des articles et le prix diminué de 20%. Il est donc possible d'effectuer des calculs directement au niveau de l'instruction SELECT.

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: = , > , < , >= , <=, <>
SELECT Libellé, PrixUnitaire FROM Produits WHERE PrixUnitaire <> 100
renverra les produits dont le prix est différent de 100.

Intervalles: BETWEEN , NOT BETWEEN
SELECT Libellé, PrixUnitaire FROM Produits

WHERE PrixUnitaire BETWEEN 100 AND 200
renverra les produits dont le prix est compris entre 100 et 200.

Listes: IN , NOT IN
SELECT Libellé, PrixUnitaire FROM Produit
WHERE PrixUnitaire IN (100, 200, 300)

renverra les produits dont le prix est soit de 100, soit de 200, soit de 300. La liste exprime la logique OU.

Correspondances de chaînes: LIKE , NOT LIKE
Quelques caractères génériques :

  • * représente: toute chaîne de caractère
  • ? représente: un seul caractère, n'importe lequel
  • # représente un chiffre
  • [! ] un caractère en dehors de l'intervalle ou de l'ensemble spécifié


SELECT Libellé, PrixUnitaire FROM Produits
WHERE Libellé LIKE *teu*

renverra les produits dont le nom contient "teu" (pelleteuse, par exemple....)

Valeurs inconnues: IS NULL, IS NOT NULL
SELECT Libellé, PrixUnitaire FROM Produits
WHERE PrixUnitaire IS NOT NULL

renverra les produits dont le prix existe, c'est à dire aura une valeur dans la table.

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.


Combinaisons logiques: AND , OR
On peut avoir jusqu'à 40 conditions reliées par les opérateurs AND et OR.

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

renverra les produits, leur quantité par facture, et la date de facturation, et ceci pour les produits dont le prix n'est pas compris entre 100 et 200 ou est égal à 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
renverra le montant total de la facture No123456

SELECT avg(PrixUnitaire) As [Prix Unitaire Moyen] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture= 123456

renverra le prix moyens par produit de la facture No123456

SELECT max(PrixUnitaire) AS [Prix Unitaire Maximum] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture =123456
renverra le plus grand prix de la facture No123456

SELECT min(PrixUnitaire) AS [Prix Unitaire Minimum] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture = 123456
renverra le plus petit prix de la facture No123456

SELECT count(PrixUnitaire) AS [Nombre de Produits] FROM Produits, DétailFacture
WHERE Produits.CodeProduit = DétailFacture.CodeProduit
AND DétailFacture.CodeFacture = 123456
renverra le nombres de produits figurants sur la facture No123456

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#;
renverra les commandes envoyées le 10 mars 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');
Renverra les commandes livrées dans les trois spécifiés.

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

renverra pour chaque facture, le nom du client, le prix moyen ainsi que la quantité des produits présents sur chaque facture.

SELECT NoDept, min(Salaire) AS [Salaire Minimum], max(Salaire) AS [Salaire Maximum] FROM Employés WHERE Fonction = 'Ingénieur' GROUP BY NoDept
renverra le salaire minimum et le salaire maximum des ingénieurs de chaque département

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:

  1. Tout d'abord il élimine les lignes qui ne satisfont pas la clause WHERE.
  2. Il calcule et forme les groupes selon les spécifications de la clause GROUP BY
  3. Il élimine tous les groupes qui ne satisfont pas la clause HAVING

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
renverra le salaire minimum et le salaire maximum des ingénieurs des départements dont le salaire minimum des ingénieurs est inférieur à 9000 frs.

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;
renverra la liste des employés qui ont vendu entre 100 et 200 commandes.:

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;
renverra le nom et le salaire des employés. Les salaires sont ordonnancés en ordre décroissant, alors que les noms des employés ayant le même salaire sont présentés dans l'ordre alphabétique croissant (par défaut). La 1ère rubrique est la clé de tri majeure.

Combinaison avec d'autres fonctions

SELECT Left([RéfProduit],9) AS categorie, Max(Right([RéfProduit],4)) AS numero
FROM tblRéfProduits GROUP BY Left([RéfProduit],9) ORDER BY Left([RéfProduit],9);
renverra la dernière référence de produit pour chaque catégorie de produit, la référence de produit étant composée d'une catégorie sur 9 positions et d'un numéro sur 4 positions. Par exemple: 2001/107/0078

Syntaxe 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

  • Les mots clés sont en majuscule (FROM, WHERE, ORDER BY etc,).
  • La chaîne SQL est assignée à une variable de type String (strSQL).
  • Les lignes se terminent avec espace " espace & espace tiret de soulignement.
  • Une nouvelle ligne débute par le signe " et sans espace derrière ce signe.
  • Les lignes ont une longueur facilitant la lisibilité.
  • Si une ligne est trop courte avec un seul mot clé, on peut en placer plusieurs. Ainsi, dans cet exemple, on a placé le mot clé ON sur la même ligne que INNER JOIN.

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

Case "Groupe Ethnique" ClauseWhere = " WHERE tblRestaurant.GroupeEthnique = " & quotes & _
Me!cboCategorie.Text & quotes
Case "Localité" ClauseWhere = " WHERE tblRestaurant.Localité = " & quotes & _
Me!cboCategorie.Text & quotes
Case "Fourchette de prix" ClauseWhere = " WHERE tblRestaurant.FourchettePrix = " & quotes & _
Me!cboCategorie.Text & quotes
End Select

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..."

Conseils de performance
Sans tomber non plus dans l'intégrisme inutile du coupeur de cheveux en 4, il est tout de même clair que la simple formulation de la requête SQL a des conséquence sur les ressources et le temps machine nécessaire pour son exécution.
On peut ainsi citer quelques précautions simples qui allègeront simplement la charge reposant sur le serveur :
  • Mettre les jointures en premier :
  • Si n tables, alors (n-1) jointures.
  • Placer les comparaisons les plus restrictives le plus tôt possible :
  • cela fera toujours autant de lignes qui ne seront plus en mémoire, et que l'ordinateur n'aura plus à traiter dans le reste de sa requête
  • EVITER ABSOLUMENT "SELECT * FROM ..." :
  • Ne demandez que les colonnes nécessaires, c'est toujours ça de moins à garder en tableau temporaire après la requête, et donc cela économise la mémoire. De plus, si un jour vous déplacez votre code, et que deux colonnes se trouvent inversées dans la nouvelle base, cela n'aura aucune conséquence pour votre développement.
  • Comparer des colonnes de même type :
  • Un CHAR(110) est considéré du même type qu'un VARCHAR(110), mais différent d'un CHAR(112) ou d'un VARCHAR(108). Cela oblige le moteur de bdd à effectuer des conversions internes.
  • Formuler les clauses de comparaison le plus précisement possible :
  • En particulier, éviter de mettre des * partout dans les clauses LIKE, c'est très lourd à traiter...
  • Mettre les identifiants en INT, et en NUMEROAUTO :
  • L'avantage principal du numéroauto est que pour chaque création d'enregistrement ne comprenant pas d'office son identifiant, le moteur se charge de lui en attribuer un [du type max(id)+1], ce qui évite des manipulations supplémentaires.
  • Utilisez des INDEX :
  • Pour accélérer les recherches.

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
SQL autorise de ne manipuler que les groupes vérifiant certaines conditions; nous pourrions ne nous intéresser qu'aux individus dont le code postal est 1000. Il faut alors utiliser HAVING qui est à GROUP BY ce que WHERE est à SELECT.

SELECT year(dateDeNaissance) AS Année, count(idElement) AS Nombre, CodePostal FROM tblHabitants GROUP BY Année HAVING CodePostal = '1000'

WHERE ou HAVING ?
Il est possible de sélectionner les lignes à traiter avec la clause WHERE standard. Seules les lignes sélectionnées par WHERE entreront dans la constitution des groupes. L'ordre d'exécution des clauses est: SELECT ... FROM ... WHERE ... GROUP BY ... 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:

  1. Pointer sur la liste des rubriques de la table concernée affichée dans le panneau supérieur de la fenêtre de création de la requête.
  2. Cliquer avec le bouton droit de la souris pour obtenir la fenêtre des Propriétés de la liste des champs,
  3. Entrer le nom choisi pour la table dans la propriété Pseudonyme.

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:
Dim strSELECT As String
Dim strFROM As String
Dim strWHERE As String
Dim strSQL As String
Dim rs As New ADODB.Recordset

strSELECT = "SELECT Société, Lieu "
strFROM = "FROM tblClients "
strWHERE = "WHERE Pays = '"Canada"' "
strSQL = strSELECT & strFROM & strWHERE

rs.Open strSQL, CurrentProject.Connection
'ici vous placez le code devant exécuter d'autres opérations
rs.Close

strWHERE = "WHERE Pays = '"France"' "
strSQL = strSELECT & strFROM & strWHERE

rs.Open strSQL, CurrentProject.Connection
'ici vous placez le code devant exécuter d'autres opérations
rs.Close

strFROM = "FROM tblFournisseurs "
strSQL = strSELECT & strFROM

rs.Open strSQL, CurrentProject.Connection
'ici vous placez le code devant exécuter d'autres opérations
rs.Close

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, " & _

"SUM(CCur([Prix unitaire]*[Quantité])) AS SousTotal " & _
"FROM (tblClients INNER JOIN Commandes " & _
"ON tblClients.CodeClient = Commandes.[Code client]) " & _
"INNER JOIN [Détails commandes] " & _
"ON Commandes.[N° commande] = [Détails commandes].[N° commande] " & _
"GROUP BY tblClients.CodeClient " & _
"HAVING (((tblClients.CodeClient)='" & Me.[Code client] & "'));"
Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenSnapshot)
CalculTotalVenteClient = rs!SousTotal
rs.Close
Set rs = Nothing
End Function

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
' Déterminer l'existence d'une requete dans la bdd active
' Retourne: Vrai si elle existe, Faux si elle n'existe pas.
Dim Bdd As Database, Test As String
Const NOT_FOUND = 3265
On Error Resume Next
Set Bdd = CurrentDb
Test = Bdd.QueryDefs(NomRequete).Name
ExisteRequete = CBool(Err.Number <> NOT_FOUND)
'CBool convertit tous les numéros à -1 (True), sauf le zéro qui est converti à False.
End Function

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()

MsgBox ExisteRequeteADO("reqNomRequete") End Sub

Private Function ExisteRequeteADO(NomRequete As String) As Boolean

Dim strConn As String, rst As Recordset
strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
"D:\DataAccess\Test.mdb"
Set rst = New Recordset
On Error Resume Next
rst.Open NomRequete, strConn, adOpenForwardOnly, adLockReadOnly
ExisteRequeteDAO = Not CBool(Err.Number)
On Error GoTo 0
' Ou pointer sur l'étiquette du traitement normal des erreurs
End Function

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()

Dim Bdd As DAO.Database, Qd As QueryDef, strSQL As String
strSQL = "SELECT * FROM tblMaTable"
Set Bdd = CurrentDb
Set Qd = Bdd.CreateQueryDef("reqMaRequete")
Qd.SQL = strSQL
End Function

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