Access
 sql >> Base de données >  >> RDS >> Access

Comment Access communique-t-il avec les sources de données ODBC ? Partie 5

Filtrer le jeu d'enregistrements

Dans la partie 5 de notre série, nous apprendrons comment Microsoft Access gère les filtres implémentés et les intègre dans les requêtes ODBC. Dans l'article précédent, nous avons vu comment Access va formuler le SELECT instructions dans les commandes SQL ODBC. Nous avons également vu dans l'article précédent comment Access va essayer de mettre à jour une ligne en appliquant un WHERE clause basée sur la clé et, le cas échéant, rowversion. Cependant, nous devons apprendre comment Access gérera les filtres fournis aux requêtes Access et les traduira dans la couche ODBC. Access peut utiliser différentes approches en fonction de la formulation des requêtes Access et vous apprendrez à prédire comment Access traduira une requête Access en une requête ODBC pour différents prédicats de filtre donnés.

Quelle que soit la manière dont vous appliquez réellement le filtre - que ce soit de manière interactive via les commandes du ruban du formulaire ou de la feuille de données ou les clics du menu droit, ou par programmation à l'aide de VBA ou en exécutant des requêtes enregistrées - Access émettra une requête SQL ODBC correspondante pour effectuer le filtrage. En général, Access essaiera de filtrer autant que possible à distance. Cependant, il ne vous dira pas s'il ne peut pas le faire. Au lieu de cela, si Access ne peut pas exprimer le filtre à l'aide de la syntaxe SQL ODBC, il tentera à la place d'effectuer lui-même le filtrage en téléchargeant l'intégralité du contenu de la table et en évaluant la condition localement. Cela peut expliquer pourquoi vous pouvez parfois rencontrer une requête qui s'exécute rapidement mais qui, avec un petit changement, ralentit jusqu'à l'exploration. Cette section, espérons-le, vous aidera à comprendre quand cela peut se produire et comment le gérer afin que vous puissiez aider à accéder à distance autant que possible aux sources de données pour appliquer le filtre.

Pour cet article, nous utiliserons des requêtes enregistrées, mais les informations présentées ici devraient toujours s'appliquer aux autres méthodes d'application de filtres.

Filtres statiques

Nous allons commencer facilement et créer une requête enregistrée avec un filtre codé en dur.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName="Boston";
Si nous ouvrons la requête, nous verrons ce SQL ODBC dans la trace :

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Hormis les changements de syntaxe, la sémantique de la requête n'a pas changé; le même filtre est passé tel quel. Notez que seul le CityID a été sélectionné car, par défaut, une requête utilise un jeu d'enregistrements de type feuille de réponse dynamique dont nous avons déjà parlé dans la section précédente.

Filtres paramétrables simples

Modifions le SQL pour utiliser un paramètre à la place :

PARAMETERS SelectedCityName Text ( 255 );
SELECT 
  c.CityID
 ,c.CityName
 ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[SelectedCityName];
Si nous exécutons la requête et saisissons "Boston" dans la valeur d'invite du paramètre, comme indiqué, nous devrions voir la trace SQL ODBC suivante :
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" =  ? ) 
Notez que nous observerons le même comportement avec les références de contrôle ou les liens de sous-formulaires. Si nous utilisions ceci à la place :

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];
Nous obtiendrions toujours le même SQL ODBC tracé que nous avons vu avec la requête paramétrée d'origine. C'est toujours le cas même si notre requête modifiée n'avait pas de PARAMETERS déclaration. Cela montre qu'Access est capable de reconnaître que de telles références de contrôle, dont la valeur peut être modifiée de temps à autre, sont mieux traitées comme un paramètre lors de la formulation du SQL ODBC.

Cela fonctionne également pour la fonction VBA. Nous pouvons ajouter une nouvelle fonction VBA :

Public Function GetSelectedCity() As String
    GetSelectedCity = "Boston"
End Function
Nous ajustons la requête enregistrée pour utiliser la nouvelle fonction VBA :

WHERE c.CityName=GetSelectedCity();
Si vous tracez cela, vous verrez que c'est toujours le même. Ainsi, nous avons démontré que, que l'entrée soit un paramètre explicite, une référence à un contrôle ou le résultat d'une fonction VBA, Access les traitera tous comme un paramètre de la requête SQL ODBC qu'il exécutera sur notre au nom de. C'est une bonne chose car nous obtenons généralement de meilleures performances lorsque nous pouvons réutiliser une requête et simplement modifier le paramètre.

Cependant, il existe un autre scénario courant que les développeurs Access configurent généralement et qui consiste à créer du SQL dynamique avec du code VBA, généralement en concaténant une chaîne, puis en exécutant la chaîne concaténée. Utilisons le code VBA suivant :

Public Sub GetSelectedCities()
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim fld As DAO.Field
    
    Dim SelectedCity As String
    Dim SQLStatement As String
    
    SelectedCity = InputBox("Enter a city name")
    SQLStatement = _
        "SELECT c.CityID, c.CityName, c.StateProvinceID " & _
        "FROM Cities AS c " & _
        "WHERE c.CityName = '" & SelectedCity & "';"
    
    Set db = CurrentDb
    Set rs = db.OpenRecordset(SQLStatement)
    Do Until rs.EOF
        For Each fld In rs.Fields
            Debug.Print fld.Value;
        Next
        Debug.Print
        rs.MoveNext
    Loop
End Sub
Le SQL ODBC tracé pour le OpenRecordset est le suivant :

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Contrairement aux exemples précédents, le SQL ODBC n'était pas paramétré. Access n'a aucun moyen de savoir que le "Boston" a été dynamiquement rempli au moment de l'exécution par un VBA.InputBox . Nous lui avons simplement remis le SQL construit qui, à partir du point de vue d'Access, n'est qu'une instruction SQL statique. Dans ce cas, on va à l'encontre du paramétrage de la requête. Il est important de reconnaître qu'un conseil populaire donné aux développeurs d'Access a été que le SQL construit dynamiquement est meilleur que l'utilisation de requêtes de paramètres car il évite le problème où le moteur d'Access peut générer un plan d'exécution basé sur une valeur de paramètre qui peut être en fait sous-optimale pour une autre valeur du paramètre. Pour plus de détails sur ce phénomène, je vous encourage à lire sur le problème du « reniflage de paramètres ». Notez qu'il s'agit d'un problème général pour tous les moteurs de base de données, pas seulement pour Access. Cependant, dans le cas d'Access, le SQL dynamique fonctionnait mieux car il est beaucoup moins cher de générer simplement un nouveau plan d'exécution. En revanche, un moteur RDBMS peut avoir des stratégies supplémentaires pour gérer le problème et peut être plus sensible à avoir trop de plans d'exécution ponctuels, car cela peut avoir un impact négatif sur sa mise en cache.

Pour cette raison, les requêtes paramétrées d'Access sur les sources ODBC peuvent être préférables au SQL dynamique. Étant donné qu'Access traitera les contrôles de référence sur un formulaire ou les fonctions VBA qui ne nécessitent pas de références de colonne comme des paramètres, vous n'avez pas besoin de paramètres explicites dans vos sources d'enregistrement ou de ligne. Cependant, si vous utilisez VBA pour exécuter SQL, il est généralement préférable d'utiliser ADO, qui offre également une bien meilleure prise en charge du paramétrage. Dans le cas de la création d'une source d'enregistrement dynamique ou d'une source de ligne, l'utilisation d'un contrôle masqué sur le formulaire/rapport peut être un moyen simple de paramétrer la requête. Cependant, si la requête est nettement différente, la construction du SQL dynamique dans VBA et son affectation à la propriété recordsource/rowsource force effectivement une recompilation complète et évite donc d'utiliser de mauvais plans d'exécution qui ne fonctionneront pas bien pour l'ensemble actuel d'entrées. Vous pouvez trouver des recommandations dans l'article traitant de WITH RECOMPILE de SQL Server utile pour décider s'il faut forcer une recompilation ou utiliser une requête paramétrée.

Utilisation des fonctions dans le filtrage SQL

Dans la section précédente, nous avons vu qu'une instruction SQL contenant une fonction VBA était paramétrée afin qu'Access puisse exécuter la fonction VBA et utiliser la sortie comme entrée de la requête paramétrée. Cependant, toutes les fonctions intégrées ne se comportent pas de cette façon. Utilisons UCase() comme exemple pour filtrer la requête. De plus, nous allons appliquer la fonction sur une colonne.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE UCase([c].[CityName])="BOSTON";
Si nous regardons le SQL ODBC tracé, nous verrons ceci :

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ({fn ucase("CityName" )}= 'BOSTON' )
Dans l'exemple précédent, Access a pu complètement paramétrer le GetSelectedCity() puisqu'il ne nécessitait aucune entrée des colonnes référencées dans la requête. Cependant, le UCase() nécessite une entrée. Si nous avions fourni UCase("Boston") , Access aurait également paramétré cela. Cependant, l'entrée est une référence de colonne, qu'Access ne peut pas facilement paramétrer. Cependant, Access peut détecter que le UCase() est l'une des fonctions scalaires ODBC prises en charge. Étant donné que nous préférons autant que possible l'accès à distance à la source de données, Access fait exactement cela en appelant la version ODBC de ucase .

Si nous créons ensuite une fonction VBA personnalisée qui émule UCase() fonction :

Public Function MyUCase(InputValue As Variant) As String
    MyUCase = UCase(InputValue)
End Function
et changé le filtrage de la requête en :

WHERE MyUCase([c].[CityName])="BOSTON";
Voici ce que nous obtenons :

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
Access est incapable de télécommander la fonction VBA personnalisée MyUCase retour à la source de données. Cependant, le SQL de la requête enregistrée est légal, Access doit donc le satisfaire d'une manière ou d'une autre. Pour ce faire, il finit par télécharger l'ensemble complet du CityName et son CityID correspondant pour passer dans la fonction VBA MyUCase() et évaluer le résultat. Par conséquent, la requête s'exécute maintenant beaucoup plus lentement car Access demande maintenant plus de données et effectue plus de travail.

Bien que nous ayons utilisé UCase() dans cet exemple, nous pouvons clairement voir qu'il est généralement préférable de déporter autant de travail que possible à la source de données. Mais que se passe-t-il si nous avons une fonction VBA complexe qui ne peut pas être réécrite dans le dialecte SQL natif de la source de données ? Bien que je pense que ce scénario est assez rare, il vaut la peine d'être considéré. Supposons que nous puissions ajouter un filtre pour restreindre l'ensemble des villes renvoyées.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName LIKE "Bos*"
  AND MyUCase([c].[CityName])="BOSTON";
Le SQL ODBC tracé sortira ainsi :

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" LIKE 'Bos%' ) 
L'accès est capable de distancer le LIKE retour à la source de données, ce qui entraîne la récupération d'un ensemble de données beaucoup plus petit. Il effectuera toujours une évaluation locale de MyUCase() sur le jeu de données obtenu. La requête s'exécute beaucoup plus rapidement simplement en raison du plus petit ensemble de données renvoyé.

Cela nous indique que si nous sommes confrontés au scénario indésirable où nous ne pouvons pas facilement refactoriser une fonction VBA complexe à partir d'une requête, nous pouvons toujours atténuer les effets néfastes en ajoutant des filtres qui peuvent être distants pour réduire l'ensemble initial d'enregistrements avec lesquels Access doit travailler.

Une note sur la sargabilité

Dans les exemples précédents, nous avons appliqué une fonction scalaire sur une colonne. Cela a le potentiel de rendre la requête comme "non-sargable", ce qui signifie que le moteur de base de données est incapable d'optimiser la requête en utilisant l'index pour rechercher et trouver des correspondances. La partie "sarg" du mot "sargability" fait référence à "Search ARGument". Supposons que l'index soit défini au niveau de la source de données sur la table :

CREATE INDEX IX_Cities_CityName
ON Application.Cities (CityName);
Des expressions telles que UCASE(CityName) empêche le moteur de base de données de pouvoir utiliser l'index IX_Cities_CityName car le moteur est obligé d'évaluer chaque ligne une par une pour trouver une correspondance, tout comme Access l'a fait avec une fonction VBA personnalisée. Certains moteurs de base de données tels que les versions récentes de SQL Server prennent en charge la création d'index basés sur une expression. Si nous voulions optimiser les requêtes en utilisant UCASE() transact-SQL, nous pourrions ajuster la définition de l'index :

CREATE INDEX IX_Cities_Boston_Uppercase
ON Application.Cities (CityName)
WHERE UCASE(CityName) = 'BOSTON';
Cela permet à SQL Server de traiter la requête avec WHERE UCase(CityName) = 'BOSTON' en tant que requête sargable car elle peut désormais utiliser l'index IX_Cities_Boston_Uppercase pour renvoyer les enregistrements correspondants. Cependant, si la requête correspond à 'CLEVELAND' au lieu de 'BOSTON' , la sargabilité est perdue.

Quel que soit le moteur de base de données avec lequel vous travaillez réellement, il est toujours préférable de concevoir et d'utiliser des requêtes sargables dans la mesure du possible pour éviter les problèmes de performances. Les requêtes cruciales doivent avoir des index couvrants pour fournir les meilleures performances. Je vous encourage à étudier davantage la sargabilité et les indices de couverture pour vous aider à éviter de concevoir des requêtes qui sont en fait non-sargables.

Conclusion

Nous avons examiné comment Access gère l'application de filtres d'Access SQL dans les requêtes ODBC. Nous avons également exploré différents cas où Access convertira différents types de références en un paramètre, permettant à Access d'effectuer l'évaluation en dehors de la couche ODBC et de les transmettre en tant qu'entrées dans l'instruction ODBC préparée. Nous avons également examiné ce qui se passe lorsqu'il ne peut pas être paramétré, généralement en raison du fait qu'il contient des références de colonne en tant qu'entrées. Cela peut avoir des conséquences sur les performances lors d'une migration vers SQL Server.

Pour certaines fonctions, Access peut être en mesure de convertir l'expression pour utiliser les fonctions scalaires ODBC à la place, ce qui permet à Access d'éloigner l'expression de la source de données ODBC. Une ramification de ceci est que si l'implémentation de la fonction scalaire est différente, cela peut entraîner un comportement différent de la requête ou une exécution plus rapide/lente. Nous avons vu comment une fonction VBA, même une simple qui encapsule une fonction scalaire autrement accessible à distance, peut vaincre les efforts visant à éloigner l'expression. Nous apprenons également que si nous avons une situation où nous ne pouvons pas refactoriser une fonction VBA complexe à partir d'une requête Access/recordsource/rowsource, nous pouvons au moins atténuer le téléchargement coûteux en ajoutant des filtres supplémentaires sur la requête qui peuvent être distants pour réduire le montant de données renvoyées.

Dans le prochain article, nous verrons comment les jointures sont gérées par Access.

Vous cherchez de l'aide avec Microsoft Access ? Appelez nos experts dès aujourd'hui au 773-809-5456 ou envoyez-nous un e-mail à [email protected].