Manipuler les données des bases Access depuis Excel

Fonctions en VBA basées sur ADODB pour les développeurs

Laurent Ott

Dans cette documentation vous allez apprendre à manipuler avec le VBA Excel les données des bases Access avec la technologie ADODB. Des exemples vous permettront de comprendre le fonctionnement de cette technologie ainsi que les fonctions présentées, qui couvrent une large gamme de besoins.

Vous pouvez déposer vos commentaires dans cette discussion.

- Titre : Manipuler les données des bases Access depuis Excel

- Auteur : Laurent Ott

- Parution : 7 décembre 2020

- Mise à jour : 14 janvier 2021

- Licence : Le contenu de cet article est rédigé par laurent_ott et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Voir également :

- Tome 1 - Des bases de la programmation à l'algorithme de classement rapide QuickRanking

- Tome 2 - Des bases de la programmation en mode graphique à la programmation d'un jeu d'arcade en VBA et Microsoft Excel

- Tome 3 - Problème du Voyageur de commerce - Une méthode d'approximation basée sur des principes simples

- Tome 4 - Un algorithme de chiffrement/déchiffrement des cellules pour une meilleure confidentialité

- Tome 5 - Sentinelle - Une application qui veille sur vos classeurs sensibles - Exemples d'utilisations des tableaux de données et des requêtes SQL en VBA

- Tome 6 - Apprendre la programmation de nouvelles fonctions Excel pour l'utilisateur

- Comprendre la méthode de factorisation du Crible Quadratique

- Programmation en VBA de menus personnalisés pour Excel

- Manipuler les données Access depuis Excel

- Transférer des fichiers volumineux avec Outlook

- Algorithme ECM de factorisation par les courbes elliptiques

- Un classeur Excel multi-utilisateur

- Compresser/décompresser des fichiers au format ZIP

- Fonctions en VBA pour gérer les Tableaux Structurés d’Excel

- Fonctions en VBA pour générer des courriels formatés HTML

Sommaire

- I. Introduction

- II. Les fonctions pour manipuler les données Access en utilisant ADODB

- II-A. Cnn_Initialise

- II-B. Cnn_Debut

- II-C. Enr_Info

- II-D. Enr_Affiche

- II-E. Enr_MAJ

- II-F. Enr_Copie

- II-G. Execute_SQL

- II-H. Cnn_Fin

- II-I. Les requêtes paramétrées

- III. Un exemple de mise à jour de données dans des bases Access

- IV. Conclusion

- V. Annexe 1 – Les requêtes SQL

- VI. Annexe 2 – Des liens utiles

- VII. Le fichier joint

- VIII. Remerciements

I - Introduction

Effectivement, on peut légitimement se poser la question de l’intérêt de programmer des fonctions complexes pour manipuler les données d’une table Access depuis Excel, alors qu’un simple copier/coller ou une requête générée par l’assistant Access, suffisent à ajouter ou supprimer des données ?

Mais contre toute attente, je me suis aperçu qu’il est parfois bien pratique d’utiliser Excel pour interagir avec Access. Par exemple pour mettre à jour les données d’une base d’après un tableau reçu d’Excel, lorsque ce traitement est récurrent.

Cette documentation est consacrée à l’étude de fonctions génériques qui permettent de manipuler les données Access en utilisant la technologie ADODB.

Elle s’adresse aux développeurs Excel qui souhaitent importer, modifier, ajouter, ou supprimer des données d’une base Access, quel que soit leur niveau, mais des connaissances en Access et en SQL faciliteront sa lecture.

L’ensemble du code a été testé avec Excel 2010 et avec Excel 2016 (en 32 bits).

II - Les fonctions pour manipuler les données Access en utilisant ADODB

Nous allons étudier les fonctions du module « ADO » que vous trouverez dans le fichier joint.

Ces fonctions génériques ont pour but de simplifier la vie du programmeur qui n’aura pas à se soucier de l’aspect technique parfois complexe de la manipulation des bases avec la technologie ADODB, ce qui lui fera gagner beaucoup de temps.

Nous n’étudierons pas ici cette technologie en détail (technologie plus riche que DAO, car elle permet d’accéder à d’autres formats de bases, par exemple SQL Server), car vous trouverez en annexe 2 quelques liens de documentations sur le sujet, rédigées par des experts.

Pour présenter ces fonctions, voici en résumé les différentes étapes à suivre pour manipuler des données et (en rouge) les fonctions génériques qui permettent de le faire. Gardez bien à l’esprit ce schéma qui sera le fil conducteur dans la suite de cette documentation :

Trois choses à préciser avant d’étudier ces fonctions et de voir des exemples d’utilisation.

- Si la notion de transaction vous est inconnue, quelques explications sont nécessaires.

Par exemple, lors de la réception d’une commande il faut réaliser deux opérations dans une base de données : passer l’état de la commande à « Reçue » dans la table des commandes ; et incrémenter le stock du produit dans la table des stocks.

Imaginez le cas où une anomalie vient à faire planter l’une de ces deux opérations : le stock n’est plus en adéquation avec les commandes. C’est bien fâcheux.

Pour se prémunir de cela, le principe est d’ouvrir une transaction, de réaliser ces deux opérations (qui restent en attente de validation), puis de fermer la transaction. Le fournisseur de données prend alors la main et ne validera la transaction que si les deux mouvements sont correctement réalisés.

Et bien sûr, en cas d’anomalie sur l’une des opérations, les deux sont automatiquement annulées de façon transparente pour le programmeur.

Dit autrement, vous utiliserez une transaction pour garantir que plusieurs enregistrements sont mis à jour simultanément, comme s’il n’y avait qu’une seule opération.

Et si les tables sont dans des bases différentes ? Aucune importance, le fournisseur de données sait gérer les transactions sur plusieurs bases.

- Les messages d’erreurs sont affichés uniquement après avoir refermé le jeu d’enregistrements, les transactions en cours et les connexions : en effet, vous constaterez qu’une connexion à une base Access génère un fichier « .laccdb » (« .ldb » dans l’ancienne version), soit un fichier de verrouillage qui bloque les autres utilisateurs qui doivent intervenir sur la base. Vous imaginez que ça va être gênant, sur une base multi-utilisateur, de laisser un verrou suite à un message d’erreur dans l’attente que l’utilisateur décide de le valider. La fermeture de ces objets permet de supprimer ce verrou (et donc de libérer la base), mais cela nécessite d’avoir une gestion différée des erreurs.

Dit autrement, les erreurs ne seront pas bloquantes, mais mémorisées et reportées à la fin du traitement pour informer le programmeur.

- Un fournisseur de données, « provider » en anglais, est « un programme qui sait manipuler les données d’une source ».

Pour ouvrir les bases Access, nous utiliserons le fournisseur :

« Microsoft.ACE.OLEDB.12.0 » pour les bases « .accdb » ;

« Microsoft.Jet.OLEDB.4.0 » et pour les bases « .mdb ».

Ces informations sont déclarées dans le module « ADO » du fichier joint :

Private Const Provider_ACCDB As String = "Microsoft.ACE.OLEDB.12.0"
Private Const Provider_MDB As String = "Microsoft.Jet.OLEDB.4.0"

et vont alimenter l’objet de connexion « Cnn1 » également déclaré dans ce module :

Public Cnn1 As ADODB.Connection

Objet qui nécessite l’installation de la bibliothèque ADODB. Soit dans l’éditeur VBA, le menu « Outils / Références… » cochez « Microsoft ActiveX Data Objects 6.1 Library » (ou 2.8 si vous n’avez que cette version).

Comme il est possible de travailler sur plusieurs bases en même temps, nous verrons bientôt pourquoi, il est donc déclaré plusieurs objets connexion, « Cnn1 », « Cnn2 » … « Cnn9 ». Soit neuf connexions simultanées possibles à des bases Access différentes.

Sachant qu’il est peu probable d’utiliser autant de connexions pour un traitement, mais j’ai préféré voir large au cas où.

II-A - Cnn_Initialise

La fonction « Cnn_Initialise » permet de définir un fournisseur de données pour initialiser une connexion, c’est-à-dire l’étape 1 du schéma :

Function Cnn_Initialise(Cnn As ADODB.Connection, StrBaseSource As String) As Boolean

Ses arguments sont :

- Cnn : l’objet connexion concerné (entre Cnn1 et Cnn9) ;

- StrBaseSource : base concernée (chemin complet + nom avec l'extension).

La fonction retourne VRAI si la connexion réussit ou FAUX en cas d’erreur.

Exemple pour déclarer en connexion 1 la base « Exemples.accdb » située dans le répertoire « C:\Formation_VBA » :

Cnn_Initialise Cnn1, "C:\_Formation_VBA\Exemples.accdb"

Par curiosité, voyons ce qu’il se cache derrière cette fonction (ici une version simplifiée sans gestion des erreurs) :

' Paramètres de connexion:
Set Cnn = New ADODB.Connection
Cnn.CommandTimeout = 30
Cnn.CursorLocation = adUseServer

' Détermine le fournisseur pour les bases .accdb, ou .mdb:
Select Case UCase(Mid(StrBaseSource, InStrRev(StrBaseSource, ".")))

Case Is = ".ACCDB"
Cnn.Open "Provider= " & Provider_ACCDB & ";" & "Data Source=" & StrBaseSource & ";" _
, "Admin", "", adAsyncConnect

Case Is = ".MDB"
Cnn.Open "Provider= " & Provider_MDB & ";" & "Data Source=" & StrBaseSource & ";" _
, "Admin", "", adAsyncConnect
End Select

' Attend la connexion:
While (Cnn.State = adStateConnecting): DoEvents: Wend

' Retourne VRAI si tout va bien:
If Cnn.State = adStateOpen Then Cnn_Initialise = True

' Désactiver la connexion pour supprimer le verrou:
If Cnn Is Nothing = False Then
If Cnn.State = adStateOpen Then Cnn.Close
End If

Cela doit vous sembler curieux d’ouvrir une connexion… pour la refermer aussitôt (Cnn.Close) !

Sauf que, si la connexion est refermée, elle n’est pas supprimée : ses paramètres sont conservés et seront utilisés le moment venu.

- La fermeture de la connexion permet simplement de supprimer le verrou sur la base, les fameux fichiers « .laccdb » ou « .ldb » qui bloquent les autres utilisateurs, ce qu’il faut éviter sur une base multi-utilisateur.

- À ne pas confondre avec la suppression d’une connexion qui se fait avec : Set Cnn = Nothing

Une connexion restant mémorisée tant que l’objet n’est pas supprimé, il est possible de n’utiliser la fonction Cnn_Initialise qu’une seule fois dans votre programme.

À condition de ne pas oublier quelle est la base concernée.

Si vous n’avez plus besoin d’une connexion initialisée, vous pouvez réutiliser un objet connexion (entre Cnn1 et Cnn9) pour initialiser une autre base.

La connexion étant désormais initialisée, nous pouvons passer à l’étape suivante.

II-B - Cnn_Debut

La fonction « Cnn_Debut » réinitialise le gestionnaire des erreurs différées et, si demandé, ouvre une transaction, c’est-à-dire les étapes 2 et 3 du schéma :

Function Cnn_Debut(ActionTransaction As EnumActionTransaction) As Boolean

Son argument est :

- ActionTransaction : l'une des énumérations suivantes :

SansTransaction = pas de transaction générée,

AvecTransaction = ouvre une transaction (sur toutes les connexions existantes).

La fonction retourne VRAI si tout s’est bien passé ou FAUX en cas d’erreur.

La réinitialisation des erreurs différées est simple, elle consiste à mettre la variable « ADO_Err_Number » à zéro. Cette variable contiendra le numéro d’erreur retournée par « Err.Number » si une erreur est déclenchée pendant le traitement.

Si une transaction doit être générée, il convient de boucler sur les connexions Cnn1 à Cnn9, préalablement initialisées, et de les déclarer avec la méthode de transaction « BeginTrans ».

Avec Cnn un objet « ADODB.Connection » :

' Ouvre la connexion et attend qu’elle soit établie:
If Cnn.State = adStateClosed Then Cnn.Open
If Cnn.State >= adStateConnecting Then
While (Cnn.State = adStateConnecting): DoEvents: Wend
End If

' Lance la transaction de Cnn:
Cnn.BeginTrans

Nous verrons plus loin que la transaction sera validée par « CommitTrans » ou annulée par « RollbackTrans » ;

- BeginTrans : commence une nouvelle transaction ;

- CommitTrans : enregistre les modifications et met fin à la transaction en cours ;

- RollbackTrans : annule toutes les modifications apportées pendant la transaction en cours et met fin à la transaction.

Résumons :

Cnn_Initialise Cnn1, "C:\_Formation_VBA\Exemples.accdb"
Cnn_Debut AvecTransaction

La connexion 1 est initialisée sur la base « Exemples.accdb », la réinitialisation du gestionnaire d’erreurs est faite, la transaction est définie (étapes 1, 2, 3 du schéma) nous pouvons maintenant intervenir sur les tables de cette base, soit pour consulter les enregistrements d’une table, soit pour les afficher, soit pour les modifier, les supprimer, les créer, soit pour exécuter une requête SQL.

II-C - Enr_Info

La fonction « Enr_Info » retourne des informations sur l’enregistrement désiré. Elle réalise les étapes 4, 5.1, 6 et 8 du schéma :

Function Enr_Info(Cnn As ADODB.Connection, StrTableSource As String, _
ByVal SQLWhere As String, _
TypeInfoEnr As EnumInfoEnr, _
ByVal StrNomChamp As String) As Variant

Ses arguments sont :

- Cnn : l’objet connexion concerné (entre Cnn1 et Cnn9) ;

- StrTableSource : la table concernée ;

- SQLWhere : requête d'instruction permettant d'identifier l’enregistrement concerné (sans le mot clé WHERE, voir annexe 1 pour plus d’informations sur les requêtes SQL). Si cet argument est vide alors toute la table est sélectionnée ;

- TypeInfoEnr : l'une des énumérations suivantes :

Valeur = retourne la valeur du champ désiré,

Compte = compte le nombre d'enregistrements (COUNT),

Somme = fait la somme des enregistrements (SUM),

Moyenne = calcule la moyenne des enregistrements (AVG),

Mini = retourne le plus petit enregistrement (MIN),

Maxi = retourne le plus grand enregistrement (MAX),

NbChamps = retourne le nombre de champs dans le jeu d'enregistrements ;

- StrNomChamp : le nom du champ concerné.

La fonction retourne le résultat trouvé ou vide si aucun enregistrement n'est sélectionné ou en cas d'erreur.

Soit la table suivante :

Exemple pour connaître la valeur du champ « Libellé » sur la connexion 1 (voir Cnn_Initialise), dans la table « Table_Direction_1 », pour l’enregistrement où « Direction » vaut « H » :

Enr_Info (Cnn:=Cnn1, StrTableSource:="Table_Directions_1", _
SQLWhere:="Direction='H'", TypeInfoEnr:=Valeur, _
StrNomChamp:="Libellé")

Retourne : « HAUT »

Pour connaître, dans cette même table, le maximum du champ « ID » :

Enr_Info(Cnn:=Cnn1, StrTableSource:="Table_Directions_1", _
SQLWhere:="", TypeInfoEnr:=Maxi, StrNomChamp:="ID")

Retourne : « 5 ».

Et pour connaître le nombre d’enregistrements :

Enr_Info(Cnn:=Cnn1, StrTableSource:="Table_Directions_1", _
SQLWhere:="", TypeInfoEnr:=Compte, StrNomChamp:="")

Retourne : « 4 ».

Cette fonction se base sur une requête SQL pour sélectionner le jeu d’enregistrements d’une table, ouverte en lecture seule. Ci-dessous une version simplifiée pour ceux qui souhaitent en comprendre le principe :

TypeOpé = Array("", "COUNT", "SUM", "AVG", "MIN", "MAX", "") ' <- énumération TypeInfoEnr

' Ouvre la connexion et attend qu’elle soit établie:
If Cnn.State = adStateClosed Then Cnn.Open
If Cnn.State >= adStateConnecting Then
While (Cnn.State = adStateConnecting): DoEvents: Wend
End If

' Définition de la requête SQL:
SQLWhere = "SELECT " & TypeOpé(TypeInfoEnr) & " (" & StrNomChamp & ") " _
& "FROM [" & StrTableSource & "] " & IIf(SQLWhere > "", "WHERE " & SQLWhere, "")

' Ouverture de la table en lecture seule:
Set MonRs = New ADODB.Recordset
MonRs.Open SQLWhere, Cnn, adOpenStatic, adLockReadOnly, adCmdText

' Si un résultat existe:
If MonRs.EOF = False Then Enr_Info = MonRs.Fields(0)

' S'il faut retourner le nombre de champs:
If TypeInfoEnr = NbChamps Then Enr_Info = MonRs.Fields.Count

' Ferme l'enregistrement et désactive la connexion:
MonRs.Close
Cnn.Close

II-D - Enr_Affiche

Cette fonction permet d’ouvrir un jeu d’enregistrements et de l’afficher dans une feuille de calcul. Elle réalise les étapes 4, 5.2, 6 et 8 du schéma :

Function Enr_Affiche(Cnn As ADODB.Connection, StrTableSource As String, _
ByVal SQLWhere As String, _
RngDest As Range) As Boolean

Ses arguments sont :

- Cnn : l’objet connexion concerné (entre Cnn1 et Cnn9) ;

- StrTableSource : la table concernée. Si vide alors l'instruction dans SQLWhere sera utilisée comme requête pour ouvrir la source ;

- SQLWhere : requête d'instruction permettant d'identifier le jeu d’enregistrements concerné (sans le mot clé WHERE, voir annexe 1 pour plus d’informations sur les requêtes SQL). Si cet argument est vide alors toute la table est sélectionnée ;

- RngDest : est la cellule où afficher le jeu d’enregistrements.

La fonction retourne VRAI si au moins un enregistrement est trouvé, ou FAUX dans le cas contraire.

Exemple pour afficher en A10 de la feuille active tous les enregistrements de la table « Table_Direction_1 » :

Enr_Affiche Cnn:=Cnn1, StrTableSource:="Table_Directions_1", _
SQLWhere:="", RngDest:=Range("A10")

Dans cet autre exemple, une requête SQL permet de réaliser une jointure entre deux tables et d’afficher les champs désirés, triés par « Libellé ». L’argument « StrTableSource » n’est pas alimenté, car l’instruction SQL contient les informations nécessaires :

Enr_Affiche Cnn1, "", "SELECT Table_1.Libellé, Table_2.Durée " _
& "FROM Table_2 INNER JOIN Table_1 ON Table_2.Id = Table_1.Id " _
& "ORDER BY Table_1.Libellé " _
, Range("A10")

Pour les requêtes complexes, pensez à utiliser Access pour simuler ce que vous souhaitez faire, puis passez en mode SQL pour récupérer la requête générée.

Un simple copier/coller vous permettra d’alimenter l’argument « SQLWhere » de la fonction « Enr_Affiche ».

Autre astuce : utilisez les requêtes paramétrées.

Une version simplifiée pour en comprendre le principe :

' Ouvre la connexion et attend qu’elle soit établie:
If Cnn.State = adStateClosed Then Cnn.Open
If Cnn.State >= adStateConnecting Then
While (Cnn.State = adStateConnecting): DoEvents: Wend
End If

' Génère la requête SQL complète (si une table est renseignée sinon prend directement SQLWhere):
If StrTableSource <> "" Then SQLWhere = "SELECT * FROM [" & StrTableSource & "] " & IIf(SQLWhere > "", "WHERE " & SQLWhere, "")

' Ouverture de la table en lecture seule:
Set MonRs = New ADODB.Recordset
MonRs.Open SQLWhere, Cnn, adOpenStatic, adLockReadOnly, adCmdText

' Si un enregistrement existe alors indique que la sélection retourne bien un enregistrement:
' Et se place sur le premier enregistrement (pour lever toute ambiguïté):
If MonRs.EOF = False Then
MonRs.MoveFirst
RngDest.CopyFromRecordset MonRs
Enr_Affiche = True
End If

' Ferme l'enregistrement et désactive la connexion:
MonRs.Close
Cnn.Close

II-E - Enr_MAJ

La fonction « Enr_MAJ » permet de modifier, créer ou supprimer un enregistrement, soit les étapes 4, 5.3, 6 et 8 du schéma :

Function Enr_MAJ(Cnn As ADODB.Connection, StrTableSource As String, _
ByVal SQLWhere As String, _
ActionMAJ As EnumActionMAJ, _
ParamArray ChampEtValeur()) As Boolean

Ses arguments sont :

- Cnn : l’objet connexion concerné (entre Cnn1 et Cnn9) ;

- StrTableSource : la table concernée ;

- SQLWhere : requête d'instruction permettant d'identifier l’enregistrement concerné (sans le mot clé WHERE, voir annexe 1 pour plus d’informations sur les requêtes SQL) ;

- ActionMAJ : l'une des énumérations suivantes :

ModificationUniquement= tente une modification, mais si l'enregistrement n'est pas trouvé alors génère une erreur,

ModificationOuCréation = tente une modification, mais si l'enregistrement n'est pas trouvé alors génère une création,

Suppression = fait une suppression de l'enregistrement (ou du jeu d'enregistrements),

CréationUniquement = création d’un nouvel enregistrement ;

- ChampEtValeur : nom du champ à modifier + Valeur du champ. Ce tableau de type Array peut avoir jusqu'à 30 couples : champ + valeur.

Si le champ est suivi des caractères distinctifs suivants (+=), (-=), (*=), (/=), (&=) alors une opération (addition, soustraction, multiplication, division, concaténation) est réalisée sur le champ et non pas une simple mise à jour.

Cet argument n’est pas utile dans le cas d’une suppression.

La fonction retourne VRAI si un enregistrement est bien mis à jour, créé ou supprimé, ou FAUX dans les autres cas.

Exemple pour modifier l'état d’une commande pour le passer à « Reçue » et ajouter au stock la quantité désirée, et indiquer la date de réception, sur la connexion 1 dans la table « T_Commandes » quand le numéro de commande = NumCommande :

Enr_MAJ Cnn1, "T_Commandes", "Id_Commande=" & NumCommande, _
ModificationUniquement, _
"Etat", "Reçue", _
"Stock(+=)", Quantité, _
"Quand_Recue", Now

Cette fonction se base sur une requête SQL pour sélectionner le jeu d’enregistrements d’une table, ouverte en modification.

Une version très simplifiée (sans la suppression ou les erreurs) pour mieux comprendre :

' Ouvre la connexion et attend qu'elle soit établie:
If Cnn.State = adStateClosed Then Cnn.Open
If Cnn.State >= adStateConnecting Then
While (Cnn.State = adStateConnecting): DoEvents: Wend
End If

' Définition de la requête complète:
SQLWhere = "SELECT * FROM [" & StrTableSource & "] " & IIf(SQLWhere > "", "WHERE " & SQLWhere, "")

' Défilement bidirectionnel + verrou à la modification:
Set MonRs = New ADODB.Recordset
MonRs.Open SQLWhere, Cnn, adOpenKeyset, adLockPessimistic, adCmdText

' Si pas d’enregistrement de trouvé et mode création alors ajoute un enregistrement,
' si un jeu enregistrements est trouvé alors se place sur le premier enregistrement:
If MonRs.EOF = True Then
If ActionMAJ = ModificationOuCréation Or ActionMAJ = CréationUniquement Then MonRs.AddNew
Else
Select Case ActionMAJ
Case CréationUniquement: MonRs.AddNew
Case Else: MonRs.MoveFirst ' Se place sur 1er enregistrement (pour lever toute ambiguïté).
End Select
End If

' Boucle sur les champs qu'il faut mettre à jour:
For i = 0 To UBound(ChampEtValeur) Step 2

' Si le nom du champ est suivi d'un signe particulier, alors faire une opération:
Select Case Right(ChampEtValeur(i), 4)

Case "(+=)" ' Ajouter
ChampEtValeur(i) = Mid(ChampEtValeur(i), 1, Len(ChampEtValeur(i)) - 4)
MonRs.Fields(ChampEtValeur(i)).Value = MonRs.Fields(ChampEtValeur(i)).Value + ChampEtValeur(i + 1)
Case "(-=)" ' Soustraire
ChampEtValeur(i) = Mid(ChampEtValeur(i), 1, Len(ChampEtValeur(i)) - 4)
MonRs.Fields(ChampEtValeur(i)).Value = MonRs.Fields(ChampEtValeur(i)).Value - ChampEtValeur(i + 1)
Case "(*=)" ' Multiplier
ChampEtValeur(i) = Mid(ChampEtValeur(i), 1, Len(ChampEtValeur(i)) - 4)
MonRs.Fields(ChampEtValeur(i)).Value = MonRs.Fields(ChampEtValeur(i)).Value * ChampEtValeur(i + 1)
Case "(/=)" ' Diviser
ChampEtValeur(i) = Mid(ChampEtValeur(i), 1, Len(ChampEtValeur(i)) - 4)
MonRs.Fields(ChampEtValeur(i)).Value = MonRs.Fields(ChampEtValeur(i)).Value / ChampEtValeur(i + 1)
Case "(&=)" ' Concatener
ChampEtValeur(i) = Mid(ChampEtValeur(i), 1, Len(ChampEtValeur(i)) - 4)
MonRs.Fields(ChampEtValeur(i)).Value = MonRs.Fields(ChampEtValeur(i)).Value & ChampEtValeur(i + 1)

Case Else ' Mise à jour simple
MonRs.Fields(ChampEtValeur(i)).Value = ChampEtValeur(i + 1)
End Select

Next i

' Valide la mise à jour:
MonRs.Update

' Ferme l'enregistrement et désactive la connexion:
MonRs.Close
Cnn.Close

Lors d’une mise à jour : si plusieurs enregistrements répondent aux critères de sélection, seul le premier est sélectionné, et mis à jour.

Pensez donc à bien identifier l’enregistrement désiré grâce à la requête de l’argument « SQLWhere ».

Pour une mise à jour en masse vous devrez :

- soit faire une boucle sur les enregistrements ;

- soit utiliser une requête SQL comme nous l’étudierons plus loin.

II-F - Enr_Copie

La fonction « Enr_Copie » permet de copier un enregistrement d’une table vers une autre, soit les étapes 4, 5.4, 6 et 8 du schéma :

Function Enr_Copie(CnnSource As ADODB.Connection, StrTableSource As String, _
SQLWhere As String, _
CnnDest As ADODB.Connection, StrTableDest As String, _
ActionCopier As EnumActionCopier) As Boolean

Ses arguments sont :

- CnnSource : l’objet connexion qui contient la source (entre Cnn1 et Cnn9) ;

- StrTableSource : la table source concernée ;

- SQLWhere : requête d'instruction permettant d'identifier l’enregistrement concerné (sans le mot clé WHERE, voir annexe 1 pour plus d’informations sur les requêtes SQL) ;

- CnnDest : l’objet connexion qui contient la destination (entre Cnn1 et Cnn9) ;

- StrTableDest : la table destination concernée ;

- ActionCopier : l'une des énumérations suivantes :

RemplacerUniquement = si l'enregistrement n'est pas trouvé alors génère une erreur,

RemplacerOuCréer = tente une modification, mais si l'enregistrement n'est pas trouvé alors génère une création,

ForcerCréation = force la création dans la destination (même si l'enregistrement existe).

La fonction retourne VRAI si l'action demandée s'est déroulée correctement, ou FAUX dans les autres cas.

Cet exemple archive un enregistrement d’un article de la table « T_Articles » d’une base initialisée en connexion 1 vers une autre table nommée « T_Articles_Historique » dans une autre base initialisée en connexion 2 (nous aurions utilisé la même connexion si les tables étaient dans la même base) pour permettre une traçabilité des mouvements.

Les deux tables ont une structure identique, sauf le champ « Id_Article » de la table « T_Articles_Historique » qui n’est pas une clé primaire afin de ne pas générer de conflit, car un article peut y être enregistré plusieurs fois :

Enr_Copie CnnSource:=Cnn1, StrTableSource:="T_Articles", _
SQLWhere:="Id_Article=" & Article, _
CnnDest:=Cnn2, StrTableDest:="T_Articles_Historique", _
ActionCopier:=ForcerCréation

Idéalement, la table source et la table destination ont la même structure.

Faites attention au respect des règles des clés primaires lors de vos copies.

Il est admis que la table destination ait des champs supplémentaires, qui ne seront donc pas alimentés, sauf par leur valeur par défaut.

II-G - Execute_SQL

La fonction « Execute_SQL » permet d’exécuter une instruction SQL, pour faire une mise à jour, une création, une suppression, sur un enregistrement ou un jeu d’enregistrements.

Voir annexe 1 pour plus d’informations sur les requêtes SQL.

Ou pour faire toute autre action que le SQL permet de faire (et il y en a beaucoup), d’où l’importance d’avoir cette fonction disponible pour le programmeur.

Elle réalise les étapes 4, 5.3, 6 et 8 du schéma :

Function Execute_SQL(Cnn As ADODB.Connection, StrSQL As String, _
Optional ActionSiErreur As EnumActionSiErreur = MémoriserErreur) As Boolean

Ses arguments sont :

- Cnn : l’objet connexion concerné (entre Cnn1 et Cnn9) ;

- StrSQL : la requête d’instruction SQL complète (voir annexe 1 pour plus d’informations sur les requêtes SQL) ;

- ActionSiErreur : l'une des énumérations suivantes :

IgnorerErreur = ignore les erreurs,

MémoriserErreur = mémorise l'erreur s'il y en a une, pour un traitement différé.

La fonction retourne VRAI si la requête est exécutée (même si aucun enregistrement n’est sélectionné) ou FAUX en cas d’erreur.

Pensez à mettre les noms des tables et des champs entre crochets « [ » et « ] » s’ils contiennent des espaces, conformément aux règles du SQL.

Reprenons l’exemple vu avec la fonction « Enr_MAJ » pour modifier l'état d’une commande pour le passer à « Reçue » et ajouter au stock la quantité désirée, et indiquer la date de réception, sur la connexion 1 dans la table « T_Commandes » quand le numéro de commande = NumCommande :

Execute_SQL Cnn:=Cnn1, _
StrSQL:="UPDATE T_Commandes SET Etat='Reçue', Stock = Stock + " & Quantité & ", Quand_Recue=#" & Now & "# WHERE Id_Commande=" & NumCommande, _
ActionSiErreur:=IgnorerErreur

Le code de cette fonction pourrait se résumer à cela :

' Ouvre la connexion et attend qu'elle soit établie:
If Cnn.State = adStateClosed Then Cnn.Open
If Cnn.State >= adStateConnecting Then
While (Cnn.State = adStateConnecting): DoEvents: Wend
End If

' Exécute la requête:
Cnn.Execute StrSQL

' Ferme la connexion:
Cnn.Close

Les instructions SQL peuvent rendre de grands services pour les traitements en masse dans une table.

Elles permettent aussi de créer ou supprimer des tables, et plein de choses encore.

Par exemple, une requête SQL permet de sélectionner les données d'un champ en supprimant les doublons et en triant le résultat par ordre croissant.

C'est très pratique pour alimenter dans un UserForm une liste déroulante des valeurs disponibles.

Syntaxe : SELECT DISTINCT [NomDuChamp] FROM [NomDeLaTable] ORDER BY [NomDuChamp]

Appliqué à la fonction « Enr_Affiche », pour afficher sur la feuille 1 en « A1 » la liste des valeurs du champ « Jour » sans doublon de la base « Table_Test » cela donne :

Enr_Affiche Cnn:=Cnn1, StrTableSource:="", _

SQLWhere:="SELECT DISTINCT [Jour] FROM [Table_Test] ORDER BY [Jour]", _

RngDest:=Sheets("Feuil1").Range("A1")

N’oubliez pas que les requêtes SQL sont très pratiques dans Excel (comme étudié au tome 5 chapitre VI et suivants) pour optimiser les traitements d’analyse des données.

Un dernier exemple pour vous convaincre qu’une requête SQL peut remplacer des dizaines de lignes de code VBA : à partir de la table des villes de France (d’une base préalablement initialisée en connexion 1) qui contient les deux champs « Departement » et « Ville », afficher en « A2 » de la feuille « Récap » la liste des départements qui possèdent le plus de villes :

Enr_Affiche Cnn:=Cnn1, StrTableSource:="", _
SQLWhere:="SELECT [Departement], COUNT ([Ville]) " _
& "FROM [Table_Villes] " _
& "GROUP BY [Departement] " _
& "ORDER BY COUNT ([Ville]) DESC", _
RngDest:=Sheets("Récap").Range("A2")

Résultat :

II-H - Cnn_Fin

La fonction « Cnn_Fin » permet de fermer la transaction en cours (en la validant si tout s’est bien passé ou l’annulant en cas d’erreur), de fermer les connexions, et d’afficher les messages d’erreur mis en différé (ou un message de confirmation du traitement), c’est-à-dire les étapes 6, 7, 8 et 9 du schéma :

Function Cnn_Fin(Optional MessageRésultat As EnumMessage = MessageSiErreur) As Boolean

Son argument est :

- MessageRésultat : l'une des énumérations suivantes :

SansMessage = pas de message d'affiché même s'il y a une erreur ;

MessageSiErreur = message seulement si erreur ;

AvecMessage = message si erreur ou message de confirmation si pas d’erreur.

La fonction retourne VRAI si tout s’est bien passé ou FAUX s’il y a eu des erreurs.

Voici un exemple qui reprend quelques fonctions déjà étudiées pour les remettre dans un environnement plus réaliste.

Ici le but est de mettre à jour deux tables (dans la même base, mais elles pourraient être dans des bases différentes Cnn1 et Cnn2) suite à la réception d’une commande, en utilisant une transaction évidemment.

Les fonctions sont préfixées ADO afin de bénéficier de « l’IntelliSense » lors de leur saisie, car les fonctions sont dans le module ADO :

'-----------------------------------------------------------------------------------------------
Sub Exemple
'-----------------------------------------------------------------------------------------------
Dim Quantité As Long, Article As Long, NumCommande As Long

NumCommande = 4   ' pour l’exemple nous travaillons sur la commande numéro 4.

' Ouvre une connexion:
If ADO.Cnn_Initialise(Cnn1, "C:\_Formation_VBA\Exemples.accdb") = True Then

' Initialisation du traitement ADO avec transaction:
ADO.Cnn_Debut AvecTransaction

' Mémorise l'article concerné et la quantité commandée:
Article = ADO.Enr_Info(Cnn1, "T_Commandes", "Id_Commande=" & NumCommande, Valeur, "Id_Article")
Quantité = ADO.Enr_Info(Cnn1, "T_Commandes", "Id_Commande=" & NumCommande, Valeur, "Quantité")

' Modifie l'état de la commande pour le passer à "Reçue" et indique la date de réception:
ADO.Enr_MAJ Cnn1, "T_Commandes", "Id_Commande=" & NumCommande, ModificationUniquement, _
"Etat", "Reçue", _
"Quand_Recue", Now

' Modifie le stock par un ajout grâce à (+=) et indique la date de modification:
ADO.Enr_MAJ Cnn1, "T_Articles", "Id_Article=" & Article, ModificationUniquement, _
"Stock(+=)", Quantité, _
"Quand", Now

End If

' Fin du traitement (avec gestion des erreurs et de la transaction):
ADO.Cnn_Fin AvecMessage

End Sub
'-----------------------------------------------------------------------------------------------

II-I - Les requêtes paramétrées

Si vous trouvez le SQL trop complexe à manipuler, Access peut venir à votre secours : car les fonctions « Enr_Info » et « Enr_Affiche » ne se limitent pas de lire les enregistrements d’une table, elles peuvent également lire les enregistrements d’une requête…

Vous pouvez donc créer une requête dans Access, éventuellement en utilisant l’assistant pour vous aider, et utiliser ces fonctions en appelant la requête, donc sans avoir besoin de maîtriser le SQL !

Reprenons notre précédent exemple pour illustrer cela.

À partir de la table des villes de France qui contient les deux champs « Departement » et « Ville », afficher en « A2 » la liste des départements qui possèdent le plus de villes.

Une requête est créée, nommée « Requête_CompteVilles » :

La programmation dans Excel se limitera à cet appel :

ADO.Enr_Affiche Cnn:=Cnn1, StrTableSource:="Requête_CompteVilles", _
SQLWhere:="", RngDest:=Range("A2")

Résultat :

III - Un exemple de mise à jour de données dans des bases Access

Je reçois régulièrement un fichier Excel pour mettre à jour des données dans Access. Le but est de modifier les dates de démarrage de chantiers et d’ouverture à la clientèle des agences. La clé primaire dans Access est le code PPI, le CDR correspond au numéro de l’agence.

Après retraitement j’obtiens ce tableau, qui contient plusieurs dizaines de lignes.

La fonction qui suit va dans un premier temps vérifier que la valeur du CDR enregistré dans la table « PPI_Table_Opérations », dont la clé primaire est le code PPI, correspond au code reporté en colonne B. Et sera affiché en colonne C.

Pour un besoin de traçabilité, la table « PPI_Table_Modif_Jalons » conserve l’historique de champs modifiés : « Date_R02 » représente la date de démarrage du chantier et « Date_R07 » représente la date d’ouverture à la clientèle.

Son champ « Quand_MAJ » est par défaut la date du jour, donc inutile de le renseigner.

'-----------------------------------------------------------------------------------------------
Sub MAJPPIR02R07()
'-----------------------------------------------------------------------------------------------
Dim i As Long, DL As Long, CDR As Long
Dim Date_R02 As Variant, Date_R07 As Variant

' Active la feuille et recherche la dernière ligne renseignée:
ThisWorkbook.Sheets("Feuil1").Activate
DL = Cells(Application.Rows.Count, 1).End(xlUp).Row

' Ouvre la base Access:
If ADO.Cnn_Initialise(Cnn1, "\\Clynas100\IMR$\BASE PILOTAGE\PPI_Base.accdb") = True Then

' Boucle sur les lignes et recherche si l'opération existe dans PPI
' et si c'est le bon CDR du numéro d'opération:
For i = 2 To DL

Debug.Print "Contrôles : " & Format(i / DL, "0%"): DoEvents

' Affiche le CDR si OK ou quitte si erreur:
CDR = ADO.Enr_Info(Cnn1, "PPI_Table_Opérations", "Clé_Opérations= " & Cells(i, "A"), Valeur, "CDR")
If CStr(Cells(i, "B")) = CDR Then
Cells(i, "C") = Cells(i, "B")
Else
MsgBox "Erreur de CDR sur la ligne : " & i
Exit Sub
End If

Next i

' Boucle sur les lignes à traiter:
For i = 2 To DL

ADO.Cnn_Debut AvecTransaction

Debug.Print "Progression : " & Format(i / DL, "0%"): DoEvents

' Recherche les anciennes dates :
Date_R02 = ADO.Enr_Info(Cnn1, "PPI_Table_Opérations", "Clé_Opérations= " & Cells(i, "A"), Valeur, "Date_R02")
Date_R07 = ADO.Enr_Info(Cnn1, "PPI_Table_Opérations", "Clé_Opérations= " & Cells(i, "A"), Valeur, "Date_R07")

' Modifie les dates dans PPI_Table_Opérations:
ADO.Enr_MAJ Cnn1, "PPI_Table_Opérations", "Clé_Opérations=" & Cells(i, "A"), ModificationUniquement, _
"Date_R02", Cells(i, "D"), _
"Date_R07", Cells(i, "E")

' Ajoute l'historique du jalon R02 dans "PPI_Table_Modif_Jalons":
ADO.Enr_MAJ Cnn1, "PPI_Table_Modif_Jalons", "", CréationUniquement, _
"Clé_Opérations", Cells(i, "A"), _
"Jalon", "R02", _
"Ancien_Date", Date_R02, _
"Nouveau_Date", Cells(i, "D")

' Ajoute l'historique du jalon R07 dans "PPI_Table_Modif_Jalons":
ADO.Enr_MAJ Cnn1, "PPI_Table_Modif_Jalons", "", CréationUniquement, _
"Clé_Opérations", Cells(i, "A"), _
"Jalon", "R07", _
"Ancien_Date", Date_R07, _
"Nouveau_Date", Cells(i, "E")

' Validation de la transaction ou si erreur indique la ligne concernée et quitte la boucle:
If ADO.Cnn_Fin(SansMessage) = False Then
MsgBox "Erreur de traitement ligne : " & i
Exit For
End If

Next i

' Si OK alors affiche que tout s'est bien passé, sinon affiche l'erreur.
ADO.Cnn_Fin AvecMessage

End If

End Sub

Remarque importante sur la gestion des transactions

Comme dit précédemment, « vous utiliserez une transaction pour garantir que plusieurs enregistrements sont mis à jour simultanément, comme s’il n’y avait qu’une seule opération ». C’est pourquoi la transaction est gérée à l’intérieur de la boucle des lignes à traiter.

Et si, inversement, la transaction avait été gérée à l’extérieur de la boucle ?

Dans notre cas, tout se passerait bien, ça serait même plus pratique, car en cas d’erreur toutes les transactions en attente sont annulées, il suffit de corriger le problème puis de relancer le traitement. Mais cela n’est valable que sur le traitement d’une petite quantité de données, car la taille du tampon de transaction, même si elle est assez grande pour mémoriser plusieurs centaines d’enregistrements, n’est pas illimitée. Et vous risquez un dépassement de capacité sur des traitements volumineux, qui fera planter Excel !

IV - Conclusion

Les huit fonctions génériques de cette documentation permettent à un développeur de programmer rapidement de petits traitements en VBA pour interagir avec des bases Access depuis Excel.

En utilisant ces fonctions il n’a pas à se soucier de l’aspect relativement technique de l’utilisation des objets ADODB, tout en laissant la base multi-utilisateur disponible pour tous.

Et l’usage des transactions assure une certaine sécurité dans les mises à jour des données.

Je n’ai pas souhaité détailler davantage le code VBA des fonctions du module ADO pour vous épargner la douloureuse lecture des 1 000 lignes de code. Ni aborder la gestion différée des erreurs pour ne pas alourdir les exemples et perdre le lecteur dans des détails hors sujet.

Ceux que cela intéresse trouveront leur bonheur en farfouillant dans le fichier joint.

Si j’ai bien travaillé, vous avez compris le principe de la technologie ADODB et êtes désormais capables, si nécessaire, de programmer d’autres fonctions adaptées à vos besoins.

Laurent OTT. 2020

V - Annexe 1 – Les requêtes SQL

Pour rechercher ou bien mettre à jour une donnée dans une table, les fonctions génériques utilisent les requêtes SQL.

Vous allez constater dans les lignes qui suivent que c'est très simple et très pratique…

Si vous programmez sous Access les requêtes SQL vous sont familières, pour ceux qui ne connaissent pas voici un bref rappel des notions de base.

SQL (Structured Query Language) est un langage de programmation qui permet de manipuler une base de données : sélections d'enregistrements, modifications, ajouts, suppressions.

Nous utiliserons le SQL principalement pour faire des sélections, avec « SELECT », soit la syntaxe :

SELECT « nom des champs » FROM « nom de la table » WHERE « conditions » ORDER BY « nom des champs »

- SELECT « nom des champs » : indiquez les champs à sélectionner (entre crochets si leur nom contient un espace), en les séparant par une virgule (l'ordre est libre).

Ou plus simplement, une étoile permet de sélectionner directement tous les champs : SELECT * FROM…

- FROM « nom de la table » : représente le nom de la table ou de la requête à sélectionner (entre crochets si leur nom contient un espace).

- WHERE « conditions » : pour restreindre la sélection, vous pouvez ajouter une condition simple ou combinée avec les opérateurs AND et OR comme en VBA. Les données alphanumériques sont à mettre entre simples quotes. Les dates sont à mettre entre deux croisillons et au format anglo-saxon (année/mois/jour). Par exemple pour sélectionner les personnes nées à Noël 1971 et qui se prénomment « Noël » :

WHERE [Date Naissance] = #1971/12/25# AND [Prenom] = 'Noël'

- ORDER BY « nom des champs » : facultatif, permet de trier le résultat de la requête, par ordre ascendant (par défaut) avec la clause ASC ou descendant avec la clause DESC. Il est possible d'ordonner par plusieurs champs, en respectant la syntaxe :

ORDER BY « nom champ 1 » [ASC, DESC], « nom champ 2 » [ASC, DESC].

DELETE * FROM « nom de la table » WHERE « conditions » permet de supprimer des enregistrements.

UPDATE « nom de la table » SET « nom du champ » = « nouvelle valeur » WHERE « conditions » permet de modifier un champ.

En SQL l’apostrophe et les guillemets sont des symboles réservés, et pour utiliser ces caractères il faut les doubler.

Par exemple, l’instruction suivante trouve le département de la ville « L'Escale » :

SELECT Departement FROM Table_Villes WHERE Ville = 'L''Escale'

Vous trouverez sur Internet d'autres informations sur les requêtes SQL.

J'aime bien cette adresse : http://www.1keydata.com/fr/sql/syntaxe-sql.php.

Attention, toutes les commandes SQL ne sont pas compatibles avec Excel.

VI - Annexe 2 – Des liens utiles

Voici quelques liens qui m’ont été bien utiles pour cette documentation et que je vous recommande :

- Comprendre les Recordset ADO (de Jean-Marc Rabilloud) : https://vb.developpez.com/bidou/recordset-ado/

- Utiliser le modèle ADOX avec Visual Basic (de Jean-Marc Rabilloud) : https://vb.developpez.com/bidou/adox/

- ADO et Visual Basic (de Sébastien Curutchet) : https://drq.developpez.com/vb/tutoriels/ADO/

- Les fichiers de verrouillage Access ldb et laccdb (de Thierry Gasperment) : https://arkham46.developpez.com/articles/access/utilisateurs-ldb/

- L’article de Pierre Fauconnier « Dialogues entre Excel et Access grâce à ADODB » où il est abordé la manipulation des données d’une base Access depuis Excel en utilisant le VBA.

VII - Le fichier joint

Vous trouverez le fichier joint MAJ_Bases_Access.xlsm qui contient les fonctions étudiées dans cette documentation.

VIII - Remerciements

Je remercie Gaby277, Arkham46, et Pierre Fauconnier pour leur relecture technique et leurs très nombreux conseils, et Claude Leloup pour la correction orthographique.

Ainsi que toute l’équipe de Developpez.com qui participe à la maintenance du site.