Introduction à SQL

Exemple requête corrélée Access

Exemple requête corrélée Access


La base de données qui sert d'exemple sur cette page contient 2 tables, une table plantes et une table fournisseurs. Les informations prix... ont été créees pour cet exemple.

Les tables Fournisseurs et Plantes
Highslide JS

Le champs CodeFrs de la table Fournisseurs correspond au champ Fournisseur de la table Plantes pour la relation clé étrangère.

Extrait des données
Highslide JS



Requête access_1010a d'affichage du prix moyen par article

SELECT pl.NomPlante, avg(pl.PrixPlante) AS PrixMoyen
FROM Plantes AS pl
GROUP BY pl.NomPlante
ORDER BY pl.NomPlante;

Ci-dessous, le résultat de la requête précédente

Extrait des données en résultat
Highslide JS

Requête d'aggrégation sans filtre ni jointures, pas de difficultés particulières, en programmation il faudrait écrire un code plus long.




Requête access_1010b d'affichage de l'article qui a le prix le plus élevé par fournisseur

SELECT fou.Nomfrs, pl.NomPlante, pl.PrixPlante
FROM Fournisseurs AS fou, Plantes AS pl
WHERE pl.Fournisseur = fou.CodeFrs
AND pl.PrixPlante = (SELECT MAX(p.PrixPlante) FROM Plantes p 
                     WHERE p.Fournisseur = fou.CodeFrs)

Affichage du résultat

Extrait des données en résultat
Highslide JS

Cette requête montre l'article vendu par un fournisseur qui a le prix le plus élevé parmi les articles vendus par ce fournisseur. Par exemple, parmi les articles vendus par le fournisseur 21 (Centaure), la camomille est l'article qui a le prix le plus élevé

Pour obtenir ce résultat, dans Access, il faut faire une requête corrélée c'est-à-dire une requête qui en contient une autre à l'étape de filtre des données. Ce n'est pas considéré comme une requête optimale sur un large volume de données en termes de temps de traitement par les moteurs de bases de données.




La requête ci-dessous utilise l'aggrégation pour afficher les résultats ci-dessus. Cependant, le moteur de base de données Access (version 2007-2019) ne considère pas la syntaxe de la requête valide.

SELECT fou.Nomfrs, pl.NomPlante, MAX(pl.PrixPlante) AS PrixMax
FROM w01_Fournisseurs AS fou, w01_Plantes AS pl
WHERE pl.Fournisseur=fou.CodeFrs
GROUP BY fou.Nomfrs;
Message d'erreur de la requête ci-dessus
Highslide JS

La requête précédente est effective sur le moteur de base de données MySQL et Sqlite.

Du fait qu'une requête corrélée n'est pas optimale en termes de performance, il faut un nombre de passes multiplié par le nombre de ligne dans la requête principale. La requête d'aggrégation avec les champs dans le SELECT peut être considérée comme plus performante sur un large volume de données.




Dans cette situation, il peut être possible d'obtenir ce résultat par programmation. Ci-dessous, une utilisation possible parmi d'autres d'un script VBA qui utilise la bibliothèque DAO.


  	Sub w01_PrixMaxDAO()	
  		
  	Dim db As DAO.Database	
  	Dim rs As DAO.Recordset	
  	Dim qdf As DAO.QueryDef	
  	'early binding
  	'Dim fso As FileSystemObject
  	'Set fso = New FileSystemObject
  	'late binding	
  	Set fso = CreateObject("Scripting.FileSystemObject")	
  		
  	vpath = Application.CurrentProject.Path	
  	vpathCSV = vpath & "\" & "w01_prix_max.csv"	
  		
  	If Not fso.FileExists(vpathCSV) Then	
  	  Set csv = fso.createtextfile(vpathCSV)	
  	Else	
  	    fso.DeleteFile vpathCSV	
  	    Set csv = fso.createtextfile(vpathCSV)	
  	End If	
  		
  	Set db = CurrentDb	
  		
  	req = "SELECT CodeFrs, Nomfrs FROM w01_Fournisseurs"	
  		
  	Set rs = db.OpenRecordset(req)	
  	rs.MoveLast: If rs.RecordCount = 0 Then Exit Sub	
  	rs.MoveFirst	
  		
  	'header ligne de titre fichier csv
  	csv.writeline "Code Fournisseur" & ";" & "Nom Fournisseur" & ";" & "Prix Max"	
  		
  	Do Until rs.EOF	
  	       	
  	    LMax = DMax("[PrixPlante]", "w01_Plantes", "[Fournisseur] = " & rs.Fields("CodeFrs"))	
  	        	
  	    If LMax <> "" Then	
  	        VNomfrs = RTrim(rs.Fields("Nomfrs"))	
  	        csv.writeline rs.Fields("CodeFrs") & ";" & VNomfrs & ";" & LMax	
  	        'Debug.Print rs.Fields("CodeFrs") & ";" & VNomfrs & ";" & LMax	
  	    End If	
  	    	
  	    rs.MoveNext	
  	Loop 'rs	
  		
  	CloseTextStream = True: Set csv = Nothing	
  		
  	'Suppression si table existe déjà
  	 If Not IsNull(DLookup("Name", "MSysObjects", "Name='w01_PrixMax' AND Type = 1")) Then	
  	    DoCmd.DeleteObject acTable, "w01_PrixMax"	
  	End If	
  		
  	'load csv create table
  	'cette table permet de voir si les données du fichier sont fiables et valides pour 
     'la requête d'affichage suivante	
  	DoCmd.TransferText acImportDelim, "Prix_max Import Specification", "w01_PrixMax", vpathCSV, False	
  		
  	      req = "SELECT tbl.[Nom Fournisseur] AS Nonfrs, pl.NomPlante, pl.PrixPlante AS PrixMax "	
  	req = req & "FROM w01_PrixMax AS tbl, w01_Plantes AS pl "	
  	req = req & "WHERE tbl.[Code Fournisseur]=pl.Fournisseur And tbl.[Prix Max]=pl.PrixPlante;"	
  		
  	'creation ou maj de la requete, suppression si elle existe déjà	
  	On Error Resume Next	
  	DoCmd.DeleteObject acQuery, "w01_select_03_PrixMax"	
  	Set qdf = db.CreateQueryDef("w01_select_03_PrixMax", req)	
  	Set db = Nothing	
  	'''	
  	'Le code ci dessus emule la requête sql ci-dessous	
  	'SELECT fou.Nomfrs, pl.NomPlante, pl.PrixPlante AS PrixMax	
  	'FROM w01_Fournisseurs AS fou, w01_Plantes AS pl	
  	'WHERE (pl.PrixPlante)=(SELECT MAX(p.PrixPlante) FROM w01_Plantes p WHERE p.Fournisseur = fou.CodeFrs)	
  	'AND ((pl.Fournisseur)=[fou].[CodeFrs]);	
  		
  	End Sub	

Le script ci-dessus ouvre un recordset sur la table fournisseur puis à travers une boucle, utilise la fonction DMAX() pour extraire le prix maximum des articles de chaque fournisseur. Il serait possible de change avec la fonction DMIN() pour inversement obtenir le prix minimum par fournisseur.

Ensuite, les données sont écrites dans un fichier csv puis importées dans une table Access. Ces étapes permettent de s'assurer de la cohérence des résultats et de remonter chaque étape pour trouver une erreur (structure des données, type de données incompatibles...)

Une requête prédéfinie ou créee si elle n'existe pas permet d'obtenir les résultats de la requête corrélée.

Pour cet exemple, la base de donnée n'est pas normalisée. Il s'agit de 2 tables avec une relation plusieurs à plusieurs. Cette caractéristique n'a pas une incidence significative sur les performances de la base. Une base de données avec une intégrité référentielle évite principalement l'ajout ou la présence de données sans liens (cependant selon les moteurs de bases de données l'absence de liens peut se faire avec la valeur NULL ou vide...).

Dans le cas de l'utilisation ponctuelle d'un moteur de base de données, par exemple pour une migration, fiabilisations de données...ce n'est pas toujours nécessaire d'implémenter des liens référentiels entre les tables.

La performance sur les requêtes peut être augmentée avec l'ajout d'un ou plusieurs index sur une table et le tri préalable des données. Ce script est une possibilité parmi d'autres pour obtenir les résultats attendus.





Pour rester cohérent avec des scripts relatifs à sql ou l'utilisation d'une base de données, je propose ci-dessous un script qui utilise la bibliothèque ADO à partir d'Excel pour se connecter à Access et effectuer la même extraction que la requête corrélée mais par programmation.


  Sub w01_PrixMaxXL_ADO_2()
  
  Dim ws_dest As Worksheet
  Dim cn As Object
  Dim rs As Object
  Dim vRs() As Variant
  Dim vPrixMax() As String
  
  Set cn = CreateObject("ADODB.Connection")
  Set rs = CreateObject("ADODB.Recordset")
  
  Set ws_dest = ThisWorkbook.Worksheets(1)
  
  'Db connection infos
  strDBName = "demo_bdd.accdb"
  strMyPath = "Z:\Documents\Demo"
  strDB = strMyPath & "\" & strDBName
  
  'connection à access 2010, 2013, 2016, 2019
  cn.Open ConnectionString:="Provider = Microsoft.ACE.OLEDB.12.0; data source=" & strDB
  
  'requete
  sSQL = "SELECT fou.NomFrs, pl.NomPlante, pl.PrixPlante "
  sSQL = sSQL & "FROM w01_Plantes as pl, w01_Fournisseurs as fou "
  sSQL = sSQL & "WHERE pl.Fournisseur = fou.Codefrs "
  sSQL = sSQL & "ORDER BY fou.NomFrs ASC;"
  
  rs.Open sSQL, cn
  
  vRs = rs.GetRows
  'closes the connections
  rs.Close: Set rs = Nothing
  
  'on delimite un tableau en mémoire pour stocker les données
  col = UBound(vRs, 1): lig = UBound(vRs, 2)
  
  'extraction du prix max par fournisseur dans le tableau
  ReDim vPrixMax(0)
  prixMax = 0: i = 0: ki = 0
  
  Do
  'boucle qui extrait le prix max du tableau rs colonne 2
  'les données sont copiées dans le tableau vPrixMax
      If ki = 0 Then ki = 0
      ReDim Preserve vPrixMax(ki)
      vPrixMax(ki) = RTrim(vRs(0, i)) & ";" & RTrim(vRs(1, i)) & ";" & vRs(2, i)
      NomFrs = vRs(0, i)
      CodePlante = vRs(1, i)
      prixMax = CCur(vRs(2, i))
         
      'un enregistrement cohérent représente un article
      cnt = 1
      
      'on détermine la ligne suivante est pour le meme article
      'les données ont été triées dans la requête
      If vRs(0, i + 1) = NomFrs Then
          j = i
          While vRs(0, i) = vRs(0, j + 1) And (j + 1) < UBound(vRs, 2)
              cnt = cnt + 1
              j = j + 1
          Wend
      End If
  
      '1 seul article
      If cnt = 1 Then
          'on ecrase l'indice ki existant dans vPrixMax
          ReDim Preserve vPrixMax(ki)
          vPrixMax(ki) = RTrim(vRs(0, i)) & ";" & RTrim(vRs(1, i)) & ";" & vRs(2, i)
          i = i + 1
      ElseIf cnt > 1 Then 'pls articles
          For k = i + 1 To j 
              If prixMax < vRs(2, k) Then
                  'on ecrase l'indice ki existant dans vPrixMax
                  ReDim Preserve vPrixMax(ki)
                  vPrixMax(ki) = RTrim(vRs(0, k)) & ";" & RTrim(vRs(1, k)) & ";" & vRs(2, k)
              End If
          Next k
          i = j + 1
      End If
      'on incremente l'indice ki de vPrixMax
      ki = ki + 1
  Loop Until i >= UBound(vRs, 2)
  
  'copie des données sur la feuille 1
  With ws_dest
  .Activate
  .Cells.ClearContents
  .Cells(2, 1).Select
  With .Cells(Rows.Count, 1).End(xlUp)
   .Range(.Offset(1, 0), .Offset(1 + UBound(vPrixMax, 1), 0)) = _
      Application.WorksheetFunction.Transpose(vPrixMax)
  End With
  
  .Columns("A:A").TextToColumns DataType:=xlDelimited, ConsecutiveDelimiter:=True, Semicolon:=True
  
  .Range("A1").Value = "Nom fournisseur"
  .Range("B1").Value = "Nom plante"
  .Range("C1").Value = "Prix Max"
  
  .Range(.Cells(1, 1), .Cells(1, 3)).Font.Bold = True
  
  End With
  
  End Sub

Ce script est effectivement plus long mais évolutif.

Extrait des données en résultat
Highslide JS

Ce script peut bien entendu évoluer pour par exemple alimenter un ou plusieurs tableaux de bords qui analysent le prix maximum et le prix minimul ou d'autres analyses. L'utilisation d'Excel (en 32 bits) aura comme principale contrainte de limiter le volume de données qui devrait être entre 50000 et 250000 enregistrements selon la vitesse du processeur et la capacité en mémoire vive.

D'un point de vu performance, l'utilisation d'un tableau en mémoire qui permet un accès direct aux données peut être un avantage en comparaison à la requête corrélée. Cependant, pour ce traitement, le script nécessite qui est redimensionnée à chaque itération. Il faudrait modifier le script significativement pour avoir un tableau en mémoire de dimensions fixes.




Une évolution possible de cet exemple serait d'afficher les prix maximum et minimum de chaque article. Cette requête est difféente de la précédente car c'est une aggrégation par article et non par fournisseur. En d'autres mots, pour un article donné quel est le prix maximum et le prix minimum proposé par un ou plusieurs fournisseurs.

Le script ci-dessous à partir du script précédent propose une réponse en utilisant une connexion à Access avec la bibliothèque ADO et en affichant les résultats dans un tableau avec un graphique.


  Sub w01_PrixMaxXL_ADO_3()
  
  Dim ws_dest As Worksheet
  Dim cn As Object
  Dim rs As Object
  Dim vRs() As Variant
  Dim vArr1() As Variant, vArr2() As Variant
  Dim vPrixMax() As String, vPrixMin() As String, vPrix() As String
  Set cn = CreateObject("ADODB.Connection")
  Set rs = CreateObject("ADODB.Recordset")
  
  Set ws_dest = ThisWorkbook.Worksheets(1)
  
  'Db connection infos
  strDBName = "demo_bdd.accdb"
  strMyPath = "Z:\Documents\Demo"
  strDB = strMyPath & "\" & strDBName
  
  'connection à access 2010, 2013, 2016, 2019
  cn.Open ConnectionString:="Provider = Microsoft.ACE.OLEDB.12.0; data source=" & strDB
  
  'requete
  sSQL = "SELECT fou.NomFrs, pl.NomPlante, pl.PrixPlante, pl.PrixPlante "
  sSQL = sSQL & "FROM w01_Plantes as pl, w01_Fournisseurs as fou "
  sSQL = sSQL & "WHERE pl.Fournisseur = fou.Codefrs "
  sSQL = sSQL & "ORDER BY pl.NomPlante ASC;"
  
  rs.Open sSQL, cn
  
  '=>Load the recordset into an array
  vRs = rs.GetRows
  
  'closes the connections
  rs.Close: Set rs = Nothing
  
  'on delimite un tableau en mémoire pour stocker les données
  col = UBound(vRs, 1): lig = UBound(vRs, 2)
  
  'extraction du prix max par fournisseur dans le tableau
  ReDim vArr1(0): ki1 = 0
  ReDim vArr2(0): ki2 = 0
  
  ki = 0
  
  Do
      NomFrs = vRs(0, i):    NomPlante = vRs(1, i)
      PrixMin = CCur(vRs(2, i)):    PrixMax = CCur(vRs(3, i))
  
      cnt = 1
      
     'on détermine la ligne suivante est pour le meme article
     'les données ont été triées dans la requête
      If vRs(1, i + 1) = NomPlante Then 'xxx
          j = i
          While vRs(1, i) = vRs(1, j + 1) And (j + 1) < UBound(vRs, 2) 'xxx
              cnt = cnt + 1
              j = j + 1
          Wend
      End If
      
      '1 seul article
      If cnt = 1 Then
          strArt = RTrim(vRs(0, i)) & ";" & RTrim(vRs(1, i)) & ";" & vRs(2, i) & ";" & vRs(3, i)
          If ki1 = 0 Then
              ki1 = 0
              ReDim Preserve vArr1(ki1)
          End If
          ReDim Preserve vArr1(ki1)
          vArr1(ki1) = Split(strArt, ";"): ki1 = ki1 + 1
          i = i + 1
          
      'pls articles
      ElseIf cnt > 1 Then
          For k = i To cnt + i - 1
              strArt = RTrim(vRs(0, k)) & ";" & RTrim(vRs(1, k)) & ";" & vRs(2, k) & ";" & vRs(3, k)
              If ki2 = 0 Then
                  ki2 = 0
                  ReDim Preserve vArr2(ki2)
              End If
              ReDim Preserve vArr2(ki2)
              vArr2(ki2) = Split(strArt, ";"): ki2 = ki2 + 1
          Next k
          i = j + 1
      End If
  
  Loop Until i >= UBound(vRs, 2)
  
  'Traitement sur vArr2 (tableau qui contient plusieurs articles par fournisseur)
  
  vPrix() = f_PrixMax(vArr1(), "prix")
  vPrixMax() = f_PrixMax(vArr2(), "prix_max")
  vPrixMin() = f_PrixMax(vArr2(), "prix_min")
  
  With ws_dest 'affichage sur feuille, trie et format
      .Activate
      .Cells.Delete
      .Cells(2, 1).Select
      derL = .Cells(.Rows.Count, 1).End(xlUp).Row
      For i = LBound(vPrixMax, 1) To UBound(vPrixMax, 1)
          .Cells(derL + i + 1, 1).Value = vPrixMax(i)
      Next i
      derL = .Cells(.Rows.Count, 1).End(xlUp).Row
      For i = LBound(vPrixMin, 1) To UBound(vPrixMin, 1)
          .Cells(derL + i + 1, 1).Value = vPrixMin(i)
      Next i
      derL = .Cells(.Rows.Count, 1).End(xlUp).Row
      For i = LBound(vPrix, 1) To UBound(vPrix, 1)
          .Cells(derL + i + 1, 1).Value = vPrix(i)
      Next i
  
      .Columns("A:A").TextToColumns DataType:=xlDelimited, ConsecutiveDelimiter:=True, Semicolon:=True
  
      'affichage des titres et format gras
      .Range("A1").Value = "Fournisseur"
      .Range("B1").Value = "Plante"
      .Range("C1").Value = "Prix"
      .Range("D1").Value = "PrixComp"
      .Range(.Cells(1, 1), .Cells(1, .Columns.Count).End(xlToRight)).Font.Bold = True
  
      'trie
      Set r = .Range("A1").CurrentRegion
      r.Sort Key1:="Plante", Order1:=xlAscending, Header:=xlYes
  End With
  
  End Sub
  
  
  Function f_PrixMax(vfArr() As Variant, z As String)
  '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  'Cette fonction convertit un tableau de type variant en string
  ' et retourne les valeurs max et min dans
  '2 colonnes distinctes avec comme paramètres
  'un tableau de valeurs et un argument s'il faut renvoyer
  'le prix max ou le prix minimum ou un tableau de valeurs uniques
  '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  Dim locArr() As String
  i = 0: k = 0: cnt = 1
  
  If z <> "prix" Then'prix de pls articles à comparer max et min
  
  Do
      
      If k = 0 Then k = 0
      ReDim Preserve locArr(k)
      locArr(k) = Trim(vfArr(i)(0)) & ";" & RTrim(vfArr(i)(1)) & ";" & vfArr(i)(2) & ";" & ""
      
      npl = RTrim(vfArr(i)(1))
      fprix = CDbl(vfArr(i)(2))
      
      j = i
      While vfArr(j + 1)(1) = npl And j < UBound(vfArr, 1) - 1
          
          If CDbl(vfArr(j + 1)(2)) > fprix And z = "prix_max" Then 'Prix MAX
              locArr(k) = Trim(vfArr(j + 1)(0)) & ";" & RTrim(vfArr(j + 1)(1)) & ";" & vfArr(j + 1)(2) & ";" & "prix max"
              fprix = CDbl(vfArr(j + 1)(2))
          End If
          
          If CDbl(vfArr(j + 1)(2)) < fprix And z = "prix_min" Then 'Prix MIN
              locArr(k) = Trim(vfArr(j + 1)(0)) & ";" & RTrim(vfArr(j + 1)(1)) & ";" & vfArr(j + 1)(2) & ";" & "prix min"
              fprix = CDbl(vfArr(j + 1)(2))
          End If
                  
          j = j + 1
      Wend
      
      i = j + 1: k = k + 1
  Loop Until i > UBound(vfArr, 1) - 1
  
  Else 'prix d un seul article
  i = 0
  Do
      If k = 0 Then k = 0
      ReDim Preserve locArr(k)
      locArr(k) = Trim(vfArr(i)(0)) & ";" & RTrim(vfArr(i)(1)) & ";" & vfArr(i)(2) & ";" & "": k = k + 1
      i = i + 1
  Loop Until i > UBound(vfArr, 1)
  
  End If
  
  f_PrixMax = locArr()
  
  End Function

Le script ci-dessus a pour priorité de privilégier la rapidité dans la connexion avec la base de données et le traitement des données. Il n'y aucune écritures intermédiaires dans un fichier sur une feuille du tableur ou dans une table. En effet, les données peuvent éventuellement être volumineuses et ça pourrait rajouter beaucoup de temps d'accéder en écriture à des objets. Accéder à une base de données en modification, nécessite un accès spécifique avec un privilège dédié (UPDATE, INSERT). En cas d'erreur de manipulation, les manipulations inverses peuvent être compliquées et des données peuvent être perdues ou être ajoutées arbitrairement à des tables.

Pour le traitement des données, ce script utilise des tableaux en mémoire de type variant qui sont coûteux en mémoire mais qui sont nécéssaires pour lire le recordset. Les données dans le recordset sont filtrées dans des tableaux intermédiaires qui sont ensuite convertis en type texte (moins coûteux en ressources qu'un tableaux de données de type variant). Le prix max, min et unique sont également extraits dans la fonction selon le paramètre z.

Le traitement dans des tableaux de mémoire et l'utilisation d'une fonction qui est également dans une zone de mémoire rapide peut être un gain de performance significatif. L'écriture sur la feuille intervient à la fin du module (lignes 94 à 123) de manière un peu répétitive et qui pourrait être rgroupée dans une procédure dédiée. Ce script est évolutif et nécessite quelques contrôles supplémentaires principalement sur les données du recordset.

Ce code affiche le prix et le prix min dans un tableau excel à partir des données dans Access. S'il n'y a qu'un article, le prix est affiché simplement (sans mention prix max ou prix min). Par contre, les articles pour lesquels le prix serait identique ne sont pas pris en compte mais c'est assez simple de le faire dans la fonction, il y a juste à ajouter la condition équivalente à la ligne 153 et 158. C'est notamment à prendre dans le cas du calcul d'une moyenne.

Quand j'ai les données pour faire ce test, je pourrais mettre à jour la fonction. Les copies écrans ci-dessous montrent un tableau de données qui alimente un tableau croisé dynamique à partir duquel un graphique est mis à jour

Extrait des données en résultat
Highslide JS

C'est une présentation possible parmi d'autres pour arriver à ce résultat. L'écriture d'une requête dans Access serait possible mais regrouper plusieurs requêtes corrélées avec une fonction d'aggrégation n'est peut être pas accepter par le moteur de la base de données. Pour le moteur Access, il semble qu'arriver au même affichage que cet exemple nécessiterait une programmation dédiée ou des tables et un traitement intermédiaires.

Avec des tables et des requêtes intermédiaires, ça serait possible mais ça ajoute des objets supplémentaires dans la base. Le script Access VBA avec la bibliothèque DAO a exposé une possibilité d'utiliser une table temporaire ou un fichier csv tampon donc il y a plusieurs méthodes pour arriver à cet affichage.




Sur les versions d'Excel antérieur à 2013, pour un volume de de données de quelques milliers de lignes (3 colonnes), il est possible d'utiliser des fonctions d'Excel. Notamment la combinaison des fonctions SI() et MAX() ou MIN() de type cse (Control-Shift-Enter) ou matricielle ={SI(MAX())} et ={SI(MIN())}ou SOMMEPRODUIT() non-matricelle.

Sur les versions d'Excel plus récentes à 2013, il y a les fonctions dédiées MAX.SI.ENS() et MIN.SI.ENS(). Ces fonctions sont très pratiques et applicables sur un grand volume de données mais ne sont pas rétro-compatibles (si le classeur doit être partagé avec des utilisateurs qui utilisent des versions antérieurs d'office). Ces formules pourront être testées avec ces données dans les pages d'Excel.

Comme il peut s'agir d'un exemple qui peut s'appliquer à une activité professionnelle, j'ai essayé d'être exhaustif. Il y a d'autres analyses possibles qui peuvent également être mises en oeuvre avec Access, Excel (PowerQuery, PowerBI) ou d'autres moteurs de bases de données.

Dernière mise à jour en avril 2021