sql >> Databasteknik >  >> RDS >> Access

Hur pratar Access med ODBC-datakällor? Del 5

Filtrera postuppsättningen

I del 5 av vår serie kommer vi att lära oss hur Microsoft Access hanterar implementerade filter och integrerar dem i ODBC-frågor. I föregående artikel såg vi hur Access formulerar SELECT satser i ODBC SQL-kommandon. Vi såg också i den tidigare artikeln hur Access kommer att försöka uppdatera en rad genom att använda en WHERE klausul baserad på nyckeln och, om tillämpligt, rowversion. Vi måste dock lära oss hur Access kommer att hantera filtren som tillhandahålls till Access-frågorna och översätta dem i ODBC-lagret. Det finns olika tillvägagångssätt som Access kan använda beroende på hur Access-frågorna är formulerade och du kommer att lära dig hur du förutsäger hur Access kommer att översätta en Access-fråga till en ODBC-fråga för olika filterpredikat som ges.

Oavsett hur du faktiskt tillämpar filtret – vare sig det är interaktivt via formulär- eller datablads kommandon eller högermenyklick, eller programmatiskt med VBA eller kör sparade frågor – kommer Access att utfärda en motsvarande ODBC SQL-fråga för att utföra filtreringen. I allmänhet kommer Access att försöka fjärrstyra så mycket filtrering som möjligt. Den kommer dock inte att berätta om den inte kan göra det. Istället, om Access inte kan uttrycka filtret med ODBC SQL-syntax, kommer det istället att försöka utföra filtrering själv genom att ladda ner hela innehållet i tabellen och utvärdera villkoret lokalt. Det kan förklara varför du ibland kan stöta på en fråga som körs snabbt men med en liten ändring, saktar ner till en genomsökning. Det här avsnittet kommer förhoppningsvis att hjälpa dig att förstå när detta kan hända och hur du ska hantera det så att du kan hjälpa Access remote så mycket som möjligt till datakällorna för att tillämpa filtret.

För den här artikeln kommer vi att använda sparade frågor, men informationen som diskuteras här bör fortfarande gälla andra metoder för att tillämpa filter.

Statiska filter

Vi börjar enkelt och skapar en sparad fråga med ett hårdkodat filter.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName="Boston";
Om vi ​​öppnar frågan kommer vi att se denna ODBC SQL i spåret:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Bortsett från ändringarna i syntaxen har frågans semantik inte ändrats; samma filter skickas som det är. Observera att endast CityID valdes eftersom en fråga som standard använder en postuppsättning av dynaset-typ som vi diskuterade redan i föregående avsnitt.

Enkla parameteriserade filter

Låt oss ändra SQL för att använda en parameter istället:

PARAMETERS SelectedCityName Text ( 255 );
SELECT 
  c.CityID
 ,c.CityName
 ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[SelectedCityName];
Om vi ​​kör frågan och matar in "Boston" i parameterpromptvärdet som visas, bör vi se följande ODBC-spår SQL:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" =  ? ) 
Observera att vi kommer att observera samma beteende med kontrollreferenser eller länkning av underformulär. Om vi ​​använde detta istället:

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];
Vi skulle fortfarande få samma spårade ODBC SQL som vi såg med den ursprungliga parameteriserade frågan. Det är fortfarande fallet även om vår modifierade fråga inte hade en PARAMETERS påstående. Detta visar att Access kan känna igen att sådana kontrollreferenser som kan ändras från tid till annan bäst behandlas som en parameter när ODBC SQL formuleras.

Det fungerar också för VBA-funktionen. Vi kan lägga till en ny VBA-funktion:

Public Function GetSelectedCity() As String
    GetSelectedCity = "Boston"
End Function
Vi justerar den sparade frågan för att använda den nya VBA-funktionen:

WHERE c.CityName=GetSelectedCity();
Om du spårar detta kommer du att se att det fortfarande är detsamma. Således har vi visat att oavsett om indata är en explicit parameter, en referens till en kontroll eller ett resultat av en VBA-funktion, kommer Access att behandla dem alla som en parameter för ODBC SQL-frågan som den kommer att köra på vår på uppdrag. Det är bra eftersom vi får bättre prestanda generellt när vi kan återanvända en fråga och helt enkelt ändra parametern.

Det finns dock ett mer vanligt scenario som Access-utvecklare vanligtvis ställer in och det är att skapa dynamisk SQL med VBA-kod, vanligtvis genom att sammanfoga en sträng och sedan exekvera den sammanfogade strängen. Låt oss använda följande VBA-kod:

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
Den spårade ODBC SQL för OpenRecordset är som följer:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Till skillnad från tidigare exempel parametriserades inte ODBC SQL. Access har inget sätt att veta att "Boston" var dynamiskt fylld under körning av en VBA.InputBox . Vi gav den helt enkelt den konstruerade SQL som från Access POV, bara är en statisk SQL-sats. I det här fallet besegrar vi parametreringen av frågan. Det är viktigt att inse att ett populärt råd som getts till Access-utvecklare har varit att dynamiskt konstruerad SQL är bättre än att använda parameterfrågor eftersom det undviker problemet där Access-motorn kan generera en exekveringsplan baserat på ett parametervärde som faktiskt kan vara suboptimalt för en annan parametervärde. För mer information om det fenomenet rekommenderar jag att du läser på problemet med "parameter-sniffing". Observera att detta är ett allmänt problem för alla databasmotorer, inte bara Access. Men i Accesss fall fungerade dynamisk SQL bättre eftersom det är mycket billigare att bara skapa en ny exekveringsplan. Däremot kan en RDBMS-motor ha ytterligare strategier för att hantera problemet och kan vara mer känslig för att ha för många engångsplaner för exekvering eftersom det kan påverka dess cachning negativt.

Av den anledningen kan parametriserade frågor från Access mot ODBC-källor vara att föredra framför dynamisk SQL. Eftersom Access kommer att behandla referenskontrollerna på ett formulär eller VBA-funktioner som inte kräver kolumnreferenser som parametrar, behöver du inte explicita parametrar i dina postkällor eller radkällor. Men om du använder VBA för att köra SQL är det vanligtvis bättre att använda ADO som också har mycket bättre stöd för parametrering. När det gäller att bygga en dynamisk postkälla eller radkälla, kan användning av en dold kontroll på formuläret/rapporten vara ett enkelt sätt att parametrisera frågan. Men om frågan skiljer sig markant, framtvingar byggandet av dynamisk SQL i VBA och tilldela den till egenskapen recordsource/rowsource effektivt en fullständig omkompilering och undviker därför att använda dåliga exekveringsplaner som inte kommer att fungera bra för den aktuella uppsättningen indata. Du kan hitta rekommendationer i artikeln som diskuterar SQL Servers WITH RECOMPILE till hjälp för att bestämma om man ska tvinga fram en omkompilering jämfört med att använda en parametriserad fråga.

Använda funktioner i SQL-filtrering

I föregående avsnitt såg vi att en SQL-sats som innehåller en VBA-funktion parametrerades så att Access kunde köra VBA-funktionen och använda utdata som indata till den parametrerade frågan. Det är dock inte alla inbyggda funktioner som beter sig så. Låt oss använda UCase() som ett exempel för att filtrera frågan. Dessutom kommer vi att tillämpa funktionen på en kolumn.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE UCase([c].[CityName])="BOSTON";
Om vi ​​tittar på spårad ODBC SQL kommer vi att se detta:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ({fn ucase("CityName" )}= 'BOSTON' )
I tidigare exempel kunde Access helt parametrisera bort GetSelectedCity() eftersom det inte krävde några indata från kolumner som refereras till i frågan. Men UCase() kräver en ingång. Hade vi tillhandahållit UCase("Boston") , Access skulle också ha parametriserat bort detta. Ingången är dock en kolumnreferens, som Access inte enkelt kan parametrera bort. Access kan dock upptäcka att UCase() är en av de skalära ODBC-funktionerna som stöds. Eftersom vi föredrar att avlägsna så mycket som möjligt till datakällan, gör Access just det genom att anropa ODBC:s version av ucase .

Om vi ​​sedan skapar en anpassad VBA-funktion som emulerar UCase() funktion:

Public Function MyUCase(InputValue As Variant) As String
    MyUCase = UCase(InputValue)
End Function
och ändrade filtreringen i frågan till:

WHERE MyUCase([c].[CityName])="BOSTON";
Det här är vad vi får:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
Access kan inte fjärrstyra den anpassade VBA-funktionen MyUCase tillbaka till datakällan. Den sparade frågans SQL är dock laglig så Access måste tillfredsställa den på något sätt. För att göra detta slutar den med att ladda ner hela uppsättningen CityName och dess motsvarande CityID för att gå in i VBA-funktionen MyUCase() och utvärdera resultatet. Följaktligen fungerar frågan nu mycket långsammare eftersom Access nu begär mer data och gör mer arbete.

Även om vi använde UCase() i det här exemplet kan vi tydligt se att det generellt sett är bättre att fjärrstyra så mycket arbete som möjligt till datakällan. Men vad händer om vi har en komplex VBA-funktion som inte kan skrivas om till datakällans ursprungliga SQL-dialekt? Även om jag tror att detta scenario är ganska sällsynt, är det värt att överväga. Låt oss anta att vi kan lägga till ett filter för att begränsa antalet returnerade städer.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName LIKE "Bos*"
  AND MyUCase([c].[CityName])="BOSTON";
Den spårade ODBC SQL kommer ut så här:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" LIKE 'Bos%' ) 
Access kan fjärrstyra LIKE tillbaka till datakällan, vilket resulterar i att få tillbaka mycket mindre datauppsättning. Den kommer fortfarande att utföra lokal utvärdering av MyUCase() på den resulterande datamängden. Frågan går mycket snabbare helt enkelt på grund av den mindre datamängden som returneras.

Detta säger oss att om vi står inför det oönskade scenariot där vi inte enkelt kan återställa en komplex VBA-funktion från en fråga, kan vi fortfarande mildra de dåliga effekterna genom att lägga till filter som kan distanseras för att minska den initiala uppsättningen poster för Access att arbeta med.

En anmärkning om sargbarhet

I de föregående exemplen tillämpade vi en skalär funktion på en kolumn. Det har potential att göra frågan "icke-sargable" vilket innebär att databasmotorn inte kan optimera frågan med hjälp av index för att söka och hitta matchningar. "Sarg"-delen av ordet "sargability" hänvisar till "Sökargument". Anta att vi har indexet definierat vid datakällan i tabellen:

CREATE INDEX IX_Cities_CityName
ON Application.Cities (CityName);
Uttryck som UCASE(CityName) förhindrar databasmotorn från att kunna använda indexet IX_Cities_CityName eftersom motorn tvingas utvärdera varje rad en efter en för att hitta matchning, precis som Access gjorde med en anpassad VBA-funktion. Vissa databasmotorer, som de senaste versionerna av SQL Server, stöder att skapa index baserat på ett uttryck. Om vi ​​ville optimera frågorna med UCASE() transact-SQL-funktionen kan vi justera indexdefinitionen:

CREATE INDEX IX_Cities_Boston_Uppercase
ON Application.Cities (CityName)
WHERE UCASE(CityName) = 'BOSTON';
Detta gör det möjligt för SQL Server att behandla frågan med WHERE UCase(CityName) ='BOSTON' som en sargbar fråga eftersom den nu kan använda indexet IX_Cities_Boston_Uppercase för att returnera de matchande posterna. Men om frågan matchade på 'CLEVELAND' istället för 'BOSTON' , är sargbarheten förlorad.

Oavsett vilken databasmotor du faktiskt arbetar med, är det alltid att föredra att designa och använda sargbara frågor där det är möjligt för att undvika prestandaproblem. Viktiga frågor bör ha täckande index för att ge bästa resultat. Jag uppmuntrar dig att studera mer om sargbarheten och täckande index för att hjälpa dig undvika att designa frågor som faktiskt inte är sargerbara.

Slutsatser

Vi har granskat hur Access hanterar att tillämpa filter från Access SQL i ODBC-frågorna. Vi undersökte också olika fall där Access kommer att konvertera olika typer av referenser till en parameter, vilket gör att Access kan utföra utvärderingen utanför ODBC-lagret och skicka dem som indata till den förberedda ODBC-satsen. Vi tittade också på vad som händer när det inte kan parametriseras, vanligtvis på grund av att kolumnreferenserna innehåller som indata. Det kan få konsekvenser för prestandan under en migrering till SQL-server.

För vissa funktioner kan Access kanske konvertera uttrycket till att använda ODBC-skalära funktioner istället, vilket gör att Access kan fjärrstyra uttrycket till ODBC-datakällan. En följd av detta är att om implementeringen av den skalära funktionen är annorlunda, kan det göra att frågan beter sig annorlunda eller kan fungera snabbare/långsammare. Vi såg hur en VBA-funktion, till och med en enkel som omsluter en annars avlägsen skalär funktion kan besegra ansträngningarna att avlägsna uttrycket. Vi lär oss också att om vi har en situation där vi inte kan omfaktorisera en komplex VBA-funktion från en Access-fråga/recordsource/rowsource, kan vi åtminstone mildra den dyra nedladdningen genom att lägga till ytterligare filter på frågan som kan distanseras för att minska mängden av data som returneras.

I nästa artikel kommer vi att titta på hur anslutningar hanteras av Access.

Letar du efter hjälp med Microsoft Access? Ring våra experter idag på 773-809-5456 eller maila oss på [email protected].


  1. Hur man gör en UPDATE Pass-Through Query i SQL Server

  2. Kolumnen finns inte?

  3. Dynamiskt svänga ett bord Oracle

  4. DBCC_OBJECT_METADATA-spärren