In questo articolo riporto alcune parti del codice che ho usato per creare un’applicazione che converte file MDB (Microsoft Access 2000) in database SQLite. Non penso che sia un problema comune a molti ma penso sia comodo al me del futuro avere degli appunti un po’ più in ordine.

Tralascio la parte relativa alla creazione dell’interfaccia, penso sia abbastanza semplice da riprodurre anche senza una guida.
Inizializzo il menu
All’apertura della maschera principale inizializzo alcuni componenti:
Private Sub Form_Open(Cancel As Integer)
Forms!Menu!tableList.RowSource = ""
Forms!Menu!logExport = ""
pathOriginal
End Sub
Svuoto la lista delle tabelle da esportare impostando il campo RowSource a vuoto.
Forms!Menu!tableList.RowSource = ""
Svuoto anche il log delle operazioni di esportazione impostando il campo logExport a vuoto.
Forms!Menu!logExport = ""
Per semplificare le cose uso come database di esportazione un file uguale per tutti e posizionato nella stessa cartella. Ricavo la posizione dell’applicazione usando CurrentProject.path
Public Function pathOriginal() As String
Dim result As String
result = CurrentProject.path & "\NewSQLiteDB.db"
Forms!Menu!pathOriginalSQLiteDB = result
pathOriginal = result
End Function
Scelgo il file da esportare
Cliccando su Choose Database scelgo il file MDB da esportare. Dopo aver scelto il file mostro l’elenco delle tabelle e le seleziono tutte (in genere voglio esportate tutto un database, quindi per me è più comodo deselezionare). Infine uso il nome del database di partenza per impostare il nome del database di destinazione:
Private Sub btnChooseDatabase_Click()
SelectDatabase
nameNewDatabaseFromOriginalPath
ShowListTable
SelectAllTables
End Sub
La funzione per selezionare un file pare abbastanza semplice ma non lo è:
Public Function SelectDatabase() As String
Dim path As String
path = GetOpenFile()
Forms!Menu!pathDatabase = path
SelectDatabase = path
End Function
Il problema è che MS Access 2000 non possiede un metodo semplice per selezionare un file. Per farlo serve una funzione apposita, GetOpenFile()
. Non riporto qui il codice, sono circa 300 righe, ma su GitHub ho caricato il file GetFile.bas. Il codice non è mio, è stato creato qualche decennio fa da Ken Getz, a sua volta frutto di codice risalente al 1998.
Comunque, dopo aver capito come selezionare il database posso ricavare il nome del database di destinazione:
Public Function nameNewDatabaseFromOriginalPath() As String
Dim strFullPath As String
strFullPath = Forms!Menu!pathDatabase
Dim nameWithExtension As String
nameWithExtension = Right(strFullPath, Len(strFullPath) - InStrRev(strFullPath, "\"))
Dim name As String
name = Left(nameWithExtension, Len(nameWithExtension) - 3) & "db"
Forms!Menu!nameNewDatabase = name
nameNewDatabaseFromOriginalPath = name
End Function
In questo caso è sufficiente estrarre l’ultima parte dell’indirizzo con Right(strFullPath, Len(strFullPath) - InStrRev(strFullPath, "\"))
e poi sostituire l’estensione mdb
con db
usano un codice simile a questo: Left(nameWithExtension, Len(nameWithExtension) - 3) & "db"
.
Per mostrare la lista delle tabelle presente nel database di origine devo scomporre il problema in due parti. La prima è capire come leggere il nome delle tabelle, la seconda come mostrare i nomi a schermo.
Per capire come riempire una listbox è stato utile il post Listbox Add/Remove Item AC2000. In sintesi basta passare la lista dei nomi, separati da ;
al controllo tramite la proprietà RowSource
: MyList.RowSource = "Table1;Table2;Table3"
.
Invece per leggere i nomi delle tabelle devo stabilire una connessione al database:
Dim db As DAO.database
Dim tdf As DAO.TableDef
Set db = OpenDatabase(pathDatabase, False)
Poi posso usare la TableDefs collection (DAO) per estrarre i nomi delle tabelle:
For Each tdf In db.TableDefs
Debug.Print tdf.name
Next
Però non mi interessano i nomi delle tabelle di sistema o di quelle temporanee. Per evitare di aggiungere alla lista posso semplicemente filtrarle:
For Each tdf In db.TableDefs
If Not (tdf.name Like "MSys*" Or tdf.name Like "~*") Then
Debug.Print tdf.name
End If
Next
Unendo il tutto ottengo la funzione ShowListTable
:
Public Function ShowListTable() As Boolean
Forms!Menu!tableList.RowSource = ""
Dim database As String
database = Forms!Menu!pathDatabase
Dim db As DAO.database
Dim tdf As DAO.TableDef
Set db = OpenDatabase(database, False)
For Each tdf In db.TableDefs
If Not (tdf.name Like "MSys*" Or tdf.name Like "~*") Then
If Forms!Menu!tableList.RowSource = "" Then
Forms!Menu!tableList.RowSource = tdf.name
Else
Forms!Menu!tableList.RowSource = Forms!Menu!tableList.RowSource & ";" & tdf.name
End If
End If
Next
ShowListTable = True
End Function
Per selezionare tutte le tabelle nella list box uso una funzione scritta da Allen Browne
Public Function SelectAllTables() As Boolean
ListBoxSelectAll Forms!Menu!tableList
SelectAllTables = ListBoxSelectAll(Forms!Menu!tableList)
End Function
Public Function ListBoxSelectAll(ByVal lst As ListBox) As Boolean
Dim lngRow As Long
If lst.MultiSelect Then
For lngRow = 0 To lst.ListCount - 1
lst.Selected(lngRow) = True
Next
ListBoxSelectAll = True
End If
ListBoxSelectAll = True
End Function
Scelgo la cartella di destinazione
Anche scegliere la cartella di destinazione pare una cosa semplice, se non fosse che non lo è. O per lo meno non lo era alla fine degli anni 90.
Private Sub btnDestinationFolder_Click()
SelectDestinationFolder
End Sub
Public Function SelectDestinationFolder() As String
Dim path As String
path = BrowseFolder("Select destination folder")
Forms!Menu!destinationFolder = path
SelectDestinationFolder = path
End Function
In questo caso la funzione BrowseFolder
è una funzione che si occupa di aprire una finestra di dialogo per selezionare una cartella. Il codice originale è di Terry Kreft. Su GitHub ho caricato il file GetFolderName.bas con una copia del codice.
Creare il database SQLite
L’esportazione vera e propria prevede due operazioni distinte:
Private Sub btnExportTo_Click()
createNewDatabase
exportSelectedTables
MsgBox "COMPLETED"
End Sub
Per prima cosa creo un nuovo database SQLite, e poi lo riempio con le tabelle di Microsoft Access.
Il metodo più semplice per creare un nuovo database SQLite è di partire da uno vuoto già esistente, copiarlo nella cartella di destinazione e rinominarlo.
Public Function createNewDatabase() As Boolean
Dim originalDB As String
originalDB = Forms!Menu!pathOriginalSQLiteDB
Dim newNameDB As String
newNameDB = Forms!Menu!nameNewDatabase
Dim destinationFolder As String
destinationFolder = Forms!Menu!destinationFolder
CreateFolder destinationFolder
Dim destinationFile As String
destinationFile = destinationFolder & "\" & newNameDB
CopyAFileDeletingOld originalDB, destinationFile
createNewDatabase = DoesFileExist(destinationFile)
End Function
Per copiare un file devo prima di tutto verificare che esista. Per farlo uso una funzione semplice:
Public Function DoesFileExist(ByRef filePath) As Boolean
DoesFileExist = Dir(filePath) <> ""
End Function
La stessa cosa per le cartelle; devo assicurarmi che a cartella di destinazione esista. La funzione CreateFolder
si occupa di questo:
Public Function DoesFolderExist(ByRef folderPath As String) As Boolean
DoesFolderExist = Dir(folderPath, vbDirectory) <> ""
End Function
Public Function CreateFolder(ByRef folderPath As String) As Boolean
If Not DoesFolderExist(folderPath) Then
MkDir folderPath
End If
CreateFolder = DoesFolderExist(folderPath)
End Function
Per copiare un file posso usare FileCopy filePath, destinationFile
. Per eliminare un eventuale file precedente con lo stesso nome posso invece usare Kill destinationFile
. Unendo questi pezzi di codice posso creare la funzione CopyAFileDeletingOld
:
Public Function CopyAFileDeletingOld(ByRef filePath As String, ByRef destinationFile As String) As Boolean
If DoesFileExist(filePath) Then
If DoesFileExist(destinationFile) Then
Kill destinationFile
End If
FileCopy filePath, destinationFile
End If
CopyAFileDeletingOld = Dir(destinationFile) <> ""
End Function
Esportare le tabelle
La funzione che si occupa di esportare le tabelle è questa:
Public Function exportSelectedTables() As Boolean
Dim dbAccess As String
dbAccess = Forms!Menu!pathDatabase
Dim newNameDB As String
newNameDB = Forms!Menu!nameNewDatabase
Dim destinationFolder As String
destinationFolder = Forms!Menu!destinationFolder
Dim destinationFile As String
destinationFile = destinationFolder & "\" & newNameDB
updateMessage "START"
Dim t As Variant
For Each t In Forms!Menu!tableList.ItemsSelected()
Dim nameTable As String
nameTable = Forms!Menu!tableList.Column(0, t)
Dim message As String
message = Forms!Menu!logExport
updateMessage nameTable & ": EXPORT" & vbCrLf & message
ExportFromOtherDatabaseToSQLite dbAccess, nameTable, destinationFile
updateMessage nameTable & ": OK" & vbCrLf & message
Next
exportSelectedTables = True
End Function
La funzione updateMessage
è solamente una funzione che aggiorna il messaggio di log e non concorre all’esportazione:
Public Function updateMessage(ByVal message As String) As String
Application.Echo False
Forms!Menu!logExport = message
Forms!Menu!logExport.Requery
Application.Echo True
updateMessage = message
End Function
La parte importante è ExportFromOtherDatabaseToSQLite dbAccess, nameTable, destinationFile
. Questa funzione accetta in ingresso la posizione del database di origine, il nome della tabella da esportare e la posizione del database di destinazione.
Public Function ExportFromOtherDatabaseToSQLite(ByVal dbAccess As String, ByVal table As String, ByVal dbSQLite As String) As Boolean
Dim db As DAO.database
Set db = OpenDatabase(dbAccess, False)
DoCmd.TransferDatabase acImport, "Microsoft Access", dbAccess, acTable, table, table, False
ExportToSQLite table, dbSQLite
DoCmd.DeleteObject acTable, table
db.Close
ExportFromOtherDatabaseToSQLite = True
End Function
Per semplificare le cose importo le tabelle che devo poi esportare usando:
DoCmd.TransferDatabase acImport, "Microsoft Access", dbAccess, acTable, table, table, False
Poi, dopo aver finito l’esportazione, elimino la tabella con
DoCmd.DeleteObject acTable, table
La funzione ExportToSQLite table, dbSQLite
cerca la tabella table
e la esporta in dbSQLite
:
Public Function ExportToSQLite(ByVal table As String, ByVal database As String) As Boolean
DoCmd.TransferDatabase acExport, "ODBC", "ODBC;DSN=SQLite3 Datasource;Database=" & database & ";StepAPI=0;SyncPragma=NORMAL;NoTXN=0;Timeout=100000;ShortNames=0;LongNames=0;NoCreat=0;NoWCHAR=0;FKSupport=0;JournalMode=;OEMCP=0;LoadExt=;BigInt=0;JDConv=0;", acTable, table, table, False
ExportToSQLite = True
End Function
Bene, con questo è tutto. Questo progetto è stato molto interessante perché mi ha richiesto un lavoro di ricerca su alcune problematiche di qualche decennio fa. Ho trovato molto istruttivo confrontarmi con alcuni limiti di MS Access. E, a dire il vero, anche molto frustante dover cercare dei workaround per risolvere cose che oggi dò per scontate.