Filtrowanie zestawu rekordów
W części 5 naszej serii dowiemy się, jak Microsoft Access obsługuje zaimplementowane filtry i integruje je z zapytaniami ODBC. W poprzednim artykule widzieliśmy, jak program Access formułuje SELECT
instrukcji w poleceniach ODBC SQL. W poprzednim artykule widzieliśmy również, jak program Access próbuje zaktualizować wiersz, stosując znacznik WHERE
klauzula oparta na kluczu i, jeśli ma to zastosowanie, rowversion. Musimy jednak dowiedzieć się, jak Access będzie obsługiwał filtry dostarczane do zapytań Access i przetłumaczy je na warstwę ODBC. Istnieją różne podejścia, które program Access może stosować w zależności od tego, jak sformułowane są zapytania programu Access, i dowiesz się, jak przewidzieć, w jaki sposób program Access przetłumaczy zapytanie programu Access na zapytanie ODBC dla różnych podanych predykatów filtra.
Niezależnie od tego, w jaki sposób faktycznie zastosujesz filtr — czy to interaktywnie za pomocą poleceń na wstążce formularza lub arkusza danych lub kliknięć w prawym menu, czy też programowo przy użyciu języka VBA lub uruchamiając zapisane zapytania — program Access wyśle odpowiednie zapytanie SQL ODBC w celu przeprowadzenia filtrowania. Ogólnie rzecz biorąc, program Access będzie próbował maksymalnie przeprowadzić zdalne filtrowanie. Jednak nie powie ci, jeśli nie może tego zrobić. Zamiast tego, jeśli program Access nie może wyrazić filtru przy użyciu składni ODBC SQL, zamiast tego spróbuje przeprowadzić samo filtrowanie, pobierając całą zawartość tabeli i lokalnie oceniając warunek. To może wyjaśniać, dlaczego czasami możesz napotkać zapytanie, które działa szybko, ale z jedną niewielką zmianą, spowalnia do indeksowania. Mamy nadzieję, że ta sekcja pomoże ci zrozumieć, kiedy może się to zdarzyć i jak sobie z tym poradzić, aby w jak największym stopniu pomóc w zdalnym dostępie do źródeł danych w celu zastosowania filtra.
W tym artykule użyjemy zapisanych zapytań, ale omówione tutaj informacje powinny nadal dotyczyć innych metod stosowania filtrów.
Filtry statyczne
Zaczniemy łatwo i utworzymy zapisane zapytanie z zakodowanym na stałe filtrem.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName="Boston";Jeśli otworzymy zapytanie, w śladzie zobaczymy ten kod SQL ODBC:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )Oprócz zmian w składni, semantyka zapytania nie uległa zmianie; ten sam filtr jest przekazywany w stanie, w jakim jest. Zwróć uwagę, że tylko
CityID
została wybrana, ponieważ domyślnie zapytanie używa zestawu rekordów typu dynaset, o którym mówiliśmy już w poprzedniej sekcji. Proste filtry parametryczne
Zmieńmy SQL, aby zamiast tego używał parametru:
PARAMETERS SelectedCityName Text ( 255 ); SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[SelectedCityName];Jeśli uruchomimy zapytanie i wprowadzimy „Boston” do wartości monitu o parametr, jak pokazano, powinniśmy zobaczyć następujący kod SQL śledzenia ODBC:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = ? )Zauważ, że będziemy zaobserwować to samo zachowanie w przypadku odwołań do kontrolek lub łączenia podformularzy. Jeśli zamiast tego użyjemy tego:
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];Nadal otrzymalibyśmy ten sam prześledzony kod SQL ODBC, który widzieliśmy w oryginalnym zapytaniu parametrycznym. Nadal tak jest, mimo że nasze zmodyfikowane zapytanie nie miało
PARAMETERS
oświadczenie. To pokazuje, że Access jest w stanie rozpoznać, że takie odwołania kontrolne, których wartość może się zmieniać od czasu do czasu, najlepiej traktować jako parametr podczas formułowania SQL ODBC. Działa to również w przypadku funkcji VBA. Możemy dodać nową funkcję VBA:
Public Function GetSelectedCity() As String GetSelectedCity = "Boston" End FunctionDostosowujemy zapisane zapytanie, aby korzystało z nowej funkcji VBA:
WHERE c.CityName=GetSelectedCity();Jeśli to prześledzisz, zobaczysz, że nadal jest tak samo. W ten sposób wykazaliśmy, że niezależnie od tego, czy dane wejściowe są jawnym parametrem, odwołaniem do kontrolki, czy wynikiem funkcji VBA, program Access potraktuje je wszystkie jako parametr zapytania ODBC SQL, które wykona na naszym w imieniu. To dobrze, ponieważ generalnie uzyskujemy lepszą wydajność, gdy możemy ponownie użyć zapytania i po prostu zmienić parametr.
Istnieje jednak jeszcze jeden typowy scenariusz, który programiści programu Access zwykle konfigurują i polega na tworzeniu dynamicznego kodu SQL z kodem VBA, zwykle przez połączenie ciągu, a następnie wykonanie połączonego ciągu. Użyjmy następującego kodu 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Śledzony SQL ODBC dla
OpenRecordset
wygląda następująco: SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )W przeciwieństwie do poprzednich przykładów, SQL ODBC nie był sparametryzowany. Access nie ma możliwości dowiedzenia się, że „Boston” został dynamicznie zapełniony w czasie wykonywania przez
VBA.InputBox
. Po prostu przekazaliśmy mu skonstruowany SQL, który z POV Accessa jest po prostu statyczną instrukcją SQL. W takim przypadku pokonujemy parametryzację zapytania. Należy pamiętać, że popularną radą udzielaną programistom Access jest to, że dynamicznie konstruowany SQL jest lepszy niż używanie zapytań parametrycznych, ponieważ pozwala uniknąć problemu polegającego na tym, że silnik Access może generować plan wykonania na podstawie jednej wartości parametru, która może być w rzeczywistości nieoptymalna dla innej wartość parametru. Po więcej szczegółów na temat tego zjawiska zachęcam do lektury problemu „podsłuchiwania parametrów”. Zwróć uwagę, że jest to ogólny problem dotyczący wszystkich aparatów baz danych, nie tylko programu Access. Jednak w przypadku Accessa dynamiczny SQL działał lepiej, ponieważ znacznie taniej jest samo wygenerowanie nowego planu wykonania. W przeciwieństwie do tego, silnik RDBMS może mieć dodatkowe strategie radzenia sobie z problemem i może być bardziej wrażliwy na posiadanie zbyt wielu jednorazowych planów wykonania, ponieważ może to negatywnie wpłynąć na jego buforowanie.
Z tego powodu zapytania parametryczne z programu Access do źródeł ODBC mogą być preferowane w porównaniu z dynamicznym SQL. Ponieważ program Access będzie traktował kontrolki odwołań w formularzu lub funkcjach VBA, które nie wymagają odwołań do kolumn jako parametry, nie potrzebujesz jawnych parametrów w źródłach rekordów ani źródłach wierszy. Jeśli jednak używasz VBA do wykonywania SQL, zwykle lepiej jest użyć ADO, które ma również znacznie lepszą obsługę parametryzacji. W przypadku budowania dynamicznego źródła rekordów lub źródła wierszy użycie ukrytej kontrolki w formularzu/raporcie może być łatwym sposobem na parametryzację zapytania. Jeśli jednak zapytanie jest znacznie inne, budowanie dynamicznego kodu SQL w języku VBA i przypisanie go do właściwości źródła rekordów/rowsource skutecznie wymusza pełną ponowną kompilację, a tym samym unika używania złych planów wykonania, które nie będą działać dobrze dla bieżącego zestawu danych wejściowych. Zalecenia można znaleźć w artykule omawiającym WITH RECOMPILE
programu SQL Server pomocne przy podejmowaniu decyzji, czy wymusić ponowną kompilację, czy użyć sparametryzowanego zapytania.
Korzystanie z funkcji w filtrowaniu SQL
W poprzedniej sekcji widzieliśmy, że instrukcja SQL zawierająca funkcję VBA została sparametryzowana, aby program Access mógł wykonać funkcję VBA i wykorzystać dane wyjściowe jako dane wejściowe do sparametryzowanego zapytania. Jednak nie wszystkie wbudowane funkcje zachowują się w ten sposób. Użyjmy UCase()
jako przykład do filtrowania zapytania. Ponadto zastosujemy tę funkcję do kolumny.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE UCase([c].[CityName])="BOSTON";Jeśli spojrzymy na śledzony SQL ODBC, zobaczymy to:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ({fn ucase("CityName" )}= 'BOSTON' )W poprzednim przykładzie program Access był w stanie całkowicie sparametryzować
GetSelectedCity()
ponieważ nie wymagało to żadnych danych wejściowych z kolumn, do których odwołuje się zapytanie. Jednak UCase()
wymaga danych wejściowych. Czy udostępniliśmy UCase("Boston")
, Access również sparametryzowałby to. Jednak dane wejściowe to odwołanie do kolumny, którego program Access nie może łatwo sparametryzować. Program Access może jednak wykryć, że UCase()
jest jedną z obsługiwanych funkcji skalarnych ODBC. Ponieważ wolimy jak najwięcej zdalnych połączeń od źródła danych, Access robi to właśnie, wywołując wersję ucase
ODBC .
Jeśli następnie utworzymy niestandardową funkcję VBA, która emuluje UCase()
funkcja:
Public Function MyUCase(InputValue As Variant) As String MyUCase = UCase(InputValue) End Functioni zmieniłem filtrowanie w zapytaniu na:
WHERE MyUCase([c].[CityName])="BOSTON";Oto, co otrzymujemy:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c"Access nie może zdalnie użyć niestandardowej funkcji VBA
MyUCase
z powrotem do źródła danych. Jednak SQL zapisanego zapytania jest legalny, więc Access musi go jakoś spełnić. Aby to zrobić, kończy się pobraniem pełnego zestawu CityName
i odpowiadający mu CityID
w celu przejścia do funkcji VBA MyUCase()
i oceń wynik. W związku z tym zapytanie działa teraz znacznie wolniej, ponieważ program Access żąda teraz większej ilości danych i wykonuje więcej pracy.
Chociaż użyliśmy UCase()
w tym przykładzie wyraźnie widzimy, że generalnie lepiej jest wykonywać jak najwięcej pracy zdalnej do źródła danych. Ale co, jeśli mamy złożoną funkcję VBA, której nie można przepisać w natywnym dialekcie SQL źródła danych? Chociaż uważam, że ten scenariusz jest dość rzadki, warto się nad nim zastanowić. Załóżmy, że możemy dodać filtr, aby zawęzić zbiór zwracanych miast.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName LIKE "Bos*" AND MyUCase([c].[CityName])="BOSTON";Prześledzony ODBC SQL będzie wyglądał tak:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" LIKE 'Bos%' )Access może zdalnie
LIKE
z powrotem do źródła danych, co skutkuje odzyskaniem znacznie mniejszego zestawu danych. Nadal będzie przeprowadzać lokalną ocenę MyUCase()
na wynikowym zbiorze danych. Zapytanie działa znacznie szybciej po prostu z powodu zwróconego mniejszego zestawu danych. To mówi nam, że jeśli mamy do czynienia z niepożądanym scenariuszem, w którym nie możemy łatwo przekonwertować złożonej funkcji VBA z zapytania, nadal możemy złagodzić złe skutki, dodając filtry, które można zdalnie zmniejszyć, aby zmniejszyć początkowy zestaw rekordów, z którymi ma pracować program Access.
Uwaga na temat sargability
W poprzednich przykładach zastosowaliśmy funkcję skalarną na kolumnie. Może to potencjalnie sprawić, że zapytanie będzie „niepodlegające sargowaniu”, co oznacza, że silnik bazy danych nie jest w stanie zoptymalizować zapytania przy użyciu indeksu do wyszukiwania i znajdowania dopasowań. Część „sarg” słowa „sargability” odnosi się do „argumentu wyszukiwania”. Załóżmy, że mamy indeks zdefiniowany w źródle danych w tabeli:
CREATE INDEX IX_Cities_CityName ON Application.Cities (CityName);Wyrażenia takie jak
UCASE(CityName)
uniemożliwia silnikowi bazy danych korzystanie z indeksu IX_Cities_CityName
ponieważ silnik jest zmuszony do oceny każdego wiersza jeden po drugim, aby znaleźć dopasowanie, tak jak zrobił to Access z niestandardową funkcją VBA. Niektóre aparaty baz danych, takie jak najnowsze wersje programu SQL Server, obsługują tworzenie indeksów na podstawie wyrażenia. Gdybyśmy chcieli zoptymalizować zapytania za pomocą UCASE()
Transact-SQL, możemy dostosować definicję indeksu: CREATE INDEX IX_Cities_Boston_Uppercase ON Application.Cities (CityName) WHERE UCASE(CityName) = 'BOSTON';Dzięki temu SQL Server może potraktować zapytanie za pomocą
WHERE UCase(CityName) = 'BOSTON'
jako zapytanie sargable, ponieważ może teraz używać indeksu IX_Cities_Boston_Uppercase
aby zwrócić pasujące rekordy. Jeśli jednak zapytanie pasuje do 'CLEVELAND'
zamiast 'BOSTON'
, sargowalność zostaje utracona. Niezależnie od tego, z jakim silnikiem bazy danych faktycznie pracujesz, zawsze lepiej jest projektować i używać zapytań sargable, gdziekolwiek jest to możliwe, aby uniknąć problemów z wydajnością. Kluczowe zapytania powinny mieć indeksy obejmujące, aby zapewnić najlepszą wydajność. Zachęcam do dokładniejszego zapoznania się z sargowalnością i pokrycia indeksów, aby uniknąć projektowania zapytań, które w rzeczywistości nie są sargowalne.
Wnioski
Sprawdziliśmy, jak program Access obsługuje stosowanie filtrów z programu Access SQL do zapytań ODBC. Zbadaliśmy również różne przypadki, w których program Access konwertuje różne typy odwołań na parametr, umożliwiając programowi Access przeprowadzenie oceny poza warstwą ODBC i przekazanie ich jako danych wejściowych do przygotowanej instrukcji ODBC. Przyjrzeliśmy się również, co się dzieje, gdy nie można go sparametryzować, zwykle z powodu zawierania odwołań do kolumn jako danych wejściowych. Może to mieć wpływ na wydajność podczas migracji na serwer SQL.
W przypadku niektórych funkcji program Access może być w stanie przekonwertować wyrażenie tak, aby zamiast tego używało funkcji skalarnych ODBC, co umożliwia programowi Access zdalne sterowanie wyrażeniem do źródła danych ODBC. Jedną z konsekwencji tego jest to, że jeśli implementacja funkcji skalarnej jest inna, może to spowodować, że zapytanie będzie zachowywać się inaczej lub może działać szybciej/wolniej. Widzieliśmy, w jaki sposób funkcja VBA, nawet prosta, która otacza zdalnie usuwalną funkcję skalarną, może zniweczyć próby oddalenia wyrażenia. Dowiadujemy się również, że jeśli mamy sytuację, w której nie możemy dokonać refaktoryzacji złożonej funkcji VBA z zapytania Access/recordsource/rowsource, możemy przynajmniej złagodzić kosztowne pobieranie, dodając dodatkowe filtry w zapytaniu, które można oddalić, aby zmniejszyć ilość zwróconych danych.
W następnym artykule przyjrzymy się, jak połączenia są obsługiwane przez program Access.
Szukasz pomocy dotyczącej programu Microsoft Access? Zadzwoń do naszych ekspertów już dziś pod numer 773-809-5456 lub napisz do nas na adres [email protected].