Access
 sql >> Datenbank >  >> RDS >> Access

Wie kommuniziert Access mit ODBC-Datenquellen? Teil 5

Filtern des Datensatzes

In Teil 5 unserer Serie erfahren Sie, wie Microsoft Access mit implementierten Filtern umgeht und diese in ODBC-Abfragen integriert. Im vorherigen Artikel haben wir gesehen, wie Access den SELECT formuliert Anweisungen in den ODBC-SQL-Befehlen. Wir haben im vorherigen Artikel auch gesehen, wie Access versucht, eine Zeile durch Anwenden eines WHERE zu aktualisieren -Klausel basierend auf dem Schlüssel und gegebenenfalls der Zeilenversion. Wir müssen jedoch lernen, wie Access mit den Filtern umgeht, die den Access-Abfragen bereitgestellt werden, und sie in der ODBC-Schicht übersetzt. Es gibt verschiedene Ansätze, die Access verwenden kann, je nachdem, wie die Access-Abfragen formuliert sind, und Sie werden lernen, wie Sie vorhersagen können, wie Access eine Access-Abfrage in eine ODBC-Abfrage für verschiedene gegebene Filterprädikate übersetzt.

Unabhängig davon, wie Sie den Filter tatsächlich anwenden – sei es interaktiv über Menübandbefehle des Formulars oder des Datenblatts oder Rechtsklicks im Menü oder programmgesteuert mit VBA oder Ausführen gespeicherter Abfragen – Access gibt eine entsprechende ODBC-SQL-Abfrage aus, um die Filterung durchzuführen. Im Allgemeinen versucht Access, so weit wie möglich zu filtern. Es wird Ihnen jedoch nicht mitgeteilt, ob dies nicht möglich ist. Wenn Access den Filter stattdessen nicht mithilfe der ODBC-SQL-Syntax ausdrücken kann, versucht es stattdessen, die Filterung selbst durchzuführen, indem es den gesamten Inhalt der Tabelle herunterlädt und die Bedingung lokal auswertet. Das kann erklären, warum Sie manchmal auf eine Abfrage stoßen, die schnell ausgeführt wird, aber mit einer kleinen Änderung zu einem Crawl verlangsamt wird. Dieser Abschnitt hilft Ihnen hoffentlich zu verstehen, wann dies passieren kann und wie Sie damit umgehen, damit Sie so weit wie möglich auf die Datenquellen für die Anwendung des Filters zugreifen können.

Für diesen Artikel verwenden wir gespeicherte Abfragen, aber die hier besprochenen Informationen sollten auch für andere Methoden zum Anwenden von Filtern gelten.

Statische Filter

Wir fangen einfach an und erstellen eine gespeicherte Abfrage mit einem hartcodierten Filter.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName="Boston";
Wenn wir die Abfrage öffnen, sehen wir dieses ODBC-SQL im Trace:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Abgesehen von den Änderungen in der Syntax hat sich die Semantik der Abfrage nicht geändert; derselbe Filter wird unverändert übergeben. Beachten Sie, dass nur die CityID wurde ausgewählt, da eine Abfrage standardmäßig ein Recordset vom Typ Dynaset verwendet, das wir bereits im vorherigen Abschnitt besprochen haben.

Einfache parametrisierte Filter

Lassen Sie uns die SQL ändern, um stattdessen einen Parameter zu verwenden:

PARAMETERS SelectedCityName Text ( 255 );
SELECT 
  c.CityID
 ,c.CityName
 ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[SelectedCityName];
Wenn wir die Abfrage ausführen und „Boston“ wie gezeigt in den Eingabeaufforderungswert des Parameters eingeben, sollten wir die folgende ODBC-Trace-SQL sehen:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" =  ? ) 
Beachten Sie, dass wir dasselbe Verhalten bei Steuerelementreferenzen oder Unterformularverknüpfungen beobachten werden. Wenn wir stattdessen dies verwendet hätten:

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];
Wir würden immer noch dasselbe nachverfolgte ODBC-SQL erhalten, das wir bei der ursprünglichen parametrisierten Abfrage gesehen haben. Das ist immer noch der Fall, obwohl unsere modifizierte Abfrage keinen PARAMETERS hatte Erklärung. Dies zeigt, dass Access erkennen kann, dass solche Steuerreferenzen, deren Wert von Zeit zu Zeit geändert werden kann, bei der Formulierung des ODBC-SQL am besten als Parameter behandelt werden.

Das funktioniert auch für die VBA-Funktion. Wir können eine neue VBA-Funktion hinzufügen:

Public Function GetSelectedCity() As String
    GetSelectedCity = "Boston"
End Function
Wir passen die gespeicherte Abfrage an, um die neue VBA-Funktion zu verwenden:

WHERE c.CityName=GetSelectedCity();
Wenn Sie dies verfolgen, werden Sie sehen, dass es immer noch dasselbe ist. Somit haben wir gezeigt, dass Access sie alle als Parameter der ODBC-SQL-Abfrage behandelt, die auf unserem ausgeführt wird, unabhängig davon, ob die Eingabe ein expliziter Parameter, ein Verweis auf ein Steuerelement oder ein Ergebnis einer VBA-Funktion ist Namen. Das ist eine gute Sache, denn wir erzielen im Allgemeinen eine bessere Leistung, wenn wir eine Abfrage wiederverwenden und einfach den Parameter ändern können.

Es gibt jedoch ein weiteres häufiges Szenario, das Access-Entwickler normalerweise einrichten, und das ist das Erstellen von dynamischem SQL mit VBA-Code, normalerweise durch Verketten einer Zeichenfolge und anschließendes Ausführen der verketteten Zeichenfolge. Lassen Sie uns den folgenden VBA-Code verwenden:

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
Das verfolgte ODBC-SQL für das OpenRecordset lautet wie folgt:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Im Gegensatz zu vorherigen Beispielen wurde das ODBC-SQL nicht parametrisiert. Access hat keine Möglichkeit zu wissen, dass „Boston“ zur Laufzeit durch eine VBA.InputBox dynamisch gefüllt wurde . Wir haben ihm einfach das konstruierte SQL übergeben, das aus der Perspektive von Access nur eine statische SQL-Anweisung ist. In diesem Fall heben wir die Parametrisierung der Abfrage auf. Es ist wichtig zu erkennen, dass ein beliebter Rat an Access-Entwickler war, dass dynamisch erstelltes SQL besser ist als die Verwendung von Parameterabfragen, da es das Problem vermeidet, dass die Access-Engine einen Ausführungsplan basierend auf einem Parameterwert generiert, der für einen anderen möglicherweise suboptimal ist Parameterwert. Für weitere Details zu diesem Phänomen ermutige ich Sie, sich über das Problem des „Parameter-Sniffing“ zu informieren. Beachten Sie, dass dies ein allgemeines Problem für alle Datenbank-Engines ist, nicht nur für Access. Im Fall von Access funktionierte dynamisches SQL jedoch besser, da es viel billiger ist, nur einen neuen Ausführungsplan zu generieren. Im Gegensatz dazu verfügt eine RDBMS-Engine möglicherweise über zusätzliche Strategien zur Behandlung des Problems und reagiert möglicherweise empfindlicher auf zu viele einmalige Ausführungspläne, da sich dies negativ auf das Caching auswirken kann.

Aus diesem Grund sind parametrisierte Abfragen von Access für ODBC-Quellen möglicherweise gegenüber dynamischem SQL vorzuziehen. Da Access die Referenzsteuerelemente in einem Formular oder VBA-Funktionen behandelt, die keine Spaltenreferenzen als Parameter erfordern, benötigen Sie keine expliziten Parameter in Ihren Datensatzquellen oder Zeilenquellen. Wenn Sie jedoch VBA zum Ausführen von SQL verwenden, ist es normalerweise besser, ADO zu verwenden, das auch eine viel bessere Unterstützung für die Parametrisierung bietet. Beim Erstellen einer dynamischen Datensatzquelle oder Zeilenquelle kann die Verwendung eines verborgenen Steuerelements im Formular/Bericht eine einfache Möglichkeit sein, die Abfrage zu parametrisieren. Wenn sich die Abfrage jedoch deutlich unterscheidet, erzwingt das Erstellen des dynamischen SQL in VBA und das Zuweisen zur Eigenschaft recordsource/rowsource effektiv eine vollständige Neukompilierung und vermeidet daher die Verwendung schlechter Ausführungspläne, die für den aktuellen Satz von Eingaben keine gute Leistung erbringen. Möglicherweise finden Sie Empfehlungen in dem Artikel über WITH RECOMPILE von SQL Server hilfreich bei der Entscheidung, ob eine Neukompilierung oder eine parametrisierte Abfrage erzwungen werden soll.

Funktionen in der SQL-Filterung verwenden

Im vorherigen Abschnitt haben wir gesehen, dass eine SQL-Anweisung, die eine VBA-Funktion enthält, parametrisiert wurde, sodass Access die VBA-Funktion ausführen und die Ausgabe als Eingabe für die parametrisierte Abfrage verwenden konnte. Allerdings verhalten sich nicht alle integrierten Funktionen auf diese Weise. Lassen Sie uns UCase() verwenden als Beispiel, um die Abfrage zu filtern. Außerdem wenden wir die Funktion auf eine Spalte an.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE UCase([c].[CityName])="BOSTON";
Wenn wir uns das verfolgte ODBC-SQL ansehen, sehen wir Folgendes:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ({fn ucase("CityName" )}= 'BOSTON' )
Im vorherigen Beispiel war Access in der Lage, GetSelectedCity() vollständig wegzuparametrieren da es keine Eingaben aus Spalten erforderte, auf die in der Abfrage verwiesen wird. Allerdings ist die UCase() erfordert eine Eingabe. Hätten wir UCase("Boston") bereitgestellt , Access hätte das auch wegparametriert. Die Eingabe ist jedoch ein Spaltenverweis, den Access nicht einfach wegparametrieren kann. Access kann jedoch erkennen, dass UCase() ist eine der unterstützten ODBC-Skalarfunktionen. Da wir es vorziehen, so weit wie möglich auf die Datenquelle zu remoten, tut Access genau das, indem es die ODBC-Version von ucase aufruft .

Wenn wir dann eine benutzerdefinierte VBA-Funktion erstellen, die UCase() emuliert Funktion:

Public Function MyUCase(InputValue As Variant) As String
    MyUCase = UCase(InputValue)
End Function
und die Filterung in der Abfrage geändert zu:

WHERE MyUCase([c].[CityName])="BOSTON";
Folgendes erhalten wir:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
Access kann die benutzerdefinierte VBA-Funktion MyUCase nicht remote ausführen zurück zur Datenquelle. Das SQL der gespeicherten Abfrage ist jedoch legal, sodass Access es irgendwie erfüllen muss. Dazu lädt es schließlich den vollständigen Satz von CityName herunter und die entsprechende CityID um in die VBA-Funktion MyUCase() zu übergeben und das Ergebnis auswerten. Folglich wird die Abfrage jetzt viel langsamer ausgeführt, da Access jetzt mehr Daten anfordert und mehr Arbeit leistet.

Obwohl wir UCase() verwendet haben In diesem Beispiel können wir deutlich sehen, dass es im Allgemeinen besser ist, so viel Arbeit wie möglich an die Datenquelle zu verlagern. Was aber, wenn wir eine komplexe VBA-Funktion haben, die nicht in den nativen SQL-Dialekt der Datenquelle umgeschrieben werden kann? Obwohl ich denke, dass dieses Szenario ziemlich selten ist, ist es eine Überlegung wert. Angenommen, wir können einen Filter hinzufügen, um die zurückgegebenen Städte einzugrenzen.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName LIKE "Bos*"
  AND MyUCase([c].[CityName])="BOSTON";
Das nachverfolgte ODBC-SQL wird wie folgt ausgegeben:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" LIKE 'Bos%' ) 
Access ist in der Lage, das LIKE zu entfernen zurück zur Datenquelle, was dazu führt, dass Sie einen viel kleineren Datensatz zurückerhalten. Es wird weiterhin eine lokale Auswertung von MyUCase() durchführen auf dem resultierenden Datensatz. Die Abfrage wird einfach aufgrund des kleineren zurückgegebenen Datensatzes viel schneller ausgeführt.

Dies sagt uns, wenn wir mit dem unerwünschten Szenario konfrontiert sind, in dem wir eine komplexe VBA-Funktion nicht einfach aus einer Abfrage umgestalten können, können wir die negativen Auswirkungen dennoch abmildern, indem wir Filter hinzufügen, die entfernt werden können, um den anfänglichen Satz von Datensätzen zu reduzieren, mit denen Access arbeiten kann.

Ein Hinweis zur Sargability

In den vorangegangenen Beispielen haben wir eine Skalarfunktion auf eine Spalte angewendet. Dies kann dazu führen, dass die Abfrage als „nicht sargable“ dargestellt wird, was bedeutet, dass die Datenbank-Engine die Abfrage nicht optimieren kann, indem sie den Index verwendet, um Übereinstimmungen zu suchen und zu finden. Der „sarg“-Teil des Wortes „sargability“ bezieht sich auf „Search ARGument“. Angenommen, wir haben den Index in der Datenquelle für die Tabelle definiert:

CREATE INDEX IX_Cities_CityName
ON Application.Cities (CityName);
Ausdrücke wie UCASE(CityName) verhindert, dass die Datenbank-Engine den Index IX_Cities_CityName verwenden kann da die Engine gezwungen ist, jede Zeile einzeln auszuwerten, um eine Übereinstimmung zu finden, genau wie Access es mit einer benutzerdefinierten VBA-Funktion getan hat. Einige Datenbank-Engines wie neuere Versionen von SQL Server unterstützen das Erstellen von Indizes basierend auf einem Ausdruck. Wenn wir die Abfragen mit UCASE() optimieren wollten transact-SQL-Funktion könnten wir die Indexdefinition anpassen:

CREATE INDEX IX_Cities_Boston_Uppercase
ON Application.Cities (CityName)
WHERE UCASE(CityName) = 'BOSTON';
Dadurch kann SQL Server die Abfrage mit WHERE UCase(CityName) = 'BOSTON' behandeln als Sargable-Abfrage, da sie jetzt den Index IX_Cities_Boston_Uppercase verwenden kann um die übereinstimmenden Datensätze zurückzugeben. Wenn die Abfrage jedoch mit 'CLEVELAND' übereinstimmte statt 'BOSTON' , geht die Sargability verloren.

Unabhängig davon, mit welcher Datenbank-Engine Sie tatsächlich arbeiten, ist es immer vorzuziehen, Sargable-Abfragen zu entwerfen und zu verwenden, wo immer dies möglich ist, um Leistungsprobleme zu vermeiden. Entscheidende Abfragen sollten abdeckende Indizes haben, um die beste Leistung zu erzielen. Ich ermutige Sie, mehr über die Sargability und das Abdecken von Indizes zu lernen, um das Entwerfen von Abfragen zu vermeiden, die tatsächlich nicht sargable sind.

Schlussfolgerungen

Wir haben überprüft, wie Access das Anwenden von Filtern von Access SQL auf die ODBC-Abfragen handhabt. Wir haben auch verschiedene Fälle untersucht, in denen Access verschiedene Arten von Verweisen in einen Parameter konvertiert, sodass Access die Auswertung außerhalb der ODBC-Schicht durchführen und sie als Eingaben an die vorbereitete ODBC-Anweisung übergeben kann. Wir haben uns auch angesehen, was passiert, wenn es nicht parametrisiert werden kann, typischerweise weil Spaltenreferenzen als Eingaben enthalten sind. Das kann Auswirkungen auf die Performance bei einer Migration auf SQL Server haben.

Bei bestimmten Funktionen kann Access möglicherweise den Ausdruck konvertieren, um stattdessen ODBC-Skalarfunktionen zu verwenden, wodurch Access den Ausdruck remote an die ODBC-Datenquelle übertragen kann. Eine Folge davon ist, dass eine andere Implementierung der Skalarfunktion dazu führen kann, dass sich die Abfrage anders verhält oder schneller/langsamer ausgeführt wird. Wir haben gesehen, wie eine VBA-Funktion, sogar eine einfache, die eine ansonsten remote-fähige Skalarfunktion umschließt, die Bemühungen um Remote-Ausdruck zunichte machen kann. Wir lernen auch, dass wir, wenn wir eine Situation haben, in der wir eine komplexe VBA-Funktion nicht aus einer Access-Abfrage/Recordsource/Rowsource umgestalten können, den teuren Download zumindest abmildern können, indem wir der Abfrage zusätzliche Filter hinzufügen, die entfernt werden können, um die Menge zu reduzieren der zurückgegebenen Daten.

Im nächsten Artikel werden wir uns ansehen, wie Joins von Access gehandhabt werden.

Suchen Sie Hilfe zu Microsoft Access? Rufen Sie noch heute unsere Experten unter 773-809-5456 an oder senden Sie uns eine E-Mail an [email protected].