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

Inserciones masivas o actualización para tablas con campos adjuntos

Desde Access 2010, Access admite el tipo de datos adjuntos que, en apariencia, parece una característica conveniente para almacenar imágenes o archivos pequeños. Sin embargo, una búsqueda rápida en Google generalmente mostrará que es mejor evitarlos. Todo esto se reduce al hecho de que un tipo de datos adjuntos es en realidad un campo de varios valores (MVF), y estos vienen con varios problemas. Por un lado, no podrá usar consultas para insertar o actualizar varios registros de una sola vez. De hecho, cualquier tabla que contenga ese tipo de datos lo obliga a hacer una gran cantidad de código y solo por esa razón, evitamos usar dichos tipos de datos normalmente.

Sin embargo, hay un problema. Nos encanta usar la galería de imágenes y los temas, los cuales dependen de una tabla del sistema, MSysResources que desafortunadamente usa los tipos de datos adjuntos. Esto ha creado un problema para administrar recursos en nuestra biblioteca estándar porque queremos usar MSysResources pero no podemos actualizarlos o insertarlos de forma masiva fácilmente.

El tipo de datos adjuntos (así como los MVF) lo obligan a usar la programación "fila por fila agonizante" cuando se trata de un campo MVF, es un twofer con el campo Adjuntos porque tendría que usar el LoadFromFile o SaveToFile métodos. Microsoft tiene un artículo con ejemplos sobre esos métodos. Por lo tanto, debe interactuar con el sistema de archivos al agregar nuevos registros. No siempre deseable en todas las situaciones. Ahora, si estamos copiando de una tabla a otra tabla, podemos evitar rebotar sobre el sistema de archivos haciendo algo como:

Dim SourceParentRs As DAO.Recordset2
Dim SourceChildRs As DAO.Recordset2
Dim TargetParentRs As DAO.Recordset2
Dim TargetChildRs As DAO.Recordset2
Dim SourceField As DAO.Field2

Set SourceParentRs = db.OpenRecordset("TableWithAttachmentField", dbOpenDynaset)
Set TargetParentRs = db.OpenRecordset("AnotherTableWithAttachmentField", dbOpenDynaset, dbAppendOnly)

Do Until SourceParentRs.EOF
  TargetParentRs.AddNew
  For Each SourceField In SourceParentRs.Fields
    If SourceField.Type <> dbAttachment Then
      TargetParentRs.Fields(SourceField.Name).Value = SourceField.Value
    End If
  Next

  TargetParentRs.Update 'Must save record first before can edit MVF fields
  TargetParentRs.Bookmark = TargetParentRs.LastModified
  Set SourceChildRs = SourceParentRs.Fields("Data").Value
  Set TargetChildRs = TargetParentRs.Fields("Data").Value
  Do Until SourcechildRs.EOF
    TargetChildRs.AddNew
    Const ChunkSize As Long = 32768
    Dim TotalSize As Long
    Dim Offset As Long

    TotalSize = SourceChildRs.Fields("FileData").FieldSize
    Offset = TotalSize Mod ChunkSize
    TargetChildRs.Fields("FileData").AppendChunk(SourceChildRs.GetChunk(0, Offset)
    Do Until Offset > TotalSize
      TargetChildRs.Fields("FileData").AppendChunk(SourceChildRs.GetChunk(Offset, ChunkSize)
      Offset = Offset + ChunkSize
    Loop
    TargetChildRs.Update
    SourceChildRs.MoveNext
  Loop
  TargetParentRs.Update
  SourceParentRs.MoveNext
Loop

¡Santo bucle, batman! Eso es mucho código, todo solo para copiar archivos adjuntos de una tabla a otra. Aunque no rebotamos sobre el sistema de archivos, también es muy lento. Según nuestra experiencia, una tabla con 1000 registros que contienen un solo archivo adjunto puede tardar minutos solo para procesar. Ahora, esto es bastante grande si se considera el tamaño. La mesa con los accesorios no es tan grande. De hecho, hagamos un experimento. Veamos qué sucede si copio y pego a través de la hoja de datos:

Así que copiar y pegar es prácticamente instantáneo. Obviamente el código que se usa al pegar no es el mismo código que usaríamos en VBA. Sin embargo, creemos firmemente que si podemos hacerlo de forma interactiva, también podemos hacerlo en VBA. ¿Podemos replicar la velocidad del pegado interactivo en VBA? La respuesta resulta ser sí, ¡podemos!

Acelera con…. XML?

Sorprendentemente, el método que proporciona la forma más rápida de copiar datos, incluidos los archivos adjuntos, es a través de archivos XML. Admito que no busco archivos XML, excepto como una solución a las limitaciones. En promedio, los archivos XML son relativamente lentos en comparación con otros formatos de archivo, pero en este caso, XML tiene una gran ventaja; no tiene problemas para describir los MVF. Creemos un archivo XML e investiguemos las capacidades que obtenemos al importar/exportar un archivo XML.

Después del diálogo habitual del asistente de exportación para establecer la ruta para guardar el archivo XML, obtendremos un diálogo como este:

Si luego hacemos clic en el botón "Más opciones...", obtendremos este cuadro de diálogo en su lugar:

De este diálogo, vemos algunas pistas más sobre lo que es posible; a saber:

  • Tenemos la opción de exportar toda la tabla o solo un subconjunto de la tabla aplicando un filtro
  • Podemos transformar la salida XML.
  • Podemos describir el esquema además del contenido de la tabla.

Encuentro que es mejor incrustar el esquema; el valor predeterminado es exportarlo, pero como un archivo separado. Sin embargo, eso puede ser propenso a errores y pueden olvidarse de incluir el archivo XSD con el archivo XML. Esto se puede cambiar a través de la pestaña de esquema que se muestra:

Terminemos de exportarlo y echemos un vistazo rápido a los datos del archivo XML resultante.

Tenga en cuenta que los archivos adjuntos se describen dentro de los Data el subárbol y el contenido del archivo están codificados en base 64. Intentemos importar el archivo XML. Después de pasar por el asistente de importación, obtendremos este cuadro de diálogo:

Tome nota de las siguientes características:

  • Al igual que con la exportación, tenemos la opción de transformar el XML.
  • Podemos controlar si importamos la estructura, los datos o ambos

Si luego terminamos de importar el archivo XML, encontramos que es tan rápido como la operación de copiar y pegar que hicimos.

Ahora sabemos que hay un camino mejor para copiar varios registros con archivos adjuntos. Pero en esta situación, queremos hacer esto de forma programática, en lugar de interactiva. ¿Podemos hacer lo mismo que acabamos de hacer? De nuevo, la respuesta es sí. Hay varias formas de hacer lo mismo, pero creo que el método más fácil es usar los 3 nuevos métodos que se agregaron a la Application objeto desde Access 2010:

  • ExportXML método
  • TransformXML método
  • ImportXML método

Tenga en cuenta que el ExportXML El método admite la exportación desde varios objetos. Sin embargo, debido a que el objetivo aquí es poder copiar o actualizar en masa los registros de una tabla con campos adjuntos, el mejor tipo de objeto que podemos usar es una consulta guardada. Con una consulta guardada, podemos controlar qué filas se deben insertar o actualizar y también podemos dar forma a la salida. Si observa el diseño del esquema de MSysResources tabla a continuación:

Hay un problema potencial. Cada vez que usamos temas o imágenes, hacemos referencia al elemento por nombre, no por ID. Sin embargo, el Name columna no es única y no es la clave principal de la tabla. Por lo tanto, cuando agregamos o actualizamos registros, queremos hacer coincidir el Name columna, no el Id columna. Esto significa que cuando exportamos, probablemente no deberíamos incluir el Id columna y deberíamos exportar solo la lista única del Name para asegurarse de que los recursos no pasen repentinamente de "Open.png" a "Close.png" o algo tonto.

Luego crearemos una consulta para que actúe como la fuente de los registros que queremos importar a MSysResources mesa. Comencemos con este SQL solo para demostrar el filtrado a un subconjunto de registros:

SELECT e.Data, e.Extension, e.Name, e.Type
FROM Example AS e
WHERE e.Name In ("blue","red","green");

Luego lo guardaremos como qryResourcesExport . Luego podemos escribir código VBA para exportar XML:

Application.ExportXML _
  ObjectType:=acExportQuery, _
  DataSource:="qryResourcesExport", _
  DataTarget:="C:\Path\to\Resources.xml", _
  OtherFlags:=acEmbedSchema

Esto emula la exportación que hicimos originalmente de forma interactiva.

Sin embargo, si luego importamos el XML resultante, solo tenemos la opción de agregar datos a una tabla existente. No podemos controlar a qué tabla se agregará; encontrará una tabla o tabla de consulta con el mismo nombre (por ejemplo, qryResourcesExport y anexar registros a esa consulta. Si la consulta es actualizable, entonces no hay problema y se insertará en el Example en el que se basa la consulta. Pero, ¿qué pasa si la consulta de origen que usamos no es actualizable o puede que no exista? En cualquiera de los casos, no podríamos importar el archivo XML tal como está. Podría fallar al importar o terminar creando una nueva tabla llamada qryResourcesExport que no nos ayuda. Y qué pasa con el caso de copiar datos de Example a MSysResources ? No queremos agregar datos al Example mesa.

Ahí es donde el TransformXML método viene al rescate. Una discusión completa sobre cómo escribir una transformación XML está más allá del alcance, pero debería poder encontrar amplios recursos sobre cómo escribir una hoja de estilo XSLT para describir la transformación. También hay varias herramientas en línea que puede usar para validar su XSLT. Aquí hay uno. Para el caso simple en el que solo queremos controlar en qué tabla el archivo XML debe agregar los registros, puede comenzar con este archivo XSLT. A continuación, puede ejecutar el siguiente código VBA:

Application.TransformXML _
  DataSource:="C:\Path\to\Resources.xml", _
  TransformSource:="C:\Path\to\ResourcesTransform.xslt", _
  OutputTarget:="C:\Path\to\Resources.xml", _
  WellFormedXMLOutput:=True, _
  ScriptOption:=acEnableScript

Podemos reemplazar el archivo XML original con el archivo XML transformado, que ahora se insertará en MSysResources tabla en lugar de en (posiblemente consulta/tabla inexistente) qryResourcesExport .

Entonces tenemos que manejar las actualizaciones. Porque en realidad estamos agregando nuevos registros y MSysResources table no tiene ninguna restricción sobre los nombres duplicados, debemos asegurarnos de que todos los registros existentes con los mismos nombres se eliminen primero. Esto se puede lograr escribiendo una consulta equivalente como esta:

DELETE FROM MSysResources AS r
WHERE r.Name In ("blue","red","green");

luego ejecútelo primero antes de ejecutar el código VBA:

Application.ImportXML DataSource:="C:\Path\to\Resources.xml", ImportOptions:=acAppendData

Debido a que el archivo XML se transformó, ImportXML El método ahora insertará los datos en MSysResources tabla en lugar de la consulta original que usamos con ExportXML método. Especificamos que debe agregar datos a una tabla existente. Sin embargo, si la tabla no existe, se creará.

Y con eso, hemos logrado una actualización/inserción masiva de la tabla con un campo adjunto que es mucho más rápido en comparación con el código VBA original de conjunto de registros y conjunto de registros secundario. ¡Espero que ayude! Además, si necesita ayuda para desarrollar aplicaciones de Access, ¡no dude en contactarnos!