Филтриране на набора от записи
В част 5 от нашата поредица ще научим как Microsoft Access обработва внедрените филтри и ги интегрира в ODBC заявки. В предишната статия видяхме как Access ще формулира SELECT
изрази в ODBC SQL командите. Също така видяхме в предишната статия как Access ще се опита да актуализира ред, като приложи WHERE
клауза въз основа на ключа и, ако е приложимо, версия на реда. Трябва обаче да научим как Access ще обработва филтрите, предоставени на заявките на Access, и ще ги превежда в слоя ODBC. Има различни подходи, които Access може да използва в зависимост от това как са формулирани заявките на Access и ще научите как да предвидите как Access ще преведе заявка на Access в ODBC заявка за различен даден предикат на филтъра.
Независимо от това как всъщност прилагате филтъра – било то интерактивно чрез команди на лентата на формуляра или листа с данни или щраквания с дясно меню, или програмно с помощта на VBA или изпълнение на запазени заявки – Access ще издаде съответната ODBC SQL заявка, за да извърши филтрирането. Като цяло Access ще се опита да отдалечи колкото е възможно повече филтриране. Той обаче няма да ви каже, ако не може да го направи. Вместо това, ако Access не може да изрази филтъра с помощта на ODBC SQL синтаксис, той ще се опита да извърши филтриране, като изтегли цялото съдържание на таблицата и ще оцени условието локално. Това може да обясни защо понякога може да срещнете заявка, която се изпълнява бързо, но с една малка промяна се забавя до обхождане. Надяваме се, че този раздел ще ви помогне да разберете кога това може да се случи и как да се справите, за да можете да помогнете на отдалечения достъп колкото е възможно повече до източниците на данни за прилагане на филтъра.
За тази статия ще използваме запазени заявки, но обсъжданата тук информация все пак трябва да се отнася за други методи за прилагане на филтри.
Статични филтри
Ще започнем лесно и ще създадем запазена заявка с твърдо кодиран филтър.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName="Boston";Ако отворим заявката, ще видим този ODBC SQL в следата:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )Освен промените в синтаксиса, семантиката на заявката не се е променила; същият филтър се предава както е. Имайте предвид, че само
CityID
беше избран, защото по подразбиране заявката използва набор от записи от тип dynaset, който обсъдихме вече в предишния раздел. Прости параметризирани филтри
Нека променим SQL, за да използваме параметър вместо това:
PARAMETERS SelectedCityName Text ( 255 ); SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[SelectedCityName];Ако изпълним заявката и въведете „Boston“ в стойността на подканата за параметър, както е показано, трябва да видим следния ODBC проследяващ SQL:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = ? )Имайте предвид, че ще наблюдаваме същото поведение с контролни препратки или свързване на подформуляр. Ако използвахме това вместо това:
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];Все пак ще получим същия проследен ODBC SQL, който видяхме с оригиналната параметризирана заявка. Това все още е така, въпреки че нашата модифицирана заявка нямаше
PARAMETERS
изявление. Това показва, че Access е в състояние да разпознае, че такива контролни препратки, чиято стойност може да се променя от време на време, се третират най-добре като параметър при формулиране на ODBC SQL. Това работи и за функцията VBA. Можем да добавим нова VBA функция:
Public Function GetSelectedCity() As String GetSelectedCity = "Boston" End FunctionКоригираме запазената заявка, за да използваме новата VBA функция:
WHERE c.CityName=GetSelectedCity();Ако проследите това, ще видите, че все още е същото. По този начин ние демонстрирахме, че независимо дали входът е изричен параметър, препратка към контрола или резултат от VBA функция, Access ще ги третира всички като параметър на ODBC SQL заявката, която ще изпълни на нашата от името. Това е хубаво нещо, защото като цяло получаваме по-добра производителност, когато можем да използваме повторно заявка и просто да променим параметъра.
Има обаче още един по-често срещан сценарий, който разработчиците на Access обикновено създават и това е създаване на динамичен SQL с VBA код, обикновено чрез обединяване на низ и след това изпълнение на конкатенирания низ. Нека използваме следния 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Проследеният ODBC SQL за
OpenRecordset
е както следва: SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" = 'Boston' )За разлика от предишните примери, ODBC SQL не беше параметризиран. Access няма начин да разбере, че „Boston“ е динамично попълван по време на изпълнение от
VBA.InputBox
. Просто му предадохме конструирания SQL, който от Access' POV е просто статичен SQL израз. В този случай прекратяваме параметризирането на заявката. Важно е да се отбележи, че един популярен съвет, даден на разработчиците на Access, е, че динамично конструираният SQL е по-добър от използването на заявки за параметри, тъй като избягва проблема, при който машината на Access може да генерира план за изпълнение на базата на една стойност на параметъра, която всъщност може да е неоптимална за друга. стойност на параметъра. За повече подробности относно този феномен ви насърчавам да прочетете проблема с „смъркането на параметри“. Имайте предвид, че това е общ проблем за всички машини за бази данни, не само за Access. Въпреки това, в случая на Access, динамичният SQL работи по-добре, защото е много по-евтино само да генерирате нов план за изпълнение. За разлика от тях, RDBMS машината може да има допълнителни стратегии за справяне с проблема и може да е по-чувствителна към наличието на твърде много еднократни планове за изпълнение, тъй като това може да повлияе негативно на неговото кеширане.
Поради тази причина параметризираните заявки от Access срещу ODBC източници може да са за предпочитане пред динамичния SQL. Тъй като Access ще третира контролите за препратки на формуляр или VBA функции, които не изискват препратки към колони като параметри, нямате нужда от изрични параметри във вашите източници на записи или източници на редове. Въпреки това, ако използвате VBA за изпълнение на SQL, обикновено е по-добре да използвате ADO, който също има много по-добра поддръжка за параметризиране. В случай на изграждане на динамичен източник на записи или източник на редове, използването на скрита контрола във формуляра/отчета може да бъде лесен начин за параметризиране на заявката. Въпреки това, ако заявката е значително различна, изграждането на динамичния SQL във VBA и присвояването му към свойството recordsource/rowsource ефективно принуждава пълна рекомпилация и следователно избягва използването на лоши планове за изпълнение, които няма да се представят добре за текущия набор от входове. Можете да намерите препоръки в статията, обсъждаща WITH RECOMPILE
на SQL Server помага при вземането на решение дали да се принуди прекомпилиране спрямо използването на параметризирана заявка.
Използване на функции в SQL филтриране
В предишния раздел видяхме, че SQL израз, съдържащ функция VBA, се параметризира, така че Access може да изпълни VBA функцията и да използва изхода като вход към параметризираната заявка. Въпреки това, не всички вградени функции се държат по този начин. Нека използваме UCase()
като пример за филтриране на заявката. Освен това ще приложим функцията върху колона.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE UCase([c].[CityName])="BOSTON";Ако погледнем проследения ODBC SQL, ще видим това:
SQLExecDirect: SELECT "c"."CityID" FROM "Application"."Cities" "c" WHERE ({fn ucase("CityName" )}= 'BOSTON' )В предишния пример Access успя напълно да параметризира
GetSelectedCity()
тъй като не изисква входове от колони, посочени в заявката. Въпреки това, UCase()
изисква въвеждане. Ако бяхме предоставили UCase("Boston")
, Access също би параметризирал това. Въпреки това, входът е препратка към колона, която Access не може лесно да параметризира. Въпреки това, Access може да открие, че UCase()
е една от поддържаните ODBC скаларни функции. Тъй като предпочитаме отдалечено, доколкото е възможно, пред източника на данни, Access прави точно това, като извиква версията на ODBC на ucase
.
Ако след това създадем персонализирана VBA функция, която емулира UCase()
функция:
Public Function MyUCase(InputValue As Variant) As String MyUCase = UCase(InputValue) End Functionи промени филтрирането в заявката на:
WHERE MyUCase([c].[CityName])="BOSTON";Ето какво получаваме:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c"Access не може да отдалечи персонализираната VBA функция
MyUCase
обратно към източника на данни. Въпреки това, SQL на запазената заявка е законен, така че Access трябва да го удовлетвори по някакъв начин. За да направи това, в крайна сметка изтегля пълния набор от CityName
и съответния му CityID
за да преминете към VBA функцията MyUCase()
и оценете резултата. Следователно заявката сега се изпълнява много по-бавно, защото Access вече изисква повече данни и върши повече работа.
Въпреки че използвахме UCase()
в този пример ясно можем да видим, че като цяло е по-добре да отдалечите възможно най-много работа към източника на данни. Но какво ще стане, ако имаме сложна VBA функция, която не може да бъде пренаписана в естествения SQL диалект на източника на данни? Въпреки че мисля, че този сценарий е доста рядък, си струва да се обмисли. Да предположим, че можем да добавим филтър, за да стесним набора от върнати градове.
SELECT c.CityID ,c.CityName ,c.StateProvinceID FROM Cities AS c WHERE c.CityName LIKE "Bos*" AND MyUCase([c].[CityName])="BOSTON";Проследеният ODBC SQL ще излезе така:
SQLExecDirect: SELECT "CityName" ,"c"."CityID" FROM "Application"."Cities" "c" WHERE ("CityName" LIKE 'Bos%' )Достъпът може да отдалечава
LIKE
обратно към източника на данни, което води до връщане на много по-малък набор от данни. Той все още ще извършва локална оценка на MyUCase()
върху получения набор от данни. Заявката се изпълнява много по-бързо просто поради върнатия по-малък набор от данни. Това ни казва, че ако се сблъскаме с нежелания сценарий, при който не можем лесно да преработим сложна VBA функция от заявка, все пак можем да смекчим лошите ефекти чрез добавяне на филтри, които могат да бъдат отдалечени, за да намалим първоначалния набор от записи, с които Access да работи.
Бележка относно възможността за саргиране
В предходните примери приложихме скаларна функция върху колона. Това има потенциал да изобрази заявката като „не-sargable“, което означава, че машината на базата данни не е в състояние да оптимизира заявката, използвайки индекс за търсене и намиране на съвпадения. Частта „sarg“ на думата „sargability“ се отнася до „Search ARGument“. Да предположим, че имаме индекса, дефиниран в източника на данни в таблицата:
CREATE INDEX IX_Cities_CityName ON Application.Cities (CityName);Изрази като
UCASE(CityName)
не позволява на машината на базата данни да може да използва индекса IX_Cities_CityName
защото машината е принудена да оценява всеки ред един по един, за да намери съвпадение, точно както Access направи с персонализирана VBA функция. Някои машини за бази данни, като последните версии на SQL Server, поддържат създаване на индекси въз основа на израз. Ако искаме да оптимизираме заявките с помощта на UCASE()
transact-SQL функция, можем да коригираме дефиницията на индекса: CREATE INDEX IX_Cities_Boston_Uppercase ON Application.Cities (CityName) WHERE UCASE(CityName) = 'BOSTON';Това позволява на SQL Server да третира заявката с
WHERE UCase(CityName) = 'BOSTON'
като заявка за sargable, защото сега може да използва индекса IX_Cities_Boston_Uppercase
за да върнете съвпадащите записи. Ако обаче заявката съответства на 'CLEVELAND'
вместо 'BOSTON'
, саргируемостта се губи. Независимо с коя база данни всъщност работите, винаги е за предпочитане да проектирате и използвате sargable заявки, когато е възможно, за да избегнете проблеми с производителността. Решаващите заявки трябва да имат покриващи индекси, за да осигурят най-добра производителност. Насърчавам ви да проучите повече за индексите на sargability и покриване, за да ви помогнем да избегнете проектирането на заявки, които всъщност не са sargable.
Заключения
Прегледахме как Access обработва прилагането на филтри от Access SQL към ODBC заявките. Ние също така проучихме различни случаи, при които Access ще преобразува различни типове препратки в параметър, позволявайки на Access да извърши оценката извън ODBC слоя и да ги предаде като входни данни в подготвената ODBC инструкция. Разгледахме също какво се случва, когато не може да се параметризира, обикновено поради съдържащи препратки към колони като входни данни. Това може да има последици за производителността по време на миграция към SQL сървър.
За определени функции Access може да е в състояние да преобразува израза, за да използва ODBC скаларни функции вместо това, което позволява на Access да отдалечава израза към източника на данни ODBC. Едно разклонение на това е, че ако реализацията на скаларната функция е различна, това може да доведе до различно поведение на заявката или може да се изпълнява по-бързо/бавно. Видяхме как VBA функция, дори обикновена, която обвива иначе отдалечена скаларна функция, може да победи усилията за отдалечаване на израза. Научаваме също, че ако имаме ситуация, в която не можем да преработим сложна VBA функция от заявка на Access/recordsource/rowsource, можем поне да смекчим скъпото изтегляне, като добавим допълнителни филтри към заявката, които могат да бъдат отдалечени, за да намалим количеството от върнатите данни.
В следващата статия ще разгледаме как Access се обработва присъединяванията.
Търсите помощ с Microsoft Access? Обадете се на нашите експерти днес на 773-809-5456 или ни изпратете имейл на [email protected].