Access
 sql >> Base de Dados >  >> RDS >> Access

Como o Access se comunica com fontes de dados ODBC? Parte 5

Filtrando o conjunto de registros


Na parte 5 de nossa série, aprenderemos como o Microsoft Access trata os filtros implementados e os integra em consultas ODBC. No artigo anterior, vimos como o Access formulará o SELECT instruções nos comandos ODBC SQL. Também vimos no artigo anterior como o Access tentará atualizar uma linha aplicando um WHERE cláusula baseada na chave e, se aplicável, rowversion. No entanto, precisamos aprender como o Access lidará com os filtros fornecidos às consultas do Access e os traduzirá na camada ODBC. Existem diferentes abordagens que o Access pode usar dependendo de como as consultas do Access são formuladas e você aprenderá como prever como o Access traduzirá uma consulta do Access em uma consulta ODBC para diferentes predicados de filtro fornecidos.

Independentemente de como você realmente aplica o filtro - seja interativamente por meio de comandos da faixa de opções do formulário ou folha de dados ou cliques no menu direito, ou programaticamente usando VBA ou executando consultas salvas - o Access emitirá uma consulta SQL ODBC correspondente para realizar a filtragem. Em geral, o Access tentará o máximo de filtragem possível. No entanto, não lhe dirá se não puder fazê-lo. Em vez disso, se o Access não puder expressar o filtro usando a sintaxe ODBC SQL, ele tentará realizar a filtragem baixando todo o conteúdo da tabela e avaliando a condição localmente. Isso pode explicar por que às vezes você pode encontrar uma consulta que é executada rapidamente, mas com uma pequena alteração, diminui a velocidade para um rastreamento. Esperamos que esta seção o ajude a entender quando isso pode acontecer e como lidar com isso para que você possa ajudar a acessar remotamente o máximo possível as fontes de dados para aplicar o filtro.

Para este artigo, usaremos consultas salvas, mas as informações discutidas aqui ainda devem se aplicar a outros métodos de aplicação de filtros.

Filtros estáticos


Começaremos fácil e criaremos uma consulta salva com um filtro codificado.
SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName="Boston";
Se abrirmos a consulta, veremos este SQL ODBC no trace:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Além das mudanças na sintaxe, a semântica da consulta não mudou; o mesmo filtro é passado no estado em que se encontra. Observe que apenas o CityID foi selecionado porque, por padrão, uma consulta usa um conjunto de registros do tipo dynaset que já discutimos na seção anterior.

Filtros parametrizados simples


Vamos alterar o SQL para usar um parâmetro em vez disso:
PARAMETERS SelectedCityName Text ( 255 );
SELECT 
  c.CityID
 ,c.CityName
 ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[SelectedCityName];
Se executarmos a consulta e inserirmos “Boston” no valor do prompt do parâmetro, conforme mostrado, devemos ver o seguinte SQL de rastreamento ODBC:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" =  ? ) 
Observe que observaremos o mesmo comportamento com referências de controle ou vinculação de subformulário. Se usarmos isso em vez disso:
SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];
Ainda obteríamos o mesmo SQL ODBC rastreado que vimos com a consulta parametrizada original. Esse ainda é o caso, embora nossa consulta modificada não tenha um PARAMETERS demonstração. Isso mostra que o Access é capaz de reconhecer que tais referências de controle que podem ter seu valor alterado de tempos em tempos, são melhor tratadas como um parâmetro ao formular o SQL ODBC.

Isso também funciona para a função VBA. Podemos adicionar uma nova função VBA:
Public Function GetSelectedCity() As String
    GetSelectedCity = "Boston"
End Function
Ajustamos a consulta salva para usar a nova função VBA:
WHERE c.CityName=GetSelectedCity();
Se você rastrear isso, verá que ainda é o mesmo. Assim, demonstramos que independentemente de a entrada ser um parâmetro explícito, uma referência a um controle ou resultado de uma função VBA, o Access tratará todos eles como um parâmetro da consulta ODBC SQL que executará em nosso em nome de. Isso é bom porque geralmente obtemos melhor desempenho quando podemos reutilizar uma consulta e simplesmente alterar o parâmetro.

No entanto, há mais um cenário comum que os desenvolvedores do Access normalmente configuram e que é a criação de SQL dinâmico com código VBA, geralmente concatenando uma string e, em seguida, executando a string concatenada. Vamos usar o seguinte código 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
O SQL ODBC rastreado para o OpenRecordset é como segue:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Diferentemente dos exemplos anteriores, o SQL ODBC não foi parametrizado. O Access não tem como saber que o 'Boston' foi preenchido dinamicamente em tempo de execução por um VBA.InputBox . Nós simplesmente entregamos a ele o SQL construído que, do POV do Access, é apenas uma instrução SQL estática. Nesse caso, derrotamos a parametrização da consulta. É importante reconhecer que um conselho popular dado aos desenvolvedores do Access é que o SQL construído dinamicamente é melhor do que usar consultas de parâmetro porque evita o problema em que o mecanismo do Access pode gerar um plano de execução com base em um valor de parâmetro que pode ser realmente abaixo do ideal para outro valor do parâmetro. Para mais detalhes sobre esse fenômeno, eu encorajo você a ler sobre o problema de “sniffing de parâmetros”. Observe que esse é um problema geral para qualquer mecanismo de banco de dados, não apenas para o Access. No entanto, no caso do Access, o SQL dinâmico funcionou melhor porque é muito mais barato apenas gerar um novo plano de execução. Em contraste, um mecanismo RDBMS pode ter estratégias adicionais para lidar com o problema e pode ser mais sensível a ter muitos planos de execução únicos, pois isso pode afetar negativamente seu armazenamento em cache.

Por esse motivo, as consultas parametrizadas do Access em fontes ODBC podem ser preferíveis ao SQL dinâmico. Como o Access tratará os controles de referências em um formulário ou funções VBA que não exigem referências de coluna como parâmetros, você não precisa de parâmetros explícitos em suas fontes de registro ou fontes de linha. No entanto, se você estiver usando o VBA para executar o SQL, geralmente é melhor usar o ADO, que também possui um suporte muito melhor para parametrização. No caso de criar uma fonte de registro ou fonte de linha dinâmica, usar um controle oculto no formulário/relatório pode ser uma maneira fácil de parametrizar a consulta. No entanto, se a consulta for marcadamente diferente, construir o SQL dinâmico no VBA e atribuí-lo à propriedade recordsource/rowsource força efetivamente uma recompilação completa e, portanto, evite usar planos de execução ruins que não terão um bom desempenho para o conjunto atual de entradas. Você pode encontrar recomendações no artigo que discute o WITH RECOMPILE do SQL Server útil para decidir se forçar uma recompilação versus usar uma consulta parametrizada.

Usando funções na filtragem SQL


Na seção anterior, vimos que uma instrução SQL contendo uma função VBA foi parametrizada para que o Access pudesse executar a função VBA e usar a saída como entrada para a consulta parametrizada. No entanto, nem todas as funções internas se comportam dessa maneira. Vamos usar UCase() como exemplo para filtrar a consulta. Além disso, aplicaremos a função em uma coluna.
SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE UCase([c].[CityName])="BOSTON";
Se olharmos para o SQL ODBC rastreado, veremos isso:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ({fn ucase("CityName" )}= 'BOSTON' )
No exemplo anterior, o Access foi capaz de parametrizar completamente o GetSelectedCity() uma vez que não exigiu entradas de colunas referenciadas na consulta. No entanto, o UCase() requer uma entrada. Se tivéssemos fornecido UCase("Boston") , o Access também teria parametrizado isso. No entanto, a entrada é uma referência de coluna, que o Access não pode parametrizar facilmente. No entanto, o Access pode detectar que o UCase() é uma das funções escalares ODBC suportadas. Como preferimos a comunicação remota o máximo possível com a fonte de dados, o Access faz exatamente isso invocando a versão do ODBC de ucase .

Se criarmos uma função VBA personalizada que emula UCase() função:
Public Function MyUCase(InputValue As Variant) As String
    MyUCase = UCase(InputValue)
End Function
e alterei a filtragem na consulta para:
WHERE MyUCase([c].[CityName])="BOSTON";
Isto é o que obtemos:
SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
O acesso não pode remotamente a função VBA personalizada MyUCase de volta à fonte de dados. No entanto, o SQL da consulta salva é legal, então o Access precisa satisfazê-lo de alguma forma. Para fazer isso, ele acaba baixando o conjunto completo do CityName e seu CityID correspondente para passar para a função VBA MyUCase() e avaliar o resultado. Conseqüentemente, a consulta agora é executada muito mais lentamente porque o Access agora está solicitando mais dados e fazendo mais trabalho.

Embora tenhamos usado UCase() neste exemplo, podemos ver claramente que geralmente é melhor transferir o máximo de trabalho possível para a fonte de dados. Mas e se tivermos uma função VBA complexa que não pode ser reescrita no dialeto SQL nativo da fonte de dados? Embora eu ache que esse cenário é bastante raro, vale a pena considerar. Vamos supor que podemos adicionar um filtro para restringir o conjunto de cidades retornadas.
SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName LIKE "Bos*"
  AND MyUCase([c].[CityName])="BOSTON";
O SQL ODBC rastreado ficará assim:
SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" LIKE 'Bos%' ) 
O acesso é capaz de controlar remotamente o LIKE de volta à fonte de dados, o que resulta na recuperação de um conjunto de dados muito menor. Ele ainda realizará a avaliação local do MyUCase() no conjunto de dados resultante. A consulta é executada muito mais rápido simplesmente por causa do conjunto de dados menor retornado.

Isso nos diz que se enfrentarmos o cenário indesejável em que não podemos refatorar facilmente uma função VBA complexa de uma consulta, ainda podemos mitigar os efeitos ruins adicionando filtros que podem ser remotos para reduzir o conjunto inicial de registros para o Access trabalhar.

Uma nota sobre sargability


Nos exemplos anteriores, aplicamos uma função escalar em uma coluna. Isso tem o potencial de renderizar a consulta como “não sargável”, o que significa que o mecanismo de banco de dados não consegue otimizar a consulta usando o índice para pesquisar e encontrar correspondências. A parte “sarg” da palavra “sargability” refere-se a “Search ARGument”. Suponha que tenhamos o índice definido na fonte de dados na tabela:
CREATE INDEX IX_Cities_CityName
ON Application.Cities (CityName);
Expressões como UCASE(CityName) impede que o mecanismo de banco de dados seja capaz de usar o índice IX_Cities_CityName porque o mecanismo é forçado a avaliar cada linha uma a uma para encontrar correspondência, assim como o Access fez com uma função VBA personalizada. Alguns mecanismos de banco de dados, como versões recentes do SQL Server, dão suporte à criação de índices com base em uma expressão. Se quisermos otimizar as consultas usando UCASE() função transact-SQL, poderíamos ajustar a definição do índice:
CREATE INDEX IX_Cities_Boston_Uppercase
ON Application.Cities (CityName)
WHERE UCASE(CityName) = 'BOSTON';
Isso permite que o SQL Server trate a consulta com WHERE UCase(CityName) = 'BOSTON' como uma consulta sargável porque agora pode usar o índice IX_Cities_Boston_Uppercase para retornar os registros correspondentes. No entanto, se a consulta corresponder em 'CLEVELAND' em vez de 'BOSTON' , a sargability é perdida.

Independentemente do mecanismo de banco de dados com o qual você está trabalhando, é sempre preferível projetar e usar consultas sargáveis ​​sempre que possível para evitar problemas de desempenho. As consultas cruciais devem ter índices de cobertura para fornecer o melhor desempenho. Eu encorajo você a estudar mais sobre a capacidade de sargibilidade e os índices de cobertura para ajudá-lo a evitar a criação de consultas que, de fato, não sejam sargáveis.

Conclusões


Revisamos como o Access lida com a aplicação de filtros do Access SQL nas consultas ODBC. Também exploramos diferentes casos em que o Access converterá diferentes tipos de referências em um parâmetro, permitindo que o Access realize a avaliação fora da camada ODBC e os passe como entradas para a instrução ODBC preparada. Também analisamos o que acontece quando não pode ser parametrizado, normalmente devido a conter referências de coluna como entradas. Isso pode ter consequências no desempenho durante uma migração para o SQL Server.

Para determinadas funções, o Access pode converter a expressão para usar funções escalares ODBC, o que permite que o Access afaste a expressão para a fonte de dados ODBC. Uma ramificação disso é que, se a implementação da função escalar for diferente, isso pode fazer com que a consulta se comporte de maneira diferente ou possa ser executada de forma mais rápida/lenta. Vimos como uma função VBA, mesmo uma simples que envolve uma função escalar que pode ser remota, pode derrotar os esforços de remoto da expressão. Também aprendemos que, se tivermos uma situação em que não podemos refatorar uma função VBA complexa de uma consulta/recordsource/rowsource do Access, podemos pelo menos mitigar o download caro adicionando filtros adicionais na consulta que podem ser remotos para reduzir a quantidade de dados retornados.

No próximo artigo, veremos como as junções são tratadas pelo Access.

Procurando ajuda com o Microsoft Access? Ligue para nossos especialistas hoje em 773-809-5456 ou envie um e-mail para [email protected].