De recordset filteren
In deel 5 van onze serie zullen we leren hoe Microsoft Access omgaat met geïmplementeerde filters en deze integreert in ODBC-query's. In het vorige artikel hebben we gezien hoe Access de SELECT
. formuleert instructies in de ODBC SQL-opdrachten. We hebben in het vorige artikel ook gezien hoe Access een rij probeert bij te werken door een WHERE
. toe te passen clausule op basis van de sleutel en, indien van toepassing, rijversie. We moeten echter leren hoe Access omgaat met de filters die aan de Access-query's worden geleverd en deze in de ODBC-laag vertalen. Er zijn verschillende benaderingen die Access kan gebruiken, afhankelijk van hoe de Access-query's zijn geformuleerd en u leert hoe u kunt voorspellen hoe Access een Access-query zal vertalen in een ODBC-query voor een ander gegeven filterpredikaat.
Ongeacht hoe u het filter daadwerkelijk toepast - of het nu interactief is via de lintopdrachten van formulieren of datasheets of klikken in het rechtermenu, of programmatisch met VBA of het uitvoeren van opgeslagen zoekopdrachten - Access zal een overeenkomstige ODBC SQL-query uitgeven om de filtering uit te voeren. Over het algemeen zal Access proberen zoveel mogelijk op afstand te filteren. Het zal u echter niet vertellen of het dit niet kan. In plaats daarvan, als Access het filter niet kan uitdrukken met behulp van de ODBC SQL-syntaxis, zal het in plaats daarvan proberen zelf te filteren door de volledige inhoud van de tabel te downloaden en de voorwaarde lokaal te evalueren. Dat kan verklaren waarom je soms een zoekopdracht tegenkomt die snel wordt uitgevoerd, maar met een kleine verandering, vertraagt tot een crawl. Dit gedeelte helpt u hopelijk te begrijpen wanneer dit kan gebeuren en hoe u hiermee om kunt gaan, zodat u op afstand toegang kunt krijgen tot de gegevensbronnen voor het toepassen van het filter.
Voor dit artikel gebruiken we opgeslagen zoekopdrachten, maar de informatie die hier wordt besproken, moet nog steeds van toepassing zijn op andere methoden voor het toepassen van filters.
Statische filters
We beginnen eenvoudig en maken een opgeslagen zoekopdracht met een hardcoded filter.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName="Boston";Als we de query openen, zien we deze ODBC SQL in de tracering:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )Afgezien van de veranderingen in de syntaxis, is de semantiek van de query niet veranderd; hetzelfde filter wordt doorgegeven zoals het is. Merk op dat alleen de
CityID
is geselecteerd omdat een query standaard een recordset van het type dynaset gebruikt die we al in de vorige sectie hebben besproken. Eenvoudige geparametriseerde filters
Laten we de SQL wijzigen om in plaats daarvan een parameter te gebruiken:
PARAMETERS SelectedCityName Text ( 255 ); SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[SelectedCityName];Als we de query uitvoeren en "Boston" invoeren in de parameterpromptwaarde zoals weergegeven, zouden we de volgende ODBC-traceer-SQL moeten zien:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = ? )Merk op dat we hetzelfde gedrag zullen waarnemen met controleverwijzingen of subformulierkoppeling. Als we dit in plaats daarvan gebruikten:
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];We zouden nog steeds dezelfde getraceerde ODBC SQL krijgen die we zagen met de oorspronkelijke geparametriseerde query. Dat is nog steeds het geval, ook al had onze aangepaste zoekopdracht geen
PARAMETERS
uitspraak. Dit toont aan dat Access in staat is te herkennen dat dergelijke controleverwijzingen waarvan de waarde van tijd tot tijd kan worden gewijzigd, het beste kunnen worden behandeld als een parameter bij het formuleren van de ODBC-SQL. Dat werkt ook voor de VBA-functie. We kunnen een nieuwe VBA-functie toevoegen:
Public Function GetSelectedCity() As String GetSelectedCity = "Boston" End FunctionWe passen de opgeslagen zoekopdracht aan om de nieuwe VBA-functie te gebruiken:
WHERE c.CityName=GetSelectedCity();Als je dit traceert, zul je zien dat het nog steeds hetzelfde is. We hebben dus aangetoond dat, ongeacht of de invoer een expliciete parameter, een verwijzing naar een besturingselement of een resultaat van een VBA-functie is, Access ze allemaal zal behandelen als een parameter van de ODBC SQL-query die het zal uitvoeren op onze namens. Dat is een goede zaak, omdat we over het algemeen betere prestaties krijgen als we een query opnieuw kunnen gebruiken en gewoon de parameter kunnen wijzigen.
Er is echter nog een veelvoorkomend scenario dat Access-ontwikkelaars doorgaans opzetten en dat is het creëren van dynamische SQL met VBA-code, meestal door een string aaneen te schakelen en vervolgens de aaneengeschakelde string uit te voeren. Laten we de volgende VBA-code gebruiken:
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 SubDe getraceerde ODBC SQL voor de
OpenRecordset
is als volgt: SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )In tegenstelling tot eerdere voorbeelden was de ODBC-SQL niet geparametriseerd. Access heeft geen manier om te weten dat de 'Boston' dynamisch werd bevolkt tijdens runtime door een
VBA.InputBox
. We hebben het gewoon de geconstrueerde SQL gegeven die van Access 'POV slechts een statische SQL-instructie is. In dit geval verslaan we de parametrering van de query. Het is belangrijk om te erkennen dat een populair advies aan Access-ontwikkelaars is geweest dat dynamisch opgebouwde SQL beter is dan het gebruik van parameterquery's, omdat het het probleem vermijdt waarbij de Access-engine een uitvoeringsplan genereert op basis van één parameterwaarde die in feite suboptimaal kan zijn voor een andere parameterwaarde. Voor meer details over dat fenomeen, raad ik je aan om je te verdiepen in het probleem van "parameter-sniffing". Merk op dat dit een algemeen probleem is voor alle database-engines, niet alleen voor Access. In het geval van Access werkte dynamische SQL echter beter omdat het veel goedkoper is om alleen een nieuw uitvoeringsplan te genereren. Daarentegen kan een RDBMS-engine aanvullende strategieën hebben om het probleem aan te pakken en gevoeliger zijn voor het hebben van te veel eenmalige uitvoeringsplannen, omdat dit de caching negatief kan beïnvloeden.
Om die reden kunnen geparametriseerde query's van Access tegen ODBC-bronnen de voorkeur hebben boven dynamische SQL. Omdat Access de verwijzingsbesturingselementen op een formulier of VBA-functies die geen kolomverwijzingen vereisen als parameters behandelt, hebt u geen expliciete parameters nodig in uw recordsources of rowsources. Als u echter VBA gebruikt om SQL uit te voeren, is het meestal beter om ADO te gebruiken, dat ook veel betere ondersteuning biedt voor parametrering. In het geval van het bouwen van een dynamische recordbron of rijbron, kan het gebruik van een verborgen besturingselement op het formulier/rapport een gemakkelijke manier zijn om de query te parametriseren. Als de query echter duidelijk anders is, dwingt het bouwen van de dynamische SQL in VBA en het toewijzen ervan aan de eigenschap recordsource/rowsource in feite een volledige hercompilatie af en vermijdt u daarom het gebruik van slechte uitvoeringsplannen die niet goed presteren voor de huidige set invoer. Mogelijk vindt u aanbevelingen in het artikel over SQL Server's WITH RECOMPILE
nuttig bij het beslissen of u een hercompilatie wilt forceren of een geparametriseerde query wilt gebruiken.
Functies gebruiken in SQL-filtering
In de vorige sectie hebben we gezien dat een SQL-instructie die een VBA-functie bevat, werd geparametriseerd zodat Access de VBA-functie kon uitvoeren en de uitvoer kon gebruiken als invoer voor de geparametriseerde query. Niet alle ingebouwde functies gedragen zich echter op deze manier. Laten we UCase()
. gebruiken als voorbeeld om de query te filteren. Verder gaan we de functie toepassen op een kolom.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE UCase([c].[CityName])="BOSTON";Als we naar de getraceerde ODBC SQL kijken, zien we dit:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ({fn ucase("CityName" )}= 'BOSTON' )In het vorige voorbeeld kon Access de
GetSelectedCity()
. volledig wegparametriseren omdat er geen invoer nodig was van kolommen waarnaar in de query wordt verwezen. Echter, de UCase()
een ingang nodig. Hadden we UCase("Boston")
. verstrekt , Access zou dit ook hebben weggeparametreerd. De invoer is echter een kolomverwijzing, die Access niet gemakkelijk kan wegparametriseren. Access kan echter detecteren dat de UCase()
is een van de ondersteunde ODBC-scalarfuncties. Omdat we er de voorkeur aan geven zoveel mogelijk naar de gegevensbron te verplaatsen, doet Access precies dat door de ODBC-versie van ucase
aan te roepen .
Als we dan een aangepaste VBA-functie maken die UCase()
emuleert functie:
Public Function MyUCase(InputValue As Variant) As String MyUCase = UCase(InputValue) End Functionen veranderde de filtering in de zoekopdracht in:
WHERE MyUCase([c].[CityName])="BOSTON";Dit is wat we krijgen:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c"Access kan de aangepaste VBA-functie niet op afstand
MyUCase
terug naar de gegevensbron. De SQL van de opgeslagen query is echter legaal, dus Access moet er op de een of andere manier aan voldoen. Om dit te doen, downloadt het uiteindelijk de volledige set van de CityName
en de bijbehorende CityID
om door te gaan naar de VBA-functie MyUCase()
en evalueer het resultaat. Bijgevolg wordt de query nu veel langzamer uitgevoerd omdat Access nu meer gegevens opvraagt en meer werk doet.
Hoewel we UCase()
. gebruikten in dit voorbeeld kunnen we duidelijk zien dat het over het algemeen beter is om zoveel mogelijk werk naar de gegevensbron te verplaatsen. Maar wat als we een complexe VBA-functie hebben die niet kan worden herschreven in het native SQL-dialect van de gegevensbron? Hoewel ik denk dat dit scenario vrij zeldzaam is, is het het overwegen waard. Stel dat we een filter kunnen toevoegen om de reeks geretourneerde steden te verkleinen.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName LIKE "Bos*" AND MyUCase([c].[CityName])="BOSTON";De getraceerde ODBC SQL komt er als volgt uit:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" LIKE 'Bos%' )Toegang is in staat om op afstand de
LIKE
terug naar de gegevensbron, wat resulteert in het terugkrijgen van een veel kleinere gegevensset. Het zal nog steeds lokale evaluatie uitvoeren van de MyUCase()
op de resulterende dataset. De query wordt veel sneller uitgevoerd, simpelweg vanwege de kleinere gegevensset die wordt geretourneerd. Dit vertelt ons dat als we geconfronteerd worden met het ongewenste scenario waarin we een complexe VBA-functie niet gemakkelijk uit een query kunnen refactoren, we nog steeds de slechte effecten kunnen verminderen door filters toe te voegen die op afstand kunnen worden geplaatst om de initiële set records voor Access om mee te werken te verminderen.
Een opmerking over sargabiliteit
In de voorgaande voorbeelden hebben we een scalaire functie op een kolom toegepast. Dat heeft het potentieel om de zoekopdracht als "niet-sargable" weer te geven, wat betekent dat de database-engine de zoekopdracht niet kan optimaliseren met behulp van index om overeenkomsten te zoeken en te vinden. Het "sarg"-gedeelte van het woord "sargability" verwijst naar "Search ARgument". Stel dat we de index hebben gedefinieerd bij de gegevensbron op de tafel:
CREATE INDEX IX_Cities_CityName ON Application.Cities (CityName);Uitdrukkingen zoals
UCASE(CityName)
voorkomt dat de database-engine de index IX_Cities_CityName
. kan gebruiken omdat de engine wordt gedwongen elke rij één voor één te evalueren om een overeenkomst te vinden, net zoals Access deed met een aangepaste VBA-functie. Sommige database-engines, zoals recente versies van SQL Server, ondersteunen het maken van indices op basis van een expressie. Als we de zoekopdrachten wilden optimaliseren met behulp van UCASE()
transact-SQL-functie, we kunnen de indexdefinitie aanpassen: CREATE INDEX IX_Cities_Boston_Uppercase ON Application.Cities (CityName) WHERE UCASE(CityName) = 'BOSTON';Hierdoor kan SQL Server de query behandelen met
WHERE UCase(CityName) = 'BOSTON'
als een sargable query omdat het nu de index IX_Cities_Boston_Uppercase
kan gebruiken om de overeenkomende records te retourneren. Als de zoekopdracht echter overeenkwam met 'CLEVELAND'
in plaats van 'BOSTON'
, gaat de sargabiliteit verloren. Ongeacht met welke database-engine u daadwerkelijk werkt, het heeft altijd de voorkeur om waar mogelijk sargable-query's te ontwerpen en te gebruiken om prestatieproblemen te voorkomen. Cruciale zoekopdrachten moeten indexen bevatten om de beste prestaties te leveren. Ik moedig je aan om meer te bestuderen over de sargability en dekkende indices om je te helpen voorkomen dat je zoekopdrachten ontwerpt die in feite niet-sargable zijn.
Conclusies
We hebben bekeken hoe Access omgaat met het toepassen van filters van Access SQL in de ODBC-query's. We hebben ook verschillende gevallen onderzocht waarin Access verschillende typen verwijzingen naar een parameter zal converteren, waardoor Access de evaluatie buiten de ODBC-laag kan uitvoeren en deze als invoer in de voorbereide ODBC-verklaring kan doorgeven. We hebben ook gekeken naar wat er gebeurt als het niet kan worden geparametriseerd, meestal omdat het kolomverwijzingen als invoer bevat. Dat kan gevolgen hebben voor de performance tijdens een migratie naar SQL server.
Voor bepaalde functies kan Access de expressie mogelijk converteren om in plaats daarvan ODBC-scalarfuncties te gebruiken, waardoor Access de expressie op afstand naar de ODBC-gegevensbron kan sturen. Een gevolg hiervan is dat als de implementatie van de scalaire functie anders is, dit ertoe kan leiden dat de query zich anders gedraagt of sneller/langzamer wordt uitgevoerd. We hebben gezien hoe een VBA-functie, zelfs een eenvoudige die een anders verwisselbare scalaire functie omhult, de inspanningen om de expressie op afstand te houden kan verslaan. We leren ook dat als we een situatie hebben waarin we een complexe VBA-functie niet kunnen refactoren uit een Access-query/recordsource/rowsource, we op zijn minst de dure download kunnen verminderen door extra filters aan de query toe te voegen die op afstand kunnen worden verwijderd om de hoeveelheid van gegevens geretourneerd.
In het volgende artikel zullen we bekijken hoe joins worden afgehandeld door Access.
Op zoek naar hulp bij Microsoft Access? Bel onze experts vandaag nog op 773-809-5456 of e-mail ons op [email protected].