Filtrering af postsættet
I del 5 af vores serie vil vi lære, hvordan Microsoft Access håndterer implementerede filtre og integrerer dem i ODBC-forespørgsler. I den foregående artikel så vi, hvordan Access vil formulere WHERE
klausul baseret på nøglen og, hvis det er relevant, rækkeversion. Vi skal dog lære, hvordan Access vil håndtere de filtre, der leveres til Access-forespørgslerne, og oversætte dem i ODBC-laget. Der er forskellige tilgange, Access kan bruge afhængigt af, hvordan Access-forespørgslerne er formuleret, og du vil lære, hvordan du forudsiger, hvordan Access vil oversætte en Access-forespørgsel til en ODBC-forespørgsel for forskellige filterprædikater.
Uanset hvordan du rent faktisk anvender filter - det være sig interaktivt via formular- eller dataarks båndkommandoer eller højremenuklik, eller programmatisk ved at bruge VBA eller køre gemte forespørgsler - vil Access udstede en tilsvarende ODBC SQL-forespørgsel for at udføre filtreringen. Generelt vil Access forsøge at fjerne så meget filtrering som muligt. Den vil dog ikke fortælle dig, hvis den ikke kan gøre det. I stedet, hvis Access ikke kan udtrykke filteret ved hjælp af ODBC SQL-syntaks, vil det i stedet forsøge at udføre filtrering selv ved at downloade hele indholdet af tabellen og evaluere betingelsen lokalt. Det kan forklare, hvorfor du nogle gange kan støde på en forespørgsel, der kører hurtigt, men med en lille ændring, bremser ned til en gennemgang. Dette afsnit vil forhåbentlig hjælpe dig med at forstå, hvornår dette kan ske, og hvordan du håndterer det, så du kan hjælpe med at få adgang til fjernadgang så meget som muligt til datakilderne for at anvende filteret.
Til denne artikel vil vi bruge gemte forespørgsler, men de oplysninger, der diskuteres her, bør stadig gælde for andre metoder til at anvende filtre.
Statiske filtre
Vi starter let og laver en gemt forespørgsel med et hårdkodet filter.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName="Boston";Hvis vi åbner forespørgslen, vil vi se denne ODBC SQL i sporet:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )Bortset fra ændringerne i syntaksen, er forespørgslens semantik ikke ændret; det samme filter sendes som det er. Bemærk, at kun
CityID
blev valgt, fordi en forespørgsel som standard bruger et rekordsæt af dynaset-typen, som vi diskuterede allerede i det foregående afsnit. Simple parameteriserede filtre
Lad os ændre SQL til at bruge en parameter i stedet:
PARAMETERS SelectedCityName Text ( 255 ); SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[SelectedCityName];Hvis vi kører forespørgslen og indtaster "Boston" i parameterpromptværdien som vist, skulle vi se følgende ODBC-sporings-SQL:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = ? )Bemærk, at vi vil observere den samme adfærd med kontrolreferencer eller linkning af underformularer. Hvis vi brugte dette i stedet:
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];Vi ville stadig få den samme sporede ODBC SQL, som vi så med den originale parametriserede forespørgsel. Det er stadig tilfældet, selvom vores ændrede forespørgsel ikke havde en
PARAMETERE
udmelding. Dette viser, at Access er i stand til at genkende, at sådanne kontrolreferencer, hvis værdi kan ændres fra tid til anden, bedst behandles som en parameter ved formulering af ODBC SQL. Det virker også for VBA-funktionen. Vi kan tilføje en ny VBA-funktion:
Public Function GetSelectedCity() As String GetSelectedCity = "Boston" End FunctionVi justerer den gemte forespørgsel for at bruge den nye VBA-funktion:
WHERE c.CityName=GetSelectedCity();Hvis du sporer dette, vil du se, at det stadig er det samme. Vi har således demonstreret, at uanset om inputtet er en eksplicit parameter, en reference til en kontrol eller et resultat af en VBA-funktion, vil Access behandle dem alle som en parameter for ODBC SQL-forespørgslen, som den vil udføre på vores på vegne. Det er en god ting, fordi vi generelt får bedre ydeevne, når vi kan genbruge en forespørgsel og blot ændre parameteren.
Der er dog et mere almindeligt scenarie, som Access-udviklere typisk opsætter, og det er at skabe dynamisk SQL med VBA-kode, normalt ved at sammenkæde en streng og derefter udføre den sammenkædede streng. Lad os bruge følgende VBA-kode:
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 SubDen sporede ODBC SQL for
OpenRecordset
er som følger: SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )I modsætning til tidligere eksempler blev ODBC SQL ikke parametriseret. Access har ingen måde at vide, at 'Boston' var dynamisk udfyldt under kørsel af en
VBA.InputBox
. Vi gav den simpelthen den konstruerede SQL, som fra Access' POV, blot er en statisk SQL-sætning. I dette tilfælde besejrer vi parametreringen af forespørgslen. Det er vigtigt at erkende, at et populært råd givet til Access-udviklere har været, at dynamisk konstrueret SQL er bedre end at bruge parameterforespørgsler, fordi det undgår problemet, hvor Access-motoren kan generere en eksekveringsplan baseret på én parameterværdi, som faktisk kan være suboptimal for en anden. parameterværdi. For flere detaljer om det fænomen, opfordrer jeg dig til at læse op på problemet med "parameter-sniffing". Bemærk, at dette er et generelt problem for alle databasemotorer, ikke kun Access. Men i Access’ tilfælde fungerede dynamisk SQL bedre, fordi det er meget billigere bare at generere en ny eksekveringsplan. I modsætning hertil kan en RDBMS-motor have yderligere strategier til at håndtere problemet og kan være mere følsom over for at have for mange engangsudførelsesplaner, da det kan have en negativ indvirkning på cachen.
Af den grund kan parametriserede forespørgsler fra Access mod ODBC-kilder være at foretrække frem for dynamisk SQL. Fordi Access vil behandle referencekontrolelementerne på en formular eller VBA-funktioner, der ikke kræver kolonnereferencer, som parametre, behøver du ikke eksplicitte parametre i dine recordsourcer eller rækkekilder. Men hvis du bruger VBA til at udføre SQL, er det normalt bedre at bruge ADO, som også har meget bedre understøttelse af parametrering. I tilfælde af at bygge en dynamisk recordsource eller rowsource, kan brug af en skjult kontrol på formularen/rapporten være en nem måde at parametrere forespørgslen på. Men hvis forespørgslen er markant anderledes, fremtvinger opbygning af den dynamiske SQL i VBA og tildeling af den til egenskaben recordsource/rowsource effektivt en fuld rekompilering og undgår derfor at bruge dårlige eksekveringsplaner, der ikke vil fungere godt for det aktuelle sæt af input. Du kan finde anbefalinger i artiklen, der diskuterer SQL Servers WITH RECOMPILE
nyttigt at beslutte, om der skal gennemtvinges en rekompilering i forhold til at bruge en parameteriseret forespørgsel.
Brug af funktioner i SQL-filtrering
I forrige afsnit så vi, at en SQL-sætning indeholdende en VBA-funktion blev parameteriseret, så Access kunne udføre VBA-funktionen og bruge outputtet som input til den parametrerede forespørgsel. Det er dog ikke alle indbyggede funktioner, der opfører sig på denne måde. Lad os bruge UCase()
som et eksempel for at filtrere forespørgslen. Desuden vil vi anvende funktionen på en kolonne.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE UCase([c].[CityName])="BOSTON";Hvis vi ser på den sporede ODBC SQL, vil vi se dette:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ({fn ucase("CityName" )}= 'BOSTON' )I et tidligere eksempel var Access i stand til fuldstændig at parametrere
GetSelectedCity()
da det ikke krævede input fra kolonner, der refereres til i forespørgslen. Men UCase()
kræver et input. Havde vi givet UCase("Boston")
, ville Access også have parametreret dette væk. Indgangen er dog en kolonnehenvisning, som Access ikke uden videre kan parametrere væk. Access kan dog registrere, at UCase()
er en af de understøttede ODBC skalarfunktioner. Da vi foretrækker at fjerne så meget som muligt til datakilden, gør Access netop det ved at påkalde ODBC's version af ucase
.
Hvis vi så opretter en brugerdefineret VBA-funktion, der emulerer UCase()
funktion:
Public Function MyUCase(InputValue As Variant) As String MyUCase = UCase(InputValue) End Functionog ændrede filtreringen i forespørgslen til:
WHERE MyUCase([c].[CityName])="BOSTON";Dette er, hvad vi får:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c"Access er ikke i stand til at fjerne den tilpassede VBA-funktion
MyUCase
tilbage til datakilden. Den gemte forespørgsels SQL er dog lovlig, så Access skal på en eller anden måde opfylde den. For at gøre dette ender det med at downloade det fulde sæt af Bynavn
og dets tilsvarende CityID
for at gå ind i VBA-funktionen MyUCase()
og vurdere resultatet. Som følge heraf fungerer forespørgslen nu meget langsommere, fordi Access nu anmoder om flere data og udfører mere arbejde.
Selvom vi brugte UCase()
i dette eksempel kan vi tydeligt se, at det generelt er bedre at fjerne så meget arbejde som muligt til datakilden. Men hvad nu hvis vi har en kompleks VBA-funktion, der ikke kan omskrives til datakildens oprindelige SQL-dialekt? Selvom jeg tror, at dette scenarie er ret sjældent, er det værd at overveje. Lad os antage, at vi kan tilføje et filter for at indsnævre antallet af returnerede byer.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName LIKE "Bos*" AND MyUCase([c].[CityName])="BOSTON";Den sporede ODBC SQL vil komme ud således:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" LIKE 'Bos%' )Access er i stand til at fjerne
LIKE
tilbage til datakilden, hvilket resulterer i at få meget mindre datasæt tilbage. Det vil stadig udføre lokal evaluering af MyUCase()
på det resulterende datasæt. Forespørgslen kører meget hurtigere simpelthen på grund af det mindre datasæt, der returneres. Dette fortæller os, at hvis vi står over for det uønskede scenarie, hvor vi ikke nemt kan omstrukturere en kompleks VBA-funktion fra en forespørgsel, kan vi stadig afbøde de dårlige effekter ved at tilføje filtre, der kan fjernes for at reducere det indledende sæt af poster, som Access kan arbejde med.
En bemærkning om sargerbarhed
I de foregående eksempler anvendte vi en skalarfunktion på en kolonne. Det har potentialet til at gøre forespørgslen som "ikke-sargerbar", hvilket betyder, at databasemotoren ikke er i stand til at optimere forespørgslen ved at bruge indeks til at søge og finde matches. "Sarg"-delen af ordet "sargability" refererer til "Søgeargument". Antag, at vi har indekset defineret ved datakilden i tabellen:
CREATE INDEX IX_Cities_CityName ON Application.Cities (CityName);Udtryk såsom
UCASE(Bynavn)
forhindrer databasemotoren i at kunne bruge indekset IX_Cities_CityName
fordi motoren er tvunget til at evaluere hver række én efter én for at finde match, ligesom Access gjorde med en brugerdefineret VBA-funktion. Nogle databasemotorer, såsom nyere versioner af SQL Server, understøtter oprettelse af indekser baseret på et udtryk. Hvis vi ønskede at optimere forespørgslerne ved hjælp af UCASE()
transact-SQL-funktion, kunne vi justere indeksdefinitionen: CREATE INDEX IX_Cities_Boston_Uppercase ON Application.Cities (CityName) WHERE UCASE(CityName) = 'BOSTON';Dette gør det muligt for SQL Server at behandle forespørgslen med
WHERE UCase(CityName) ='BOSTON'
som en sargerbar forespørgsel, fordi den nu kan bruge indekset IX_Cities_Boston_Uppercase
for at returnere de matchende poster. Men hvis forespørgslen matchede på 'CLEVELAND'
i stedet for 'BOSTON'
, sargerbarheden er tabt. Uanset hvilken databasemotor du rent faktisk arbejder med, er det altid at foretrække at designe og bruge sargerbare forespørgsler, hvor det er muligt for at undgå ydeevneproblemer. Afgørende forespørgsler bør have dækkende indekser for at give den bedste ydeevne. Jeg opfordrer dig til at studere mere om sargerbarheden og dækkende indekser for at hjælpe dig med at undgå at designe forespørgsler, der faktisk ikke er sargerbare.
Konklusioner
Vi gennemgik, hvordan Access håndterer anvendelse af filtre fra Access SQL i ODBC-forespørgslerne. Vi undersøgte også forskellige tilfælde, hvor Access vil konvertere forskellige typer referencer til en parameter, hvilket giver Access mulighed for at udføre evalueringen uden for ODBC-laget og overføre dem som input til den forberedte ODBC-sætning. Vi har også set på, hvad der sker, når det ikke kan parametreres, typisk på grund af at indeholde kolonnereferencer som input. Det kan have konsekvenser for ydeevnen under en migrering til SQL-server.
For visse funktioner kan Access muligvis konvertere udtrykket til at bruge ODBC-skalarfunktioner i stedet, hvilket giver Access mulighed for at fjerne udtrykket til ODBC-datakilden. En konsekvens af dette er, at hvis implementeringen af den skalære funktion er anderledes, kan det få forespørgslen til at opføre sig anderledes eller kan udføre hurtigere/langsommere. Vi så, hvordan en VBA-funktion, selv en simpel en, der omslutter en ellers fjernbar skalarfunktion, kan besejre bestræbelserne på at fjerne udtrykket. Vi lærer også, at hvis vi har en situation, hvor vi ikke kan refaktorisere en kompleks VBA-funktion fra en Access-forespørgsel/recordsource/rowsource, kan vi i det mindste mindske den dyre download ved at tilføje yderligere filtre på forespørgslen, der kan fjernes for at reducere mængden af returnerede data.
I den næste artikel vil vi se på, hvordan joins håndteres af Access.
Leder du efter hjælp til Microsoft Access? Ring til vores eksperter i dag på 773-809-5456 eller e-mail os på [email protected].