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

¿Cómo se comunica Access con las fuentes de datos ODBC? parte 4

¿Qué hace Access cuando un usuario realiza cambios en los datos de una tabla vinculada ODBC?

Nuestra serie de rastreo ODBC continúa, y en este cuarto artículo explicaremos cómo insertar y actualizar un registro de datos en un conjunto de registros, así como el proceso de eliminación de un registro. En el artículo anterior, aprendimos cómo maneja Access el llenado de datos de fuentes ODBC. Vimos que el tipo de conjunto de registros tiene un efecto importante en cómo Access formulará las consultas a la fuente de datos ODBC. Más importante aún, descubrimos que con un conjunto de registros de tipo dynaset, Access realiza un trabajo adicional para tener toda la información necesaria para poder seleccionar una sola fila con una clave. Esto se aplicará en este artículo donde exploramos cómo se manejan las modificaciones de datos. Comenzaremos con las inserciones, que es la operación más complicada, luego pasaremos a las actualizaciones y finalmente a las eliminaciones.

Insertar un registro en un conjunto de registros

El comportamiento de inserción de un conjunto de registros de tipo dynaset dependerá de cómo Access perciba las claves de la tabla subyacente. Habrá 3 comportamientos distintos. Los dos primeros se ocupan del manejo de claves primarias que el servidor genera automáticamente de alguna manera. El segundo es un caso especial del primer comportamiento que se aplica solo con el backend de SQL Server usando una IDENTITY columna. El último se ocupa del caso en que las claves son proporcionadas por el usuario (por ejemplo, claves naturales como parte de la entrada de datos). Comenzaremos con el caso más general de las claves generadas por el servidor.

Insertar un registro; una tabla con clave principal generada por el servidor

Cuando insertamos un conjunto de registros (nuevamente, no importa cómo lo hagamos, a través de la interfaz de usuario de Access o VBA), Access debe hacer cosas para agregar la nueva fila a la memoria caché local.

Lo importante a tener en cuenta es que Access tiene diferentes comportamientos de inserción dependiendo de cómo esté configurada la clave. En este caso, las Cities la tabla no tiene una IDENTITY atributo sino que usa una SEQUENCE objeto para generar una nueva clave. Aquí está el SQL rastreado formateado:

SQLExecDirect:
INSERT INTO  "Application"."Cities"  (
   "CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
) VALUES (
   ?
  ,?
  ,?
  ,?)

SQLPrepare:
SELECT
   "CityID"
  ,"CityName"
  ,"StateProvinceID"
  ,"Location"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
FROM "Application"."Cities"
WHERE "CityID" IS NULL

SQLExecute: (GOTO BOOKMARK)

SQLExecDirect:
SELECT
  "Application"."Cities"."CityID"
FROM "Application"."Cities"
WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Tenga en cuenta que Access solo enviará las columnas que el usuario realmente modificó. Aunque la consulta en sí incluía más columnas, solo editamos 4 columnas, por lo que Access solo las incluirá. Eso garantiza que Access no interfiera con el comportamiento predeterminado establecido para las otras columnas que el usuario no modificó, ya que Access no tiene conocimiento específico sobre cómo la fuente de datos manejará esas columnas. Más allá de eso, la declaración de inserción es más o menos lo que esperaríamos.

La segunda declaración, sin embargo, es un poco extraña. Selecciona para WHERE "CityID" IS NULL . Eso parece imposible, ya que sabemos que el CityID columna es una clave principal y, por definición, no puede ser nula. Sin embargo, si observa la captura de pantalla, nunca modificamos el CityID columna. Desde el punto de vista de Access, es NULL . Lo más probable es que Access adopte un enfoque pesimista y no asuma que la fuente de datos se adherirá de hecho al estándar SQL. Como vimos en la sección que analiza cómo Access selecciona un índice para usar para identificar de forma única una fila, es posible que no sea una clave principal, sino simplemente un UNIQUE índice que puede permitir NULL . Para ese caso extremo improbable, realiza una consulta solo para asegurarse de que la fuente de datos no haya creado un nuevo registro con ese valor. Una vez que ha examinado que no se devolvieron datos, intenta localizar el registro nuevamente con el siguiente filtro:

WHERE "CityName" = ?
  AND "StateProvinceID" = ?
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
que eran las mismas 4 columnas que el usuario realmente modificó. Dado que solo había una ciudad llamada "Zeke", solo obtuvimos un registro y, por lo tanto, Access puede llenar el caché local con el nuevo registro con los mismos datos que tiene la fuente de datos. Incorporará cualquier cambio a otras columnas, ya que SELECT la lista solo incluye el CityID clave, que luego usará en su declaración ya preparada para luego completar la fila completa usando el CityID clave.

Insertar un registro; una tabla con clave primaria autoincrementable

Sin embargo, ¿qué sucede si la tabla proviene de una base de datos de SQL Server y tiene una columna de incremento automático como IDENTITY? ¿atributo? El acceso se comporta de manera diferente. Así que vamos a crear una copia de las Cities pero edite para que el CityID la columna ahora es una IDENTITY columna.

Veamos cómo maneja Access esto:

SQLExecDirect:
INSERT INTO "Application"."Cities" (
   "CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
) VALUES (
   ?
  ,?
  ,?
  ,?
  ,?
  ,?)

SQLExecDirect:
SELECT @@IDENTITY

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)
Hay significativamente menos charla; simplemente hacemos un SELECT @@IDENTITY para encontrar la identidad recién insertada. Desafortunadamente, este no es un comportamiento general. Por ejemplo, MySQL admite la capacidad de hacer SELECT @@IDENTITY , sin embargo, Access no proporcionará este comportamiento. El controlador ODBC de PostgreSQL tiene un modo para emular SQL Server con el fin de engañar a Access para que envíe el @@IDENTITY a PostgreSQL para que pueda asignarse al equivalente serial tipo de datos.

Insertar un registro con un valor explícito para la clave principal

Hagamos un tercer experimento usando una tabla con un int normal columna, sin una IDENTITY atributo. Aunque seguirá siendo una clave principal en la tabla, queremos ver cómo se comporta cuando insertamos explícitamente la clave nosotros mismos.

SQLExecDirect:
INSERT INTO  "Application"."Cities" (
   "CityID"
  ,"CityName"
  ,"StateProvinceID"
  ,"LatestRecordedPopulation"
  ,"LastEditedBy"
  ,"ValidFrom"
  ,"ValidTo"
) VALUES (
   ?
  ,?
  ,?
  ,?
  ,?
  ,?
  ,?
)

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)
Esta vez, no hay gimnasia extra; dado que ya proporcionamos el valor de la clave principal, Access sabe que no tiene que intentar encontrar la fila nuevamente; simplemente ejecuta la declaración preparada para resincronizar la fila insertada. Volviendo al diseño original donde las Cities la tabla usó una SEQUENCE objeto para generar una nueva clave, podemos agregar una función VBA para obtener el nuevo número usando NEXT VALUE FOR y así poblar la clave de manera proactiva para obtener este comportamiento. Esto se aproxima más a cómo funciona el motor de base de datos de Access; tan pronto como ensuciamos un registro, obtiene una nueva clave del AutoNumber tipo de datos, en lugar de esperar hasta que el registro haya sido realmente insertado. Por lo tanto, si su base de datos usa SEQUENCE u otras formas de crear claves, puede valer la pena proporcionar un mecanismo para obtener la clave de manera proactiva para ayudar a eliminar las conjeturas que vimos que hacía Access con el primer ejemplo.

Actualización de un registro en un conjunto de registros

A diferencia de las inserciones en la sección anterior, las actualizaciones son relativamente más fáciles porque ya tenemos la clave presente. Por lo tanto, Access suele comportarse de forma más sencilla a la hora de actualizar. Hay dos comportamientos principales que debemos tener en cuenta al actualizar un registro que depende de la presencia de una columna de versión de fila.

Actualizar un registro sin una columna de versión de fila

Supongamos que modificamos solo una columna. Esto es lo que vemos en ODBC.

SQLExecute: (GOTO BOOKMARK)

SQLExecDirect:
UPDATE "Application"."Cities"
SET "CityName"=?
WHERE "CityID" = ?
  AND "CityName" = ?
  AND "StateProvinceID" = ?
  AND "Location" IS NULL
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
  AND "ValidFrom" = ?
  AND "ValidTo" = ?
Hmm, ¿cuál es el trato con todas esas columnas adicionales que no modificamos? Bueno, nuevamente, Access tiene que adoptar una perspectiva pesimista. Tiene que asumir que alguien posiblemente podría haber cambiado los datos mientras el usuario buscaba lentamente a través de las ediciones. Pero, ¿cómo sabría Access que alguien más cambió los datos en el servidor? Bueno, lógicamente, si todas las columnas son exactamente iguales, entonces debería haber actualizado solo una fila, ¿no? Eso es lo que busca Access cuando compara todas las columnas; para asegurarse de que la actualización solo afectará exactamente a una fila. Si encuentra que actualizó más de una fila o cero filas, revierte la actualización y devuelve un error o #Deleted al usuario.

Pero… eso es un poco ineficiente, ¿no? Además, esto podría traer problemas si hay una lógica del lado del servidor que podría cambiar los valores ingresados ​​por el usuario. Para ilustrar, supongamos que agregamos un disparador tonto que cambia el nombre de la ciudad (no recomendamos esto, por supuesto):

CREATE TRIGGER SillyTrigger
ON Application.Cities AFTER UPDATE AS
BEGIN
  UPDATE Application.Cities
  SET CityName = 'zzzzz'
  WHERE EXISTS (
    SELECT NULL
    FROM inserted AS i
    WHERE Cities.CityID = i.CityID
  );
END;
Entonces, si luego intentamos actualizar una fila cambiando el nombre de la ciudad, parecerá haber tenido éxito.

Pero si luego intentamos editarlo nuevamente, obtenemos un mensaje de error con un mensaje actualizado:

Esta es la salida de sqlout.txt :

SQLExecDirect:
UPDATE "Application"."Cities"
SET "CityName"=?
WHERE "CityID" = ?
  AND "CityName" = ?
  AND "StateProvinceID" = ?
  AND "Location" IS NULL
  AND "LatestRecordedPopulation" = ?
  AND "LastEditedBy" = ?
  AND "ValidFrom" = ?
  AND "ValidTo" = ?

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (GOTO BOOKMARK)

SQLExecute: (MULTI-ROW FETCH)

SQLExecute: (MULTI-ROW FETCH)
Es importante tener en cuenta que el segundo GOTO BOOKMARK y el subsiguiente MULTI-ROW FETCH es no sucedió hasta que recibimos el mensaje de error y lo descartamos. La razón es que cuando ensuciamos un registro, Access realiza un GOTO BOOKMARK , se da cuenta de que los datos devueltos ya no coinciden con los que tienen en el caché, lo que hace que recibamos el mensaje "Los datos han sido cambiados". Eso evita que perdamos tiempo editando un registro que está condenado al fracaso porque ya está obsoleto. Tenga en cuenta que Access también descubrirá el cambio si le damos suficiente tiempo para actualizar los datos. En ese caso, no habría ningún mensaje de error; la hoja de datos simplemente se actualizaría para mostrar los datos correctos.

Sin embargo, en esos casos, Access tenía la clave correcta, por lo que no tuvo problemas para descubrir los nuevos datos. ¿Pero si es la llave la que es frágil? Si el activador había cambiado la clave principal o la fuente de datos ODBC no representaba el valor exactamente como Access pensó que lo haría, eso haría que Access pintara el registro como #Deleted ya que no puede saber si fue editado por el servidor o por otra persona o si fue eliminado legítimamente por otra persona.

Actualizar un registro con la columna rowversion

De cualquier manera, aparece un mensaje de error o #Deleted puede ser bastante molesto. Pero hay una manera de evitar que Access compare todas las columnas. Eliminemos el disparador y agreguemos una nueva columna:

ALTER TABLE Application.Cities
ADD RV rowversion NOT NULL;
Agregamos una rowversion que tiene la propiedad de estar expuesto a ODBC como si tuviera SQLSpecialColumns(SQL_ROWVER) , que es lo que Access necesita saber para que pueda usarse como una forma de versionar la fila. Veamos cómo funcionan las actualizaciones con este cambio.

SQLExecDirect: UPDATE "Application"."Cities" 
SET "CityName"=?  
WHERE "CityID" = ? 
  AND "RV" = ?

SQLExecute: (GOTO BOOKMARK)
A diferencia del ejemplo anterior donde Access comparó el valor en cada columna, ya sea que el usuario lo haya editado o no, solo actualizamos el registro usando el RV como criterio de filtrado. El razonamiento es que si el RV todavía tiene el mismo valor que el que pasó Access, entonces Access puede estar seguro de que esta fila no fue editada por nadie más porque si lo fue, entonces el RV el valor de habría cambiado.

También significa que si un disparador alteró los datos o si SQL Server y Access no representaron un valor exactamente de la misma manera (por ejemplo, números flotantes), Access no se bloqueará cuando vuelva a seleccionar la fila actualizada y vuelva con diferentes valores en otras columnas que los usuarios no editaron.

NOTA :No todos los productos DBMS utilizarán los mismos términos. Como ejemplo, la timestamp de MySQL se puede usar como una versión de fila para los propósitos de ODBC. Deberá consultar la documentación del producto para ver si admiten la función de versión de fila para que pueda aprovechar este comportamiento con Access.

Vistas y versión de fila

Las vistas también se ven afectadas por la presencia o ausencia de una versión de fila. Supongamos que creamos una vista en SQL Server con la definición:

CREATE VIEW dbo.vwCities AS
SELECT
	CityID,
	CityName
FROM Application.Cities;
Actualizar un registro en la vista revertiría a la comparación columna por columna como si la columna de versión de fila no existiera en la tabla:

SQLExecDirect: UPDATE "dbo"."vwCities" SET "CityName"=?  WHERE "CityID" = ? AND "CityName" = ?
Por lo tanto, si necesita el comportamiento de actualización basado en rowversion, debe asegurarse de que las columnas de rowversion estén incluidas en las vistas. En el caso de una vista que contiene varias tablas unidas, es mejor incluir al menos las columnas de versión de fila de la(s) tabla(s) donde pretende actualizar. Debido a que, por lo general, solo se puede actualizar una tabla, incluir solo una versión de fila puede ser suficiente como regla general.

Eliminar un registro en un conjunto de registros

La eliminación de un registro se comporta de manera similar a las actualizaciones y también utilizará la versión de fila si está disponible. En una tabla sin una versión de fila, obtenemos:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "CityName" = ? 
  AND "StateProvinceID" = ? 
  AND "Location" IS NULL 
  AND "LatestRecordedPopulation" = ? 
  AND "LastEditedBy" = ? 
  AND "ValidFrom" = ? 
  AND "ValidTo" = ?
En una tabla con una versión de fila, obtenemos:

SQLExecDirect: 
DELETE FROM "Application"."Cities" 
WHERE "CityID" = ? 
  AND "RV" = ?
Una vez más, Access tiene que ser pesimista sobre la eliminación, ya que se trata de actualizar; no querría eliminar una fila que otra persona cambió. Por lo tanto, utiliza el mismo comportamiento que vimos con la actualización para evitar que varios usuarios cambien los mismos registros.

Conclusiones

Hemos aprendido cómo Access maneja las modificaciones de datos y mantiene su caché local sincronizada con la fuente de datos ODBC. Vimos cuán pesimista era Access, que se vio impulsado por la necesidad de admitir tantas fuentes de datos ODBC como fuera posible sin depender de suposiciones o expectativas específicas de que dichas fuentes de datos ODBC admitirán una característica determinada. Por esa razón, vimos que Access se comportará de manera diferente dependiendo de cómo se defina la clave para una tabla vinculada ODBC determinada. Si pudimos insertar explícitamente una nueva clave, esto requirió el trabajo mínimo de Access para volver a sincronizar el caché local para el registro recién insertado. Sin embargo, si permitimos que el servidor complete la clave, Access tendrá que hacer un trabajo adicional en segundo plano para volver a sincronizar.

También vimos que tener una columna en la tabla que se puede usar como una versión de fila puede ayudar a reducir la conversación entre Access y la fuente de datos ODBC en una actualización. Debería consultar la documentación del controlador ODBC para determinar si es compatible con la versión de fila en la capa ODBC y, de ser así, incluir dicha columna en las tablas o las vistas antes de vincular a Access para obtener los beneficios de las actualizaciones basadas en la versión de fila.

Ahora sabemos que para cualquier actualización o eliminación, Access siempre intentará verificar que la fila no haya cambiado desde la última vez que Access la buscó, para evitar que los usuarios realicen cambios inesperados. Sin embargo, debemos considerar los efectos que surgen al realizar cambios en otros lugares (por ejemplo, activación del lado del servidor, ejecución de una consulta diferente en otra conexión) que pueden hacer que Access concluya que la fila se modificó y, por lo tanto, no permita el cambio. Esa información nos ayudará a analizar y evitar crear una secuencia de modificaciones de datos que pueda contradecir las expectativas de Access cuando vuelva a sincronizar el caché local.

En el próximo artículo, veremos los efectos de aplicar filtros en un conjunto de registros.

Obtenga ayuda de nuestros expertos en acceso hoy. Llame a nuestro equipo al 773-809-5456 o envíenos un correo electrónico a [email protected].