I have an Access database which I would like to export to a text file. I have a schema defined within Access, and currently use a macro to export it. I would like to use VBScript to always append the result of a query to the same file. If it is not possible to use my defined schema, I only need the fields to be comma separated and enclosed by the ", and the text file must be in UTF-8 format.
I found the following code snippet, but I am unsure how to adopt it for my needs.
db = "C:\Docs\LTD.mdb"
TextExportFile = "C:\Docs\Exp.txt"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open _
"Provider = Microsoft.Jet.OLEDB.4.0; " & _
"Data Source =" & db
strSQL = "SELECT * FROM tblMembers"
rs.Open strSQL, cn, 3, 3
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.CreateTextFile(TextExportFile, True)
a = rs.GetString
f.WriteLine a
f.Close
DIRECTION (2)
This is some VBA, run from the Access database:
Sub InsertRecs()
Set db = CurrentDb
'DSN=Suitable system DSN for MySQL
'Then, depending on your set up, you can incude:
'Database=DBName;
'Trusted_Connection=Yes;
'NameOfMySQLTable
strSQL = "INSERT INTO [ODBC;DSN=baywotch;].tblAuction Select * FROM tblAuction;"
db.Execute strSQL, dbFailOnError
End Sub
This is the same thing, but in VBScript, using DAO:
Dim objEngine
Dim objWS
Dim objDB
Dim db: db = "C:\Docs\baywotch.db5"
Set objEngine = wscript.CreateObject("DAO.DBEngine.36")
Set objDB = objEngine.OpenDatabase(db)
objDB.Execute "INSERT INTO [ODBC;DSN=baywotch].[tblAuction] SELECT * FROM tblAuction;"
DIRECTION (1)
I suggest a completely different direction, and that is to let MySQL do the work:
MySQL Migration Toolkit
I tested this against your database, and it appears to import correctly, only takes a few minutes, and will generate all sorts of reusable scripts and so on.
If you are having problems with the set-up of MySQL, you may wish to read:
9.1.4. Connection Character Sets and Collations
DiRECTION (0)
REWRITE (2)
'========================================================================'
'
' FROM: AnthonyWJones, see post '
'
'========================================================================'
Dim db: db = "C:\Docs\baywotch.db5"
Dim exportDir: exportDir = "C:\Docs\" '" SO prettify does not do VB well
Dim exportFile: exportFile=NewFileName(exportDir)
Dim cn: Set cn = CreateObject("ADODB.Connection")
cn.Open _
"Provider = Microsoft.Jet.OLEDB.4.0; " & _
"Data Source =" & db
cn.Execute "SELECT * INTO [text;HDR=Yes;Database=" & exportDir & _
";CharacterSet=65001]." & exportFile & " FROM tblAuction"
'Export file
'========================================================================'
'Support functions
Function NewFileName(ExportPath)
Dim fs
Dim NewFileTemp
Set fs = CreateObject("Scripting.FileSystemObject")
NewFileTemp = "CSV" & Year(Date) _
& Right("00" & Month(Date),2) & Right("00" & Day(Date) ,2) & ".csv"
a = fs.FileExists(ExportPath & NewFileTemp)
i = 1
Do While a
NewFileTemp = "CSV" & Year(Date) _
& Right("00" & Month(Date),2) & Right("00" & Day(Date) ,2) & "_" & i & ".csv"
a = fs.FileExists(ExportPath & NewFileTemp)
i = i + 1
If i > 9 Then
'Nine seems enough times per day to be
'exporting a table
a = True
MsgBox "Too many attempts"
WScript.Quit
End If
Loop
NewFileName = NewFileTemp
End Function
Perhaps the easiest way is to use [text...].filename approach:-
Dim db: db = "C:\Docs\LTD.mdb"
Dim exportDir: exportDir = "C:\Docs\" '" SO prettify does not do VB well
Dim exportFile: exportFile = "Exp.txt"
Dim cn: Set cn = CreateObject("ADODB.Connection")
cn.Open _
"Provider = Microsoft.Jet.OLEDB.4.0; " & _
"Data Source =" & db
cn.Execute "SELECT * INTO [text;HDR=Yes;Database=" & exportDir & _
";CharacterSet=65001]." & exportFile & " FROM tblMembers"
FileSystemObject won't help you since it doesn't do UTF-8. UTF-8 is acheived by specifying CharacterSet=65001 (65001 is the UTF-8 codepage). Note the file generated does not contain a UTF-8 BOM but the schema.ini file created will note that the CharacterSet is UTF-8.
Note this doesn't achieve your append requirements are you sure that makes sense anyway, won't you end up with lots of duplicates?
Edit:
The above is adjusted to include the UTF-8 requirement. You can simply append something like the date to create multiple snapshot files for the table.
I've numbered the lines for reference.
1. db = "C:\Docs\LTD.mdb"
2. TextExportFile = "C:\Docs\Exp.txt"
3. strSQL = "SELECT * FROM tblMembers"
4. Set f = fs.CreateTextFile(TextExportFile, True)
Line 1 - is the current access database file you are working with. this case it's LTD.mdb
Line 2 - is the name of the file that you are going to write/append. It's Exp.txt
Line 3 - is the sql statement that will be used to collect the data.
Line 4 - is the command to open the file to write to.
Change line 2 to the name of file you want.
Change line 3 to the table you want to use. Select * will use all the columns if you want only a couple identify them by name. select col1, col2 ... from mytable. You will want to look into using where clauses also.
Change line 4 from CreateTextFile to OpenTextFile and use ForAppending to append.
MSDN VBA
I'm drawing a blank on formatting the line. One of the ways I use is modify the select statement to include commas. example select col1 & "," & col2 from mytable.
For UTF-8 (I don't have a working example) Try:
utf = new String(a, 0, a.length, UTF-8);
f.WriteLine utf;
UTF-8 VBA
Const adOpenStatic = 3
Const adLockOptimistic = 3
Const adClipString = 2
Const ForWriting = 2
Const ForAppending = 8
Const strDB = "C:\Docs\LTD.mdb"
Const strCSV = "C:\Docs\Exp.csv"
Set objAccessConnection = CreateObject("ADODB.Connection")
objAccessConnection.Open "Provider=Microsoft.Jet.OLEDB.4.0;Persist Security Info=False;Data Source=" & strDB
Set objAccessRecordset = CreateObject("ADODB.Recordset")
strAccessQuery = "SELECT * FROM tblMembers"
objAccessRecordset.Open strAccessQuery, objAccessConnection, adOpenStatic, adLockOptimistic
Set objCSV = CreateObject("Scripting.FileSystemObject").OpenTextFile(strCSV, ForAppending, True)
objCSV.Write objAccessRecordset.GetString(adClipString,,",",CRLF)
objCSV.Close
Set objCSV = Nothing
objAccessRecordset.Close
Set objAccessRecordset = Nothing
objAccessConnection.Close
Set objAccessConnection = Nothing
Related
I have an issue while trying to get every line from a table in my database. In VBA when requesting the table with 'SELECT * FROM companies;', the results duplicates the first row, and remove the last one. As a result, I have 3 records, which corresponds to the real number of records in my DB, but instead of having 1, 2 and 3, I have 1, 1 and 2.
Any idea?
You can see here Database records for table 'companies', when requesting 'SELECT * FROM companies': DB Records
You can see here the result of the same request in Excel/VBA using the following code:
sqlQuery = "SELECT ALL * FROM companies;"
rsDB.Open sqlQuery, conDB, adOpenDynamic
Do While Not rsDB.EOF
For Each col In rsDB.GetRows
Debug.Print col
Next
Loop
Results: VBA request
Would love to get any piece of advice on this issue!
The fun fact is that if I try to select only one column of the table, such as 'idCompany', then I have the result '1, 2, 3' with VBA, which is fine. The real issue only appears when using '*'.
Thanks a lot for your time,
--- EDIT
The connection string used to connect to the DB:
Set conDB = New ADODB.Connection
Set rsDB = New ADODB.recordSet
Set rsDBCol = New ADODB.recordSet
conDB.connectionString = "DRIVER={MariaDB ODBC 3.1 Driver};" _
& "SERVER=s-mypricing-1;" _
& "DATABASE=db_pricing;" _
& "PORT=3306" _
& "UID=user;" _
& "PWD=pwd;" _
& "OPTION=3"
conDB.Open
rsDB.CursorLocation = adUseServer
rsDBCol.CursorLocation = adUseServer
Difficult to test, but I suspect you need this instead:
rsDB.MoveFirst
Do While Not rsDB.EOF
For Each fld In rsDB.Fields
Debug.Print fld.Name & ": " & fld.Value
Next
rsDB.MoveNext
Loop
When you iterate an ADO recordset, the object itself represents a current row. So you refer to the Fields of the current row to get the columns. And the properties of each field to get descriptive information about that cell (name of column, value in cell).
Through the comments we learned that the issue is related to opening the recordset with adOpenDynamic mode. What follows is code that should represent a working case for MaraiaDB.
Set conDB = New ADODB.Connection
Set rsDB = New ADODB.recordSet
Set rsDBCol = New ADODB.recordSet
conDB.connectionString = "DRIVER={MariaDB ODBC 3.1 Driver};" _
& "SERVER=s-mypricing-1;" _
& "DATABASE=db_pricing;" _
& "PORT=3306" _
& "UID=user;" _
& "PWD=pwd;" _
& "OPTION=3"
conDB.Open
rsDB.CursorLocation = adUseServer
rsDBCol.CursorLocation = adUseServer
sqlQuery = "SELECT ALL * FROM companies;"
With rsDB.Open(sqlQuery, conDB)
If Not (.BOF And .EOF) Then
.MoveFirst
Do Until .EOF
For Each fld In .Fields
Debug.Print fld.Name & ": " & fld.Value
Next
.MoveNext
Loop
End If
.Close
End With
conDB.Close
I would like to import a dataset from a MySQL database into Excel not using additional references or add-ins (so colleagues can use it without changing anything in their setup). The solutions I have found so far all use additional references or things that are not active by default.
The database contains a growing number of datasets all named in a standardised way and the user should be able to choose which dataset to import.
I am a VBA-semi-noob and have managed to get the basic idea working for one specific dataset (using macro editor) , but I am unable to get it working with variable dataset names.
What works so far is the following (dataset name in this example is "scada_pl_oxidation_study_14102020", database is currently local but will change to remote in future)
'Insert table from MySQL database
Application.CutCopyMode = False
Sheets("Raw Data").Select
Range("A1").Select
ActiveWorkbook.Queries.Add Name:= _
"cndatabase scada_pl_oxidation_study_14102020", Formula:= _
"let" & Chr(13) & "" & Chr(10) & " Source = MySQL.Database(""localhost"", ""cndatabase"", [ReturnSingleDatabase=true])," & Chr(13) & "" & Chr(10) & " cndatabase_scada_pl_oxidation_study_14102020 = Source{[Schema=""cndatabase"",Item=""scada_pl_oxidation_study_14102020""]}[Data]" & Chr(13) & "" & Chr(10) & "in" & Chr(13) & "" & Chr(10) & " cndatabase_scada_pl_oxidation_study_14102020"
With ActiveSheet.ListObjects.Add(SourceType:=0, Source:=Array( _
"OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=""cndatabase scada_pl_oxidation_study_14102020"";Extended Pr" _
, "operties="""""), Destination:=Range("'Raw Data'!$A$3")).QueryTable
.CommandType = xlCmdSql
.CommandText = Array( _
"SELECT * FROM [cndatabase scada_pl_oxidation_study_14102020]")
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.BackgroundQuery = True
.RefreshStyle = xlInsertDeleteCells
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = True
.RefreshPeriod = 0
.PreserveColumnInfo = True
.ListObject.DisplayName = "cndatabase_scada_pl_oxidation_study_14102020"
.Refresh BackgroundQuery:=False
End With
My initial idea was to use a Userform to just type the name of the dataset to be imported, but replacing the "scada_pl_oxidation_study_14102020" with a variable based on the Userform input does not seem to work. A solution where the user can choose from a list of datasets contained in the database would be preferred, but that is way beyond my capabilities.
Can anybody help me with this?
"A solution where the user can choose from a list of datasets contained
in the database would be preferred"
Create a UserForm with a ListBox and CommandButton and put this code on the form. When the form initializes it populates the list box with all the tables in the database that start with the word "scada". Select a table and press the button it should populate the "Raw Data" sheet with records from the selected table. You will have to amend the DSNless connection details to the driver you have.
Option Explicit
Private Sub UserForm_Initialize()
Const FILTER = "scada*"
Dim conn, cmd, rs
Set conn = DbConnect()
Set cmd = CreateObject("ADODB.Command")
With cmd
.CommandType = 1 'adCmdText
.CommandText = "SHOW TABLES"
.ActiveConnection = conn
End With
' populate list box
UserForm1.ListBox1.Clear
Set rs = CreateObject("ADODB.Recordset")
Set rs = cmd.Execute
rs.MoveFirst
While Not rs.EOF
If LCase(rs(0)) Like LCase(FILTER) Then
UserForm1.ListBox1.AddItem rs(0)
End If
rs.MoveNext
Wend
conn.Close
End Sub
' select table
Private Sub CommandButton1_Click()
Dim ws As Worksheet
Dim i As Long, sTable As String
Dim conn, cmd, rs
' select table
For i = 0 To ListBox1.ListCount
If ListBox1.Selected(i) Then sTable = ListBox1.List(i)
Next
If Len(sTable) = 0 Then Exit Sub
' connect to db
Set conn = DbConnect()
Set cmd = CreateObject("ADODB.Command")
With cmd
.CommandType = 1 'adCmdText
.CommandText = "SELECT * FROM " & sTable
.ActiveConnection = conn
End With
' run query
Set rs = CreateObject("ADODB.Recordset")
Set rs = cmd.Execute
' dump data to sheet
Set ws = ThisWorkbook.Sheets("Raw Data")
ws.Cells.Clear ' clear sheet
ws.Range("A3").CopyFromRecordset rs
conn.Close
End Sub
Function DbConnect() As Object
Const SERVER = "127.0.0.1" 'localhost
Const DB = "cndatabase"
Const UID = "****" ' user I suggest with SELECT only privilidges
Const PWD = "****" ' password
Set DbConnect = CreateObject("ADODB.Connection")
DbConnect.ConnectionString = "Driver={MySQL ODBC 8.0 ANSI Driver};" & _
"UID=" & UID & "; PWD=" & PWD & ";" & _
"SERVER=" & SERVER & ";" & _
"DATABASE=" & DB & ";" & _
"PORT=3306;" & _
"Initial Catalog=" & DB
DbConnect.Open
End Function
I have an issue that has developed while modifying an update event that previously had been working. I am now getting a Runtime Error 3144, "Syntax error in UPDATE statement." When I go to debug the following line of code is flagged.
Set qdf = db.CreateQueryDef(vbNullString, strUpdate)
This function previously was working as it it was meant to in creating a SQL string to run an Update command. However I needed to adapt this same function to a different but mostly similar form.
I have included the complete code below for review and could use some assistance in spotting whatever little detail I missed/messed up in the transfer.
Private Sub btnEntEdt_Click()
Dim strUpdate As String
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
strUpdate = "UPDATE EntList AS e" & vbCrLf & _
"SET e.BusinessUnit = pBusinessUnit, " & _
"e.EntityID = pEntityID, " & vbCrLf & _
"e.EntityName = pEntityName, " & vbCrLf & _
"e.Location = pLoc, " & vbCrLf & _
"e.Client = pCli, " & vbCrLf & _
"e.Dept = pDept, " & vbCrLf & _
"WHERE e.EntityID = pEntityID;"
Debug.Print strUpdate
Set db = CurrentDb
Set qdf = db.CreateQueryDef(vbNullString, strUpdate)
qdf.Parameters("pBusinessUnit") = Me.cboBUnit.Value
qdf.Parameters("pEntityName") = Me.txtEntName.Value
qdf.Parameters("pEntityID") = Me.txtEntID.Value
qdf.Parameters("pLoc") = Me.cboLoc.Value
qdf.Parameters("pCli") = Me.cboClient.Value
qdf.Parameters("pDept") = Me.cboDept.Value
qdf.Execute dbFailOnError
Set qdf = Nothing
Set db = Nothing
Me.lstEntName.Requery
End Sub
You were wise to include Debug.Print strUpdate. Examine its output ...
UPDATE EntList AS e
SET e.BusinessUnit = pBusinessUnit, e.EntityID = pEntityID,
e.EntityName = pEntityName,
e.Location = pLoc,
e.Client = pCli,
e.Dept = pDept,
WHERE e.EntityID = pEntityID;
That statement triggers an error because of the comma at the end of the SET clause.
e.Dept = pDept,
^ here
Eliminate that comma, test the revised UPDATE statement in the query designer, and once you have it working correctly, modify your VBA code to produce the same statement text.
Or you could save the working version as a named query, qryUpdateEntList, and then reference the named query from your VBA code instead of re-creating the statement text at run time.
Set qdf = db.QueryDefs("qryUpdateEntList")
Apart from the syntax error, this combination looks wrong to me ...
SET e.EntityID = pEntityID
WHERE e.EntityID = pEntityID
Because of the WHERE clause, the UPDATE will only affect rows where EntityID = pEntityID. So there is no need to SET EntityID = pEntityID; they are already equal.
I am attempting to write a number of records back to the database from a VB6 application using ADO. The records were read in from the database into an ADODB.Recordset and subsequently updated. I would like to be able to have each record in the set use the current date and time of the database server because I have no confidence that all our machines are time-synchronized. Is there a way to do this without having to query the database for CURRENT_TIMESTAMP (or something similar)? Ideally I'm hoping for some sort of flag that I can set the field value to so that the Recordset works this out on the database server.
See the question in my prototype code below (within the while-loop).
Dim oConn As ADODB.Connection
Dim oRs As ADODB.Recordset
Dim sSQL As String
Dim sColumnName1 As String: sColumnName1 = "UserNID"
Dim sColumnName2 As String: sColumnName2 = "Username"
Dim sColumnName3 As String: sColumnName3 = "LastLoginDTM"
' Open a connection.
Set oConn = New ADODB.Connection
oConn.Open 'my connection string was here
' Make a query over the connection.
sSQL = "SELECT TOP 10 " & sColumnName1 & ", " & sColumnName2 & ", " & sColumnName3 & " " & _
"FROM theTable"
Set oRs = New ADODB.Recordset
oRs.CursorLocation = adUseClient
oRs.Open sSQL, oConn, adOpenStatic, adLockBatchOptimistic, adCmdText
oRs.MoveFirst
While Not oRs.EOF
oRs.Fields(sColumnName3).Value = '-=-=-=-=-What do I put here???-=-=-=-=-
oRs.MoveNext
Wend
' Close the connection.
oConn.Close
Set oConn = Nothing
You could use the TSQL GetDate() Function
Change your Select to:
sSQL = "SELECT TOP 10 " & sColumnName1 & ", " & sColumnName2 & _
", " & sColumnName3 & ", GETDATE() AS ServerTimeStamp " & _
"FROM theTable"
Then, your code could be:
While Not oRs.EOF
oRs.Fields(sColumnName3).Value = oRS.Fields("ServerTimeStamp").Value
oRs.MoveNext
Wend
This code:
db = "C:\Dokumente und Einstellungen\hom\Anwendungsdaten\BayWotch4\Neuer Ordner\baywotch.db5"
TextExportFile = "C:\Dokumente und Einstellungen\hom\Anwendungsdaten\BayWotch4\Neuer Ordner\Exp.txt"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open _
"Provider = Microsoft.Jet.OLEDB.4.0; " & _
"Data Source =" & db
strSQL = "SELECT * FROM tblAuction1"
rs.Open strSQL, cn, 3, 3
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.CreateTextFile(TextExportFile, True, True)
a = rs.GetString
f.WriteLine a
f.Close
generates a tab delimited file, however it is not suitable for importing into mysql. I would like it to generate a file similar to a file produced by an access macro, which can be seen here:
http://www.yousendit.com/download/TTZtWmdsT01kMnVGa1E9PQ
The file produced by the vbscript is here:
http://www.yousendit.com/download/TTZtWmdsT00wVWtLSkE9PQ
I would also like to know why the file sizes differ by 50k or so.
Edit: The result from the vbscript file uses newline characters that are not recognized by notepad, so the above looks substantially messier when viewed. The macro does not seem to be exporting the html code, which explains why it is a smaller file, however the vbscript does not appear to be tab delimited, as it will not import into mysql.
Edit: the files look ok under a linux system, so it could be something to do with windows handling. However it is still not correct.
Both files contain what looks like tab-delimited data as well as HTML code (generated by some MS Office app, by the looks of it). Does tblAuction1 store any OLE Objects? Perhaps when you're exporting those objects it's exporting the file contents?
It looks like an encoding issue to me. I see that you are passing the Unicode parameter when you create the text file, but there is clearly an encoding difference between the two files.
What is the goal of this project? What is the purpose of creating the file? If you are just looking to move data from Access to MySQL, why not do it directly with something like this
Const Access = "Provider=Microsoft.Jet.OLEDB.4.0;Persist Security Info=False;Data Source=C:\Dokumente und Einstellungen\hom\Anwendungsdaten\BayWotch4\Neuer Ordner\baywotch.db5"
Const SQLServer = "Provider=SQLOLEDB.1;Integrated Security=SSPI;Data Source=.\SQLEXPRESS"
Dim arrFields
Set SQLConn = CreateObject("ADODB.Connection")
WITH SQLConn
.ConnectionString = SQLServer
.Open
End WITH
Set AccessConn = CreateObject("ADODB.Connection")
WITH AccessConn
.ConnectionString = Access
.Open
End WITH
Set SQLRS = CreateObject("ADODB.Recordset")
WITH SQLRS
.CursorType = 3
.LockType = 3
End WITH
Set AccessRS = CreateObject("ADODB.Recordset")
WITH AccessRS
.ActiveConnection = AccessConn
.CursorType = 3
.LockType = 3
End WITH
strSQL = "SELECT * FROM tblAuction1"
AccessRS.Open strSQL
If AccessRS.RecordCount <> 0 Then
AccessRS.MoveFirst
ReDim arrFields(AccessRS.Fields.Count)
Do Until AccessRS.BOF OR AccessRS.EOF
For i = 0 To AccessRS.Fields.Count - 1
If AccessRS.Fields(i).Type = 202 Then
arrFields(i) = Chr(39) & AccessRS.Fields(i).Value & Chr(39)
Else
arrFields(i) = AccessRS.Fields(i).Value
End If
Next
strSQL1 = "INSERT INTO {Table in mySQL} VALUES("
For j = 1 To UBound(arrFields) - 2
strSQL1 = strSQL1 & arrFields(j) & ","
Next
strSQL1 = strSQL1 & arrFields(UBound(arrFields) - 1) & ")"
SQLRS = SQLConn.Execute(strSQL1)
AccessRS.MoveNext
Loop
Else
MsgBox "No records found"
End If
This will add all of the records returned by the recordset to a table in an SQLExpress database,it should not be difficult to tweak to your needs (if your needs are transferring data from one database to another).