sql >> Base de Datos >  >> RDS >> Access

¿Cómo se comunica Access con las fuentes de datos ODBC? Parte 5

Filtrar el conjunto de registros

En la parte 5 de nuestra serie, aprenderemos cómo Microsoft Access maneja los filtros implementados y los integra en las consultas ODBC. En el artículo anterior, vimos cómo Access formulará el SELECT declaraciones en los comandos SQL de ODBC. También vimos en el artículo anterior cómo Access intentará actualizar una fila aplicando un WHERE cláusula basada en la clave y, si corresponde, en la versión de fila. Sin embargo, necesitamos saber cómo manejará Access los filtros que se proporcionan a las consultas de Access y cómo los traducirá en la capa ODBC. Hay diferentes enfoques que Access puede usar dependiendo de cómo se formulen las consultas de Access y aprenderá cómo predecir cómo Access traducirá una consulta de Access en una consulta ODBC para diferentes predicados de filtro dados.

Independientemente de cómo aplique realmente el filtro, ya sea de forma interactiva a través de los comandos de la cinta de opciones de la hoja de datos o del formulario, o haciendo clic en el menú derecho, o programáticamente usando VBA o ejecutando consultas guardadas, Access emitirá una consulta ODBC SQL correspondiente para realizar el filtrado. En general, Access intentará filtrar de forma remota tanto como sea posible. Sin embargo, no le dirá si no puede hacerlo. En cambio, si Access no puede expresar el filtro usando la sintaxis SQL de ODBC, intentará realizar el filtrado descargando todo el contenido de la tabla y evaluando la condición localmente. Eso puede explicar por qué en algún momento puede encontrarse con una consulta que se ejecuta rápidamente pero con un pequeño cambio, se ralentiza a paso de tortuga. Con suerte, esta sección lo ayudará a comprender cuándo puede suceder esto y cómo manejarlo para que pueda ayudar a Acceder de forma remota tanto como sea posible a las fuentes de datos para aplicar el filtro.

Para este artículo, utilizaremos consultas guardadas, pero la información que se analiza aquí aún debería aplicarse a otros métodos de aplicación de filtros.

Filtros estáticos

Comenzaremos de manera fácil y crearemos una consulta guardada con un filtro codificado.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName="Boston";
Si abrimos la consulta, veremos este ODBC SQL en la traza:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
Aparte de los cambios en la sintaxis, la semántica de la consulta no ha cambiado; el mismo filtro se pasa tal cual. Tenga en cuenta que solo el CityID se seleccionó porque, de manera predeterminada, una consulta usa un conjunto de registros de tipo dynaset que ya analizamos en la sección anterior.

Filtros parametrizados simples

Cambiemos el SQL para usar un parámetro en su lugar:

PARAMETERS SelectedCityName Text ( 255 );
SELECT 
  c.CityID
 ,c.CityName
 ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[SelectedCityName];
Si ejecutamos la consulta e ingresamos "Boston" en el valor de solicitud del parámetro como se muestra, deberíamos ver el siguiente seguimiento SQL de ODBC:
SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" =  ? ) 
Tenga en cuenta que observaremos el mismo comportamiento con las referencias de control o la vinculación de subformularios. Si usamos esto en su lugar:

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName=[Forms]![frmSomeForm]![txtSomeText];
Todavía obtendríamos el mismo SQL ODBC rastreado que vimos con la consulta parametrizada original. Ese sigue siendo el caso a pesar de que nuestra consulta modificada no tenía un PARAMETERS declaración. Esto muestra que Access es capaz de reconocer que dichas referencias de control cuyo valor puede cambiarse de vez en cuando, se tratan mejor como un parámetro al formular el SQL ODBC.

Eso también funciona para la función VBA. Podemos agregar una nueva función VBA:

Public Function GetSelectedCity() As String
    GetSelectedCity = "Boston"
End Function
Ajustamos la consulta guardada para usar la nueva función VBA:

WHERE c.CityName=GetSelectedCity();
Si rastreas esto, verás que sigue siendo el mismo. Por lo tanto, hemos demostrado que, independientemente de si la entrada es un parámetro explícito, una referencia a un control o el resultado de una función VBA, Access los tratará a todos como un parámetro de la consulta SQL ODBC que ejecutará en nuestro en nombre de. Eso es bueno porque generalmente obtenemos un mejor rendimiento cuando podemos reutilizar una consulta y simplemente cambiar el parámetro.

Sin embargo, hay un escenario más común que los desarrolladores de Access suelen configurar y que es la creación de SQL dinámico con código VBA, generalmente mediante la concatenación de una cadena y luego la ejecución de la cadena concatenada. Usemos el siguiente 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
El SQL ODBC rastreado para el OpenRecordset es como sigue:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" = 'Boston' ) 
A diferencia de los ejemplos anteriores, el SQL de ODBC no se parametrizó. Access no tiene forma de saber que 'Boston' se llenó dinámicamente en tiempo de ejecución por un VBA.InputBox . Simplemente le entregamos el SQL construido que, desde el punto de vista de Access, es solo una declaración SQL estática. En este caso, derrotamos la parametrización de la consulta. Es importante reconocer que un consejo popular dado a los desarrolladores de Access ha sido que el SQL construido dinámicamente es mejor que usar consultas de parámetros porque evita el problema en el que el motor de Access puede generar un plan de ejecución basado en el valor de un parámetro que en realidad puede ser subóptimo para otro. valor del parámetro. Para obtener más detalles sobre ese fenómeno, lo animo a que lea sobre el problema del "olfateo de parámetros". Tenga en cuenta que este es un problema general para cualquier motor de base de datos, no solo para Access. Sin embargo, en el caso de Access, SQL dinámico funcionó mejor porque es mucho más económico generar un nuevo plan de ejecución. Por el contrario, un motor RDBMS puede tener estrategias adicionales para manejar el problema y puede ser más sensible a tener demasiados planes de ejecución únicos, ya que eso puede afectar negativamente su almacenamiento en caché.

Por ese motivo, las consultas parametrizadas de Access en fuentes ODBC pueden ser preferibles a SQL dinámico. Debido a que Access tratará los controles de referencias en un formulario o funciones de VBA que no requieren referencias de columna como parámetros, no necesita parámetros explícitos en sus fuentes de registros o fuentes de filas. Sin embargo, si usa VBA para ejecutar SQL, generalmente es mejor usar ADO, que también tiene un soporte mucho mejor para la parametrización. En el caso de crear un origen de registros dinámico o un origen de filas, usar un control oculto en el formulario/informe puede ser una forma fácil de parametrizar la consulta. Sin embargo, si la consulta es marcadamente diferente, construir el SQL dinámico en VBA y asignarlo a la propiedad recordsource/rowsource efectivamente fuerza una recompilación completa y, por lo tanto, evita el uso de planes de ejecución incorrectos que no funcionarán bien para el conjunto actual de entradas. Puede encontrar recomendaciones en el artículo sobre WITH RECOMPILE de SQL Server. útil para decidir si forzar una recompilación o usar una consulta parametrizada.

Uso de funciones en el filtrado de SQL

En la sección anterior, vimos que una declaración SQL que contenía una función VBA se parametrizó para que Access pudiera ejecutar la función VBA y usar la salida como entrada para la consulta parametrizada. Sin embargo, no todas las funciones integradas se comportan de esta manera. Usemos UCase() como ejemplo para filtrar la consulta. Además, aplicaremos la función en una columna.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE UCase([c].[CityName])="BOSTON";
Si observamos el SQL ODBC rastreado, veremos esto:

SQLExecDirect: 
SELECT "c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ({fn ucase("CityName" )}= 'BOSTON' )
En el ejemplo anterior, Access pudo parametrizar completamente el GetSelectedCity() ya que no requería entradas de las columnas a las que se hace referencia en la consulta. Sin embargo, el UCase() requiere una entrada. Si hubiéramos proporcionado UCase("Boston") , Access también habría parametrizado esto. Sin embargo, la entrada es una referencia de columna, que Access no puede parametrizar fácilmente. Sin embargo, Access puede detectar que UCase() es una de las funciones escalares de ODBC admitidas. Dado que preferimos la comunicación remota tanto como sea posible a la fuente de datos, Access hace exactamente eso invocando la versión de ODBC de ucase .

Si luego creamos una función VBA personalizada que emula UCase() función:

Public Function MyUCase(InputValue As Variant) As String
    MyUCase = UCase(InputValue)
End Function
y cambió el filtrado en la consulta a:

WHERE MyUCase([c].[CityName])="BOSTON";
Esto es lo que obtenemos:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
El acceso no puede controlar de forma remota la función VBA personalizada MyUCase volver a la fuente de datos. Sin embargo, el SQL de la consulta guardada es legal, por lo que Access debe cumplirlo de alguna manera. Para hacer esto, termina descargando el conjunto completo de CityName y su correspondiente CityID para pasar a la función VBA MyUCase() y evaluar el resultado. En consecuencia, la consulta ahora se realiza mucho más lentamente porque Access ahora solicita más datos y realiza más trabajo.

Aunque usamos UCase() en este ejemplo, podemos ver claramente que, por lo general, es mejor enviar de forma remota la mayor cantidad de trabajo posible a la fuente de datos. Pero, ¿qué sucede si tenemos una función VBA compleja que no se puede reescribir en el dialecto SQL nativo de la fuente de datos? Aunque creo que este escenario es bastante raro, vale la pena considerarlo. Supongamos que podemos agregar un filtro para acotar el conjunto de ciudades devueltas.

SELECT 
   c.CityID
  ,c.CityName
  ,c.StateProvinceID
FROM Cities AS c
WHERE c.CityName LIKE "Bos*"
  AND MyUCase([c].[CityName])="BOSTON";
El SQL ODBC rastreado aparecerá así:

SQLExecDirect: 
SELECT 
   "CityName"
  ,"c"."CityID" 
FROM "Application"."Cities" "c" 
WHERE ("CityName" LIKE 'Bos%' ) 
El acceso es capaz de remotamente el LIKE de vuelta a la fuente de datos, lo que resulta en recuperar un conjunto de datos mucho más pequeño. Seguirá realizando una evaluación local de MyUCase() en el conjunto de datos resultante. La consulta se ejecuta mucho más rápido simplemente porque el conjunto de datos devuelto es más pequeño.

Esto nos dice que si nos enfrentamos al escenario indeseable en el que no podemos refactorizar fácilmente una función VBA compleja de una consulta, aún podemos mitigar los efectos negativos agregando filtros que se pueden controlar de forma remota para reducir el conjunto inicial de registros para que funcione Access.

Una nota sobre sargabilidad

En los ejemplos anteriores, aplicamos una función escalar en una columna. Eso tiene el potencial de hacer que la consulta sea "no sargable", lo que significa que el motor de la base de datos no puede optimizar la consulta usando el índice para buscar y encontrar coincidencias. La parte "sarg" de la palabra "sargability" se refiere a "Argumento de búsqueda". Supongamos que tenemos el índice definido en la fuente de datos de la tabla:

CREATE INDEX IX_Cities_CityName
ON Application.Cities (CityName);
Expresiones como UCASE(CityName) evita que el motor de la base de datos pueda usar el índice IX_Cities_CityName porque el motor se ve obligado a evaluar cada fila una por una para encontrar coincidencias, tal como lo hizo Access con una función VBA personalizada. Algunos motores de bases de datos, como las versiones recientes de SQL Server, admiten la creación de índices basados ​​en una expresión. Si quisiéramos optimizar las consultas usando UCASE() función transact-SQL, podríamos ajustar la definición del índice:

CREATE INDEX IX_Cities_Boston_Uppercase
ON Application.Cities (CityName)
WHERE UCASE(CityName) = 'BOSTON';
Esto permite que SQL Server trate la consulta con WHERE UCase(CityName) = 'BOSTON' como una consulta sargable porque ahora puede usar el índice IX_Cities_Boston_Uppercase para devolver los registros coincidentes. Sin embargo, si la consulta coincide con 'CLEVELAND' en lugar de 'BOSTON' , se pierde la sargabilidad.

Independientemente del motor de base de datos con el que esté trabajando, siempre es preferible diseñar y utilizar consultas sargable siempre que sea posible para evitar problemas de rendimiento. Las consultas cruciales deben tener índices de cobertura para proporcionar el mejor rendimiento. Lo animo a que estudie más sobre la sargabilidad y los índices de cobertura para ayudarlo a evitar diseñar consultas que de hecho no son sargables.

Conclusiones

Revisamos cómo maneja Access la aplicación de filtros de Access SQL en las consultas ODBC. También exploramos diferentes casos en los que Access convertirá diferentes tipos de referencias en un parámetro, lo que permitirá a Access realizar la evaluación fuera de la capa ODBC y pasarlos como entradas a la declaración ODBC preparada. También analizamos lo que sucede cuando no se puede parametrizar, generalmente debido a que contiene referencias de columna como entradas. Eso puede tener consecuencias en el rendimiento durante una migración al servidor SQL.

Para ciertas funciones, Access puede convertir la expresión para usar funciones escalares ODBC en su lugar, lo que permite que Access controle de forma remota la expresión a la fuente de datos ODBC. Una ramificación de esto es que si la implementación de la función escalar es diferente, puede hacer que la consulta se comporte de manera diferente o puede funcionar más rápido o más lento. Vimos cómo una función de VBA, incluso una simple que envuelve una función escalar que de otro modo se podría controlar de forma remota, puede frustrar los esfuerzos para controlar de forma remota la expresión. También aprendemos que si tenemos una situación en la que no podemos refactorizar una función VBA compleja de una consulta de Access/recordsource/rowsource, al menos podemos mitigar la costosa descarga agregando filtros adicionales en la consulta que se pueden controlar de forma remota para reducir la cantidad. de datos devueltos.

En el próximo artículo veremos cómo Access maneja las uniones.

¿Busca ayuda con Microsoft Access? Llame a nuestros expertos hoy al 773-809-5456 o envíenos un correo electrónico a [email protected].