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

Inserções em massa ou atualização para tabelas com campos de anexo


Desde o Access 2010, o Access oferece suporte ao tipo de dados Anexos que, à primeira vista, parece um recurso conveniente para armazenar pequenas imagens ou arquivos. No entanto, uma pesquisa rápida no Google geralmente mostrará que é melhor evitá-los. Tudo isso se resume ao fato de que um tipo de dados Attachments é na verdade um Multi-Valued Field (MVF), e estes vêm com vários problemas. Por um lado, você não poderá usar consultas para inserir ou atualizar vários registros de uma só vez. De fato, quaisquer tabelas que contenham esse tipo de dados forçam você a fazer muito código e, por esse motivo, evitamos usar esses tipos de dados normalmente.

No entanto, há um problema. Adoramos usar a galeria de imagens e temas, os quais dependem de uma tabela de sistema, MSysResources que infelizmente usa os tipos de dados de anexo. Isso criou um problema para gerenciar recursos em nossa biblioteca padrão porque queremos usar o MSysResources mas não podemos facilmente atualizá-los ou inseri-los em massa.

O tipo de dados do anexo (assim como os MVFs) força você a usar a programação “linha por linha agonizante” ao lidar com um campo MVF, é um twofer com o campo Attachments porque você teria que usar o LoadFromFile ou SaveToFile métodos. A Microsoft tem um artigo com exemplos sobre esses métodos. Assim, você deve interagir com o sistema de arquivos ao adicionar novos registros. Nem sempre desejável em todas as situações. Agora, se estamos copiando de uma tabela para outra, podemos evitar saltar sobre o sistema de arquivos fazendo 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 looping, batman! Isso é muito código, tudo apenas para copiar anexos de uma tabela para outra. Mesmo que não saltemos sobre o sistema de arquivos, também é muito lento. Em nossa experiência, uma tabela com 1.000 registros contendo um único anexo pode levar minutos apenas para processar. Agora, isso é bastante desproporcional quando você considera o tamanho. A mesa com os acessórios não é tão grande. Na verdade, vamos fazer um experimento. Vamos ver o que acontece se eu copiar e colar via folha de dados:



Portanto, copiar e colar é praticamente instantâneo. Obviamente, o código usado para colar não é o mesmo código que usaríamos no VBA. No entanto, acreditamos muito que, se pudermos fazer isso de forma interativa, também podemos fazê-lo em VBA. Podemos replicar a velocidade da colagem interativa no VBA? A resposta é sim, nós podemos!

Acelere com …. XML?


Surpreendentemente, o método que fornece a maneira mais rápida de copiar dados, incluindo anexos, é por meio de arquivos XML. Admito que não busco arquivos XML, exceto como solução alternativa para limitações. Em média, os arquivos XML são relativamente lentos para outros formatos de arquivo, mas, neste caso, o XML tem uma grande vantagem; ele não tem nenhum problema em descrever MVFs. Vamos criar um arquivo XML e investigar os recursos que obtemos com a importação/exportação de um arquivo XML.



Após a caixa de diálogo do assistente de exportação usual para definir o caminho para salvar o arquivo XML, obteremos uma caixa de diálogo como esta:



Se clicarmos no botão “More Option…”, obteremos esta caixa de diálogo:



A partir desse diálogo, vemos mais algumas pistas sobre o que é possível; nomeadamente:
  • Temos a opção de exportar a tabela inteira ou apenas um subconjunto da tabela aplicando um filtro
  • Podemos transformar a saída XML.
  • Podemos descrever o esquema além do conteúdo da tabela.

Acho que é melhor incorporar o esquema; o padrão é exportá-lo, mas como um arquivo separado. No entanto, isso pode ser propenso a erros e eles podem esquecer de incluir o arquivo XSD com o arquivo XML. Isso pode ser alterado por meio da guia de esquema mostrada:



Vamos terminar de exportá-lo e dar uma olhada rápida nos dados do arquivo XML resultante.



Observe que os anexos são descritos dentro do Data o conteúdo da subárvore e do arquivo é codificado em base 64. Vamos tentar importar o arquivo XML. Depois de passar pelo assistente de importação, obteremos esta caixa de diálogo:



Observe as seguintes características:
  • Assim como na exportação, temos a opção de transformar o XML.
  • Podemos controlar se importamos a estrutura, os dados ou ambos

Se terminarmos de importar o arquivo XML, descobriremos que é tão rápido quanto a operação de copiar e colar que fizemos.

Agora sabemos que há um caminho melhor para copiar vários registros com anexos. Mas nesta situação, queremos fazer isso de forma programática, em vez de interativamente. Podemos fazer a mesma coisa que acabamos de fazer? Novamente a resposta é sim. Existem várias maneiras de fazer a mesma coisa, mas acho que o método mais fácil é usar os 3 novos métodos que foram adicionados ao Application objeto desde o Access 2010:
  • ExportXML método
  • TransformXML método
  • ImportXML método

Observe que o ExportXML O método suporta a exportação de vários objetos. No entanto, como o objetivo aqui é poder copiar ou atualizar em massa os registros de uma tabela com campos de anexo, o melhor tipo de objeto para usarmos é uma consulta salva. Com uma consulta salva, podemos controlar quais linhas devem ser inseridas ou atualizadas e também podemos moldar a saída. Se você observar o design do esquema do MSysResources tabela abaixo:



Há um problema em potencial. Sempre que usamos temas ou imagens, referenciamos o item pelo nome, não pelo ID. No entanto, o Nome coluna não é exclusiva e não é a chave primária da tabela. Portanto, quando adicionamos ou atualizamos registros, queremos corresponder no Nome coluna, não o Id coluna. Isso significa que, quando exportamos, provavelmente não devemos incluir o Id coluna e devemos exportar apenas a lista exclusiva do Nome para garantir que os recursos não vão repentinamente de “Open.png” para “Close.png” ou algo bobo.

Em seguida, criaremos uma consulta para atuar como a origem dos registros que queremos importar para o MSysResources tabela. Vamos começar com este SQL apenas para demonstrar a filtragem para um subconjunto de registros:
SELECT e.Data, e.Extension, e.Name, e.Type
FROM Example AS e
WHERE e.Name In ("blue","red","green");

Vamos salvá-lo como qryResourcesExport . Podemos então escrever código VBA para exportar XML:
Application.ExportXML _
  ObjectType:=acExportQuery, _
  DataSource:="qryResourcesExport", _
  DataTarget:="C:\Path\to\Resources.xml", _
  OtherFlags:=acEmbedSchema

Isso emula a exportação que originalmente fizemos interativamente.

No entanto, se importarmos o XML resultante, teremos apenas a opção de anexar dados a uma tabela existente. Não podemos controlar em qual tabela ele será anexado; ele irá encontrar uma tabela ou tabela de consulta com o mesmo nome (por exemplo, qryResourcesExport e anexar registros a essa consulta. Se a consulta for atualizável, não há problema e ela será inserida no Exemplo em que a consulta se baseia. Mas e se a consulta de origem que usamos não for atualizável ou não existir? Em ambos os casos, não poderíamos importar o arquivo XML como está. Ele pode falhar ao importar ou acabar criando uma nova tabela chamada qryResourcesExport o que não nos ajuda. E o caso de copiar dados de Exemplo para MSysResources ? Não queremos anexar dados ao Exemplo tabela.

É aí que o TransformXML método vem para resgatar. Uma discussão completa sobre como escrever uma transformação XML está além do escopo, mas você poderá encontrar amplos recursos sobre como escrever uma folha de estilo XSLT para descrever a transformação. Existem várias ferramentas online que você pode usar para validar seu XSLT também. Aqui está um. Para o caso simples em que queremos apenas controlar em qual tabela o arquivo XML deve anexar os registros, você pode começar com este arquivo XSLT. Você pode então executar o seguinte 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 substituir o arquivo XML original pelo arquivo XML transformado, que agora será inserido no MSysResources tabela em vez de em (possivelmente consulta/tabela inexistente) qryResourcesExport .

Em seguida, precisamos lidar com as atualizações. Porque na verdade estamos anexando novos registros, e o MSysResources table não tem nenhuma restrição nos nomes duplicados, precisamos garantir que todos os registros existentes com os mesmos nomes sejam excluídos primeiro. Isso pode ser feito escrevendo uma consulta equivalente assim:
DELETE FROM MSysResources AS r
WHERE r.Name In ("blue","red","green");

em seguida, execute-o primeiro antes de executar o código VBA:
Application.ImportXML DataSource:="C:\Path\to\Resources.xml", ImportOptions:=acAppendData

Como o arquivo XML foi transformado, o ImportXML O método agora inserirá os dados no MSysResources tabela em vez da consulta original que usamos com o ExportXML método. Especificamos que ele deve anexar dados em uma tabela existente. No entanto, se a tabela não existir, ela será criada.

E com isso, conseguimos uma atualização/inserção em massa da tabela com um campo de anexo que é muito mais rápido em comparação com o código VBA original do conjunto de registros e do conjunto de registros filho. Espero que ajude! Além disso, se precisar de ajuda com o desenvolvimento de aplicativos Access, não hesite em nos contatar!