VBA convert DAO connection string and Recordset to ODBC - vba

I have an add-in that's written in VBA in Word that is rather old. I think it dates back to 1997. Currently the VBA code will connect to an Access 2003 database and query a table and return a recordset of the data and generate a list of vendors from that table query.
Below is the code that uses the DAO method. The problem now is the newer computers that we are receiving that have windows 10 do not have the older libraries that the DAO method works with. (Plus DAO itself is old and outdated).
Public strData() As String
Sub GetData(strTable As String)
Dim dbf As DAO.Database
Dim rst As DAO.Recordset
Dim Counter As Long
Dim strCriteria As String
If strTable = "VendorQ" Then
strCriteria = "SELECT NameSort FROM Vendor Where Qualified = -1 ORDER BY NameSort"
ElseIf strTable = "VendorU" Then
strCriteria = "SELECT NameSort FROM Vendor Where Qualified = 0 ORDER BY NameSort"
ElseIf strTable = "MainR" Then
strCriteria = "SELECT NameSort FROM Vendor ORDER BY NameSort"
Else
MsgBox "Error"
End If
Set dbf = OpenDatabase("\\fileLocation\center.mdb")
Set rst = dbf.OpenRecordset(strCriteria)
frmCenter.MousePointer = fmMousePointerHourGlass
Counter = 0
If rst.RecordCount > 0 Then
rst.MoveLast
ReDim strData(rst.RecordCount - 1) As String
rst.MoveFirst
Do Until rst.EOF
strData(Counter) = rst![NameSort]
Counter = Counter + 1
rst.MoveNext
Loop
Else
ReDim strData(0)
End If
frmCenter.MousePointer = fmMousePointerArrow
rst.Close
End Sub
This will return a list:
The table already exists in a MySQL database so I could use an ODBC connection to retrieve the data and not mess with Access as a pass thru to a linked table. I tried converting the connection string and it connects to the database but for some reason doesn't show the list of vendors.
Here is the converted code:
Public strData() As String
Sub GetData(strTable As String)
Dim rst As ADODB.Recordset
Dim Counter As Long
Dim strCriteria As String
Dim conn As ADODB.Connection
Set remoteCon = New ADODB.Connection
conStr = "DRIVER={MySQL ODBC 5.2 ANSI Driver};" & _
"SERVER=server;DATABASE=database;" & _
"UID=uid;PWD=pwd"
remoteCon.ConnectionString = conStr
remoteCon.Open
remoteCon.Execute ("USE database;")
Set rst = New ADODB.Recordset
If strTable = "VendorQ" Then
strCriteria = "SELECT NameSort FROM Vendor Where Qualified = -1 ORDER BY NameSort"
ElseIf strTable = "VendorU" Then
strCriteria = "SELECT NameSort FROM Vendor Where Qualified = 0 ORDER BY NameSort"
ElseIf strTable = "MainR" Then
strCriteria = "SELECT NameSort FROM Main ORDER BY NameSort"
Else
MsgBox "Error"
End If
With rst
.ActiveConnection = remoteCon
.CursorType = adOpenDynamic
.LockType = adLockOptimistic
.Source = strCriteria
.Open
End With
frmCenter.MousePointer = fmMousePointerHourGlass
Counter = 0
If rst.RecordCount > 0 Then
rst.MoveLast
ReDim strData(rst.RecordCount - 1) As String
rst.MoveFirst
Do Until rst.EOF
strData(Counter) = rst![NameSort]
Counter = Counter + 1
rst.MoveNext
Loop
Else
ReDim strData(0)
End If
frmCenter.MousePointer = fmMousePointerArrow
rst.Close
End Sub
Is there a different way to populate the recordset from an ODBC source?

RecordCount is often unreliable until after you've run to the end of the recordset (as in MoveLast for example) so I'd use a check on EOF instead:
If Not rst.EOF Then
rst.MoveLast
ReDim strData(rst.RecordCount - 1) As String
rst.MoveFirst
Do Until rst.EOF
strData(Counter) = rst![NameSort]
Counter = Counter + 1
rst.MoveNext
Loop
Else
ReDim strData(0)
End If
EDIT:
FYI in my tests RecordCount was always -1 using adOpenDynamic but I got the correct value with adOpenKeyset
If RecordCount is not reliable then you can use GetRows() to transfer the records to a 2D array, then use that to resize and populate strData
If Not rs.EOF Then
arrRecs = rs.GetRows 'a 2D array (0 to #cols-1, 0 to #rows-1)
ReDim strData(UBound(arrRecs, 2))
For i = 0 To UBound(arrRecs, 2)
strData(i) = arrRecs(0, i)
Next i
Else
ReDim strData(0)
End If

Please see this answer by Erik A https://stackoverflow.com/a/46130089/2359206
Adding .CursorLocation = adUseClient to the rst open string solved the issue.

Related

How to Transfer VBA UserForm Data To Access Database?

I have created a user form in excel to save my records in a sheets like sheet1.
But after few days working with this UserForm, it is now goes slower, because of heavy data saving in sheet1.
Now I want to save all records to a database and want to keep clean my sheet1.
So I can work on my UserForm easily or without any delay. Also wants updates my record by calling it via serial numbers.
but I don't want to keep any record in my sheet1.
my little code is below: -
Sub cmdAdd_Click()
On Error GoTo ErrOccured
BlnVal = 0
If BlnVal = 0 Then Exit Sub
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Dim txtId, txtName, GenderValue, txtLocation, txtCNum, txtEAddr, txtRemarks
Dim iCnt As Integer
iCnt = fn_LastRow(Sheets("Data")) + 1
If frmData.obMale = True Then
GenderValue = "Male"
Else
GenderValue = "Female"
End If
With Sheets("Data")
.Cells(iCnt, 1) = iCnt - 1
.Cells(iCnt, 2) = frmData.txtName
.Cells(iCnt, 3) = GenderValue
.Cells(iCnt, 4) = frmData.txtLocation.Value
.Cells(iCnt, 5) = frmData.txtEAddr
.Cells(iCnt, 6) = frmData.txtCNum
.Cells(iCnt, 7) = frmData.txtRemarks
.Columns("A:G").Columns.AutoFit
.Range("A1:G1").Font.Bold = True
.Range("A1:G1").LineStyle = xlDash
End If
End With
Dim IdVal As Integer
IdVal = fn_LastRow(Sheets("Data"))
frmData.txtId = IdVal
ErrOccured:
'TurnOn screen updating
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
I will always be grateful to you.
Then, please try the next way. I will try creating of the necessary DB, table and fields using Excel VBA, too:
Copy the next piece of code which will create an empty DB, on the path you want:
Sub CreateEmptyDB()
Dim strPath As String, objAccess As Object
strPath = "C:\Your path\testDB"
Set objAccess = CreateObject("Access.Application")
Call objAccess.NewCurrentDatabase(strPath)
objAccess.Quit
End Sub
Programatically create the necessary table with its fields (`Start Date' added only to see how this type of data is handled...):
Sub createTableFields()
'It needs a reference to `Microsoft ActiveX Data Objects 2.x Library` (x = 2 to 9)
Dim Catalog As Object, cn As ADODB.Connection
Dim dbPath As String, scn As String, strTable As String
dbPath = "C:\Teste VBA Excel\testAccess\testDB.accdb"
strTable = "MySpecial_Table"
scn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath & ";"
Set Catalog = CreateObject("ADOX.Catalog")
Set cn = New ADODB.Connection
With cn
.Open scn
.Execute "CREATE TABLE " & strTable & " ([Name] text(255) WITH " & _
"Compression, " & "[Gender] text(255) WITH Compression, " & _
"[Location] text(255) WITH Compression, " & _
"[Address] text(255) WITH Compression, " & _
"[Number] number, " & _
"[Remarks] text(255) WITH Compression, " & _
"[Start Date] datetime)"
End With
cn.Close
End Sub
Add records to the newly created DB/Table:
Sub FillDataInDB()
'It needs a reference to `Microsoft ActiveX Data Objects 2.x Library` (x = 2 to 9)
Dim AccessDB As String, strTable As String, sql As String
Dim con As ADODB.Connection, rs As ADODB.Recordset, lastNo As Long
AccessDB = "C:\Teste VBA Excel\testAccess\testDB.accdb"
strTable = "MySpecial_Table"
Set con = CreateObject("ADODB.connection")
con.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & AccessDB
sql = "SELECT * FROM " & strTable
Set rs = CreateObject("ADODB.Recordset")
rs.CursorType = 1 'adOpenKeyset on early binding
rs.LockType = 3 'adLockOptimistic on early binding
rs.Open sql, con
If rs.RecordCount = 0 Then
lastNo = 0 'when no records in the table
Else
rs.MoveLast: lastNo = rs("Number") 'the last recorded value
End If
rs.AddNew
rs("Name") = "Test name" 'frmData.txtName
rs("Gender") = "Test gender" 'GenderValue
rs("Location") = "Test Location" 'frmData.txtLocation.Value
rs("Address") = "Test Address" 'frmData.txtEAddr
rs("Number") = IIf(lastNo = 0, 100, lastNo + 1) 'auto incrementing against the last value
'but starting from 100
'you can use frmData.txtCNum
rs("Remarks") = "Remarkable table..." 'frmData.txtRemarks
rs("Start Date") = Date
rs.Update
rs.Close: con.Close
Set rs = Nothing: Set con = Nothing
End Sub
Run the first two pieces of code in consecutive order (only once) and then start playing with the third one...
You can read the newly created DB Table (returning in an Excel sheet) in this way:
Sub ADO_Connection_ReadTable()
Dim conn As New Connection, rec As New Recordset, sh As Worksheet
Dim AccessDB As String, connString, query As String, strTable As String
AccessDB = "C:\Teste VBA Excel\testAccess\testDB.accdb"
strTable = "MySpecial_Table"
Set sh = ActiveSheet 'use here the sheet you want
connString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & AccessDB
conn.Open connString
query = "SELECT * from " & strTable & ";"
rec.Open query, conn
'return in the sheet
sh.cells.ClearContents
'getting data from the recordset if any and returning some in columns A:B:
If (rec.RecordCount <> 0) Then
Do While Not rec.EOF
With sh.Range("A" & sh.cells(Rows.count, 1).End(xlUp).row).Offset(1, 0)
.Value2 = rec.fields(0).Value
.Offset(0, 1).Value2 = rec.fields(3)
End With
rec.MoveNext
Loop
End If
rec.Close: conn.Close
End Sub
You can use a query to return specific data according to a specific table field. You can find plenty of examples on the internet.
I tried also showing how to handle an automate recording for the 'Number' field. Of course, if you are able to keep track of it in a different way, you can record it as you need/wont.
Please, test the above code(s) and send some feedback. You can use the DB path as a Private constant at the module level and much other ways to optimize the code. It is just a minimum workable solution only showing the way... :)

Excel VBA - writing Data from SQL/Recordset very slow

I am trying to write SQL Server data to an Excel sheet but it is very slow. Is there something to optimize? Approximately, 4000 entries at 20 cColumns takes 6-7 minutes.
Database ("freigabe") Module: Connecting to Database and get RecordSet
(this works like a charm)
Private Function ConnectSQL() As ADODB.Connection
Set conn = New ADODB.Connection
conn.ConnectionString = "DRIVER={SQL Server};" _
& "SERVER=xxxxx;" _
& " DATABASE=xxxxx;" _
& "UID=xxxxxx;PWD=xxxxx; OPTION=3"
conn.Open
Set ConnectSQL = conn
End Function
Public Function load(Optional ByVal FieldName As String = "", Optional ByVal fieldValue As String = "", Optional ByVal ComparisonOperator As String = "=")
'wenn fehler return?
'-> Über errorhandler retun rs oder boolen
Dim rs As New ADODB.Recordset
Dim sql As String
Dim contition As String
contition = " "
Dim sqlfrom As String
Dim sqlto As String
On Error GoTo Fehler:
sql = "SELECT * FROM " & TBLNAME & " WHERE storno='0' AND created BETWEEN '2020-02-01' AND '2020-02-15'"
Set conn = ConnectSQL()
rs.Open sql, conn, adOpenStatic
Set load = rs
Exit Function
End If
Fehler:
load = Err.Description
End Function
Get/Write: Build a connection and retrieving recordset. The While loop is taking long. I am skipping text-rich columns (it gets faster but still too long). Showing a load-window so the person doesn't think that Excel "isn't working". After that, the data get's validated (not included).
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim rs As Recordset
Dim k As Integer
Dim i As Integer
Dim startt As Double
Dim endt As Double
Dim rngDst As Range
Set rs = freigabe.load()
Set rngDst = Worksheets("Freigaben").Range("G2")
With Worksheets("Freigaben").Range("g2:Z50000")
.ClearContents
'.CopyFromRecordset rs
End With
Count = rs.RecordCount
k = 0
gui_laden.Show
startt = Timer
With rs
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
While Not .EOF
For i = 0 To .Fields.Count - 1
If i <> 13 And i <> 2 And i <> 10 And i <> 5 And i <> 6 And i <> 0 Then rngDst.Offset(, i) = .Fields(i).Value 'skip unneccessary data and write
Next i
k = k + 1
Debug.Print k & "/" & Count
gui_laden.lbl_status = "Lade Daten herunter: " & k & "/" & Count
gui_laden.Repaint
.MoveNext
DoEvents 'Ensure Application doesn't freeze
Set rngDst = rngDst.Offset(1)
Wend
End If
End With
endt = Timer - startt
Debug.Print "Dauer: " & endt
What I tried:
CopyFromRecordSet -> Application freezes
Test in new workbook -> same
Thank you very much!

Add list item to sharepoint list using vba

Im trying to create an excel tool that will add list item to sharepoint custom list. I had theinitial code but i am getying an error "couldnt find installable ISAM". My excel is 2016 and running in windows 10. How can i fix this issue?
Public Const sDEMAND_ROLE_GUID As String = "{6AA0B273-2548-49ED-9592-78243D4353AC}"
Public Const sSHAREPOINT_SITE As String = "https://eu001-sp.domain.com/sites/"
Sub TestPullFromSharepoint()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sConn As String
Dim sSQL As String
Dim ID As String
sConn = "Provider=Microsoft.ACE.OLEDB.12.0;DATABASE=" & sSHAREPOINT_SITE & ";" & _
"LIST=" & sDEMAND_ROLE_GUID & ";Extended Properties='Excel 8.0;HDR=YES;IMEX=1;';"
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
With cn
.ConnectionString = sConn
.Open
End With
sSQL = "SELECT tbl.[name] FROM [Library Name] as tbl where tbl.[id] = 14"
rs.Open sSQL, cn, adOpenStatic, adLockOptimistic
End Sub
I know it isn't super pretty, but I have a solution... Make sure that you replace YOURSHAREPOINTSITE with the url of your site.
The beauty of my solution, is that the code allows for:
Creation of new SP list
Addition of list items with all original column of the list
Addition of list items with any number of columns of the list (as
long as all required columns are represented)
No link required for the addition of new data (does create a link
when you use #1 but not a syncing link)
Limitations:
Column validation will cause a failed run if you pass data that
shouldn't go in that column (text to number column)
Absent required columns cause a failed run
Untested with lookup, people/group, or other record related column
types... but it would cause invalid data, potentially a failed run
unless you input the ID of the lookup value... which you probably
don't have.
It does require correct typing of column names and list name in
input boxes...
Public Sub PushSPList()
Dim lname As String, guid As String
Dim arr, arrr
Dim NewList As ListObject
Dim L As ListObjects
' Get the collection of lists for the active sheet
Set L = ThisWorkbook.ActiveSheet.ListObjects
' Add a new list
If MsgBox("Have you selected the new data?", vbYesNo) = vbNo Then
Exit Sub
Else
If MsgBox("New?", vbYesNo) = vbYes Then
lname = InputBox("What is the name of your new list?")
Set NewList = L.Add(xlSrcRange, Selection, , xlYes, True)
NewList.Name = lname
' Publish it to a SharePoint site
NewList.Publish Array("https://YOURSHAREPOINTSITE", lname), False
Else
arr = getSPitems
lname = arr(2)
guid = arr(1)
Set NewList = L(1)
Set arrr = Selection
Call addSPListItem(arrr, lname, guid)
End If
End If
End Sub
Sub addSPListItem(rar As Variant, lnme, guid)
Dim arr, lguid As String, spurl As String, lname As String, uitem As Object
lguid = guid
lname = lnme
spurl = "https://YOURSHAREPOINTSITE"
Dim cnt As ADODB.Connection
Dim rst As ADODB.Recordset 'tb
Dim mySQL As String
Set cnt = New ADODB.Connection
Set rst = New ADODB.Recordset
mySQL = "SELECT * FROM [" & lname & "];"
With cnt
.ConnectionString = _
"Provider=Microsoft.ACE.OLEDB.12.0;WSS;IMEX=0;RetrieveIds=Yes;" & _
"DATABASE=" & spurl & _
";LIST=" & lguid & ";"
.Open
End With
rst.Open mySQL, cnt, adOpenDynamic, adLockOptimistic
Dim fld As Object
Dim arrr()
i = -1
For Each fld In rst.Fields
i = i + 1
ReDim Preserve arrr(0 To i)
arrr(i) = rst.Fields(i).Name
Next
Dim clmns
clmns = Split(InputBox("Select columns, separated by commas, no spaces after commas... " & Join(arrr, ", ")), ",")
Dim Colmns As Object
Set Colmns = CreateObject("Scripting.Dictionary")
For i = 0 To UBound(clmns)
Colmns(i) = clmns(i)
Next
jj = 1
Do While rar(jj, 1) ""
rst.AddNew
For kk = 0 To UBound(clmns)
rst.Fields(Colmns(kk)) = rar(jj, kk + 1)
Next
jj = jj + 1
Loop
rst.Update
If CBool(rst.State And adStateOpen) = True Then rst.Close
Set rst = Nothing
If CBool(cnt.State And adStateOpen) = True Then cnt.Close
Set cnt = Nothing
MsgBox "Done"
End Sub

How to set the parsing query result range without knowing the number of records?

I run SQL query in excel through excel-vba& ADO.
I parse the result using looping
Then ,i find that i have to know how many records sql will generate before parsing the result .
In reality , I don't know the numbers of query result records before generating the query .
Any functions or method can let me know so that i can put on the loop statement ?
(i have got insight of .Fields() .Properties() ,but not work )
Sub sbADO()
Dim sSQLQry As String
Dim ReturnArray
Dim Conn As New ADODB.Connection
Dim mrs As New ADODB.Recordset
Dim DBPath As String, sconnect As String
DBPath = ThisWorkbook.FullName
'You can provide the full path of your external file as shown below
'DBPath ="C:\InputData.xlsx"
sconnect = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & DBPath & ";HDR=Yes';"
Conn.Open sconnect
sSQLSting = select ....' it is too long so i choose to skip
Set rs = Conn.Execute(sSQLSting)
Do While Not rs.EOF
'Officer = rs.Fields(i).Value
For j = 5 To 29 ' THIS IS THE MAIN CODE I NEED TO IMPROVE
'worksheet1.Cells(7, 11) = rs.Properties.Count
worksheet1.Cells(j, 1) = rs.Fields(0).Value
worksheet1.Cells(j, 3) = rs.Fields(2).Value
worksheet1.Cells(j, 4) = rs.Fields(3).Value
worksheet1.Cells(j, 7) = rs.Fields(6).Value
' Insert data to your worksheet here
rs.MoveNext
Next j
Loop
rs.Close
End Sub
For j = 5 To 29 '
next j
This is the range I should set that affect the range of worksheet parsing .There is totally 24 numbers of query record result in this case .If I set j too large (e.g. To 30), it will prompt out 3021 error -BOF & EOF should be true .Hence , the range should fit the numbers of record well .
You don't need a loop for j. Just increment j after all the cell assignments and before the end of the do loop.
Sub sbADO()
Dim sSQLQry As String
Dim ReturnArray
Dim Conn As New ADODB.Connection
Dim mrs As New ADODB.Recordset
Dim DBPath As String, sconnect As String
DBPath = ThisWorkbook.FullName
'You can provide the full path of your external file as shown below
'DBPath ="C:\InputData.xlsx"
sconnect = "Provider=MSDASQL.1;DSN=Excel Files;DBQ=" & DBPath & ";HDR=Yes';"
Conn.Open sconnect
sSQLSting = select .... ' it is too long so i choose to skip
Set rs = Conn.Execute(sSQLSting)
j = 5
Do While Not rs.EOF
'worksheet1.Cells(7, 11) = rs.Properties.Count
worksheet1.Cells(j, 1) = rs.Fields(0).Value
worksheet1.Cells(j, 3) = rs.Fields(2).Value
worksheet1.Cells(j, 4) = rs.Fields(3).Value
worksheet1.Cells(j, 7) = rs.Fields(6).Value
j = j + 1
rs.MoveNext
Loop
rs.Close
End Sub

Excel VBA Adding records below existing values

I've been developing a little tool that query's our database and returns some references.
I'm having a problem adding the newly query'd values below already existing values in the excel Sheet1.
Option Explicit
Public Ref As String
Const DWConnectString = "Provider=SQLOLEDB... "
Public Property Get rRef() As String
rRef = Me.TextBox1.Value
Ref = Trim(rRef)
End Property
Private Sub TextBox1_Change()
Dim rRef As String
rRef = Me.TextBox1.Value
End Sub
Private Sub ZoekRef_Click()
Dim cn As Object
Dim rs As Object
Dim cm As Object
Dim Ref As String
Dim StrSource As String
Dim startrow As Integer
Ref = rRef
Set cn = CreateObject("ADODB.Connection")
cn.Open DWConnectString
Set rs = CreateObject("ADODB.Recordset")
'rs = New ADODB.Recordset
StrSource = "Select CONSIGNMENT.CONSIGNMENT, CONSIGNMENT.DOCUMENT_REMARK_2, INVOICE_HIST.NET_AMOUNT, INVOICE_HIST.VAT_AMOUNT, INVOICE_HIST.INV_CURRENCY "
StrSource = StrSource & "from CONSIGNMENT left outer join INVOICE_HIST ON CONSIGNMENT.CONSIGNMENT=INVOICE_HIST.CONSIGNMENT "
StrSource = StrSource & "where DOCUMENT_REMARK_2 like '%"
StrSource = StrSource & Ref & "%'"
rs.Open StrSource, cn
If rs.EOF Then
MsgBox "Geen Resultaten"
Exit Sub
Else
Dim fieldNames, j
rs.MoveFirst
ReDim fieldNames(rs.Fields.Count - 1)
For j = 0 To rs.Fields.Count - 1
fieldNames(j) = rs.Fields(j).Name
Next
Sheet1.Range(Sheet1.Cells(1, 1), Sheet1.Cells(1, rs.Fields.Count)).Value = fieldNames
For j = 1 To rs.Fields.Count
Sheet1.Columns(j).AutoFit
Next
Sheet1.Cells.CopyFromRecordset rs
'fldcount2 = Sheets("sheet1").UsedRange.Rows.Count
Sheet1.Rows(1).Insert
Sheet1.Range(Sheet1.Cells(1, 1), Sheet1.Cells(1, rs.Fields.Count)).Value = fieldNames
startrow = 3
Do Until rs.EOF
rs.MoveNext
startrow = startrow + 1
Loop
End If
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
I thought about using the line:
Do until trim(cells(startrow,1).Value) = ""
startrow = startrow + 1
Loop
Before the rs.Movenext lines, but that seems to test the recordset, not the actual excel file.
Can I test my current Sheet1's values before adding the new recordset so it comes below what's already existing?
Thanks for the help.
Expand the scope of your loop.
rs.MoveFirst
Do Until rs.EOF
'Do all your work here
'Then increment your counter and the recordset
rs.MoveNext
startrow = startrow + 1
Loop