Access
 sql >> Database >  >> RDS >> Access

In che modo Access comunica con le origini dati ODBC? Parte 5

Filtraggio del recordset

Nella parte 5 della nostra serie, impareremo come Microsoft Access gestisce i filtri implementati e li integra nelle query ODBC. Nell'articolo precedente, abbiamo visto come Access formulerà il SELECT istruzioni nei comandi SQL ODBC. Abbiamo anche visto nell'articolo precedente come Access tenterà di aggiornare una riga applicando un WHERE clausola basata sulla chiave e, se applicabile, rowversion. Tuttavia, dobbiamo imparare come Access gestirà i filtri forniti alle query di Access e li tradurrà nel livello ODBC. Esistono diversi approcci che Access può utilizzare a seconda di come vengono formulate le query di Access e imparerai come prevedere in che modo Access tradurrà una query di Access in una query ODBC per diversi predicati di filtro forniti.

Indipendentemente da come si applica effettivamente il filtro, sia in modo interattivo tramite i comandi del modulo o del foglio dati o i clic del menu a destra, sia a livello di codice utilizzando VBA o eseguendo query salvate, Access emetterà una query SQL ODBC corrispondente per eseguire il filtraggio. In generale, Access tenterà di filtrare il più possibile in remoto. Tuttavia, non ti dirà se non può farlo. Se invece Access non è in grado di esprimere il filtro utilizzando la sintassi SQL ODBC, tenterà invece di eseguire il filtraggio scaricando l'intero contenuto della tabella e valutando la condizione in locale. Questo può spiegare perché a volte potresti riscontrare una query che viene eseguita rapidamente ma con una piccola modifica, rallenta fino a una scansione. Si spera che questa sezione ti aiuti a capire quando ciò può accadere e come gestirlo in modo che tu possa aiutare ad accedere il più possibile in remoto alle origini dati per l'applicazione del filtro.

Per questo articolo utilizzeremo le query salvate, ma le informazioni discusse qui dovrebbero essere comunque valide per altri metodi di applicazione dei filtri.

Filtri statici

Inizieremo facilmente e creeremo una query salvata con un filtro hardcoded.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName="Boston";
Se apriamo la query, vedremo questo SQL ODBC nella traccia:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
A parte le modifiche alla sintassi, la semantica della query non è cambiata; lo stesso filtro viene passato così com'è. Nota che solo il CityID è stato selezionato perché per impostazione predefinita una query utilizza un recordset di tipo dynaset di cui abbiamo già parlato nella sezione precedente.

Filtri parametrizzati semplici

Cambiamo l'SQL per usare invece un parametro:

PARAMETERS SelectedCityName Text ( 255 );
SELECT 
  c.CityID
 ,c.CityName
 ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[SelectedCityName];
Se eseguiamo la query e inseriamo "Boston" nel valore del prompt del parametro come mostrato, dovremmo vedere il seguente SQL di traccia ODBC:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" =  ? ) 
Si noti che osserveremo lo stesso comportamento con i riferimenti di controllo o il collegamento di sottomoduli. Se invece usassimo questo:

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];
Otterremmo comunque lo stesso SQL ODBC tracciato che abbiamo visto con la query parametrizzata originale. Questo è ancora il caso anche se la nostra query modificata non aveva un PARAMETERS dichiarazione. Ciò mostra che Access è in grado di riconoscere che tali riferimenti di controllo, il cui valore può essere modificato di volta in volta, sono trattati al meglio come parametro durante la formulazione dell'SQL ODBC.

Funziona anche per la funzione VBA. Possiamo aggiungere una nuova funzione VBA:

Public Function GetSelectedCity() As String
    GetSelectedCity = "Boston"
End Function
Regoliamo la query salvata per utilizzare la nuova funzione VBA:

WHERE c.CityName=GetSelectedCity();
Se tracci questo, vedrai che è sempre lo stesso. Pertanto, abbiamo dimostrato che indipendentemente dal fatto che l'input sia un parametro esplicito, un riferimento a un controllo o il risultato di una funzione VBA, Access li tratterà tutti come un parametro della query SQL ODBC che eseguirà sul nostro per conto. Questa è una buona cosa perché otteniamo prestazioni migliori in generale quando possiamo riutilizzare una query e modificare semplicemente il parametro.

Tuttavia, esiste un altro scenario comune che gli sviluppatori di Access in genere impostano e che consiste nella creazione di SQL dinamico con codice VBA, in genere concatenando una stringa e quindi eseguendo la stringa concatenata. Usiamo il seguente codice VBA:

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
L'SQL ODBC tracciato per OpenRecordset è il seguente:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
A differenza degli esempi precedenti, l'SQL ODBC non è stato parametrizzato. Access non ha modo di sapere che "Boston" è stato popolato dinamicamente in fase di esecuzione da un VBA.InputBox . Gli abbiamo semplicemente consegnato l'SQL costruito che dal POV di Access è solo un'istruzione SQL statica. In questo caso, sconfiggiamo la parametrizzazione della query. È importante riconoscere che un consiglio popolare dato agli sviluppatori di Access è stato che SQL costruito dinamicamente è meglio dell'utilizzo di query di parametri perché evita il problema in cui il motore di Access può generare un piano di esecuzione basato su un valore di parametro che potrebbe essere effettivamente non ottimale per un altro valore del parametro. Per maggiori dettagli su quel fenomeno, ti incoraggio a leggere il problema dello "sniffing dei parametri". Si noti che questo è un problema generale per qualsiasi motore di database, non solo per Access. Tuttavia, nel caso di Access, l'SQL dinamico ha funzionato meglio perché è molto più economico generare un nuovo piano di esecuzione. Al contrario, un motore RDBMS potrebbe avere strategie aggiuntive per gestire il problema e potrebbe essere più sensibile all'avere troppi piani di esecuzione una tantum in quanto ciò può influire negativamente sulla sua memorizzazione nella cache.

Per questo motivo, le query parametrizzate da Access su origini ODBC possono essere preferibili rispetto all'SQL dinamico. Poiché Access tratterà i controlli dei riferimenti in una maschera o in funzioni VBA che non richiedono riferimenti alle colonne come parametri, non sono necessari parametri espliciti nelle origini record o nelle origini riga. Tuttavia, se stai usando VBA per eseguire SQL, di solito è meglio usare ADO che ha anche un supporto molto migliore per la parametrizzazione. Nel caso della creazione di un recordsource dinamico o di un rowsource, l'uso di un controllo nascosto nel form/report può essere un modo semplice per parametrizzare la query. Tuttavia, se la query è notevolmente diversa, la creazione dell'SQL dinamico in VBA e l'assegnazione alla proprietà recordsource/rowsource forza efficacemente una ricompilazione completa e quindi evita di utilizzare piani di esecuzione non validi che non funzioneranno bene per l'attuale set di input. È possibile trovare consigli nell'articolo relativo a WITH RECOMPILE di SQL Server utile per decidere se forzare una ricompilazione rispetto all'utilizzo di una query con parametri.

Utilizzo delle funzioni nel filtraggio SQL

Nella sezione precedente, abbiamo visto che un'istruzione SQL contenente una funzione VBA è stata parametrizzata in modo che Access potesse eseguire la funzione VBA e utilizzare l'output come input per la query parametrizzata. Tuttavia, non tutte le funzioni integrate si comportano in questo modo. Usiamo UCase() come esempio per filtrare la query. Inoltre, applicheremo la funzione su una colonna.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE UCase([c].[CityName])="BOSTON";
Se osserviamo l'SQL ODBC tracciato, vedremo questo:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ({fn ucase("CityName" )}= 'BOSTON' )
Nell'esempio precedente, Access è stato in grado di parametrizzare completamente il GetSelectedCity() poiché non richiedeva input dalle colonne a cui si fa riferimento nella query. Tuttavia, il UCase() richiede un input. Se avessimo fornito UCase("Boston") , Access avrebbe parametrizzato anche questo. Tuttavia, l'input è un riferimento di colonna, che Access non può parametrizzare facilmente. Tuttavia, Access può rilevare che UCase() è una delle funzioni scalari ODBC supportate. Dal momento che preferiamo il remoting il più possibile all'origine dati, Access fa proprio questo richiamando la versione ODBC di ucase .

Se poi creiamo una funzione VBA personalizzata che emula UCase() funzione:

Public Function MyUCase(InputValue As Variant) As String
    MyUCase = UCase(InputValue)
End Function
e modificato il filtro nella query in:

WHERE MyUCase([c].[CityName])="BOSTON";
Questo è ciò che otteniamo:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
L'accesso non è in grado di eseguire in remoto la funzione VBA personalizzata MyUCase torna all'origine dati. Tuttavia, l'SQL della query salvata è legale, quindi Access deve soddisfarlo in qualche modo. Per fare ciò, finisce per scaricare il set completo di CityName e il corrispondente CityID per passare alla funzione VBA MyUCase() e valutare il risultato. Di conseguenza, la query ora viene eseguita molto più lentamente perché Access ora richiede più dati e svolge più lavoro.

Anche se abbiamo usato UCase() in questo esempio, possiamo vedere chiaramente che generalmente è meglio trasferire in remoto quanto più lavoro possibile all'origine dati. Ma cosa succede se abbiamo una funzione VBA complessa che non può essere riscritta nel dialetto SQL nativo dell'origine dati? Anche se penso che questo scenario sia piuttosto raro, vale la pena considerare. Supponiamo di poter aggiungere un filtro per restringere l'insieme di città restituite.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName LIKE "Bos*"
  AND MyUCase([c].[CityName])="BOSTON";
L'SQL ODBC tracciato verrà visualizzato in questo modo:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" LIKE 'Bos%' ) 
L'accesso è in grado di remotare il LIKE all'origine dati, il che si traduce nel recupero di un set di dati molto più piccolo. Eseguirà comunque la valutazione locale di MyUCase() sul dataset risultante. La query viene eseguita molto più velocemente semplicemente a causa del set di dati più piccolo restituito.

Questo ci dice che se affrontiamo lo scenario indesiderabile in cui non possiamo facilmente refactoring di una complessa funzione VBA da una query, possiamo comunque mitigare gli effetti negativi aggiungendo filtri che possono essere remoti per ridurre il set iniziale di record con cui Access può lavorare.

Una nota sulla sargability

Negli esempi precedenti, abbiamo applicato una funzione scalare su una colonna. Ciò ha il potenziale per rendere la query "non sargable", il che significa che il motore di database non è in grado di ottimizzare la query utilizzando l'indice per cercare e trovare corrispondenze. La parte "sarg" della parola "sargability" si riferisce a "Search ARGument". Supponiamo di avere l'indice definito nell'origine dati sulla tabella:

CREATE INDEX IX_Cities_CityName
ON Application.Cities (CityName);
Espressioni come UCASE(CityName) impedisce al motore di database di utilizzare l'indice IX_Cities_CityName perché il motore è costretto a valutare ogni riga una per una per trovare una corrispondenza, proprio come ha fatto Access con una funzione VBA personalizzata. Alcuni motori di database, come le versioni recenti di SQL Server, supportano la creazione di indici basati su un'espressione. Se volessimo ottimizzare le query usando UCASE() funzione transact-SQL, potremmo modificare la definizione dell'indice:

CREATE INDEX IX_Cities_Boston_Uppercase
ON Application.Cities (CityName)
WHERE UCASE(CityName) = 'BOSTON';
Ciò consente a SQL Server di trattare la query con WHERE UCase(CityName) = 'BOSTON' come query sargable perché ora può utilizzare l'indice IX_Cities_Boston_Uppercase per restituire i record corrispondenti. Tuttavia, se la query corrisponde a 'CLEVELAND' invece di 'BOSTON' , la sargabilità è persa.

Indipendentemente dal motore di database con cui stai effettivamente lavorando, è sempre preferibile progettare e utilizzare query sargable ove possibile per evitare problemi di prestazioni. Le query cruciali dovrebbero coprire gli indici per fornire le migliori prestazioni. Ti incoraggio a studiare di più sulla sargability e sugli indici di copertura per aiutarti a evitare di progettare query che in realtà non sono sargable.

Conclusioni

Abbiamo esaminato il modo in cui Access gestisce l'applicazione dei filtri da Access SQL alle query ODBC. Abbiamo anche esplorato diversi casi in cui Access convertirà diversi tipi di riferimenti in un parametro, consentendo ad Access di eseguire la valutazione al di fuori del livello ODBC e passandoli come input nell'istruzione ODBC preparata. Abbiamo anche esaminato cosa succede quando non può essere parametrizzato, in genere a causa del contenuto di riferimenti di colonna come input. Ciò può avere conseguenze sulle prestazioni durante una migrazione al server SQL.

Per determinate funzioni, Access potrebbe essere in grado di convertire l'espressione per utilizzare invece le funzioni scalari ODBC, che consentono ad Access di eseguire in remoto l'espressione nell'origine dati ODBC. Una ramificazione di ciò è che se l'implementazione della funzione scalare è diversa, la query potrebbe comportarsi in modo diverso o potrebbe funzionare più velocemente/lentamente. Abbiamo visto come una funzione VBA, anche una semplice che avvolge una funzione scalare altrimenti remotabile, può vanificare gli sforzi per remotare l'espressione. Impariamo anche che se abbiamo una situazione in cui non possiamo eseguire il refactoring di una complessa funzione VBA da una query/recordsource/rowsource di Access, possiamo almeno mitigare il costoso download aggiungendo filtri aggiuntivi sulla query che possono essere remoti per ridurre l'importo dei dati restituiti.

Nel prossimo articolo vedremo come vengono gestiti i join da Access.

Cerchi aiuto con Microsoft Access? Chiama i nostri esperti oggi al 773-809-5456 o inviaci un'e-mail a [email protected].