Query fails inside loop (returns EOF) - excel

My query works fine outside of the loop when I have the hard-coded values in. When I put the query inside my loop and use variables to hold the correct values it returns EOF. I've printed out the query and run it directly in SQL server and it returns the correct results. Which makes me think my SQL syntax is ok, but I can't figure out why it doesn't return anything in the loop. Any Ideas?
Public Function getPOs()
Dim TotalPos, Curpo, Query, ClaimNum, Color, DCloc As String
Dim i As Integer
Dim Row, Style, LastRow As LongPtr
Dim ws As Worksheet
Set ws = Worksheets("test")
' Set up database connection
Dim cnn As ADODB.Connection
Dim rs As New ADODB.Recordset
Set cnn = New ADODB.Connection
cnn.ConnectionString = SQL_SERVER_CONNECTION
cnn.ConnectionTimeout = 0
cnn.CommandTimeout = 0
cnn.Open
'This query works fine, it returns results that I can iterate through.
rs.Open "SELECT PO " & _
"FROM [catalog].[dbo].[table] " & _
"WHERE CLAIM_NUMBER = '1337' AND STYLE = '293493' and COLOR = '03' AND DC_LOCATION = 'PFC'", cnn, adOpenDynamic, adLockOptimistic
' Itereate through the results
i = 0
Do While Not rs.EOF
If rs![PO] = "" Then
Exit Do
End If
If i = 0 Then
Curpo = rs![PO]
TotalPos = Curpo
Else
Curpo = rs![PO]
TotalPos = TotalPos & ", " & Curpo
End If
i = i + 1
rs.MoveNext
Loop
MsgBox TotalPos ' Works fine!
' For some reason adding the query inside this loop messes it up.
Row = 11
LastRow = ws.Cells(ws.Rows.Count, "D").End(xlUp).Row
rs.Close
While Row < 12 ' will change back to LastRow once working
'Parse the claim number
ClaimNum = Replace(ws.Cells(Row, 10), "IC - ", "")
MsgBox ClaimNum
'Style
Style = Left(ws.Cells(Row, 11), Len(ws.Cells(Row, 11)) - 2)
MsgBox Style
'Color
Color = ws.Cells(Row, 12)
MsgBox Color
'DCloc
DCloc = ws.Cells(Row, 13)
MsgBox DCloc
' When I add the query here it returns nothing...
rs.Open "SELECT PO " & _
"FROM [catalog].[dbo].[table] " & _
"WHERE CLAIM_NUMBER = " & ClaimNum & " AND STYLE = " & Style & " and COLOR = '" & Color & "' AND DC_LOCATION = ' " & DCloc & "'", cnn, adOpenDynamic, adLockOptimistic
'add the entire sql statement to the Query var so I can print it out and run it in SQL Server
Query = "SELECT PO " & _
"FROM [catalog].[dbo].[table] " & _
"WHERE CLAIM_NUMBER = " & ClaimNum & " AND STYLE = " & Style & " and COLOR = '" & Color & "' AND DC_LOCATION = '" & DCloc & "'"
' print the query... when I run this exact thing in SQL server it returns results just fine'
MsgBox Query
' iterate through results
i = 0
'rs.EOF now that it's in the loop... but why? I know the syntax of the query is correct, it returns results when I run it directly in SQL server
If rs.EOF Then
MsgBox "why???"
End If
Do While Not rs.EOF
If rs![PO] = "" Then
Exit Do
End If
If i = 0 Then
Curpo = rs![PO]
TotalPos = Curpo
Else
Curpo = rs![PO]
TotalPos = TotalPos & ", " & Curpo
End If
MsgBox TotalPos
i = i + 1
rs.MoveNext
Loop
rs.Close
Row = Row + 1
Wend
cnn.Close
End Function

rs.Open "SELECT PO " & _
"FROM [catalog].[dbo].[table] " & _
"WHERE CLAIM_NUMBER = " & ClaimNum & " AND STYLE = " & Style & " and COLOR = '" & Color & "' AND DC_LOCATION = ' " & DCloc & "'"
Don't concatenate parameter values into your SQL string - that way you don't need to care about quoting strings and worry about whether a string contains apostrophes, or worse - the widely-known tale of Little Bobby Tables captures just how impactful this careless value concatenation practice can be, if you let it.
Instead, define your query once, and let the server deal with the parameters (it's its job).
Const sql As String = _
"SELECT PO " & _
"FROM [catalog].[dbo].[table] " & _
"WHERE CLAIM_NUMBER = ? AND STYLE = ? AND COLOR = ? AND DC_LOCATION = ?"
Each ? is understood by ADODB as a positional parameter: all you need to do now, is to execute a ADODB.Command with 4 parameters, appended in the order they are specified.
Now you can write a Function that takes the values for the 4 parameters you need, and the function can return a ADODB.Recordset that contains the results - no need to redefine the SQL string every time you need it!
Private Function GetPO(ByVal cnn As ADODB.Connection, ByVal ClaimNum As String, ByVal Style As String, ByVal Color As String, ByVal DCloc As String) As ADODB.Recordset
Const sql As String = _
"SELECT PO " & _
"FROM [catalog].[dbo].[table] " & _
"WHERE CLAIM_NUMBER = ? AND STYLE = ? AND COLOR = ? AND DC_LOCATION = ?"
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
Set cmd.ActiveConnection = cnn
cmd.CommandType = adCmdText
cmd.CommandText = sql
'TODO: verify parameter types & sizes - here NVARCHAR(200).
'NOTE: parameters must be added in the order they are specified in the SQL.
cmd.Parameters.Append cmd.CreateParameter(Type:=adVarWChar, Size:=200, Value:=ClaimNum)
cmd.Parameters.Append cmd.CreateParameter(Type:=adVarWChar, Size:=200, Value:=Style)
cmd.Parameters.Append cmd.CreateParameter(Type:=adVarWChar, Size:=200, Value:=Color)
cmd.Parameters.Append cmd.CreateParameter(Type:=adVarWChar, Size:=200, Value:=DCloc)
Set GetPO = cmd.Execute
End Function
You can use it from anywhere you have an ADODB.Connection that's ready to use:
Dim rs As ADODB.Recordset
Set rs = GetPO(cnn, ClaimNum, Style, Color, DCloc)
Do While Not rs.EOF
'...
Loop

You need to wrap variables in quotes to make it work, a string type isn't enough.
"WHERE CLAIM_NUMBER = " & ClaimNum & " ...
Needs to become:
"WHERE CLAIM_NUMBER = " & "'" & ClaimNum & "'" & " ...
In addition to all the other variables you are concatenating into the SQL statement
As an aside
Dim TotalPos, Curpo, Query, ClaimNum, Color, DCloc As String
is only declaring DCloc as a string and all the others are variants.
To make them all string you need to add as string to all of them.
Dim TotalPos as string, Curpo as string, Query as string, ClaimNum as string, Color as string, DCloc As String

Related

Transposing Values In a Listbox

Good day,
I am trying to transpose the values added to my listbox via a query. Also does anyone have tips on how to get the column names on the Column Header. This is what the values in my listbox looks like:
Below is my code thus far:
Dim conn As New ADODB.Connection
Dim reCs As ADODB.Recordset
Dim tarSheet As Worksheet
Dim strbooK As String, strTar As String
Dim arTar As Range
strbooK = ThisWorkbook.Path & "\Excel_VBA.xlsm"
If opTar.Value = True Then
strTar = "SELECT TargetNumber as [Target Number] " & _
",TargetName as [Target Name] " & _
",TargetPrefix as [Target Nickname] " & _
",Count(TargetNumber) as [Number of Events] " & _
"FROM [Master$] " & _
"GROUP BY TargetNumber, TargetName, TargetPrefix " & _
"HAVING TargetNumber = '" & txt_search.Value & "';"
Set conn = New ADODB.Connection
conn.ConnectionString = _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & strbooK & ";" & _
"Extended Properties='Excel 12.0 Xml;HDR=YES';"
conn.Open
Set reCs = New ADODB.Recordset
reCs.ActiveConnection = conn
reCs.Source = strTar
reCs.Open
With lbx_reCs
.BoundColumn = 1
.ColumnCount = 4
.ColumnHeads = True
.TextAlign = fmTextAlignCenter
.ColumnWidths = "136;136;136;136;"
.MultiSelect = fmMultiSelectMulti
' .RowSource = arTar.Address
.List() = reCs.GetRows
End With
'Releasing Objects
reCs.Close
conn.Close
End If
Thank you
As said in the titel you need no transpose of the values of the recordset, just use the column property of the listbox.
.Column = reCs.GetRows
For the other question you might look here. That seems not to be possible in case you have an array like in this case. On the other hand you also might want to look at this solution approach in the above mentioned post.

How to populate a text box from a listbox?

I have a listbox with combined Employee Number and Name. so what you see is 0001-John Doe but now when I try and populate a text box with the list box info it does not work.
How I populate my listbox:
Private Sub UserForm_Initialize()
Dim conn As New ADODB.Connection
Dim rsst As New ADODB.Recordset
dbPath = Sheets("Info").Range("a2").Value
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
rsst.Open "SELECT EmpNumber,EmpFirstName,EmpSurname FROM Employees;", _
conn, adOpenStatic
With rsst
.MoveFirst
Do Until .EOF
Me.lbxNextOfKinEmployeeNumber.AddItem rsst.Fields(0).Value & " - " & rsst.Fields(1) & " " & " " & rsst.Fields(2) & " "
rsst.MoveNext
Loop
End With
End Sub
What's in my view button:
Private Sub btnNextOfKinSelect_Click()
Dim CNOK As New ADODB.Connection
Dim RNOK As New ADODB.Recordset
txtNextofKinEmployeeNumber.Enabled = False
'btnEditNextOfKin.Visible = True
If lbxNextOfKinEmployeeNumber.ListIndex = -1 Then
MsgBox "Please Select a Employee Number"
Else
dbPath = Sheets("Info").Range("a2").Value
CNOK.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & dbPath
RNOK.Open "SELECT * FROM EmployeeNextOfKin Where EmpNumber ='" &
Me.lbxNextOfKinEmployeeNumber & "'", _
CNOK, adOpenStatic
RNOK.MoveFirst
txtNextofKinEmployeeNumber.Value = RNOK("EmpNumber")
txtNextOfKinName.Value = RNOK("NextOfKinName")
txtNextOfKinSurname.Value = RNOK("NextOfKinSurname")
txtContactNumber.Value = RNOK("NextofKinContactNumber")
txtContactAddressLine1.Value = RNOK("NextofKinAddress")
txtNextofKinCity.Value = RNOK("NextofKinCity")
txtCellNumber.Value = RNOK("NextofKinCellNumber")
End If
End Sub
What I want is when I select 0001-John Doe it should get the data from my database and populate my textboxes.
Code is trying to match concatenated string 0001-john doe with EmpNumber field value 0001. Options:
set listbox RowSource as multi-column
Do Until rsst.EOF
With Me.lbxNextOfKinEmployeeNumber
.ColumnCount = 2
.ColumnWidths = "0;2"
.AddItem rsst(0) & ";" & rsst(0) & " - " & rsst(1) & " " & rsst(2)
End With
rsst.MoveNext
Loop
If users would prefer to type name, don't include EmpNumber in concatenated string. Last name first might be more appropriate:
.AddItem rsst(0) & ";" & rsst(2) & ", " & rsst(1)
Might want to sort recordset: ORDER BY EmpSurname
extract EmpNumber from concatenated string
Left(Me.lbxNextOfKinEmployeeNumber, 4)
use LIKE and wildcard
WHERE '" & Me.lbxNextOfKinEmployeeNumber & "' LIKE [EmpNumber] & '*'"

Excel VBA Macro to copy a macro

SOLVED. Solution at bottom!
Hopefully you brainiacs can help me out as I've apparently reached the limit of my programming capabilities.
I am looking for a way to write a VBA sub which duplicates another VBA Sub, but replace the name and another input. The case in details:
I am trying to build an Excel template for the organization, which will allow the users to inport/export data to/from Access databases (.accdb), as the end-users reluctance towards using real databases (as opposed to excel lists) apparently lies in their inability to extract/submit the data to/from Excel, where they are comfortable working with the data.
The challenge is, that users who don't know how to link to Access, for sure don't know anything about VBA code. Hence, I have created a worksheet from which the users selects a database using a file-path, table, password, set filters, define where to copy/insert datasets, fields to import etc. A Macro then handles the rest.
However, I want to create a macro which allows the user to create additional database links. As it is right now, this would require the user to open VBE and copy two macros and change one line of code... but that is a recipe for disaster. So how can I add a button to the sheet that copies the code I have written and rename the macro?
... I was considering if using a function, but cannot get my head around how that should Work.
Does it make sense? Any ideas/ experiences? Is there a completely different way around it that I haven't considered?
I'd really appreciate your inputs - even if this turns out to be impossible.
Edit:
Macro Man, you asked for the code - it's rather long due to all the user input fields, so I was trying to save you Guys for it since the code in and of itself is working fine...
Sub GetData1()
' Click on Tools, References and select
' the Microsoft ActiveX Data Objects 2.0 Library
Dim DBFullName As String
Dim Connect As String, Source As String
Dim Connection As ADODB.Connection
Dim Recordset As ADODB.Recordset
Dim Col As Integer
Dim DBInfoLocation As Range
Dim PW As String
Dim WSforData As String
Dim CellforData As String
Dim FieldList As Integer
'******************************
'Enter location for Database conectivity details below:
'******************************
Set DBInfoLocation = ActiveWorkbook.Sheets("DBLinks").Range("C15:I21")
FieldList = ActiveWorkbook.Sheets("DBLinks").Range("P1").Value
'******************************
' Define data location
WSforData = DBInfoLocation.Rows(4).Columns(1).Value
CellforData = DBInfoLocation.Rows(5).Columns(1).Value
'Set filters
Dim FilField1, FilField2, FilFieldA, FilFieldB, FilFieldC, FilFieldD, FilFieldE, FilOperator1, FilOperator2, FilOperatorA, FilOperatorB, FilOperatorC, FilOperatorD, FilOperatorE, FilAdMth1, FilAdMthA, FilAdMthB, FilAdMthC, FilAdMthD As String
Dim Filtxt1, Filtxt2, FiltxtA, FiltxtB, FiltxtC, FiltxtD, FiltxtE As String
Dim ExtFld1, ExtFld2, ExtFld3, ExtFld4, ExtFld5, ExtFld6, ExtFld7, ExtFld As String
Dim FilCnt, FilCntA As Integer
Dim FilVar1 As String
'Set DB field names
FilField1 = DBInfoLocation.Rows(1).Columns(5).Value
FilField2 = DBInfoLocation.Rows(2).Columns(5).Value
FilFieldA = DBInfoLocation.Rows(3).Columns(5).Value
FilFieldB = DBInfoLocation.Rows(4).Columns(5).Value
FilFieldC = DBInfoLocation.Rows(5).Columns(5).Value
FilFieldD = DBInfoLocation.Rows(6).Columns(5).Value
FilFieldE = DBInfoLocation.Rows(7).Columns(5).Value
'Set filter operators
FilOperator1 = DBInfoLocation.Rows(1).Columns(6).Value
FilOperator2 = DBInfoLocation.Rows(2).Columns(6).Value
FilOperatorA = DBInfoLocation.Rows(3).Columns(6).Value
FilOperatorB = DBInfoLocation.Rows(4).Columns(6).Value
FilOperatorC = DBInfoLocation.Rows(5).Columns(6).Value
FilOperatorD = DBInfoLocation.Rows(6).Columns(6).Value
FilOperatorE = DBInfoLocation.Rows(7).Columns(6).Value
'Run through criteria to find VarType(FilCrit1) (the Dimension data type) for the criteria field and set the appropriate data type for the filter
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(1).Columns(7).Value), CDbl(FilCrit1), IIf((DBInfoLocation.Rows(1).Columns(7).Value = "True" Or DBInfoLocation.Rows(1).Columns(7).Value = "False"), CBool(FilCrit1), IIf(IsDate(DBInfoLocation.Rows(1).Columns(7).Value), CDate(FilCrit1), CStr(FilCrit1))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(2).Columns(7).Value), CDbl(FilCrit2), IIf((DBInfoLocation.Rows(2).Columns(7).Value = "True" Or DBInfoLocation.Rows(2).Columns(7).Value = "False"), CBool(FilCrit2), IIf(IsDate(DBInfoLocation.Rows(2).Columns(7).Value), CDate(FilCrit2), CStr(FilCrit2))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(3).Columns(7).Value), CDbl(FilCrit3), IIf((DBInfoLocation.Rows(3).Columns(7).Value = "True" Or DBInfoLocation.Rows(3).Columns(7).Value = "False"), CBool(FilCrit3), IIf(IsDate(DBInfoLocation.Rows(3).Columns(7).Value), CDate(FilCrit3), CStr(FilCrit3))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(4).Columns(7).Value), CDbl(FilCrit4), IIf((DBInfoLocation.Rows(4).Columns(7).Value = "True" Or DBInfoLocation.Rows(4).Columns(7).Value = "False"), CBool(FilCrit4), IIf(IsDate(DBInfoLocation.Rows(4).Columns(7).Value), CDate(FilCrit4), CStr(FilCrit4))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(5).Columns(7).Value), CDbl(FilCrit5), IIf((DBInfoLocation.Rows(5).Columns(7).Value = "True" Or DBInfoLocation.Rows(5).Columns(7).Value = "False"), CBool(FilCrit5), IIf(IsDate(DBInfoLocation.Rows(5).Columns(7).Value), CDate(FilCrit5), CStr(FilCrit5))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(6).Columns(7).Value), CDbl(FilCrit6), IIf((DBInfoLocation.Rows(6).Columns(7).Value = "True" Or DBInfoLocation.Rows(6).Columns(7).Value = "False"), CBool(FilCrit6), IIf(IsDate(DBInfoLocation.Rows(6).Columns(7).Value), CDate(FilCrit6), CStr(FilCrit6))))
currentLoad = IIf(IsNumeric(DBInfoLocation.Rows(7).Columns(7).Value), CDbl(FilCrit7), IIf((DBInfoLocation.Rows(7).Columns(7).Value = "True" Or DBInfoLocation.Rows(7).Columns(7).Value = "False"), CBool(FilCrit7), IIf(IsDate(DBInfoLocation.Rows(7).Columns(7).Value), CDate(FilCrit7), CStr(FilCrit7))))
'Set Filter criteria
FilCrit1 = DBInfoLocation.Rows(1).Columns(7).Value
FilCrit2 = DBInfoLocation.Rows(2).Columns(7).Value
FilCrit3 = DBInfoLocation.Rows(3).Columns(7).Value
FilCrit4 = DBInfoLocation.Rows(4).Columns(7).Value
FilCrit5 = DBInfoLocation.Rows(5).Columns(7).Value
FilCrit6 = DBInfoLocation.Rows(6).Columns(7).Value
FilCrit7 = DBInfoLocation.Rows(7).Columns(7).Value
'Set additional filter-method
FilAdMth1 = DBInfoLocation.Rows(1).Columns(8).Value
FilAdMthA = DBInfoLocation.Rows(3).Columns(8).Value
FilAdMthB = DBInfoLocation.Rows(4).Columns(8).Value
FilAdMthC = DBInfoLocation.Rows(5).Columns(8).Value
FilAdMthD = DBInfoLocation.Rows(6).Columns(8).Value
'Set which fields to extract
ExtFld1 = DBInfoLocation.Rows(1).Columns(9).Value
ExtFld2 = DBInfoLocation.Rows(2).Columns(9).Value
ExtFld3 = DBInfoLocation.Rows(3).Columns(9).Value
ExtFld4 = DBInfoLocation.Rows(4).Columns(9).Value
ExtFld5 = DBInfoLocation.Rows(5).Columns(9).Value
ExtFld6 = DBInfoLocation.Rows(6).Columns(9).Value
ExtFld7 = DBInfoLocation.Rows(7).Columns(9).Value
'Filter on query
'Only criteria of value type string should have single quotation marks around them
FilCnt = 0
If FilField1 <> "" Then
If VarType(FilCrit1) = vbString Then
Filtxt1 = " WHERE [" & FilField1 & "] " & FilOperator1 & " '" & FilCrit1 & "'"
Else
Filtxt1 = " WHERE [" & FilField1 & "] " & FilOperator1 & " " & FilCrit1
End If
FilCnt = 1
End If
If FilField2 <> "" And FilCnt = 1 Then
If VarType(FilCrit2) = vbString Then
Filtxt2 = " " & FilAdMth1 & " [" & FilField2 & "] " & FilOperator2 & " '" & FilCrit2 & "'"
Else
Filtxt2 = " " & FilAdMth1 & " [" & FilField2 & "] " & FilOperator2 & " " & FilCrit2
End If
FilCnt = 2
End If
'Filter on Dataset
FilCntA = 0
If FilFieldA <> "" Then
If VarType(FilCrit3) = vbString Then
FiltxtA = FilFieldA & " " & FilOperatorA & " '" & FilCrit3 & "'"
Else
FiltxtA = FilFieldA & " " & FilOperatorA & " " & FilCrit3
End If
FilCntA = 1
End If
If FilFieldB <> "" And FilCntA = 1 Then
If VarType(FilCrit4) = vbString Then
FiltxtB = " " & FilAdMthA & " " & FilFieldB & " " & FilOperatorB & " '" & FilCrit4 & "'"
Else
FiltxtB = " " & FilAdMthA & " " & FilFieldB & " " & FilOperatorB & " " & FilCrit4
End If
FilCntA = 2
End If
If FilFieldC <> "" And FilCntA = 2 Then
If VarType(FilCrit5) = vbString Then
FiltxtC = " " & FilAdMthB & " " & FilFieldC & " " & FilOperatorC & " '" & FilCrit5 & "'"
Else
FiltxtC = " " & FilAdMthB & " " & FilFieldC & " " & FilOperatorC & " " & FilCrit5
End If
FilCntA = 3
End If
If FilFieldD <> "" And FilCntA = 3 Then
If VarType(FilCrit6) = vbString Then
FiltxtD = " " & FilAdMthC & " " & FilFieldD & " " & FilOperatorD & " '" & FilCrit6 & "'"
Else
FiltxtD = " " & FilAdMthC & " " & FilFieldD & " " & FilOperatorD & " " & FilCrit6
End If
FilCntA = 4
End If
If FilFieldE <> "" And FilCntA = 4 Then
If VarType(FilCrit7) = vbString Then
FiltxtE = " " & FilAdMthD & " " & FilFieldE & " " & FilOperatorE & " '" & FilCrit7 & "'"
Else
FiltxtE = " " & FilAdMthD & " " & FilFieldE & " " & FilOperatorE & " " & FilCrit7
End If
FilCntA = 5
End If
' Select Fields to Extract
ExtFld = "*"
If ExtFld1 <> "" Then
ExtFld = "[" & ExtFld1 & "]"
End If
If ExtFld2 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "]"
End If
If ExtFld3 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "]"
End If
If ExtFld4 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "]"
End If
If ExtFld5 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "]"
End If
If ExtFld6 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "],[" & ExtFld6 & "]"
End If
If ExtFld7 <> "" Then
ExtFld = "[" & ExtFld1 & "],[" & ExtFld2 & "],[" & ExtFld3 & "],[" & ExtFld4 & "],[" & ExtFld5 & "],[" & ExtFld6 & "],[" & ExtFld7 & "]"
End If
' Database path info
PW = DBInfoLocation.Rows(3).Columns(1).Value
' Your path will be different
DBFullName = DBInfoLocation.Rows(1).Columns(1).Value
DBTable = DBInfoLocation.Rows(2).Columns(1).Value
' Open the connection
Set Connection = New ADODB.Connection
Connect = "Provider=Microsoft.ACE.OLEDB.12.0;"
Connect = Connect & "Data Source=" & DBFullName & ";Jet OLEDB:Database Password=" & PW & ";"
Connection.Open ConnectionString:=Connect
' Create RecordSet & Define data to extract
Set Recordset = New ADODB.Recordset
With Recordset
'Get All Field Names by opening the DB, extracting a recordset, entering the field names and closing the dataset
Source = DBTable
.Open Source:=Source, ActiveConnection:=Connection
For ColH = 0 To Recordset.Fields.Count - 1
ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(ColH + 3, FieldList - 1).Cells.Clear
ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(ColH + 3, FieldList - 1).Value = Recordset.Fields(ColH).Name
ActiveWorkbook.Worksheets("RangeNames").Range("A1").Offset(ColH + 2, (DBInfoLocation.Rows(1).Columns(2).Value) - 1).Cells.Clear
ActiveWorkbook.Worksheets("RangeNames").Range("A1").Offset(ColH + 2, (DBInfoLocation.Rows(1).Columns(2).Value) - 1).Value = Recordset.Fields(ColH).Name
Next
Set Recordset = Nothing
End With
' Get the recordset, but only extract the field names of those defined in the spreadsheet.
' If no fields have been selected, all fields will be extracted.
Set Connection = New ADODB.Connection
Connect = "Provider=Microsoft.ACE.OLEDB.12.0;"
Connect = Connect & "Data Source=" & DBFullName & ";Jet OLEDB:Database Password=" & PW & ";"
Connection.Open ConnectionString:=Connect
Set Recordset = New ADODB.Recordset
With Recordset
If FilCnt = 0 Then 'No filter
Source = "SELECT " & ExtFld & " FROM " & DBTable
End If
' Filter Data if selected
If FilCnt = 1 Then
Source = "SELECT " & ExtFld & " FROM " & DBTable & Filtxt1
End If
If FilCnt = 2 Then
Source = "SELECT " & ExtFld & " FROM " & DBTable & Filtxt1 & Filtxt2
End If
.Open Source:=Source, ActiveConnection:=Connection
If FilCntA = 1 Then
Recordset.Filter = FiltxtA
End If
If FilCntA = 2 Then
Recordset.Filter = FiltxtA & FiltxtB
End If
If FilCntA = 3 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC
End If
If FilCntA = 4 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC & FiltxtD
End If
If FilCntA = 5 Then
Recordset.Filter = FiltxtA & FiltxtB & FiltxtC & FiltxtD & FiltxtE
End If
'Debug.Print Recordset.Filter
' Clear data
For Col = 0 To Recordset.Fields.Count - 1
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(0, Col).EntireColumn.Clear
End If
'ActiveWorkbook.Worksheets("DBLinks").Range("A1").Offset(Col + 3, FieldList - 1).Cells.Clear
Next
' Write field names
For Col = 0 To Recordset.Fields.Count - 1
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(0, Col).Value = Recordset.Fields(Col).Name
End If
Next
' Write recordset
If WSforData <> "" Then
ActiveWorkbook.Worksheets(WSforData).Range(CellforData).Offset(1, 0).CopyFromRecordset Recordset
ActiveWorkbook.Worksheets(WSforData).Columns.AutoFit
End If
End With
' Clear recordset and close connection
Set Recordset = Nothing
Connection.Close
Set Connection = Nothing
End Sub
This piece of the "DBLinks" worksheet is probably also needed for full understanding of the code:
DBLinks user input area for database connectivity
SOLUTION:
I followed the advice to look into VBProject.VBComponents which copied the macro. I created a simple form which asked for the name to use for the macro and the rest of the inputs comes from the relative reference. I will spare you for a full copy of my long and less than graceful code, but the essential of the code are:
In case someone else could benefit from my experience: In the Click-action of the command button on the form:
Private Sub cmdCreateDB_Click()
'Go to Tools, References and add: Microsoft Visual Basic for Applications Extensibility 5.3
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim LineNum As Long
Const DQUOTE = """" ' one " character
Set VBProj = ActiveWorkbook.VBProject
Set VBComp = VBProj.VBComponents("Module1")
Set CodeMod = VBComp.CodeModule
Dim txtDBLinkName As String
txtDBLinkName = Me.txtDBName
With CodeMod
LineNum = .CountOfLines + 1
.InsertLines LineNum, " Sub " & txtDBLinkName & "()"
LineNum = LineNum + 1
.InsertLines LineNum, " ' Click on Tools, References and select"
LineNum = LineNum + 1
.InsertLines LineNum, " ' the Microsoft ActiveX Data Objects 2.0 Library"
' And then it goes on forever through all the lines of the original code...
' just remember to replace all double quotations with(Without Square brackets):
' [" & DQUOTE & "]
'And it ends up with:
LineNum = LineNum + 1
.InsertLines LineNum, " Set Recordset = Nothing"
LineNum = LineNum + 1
.InsertLines LineNum, " Connection.Close"
LineNum = LineNum + 1
.InsertLines LineNum, " Set Connection = Nothing"
LineNum = LineNum + 1
.InsertLines LineNum, " End Sub"
End With
Unload Me
End Sub
Thank you everyone for your help. - Especially you #findwindow for coming up with the path to a solution.
For the sake of completion, here's how this could be dealt with without metaprogramming.
Problems that boil down to "do the same thing - but..." can often be solved by making the program as generic as possible. All data specific to a single use-case should be passed down from above in a clear manner, allowing the program to be reused.
Let's look at an example of how this could be implemented in order to generate query strings from one or many ranges of varying sizes.
The first step is to group all data that belongs to the concept of a Filter. Since VBA doesn't have object literals, we can use an Array, a Collection or a Type to represent a Filter instead.
Generating the query strings requires distinction between QueryFilters and RecordFilters. Looking at the code, the two variants are similar enough to be handled by a simple Boolean within a single Type.
Option Explicit
Private Type Filter
Field As String
Operator As String
Criteria As Variant
AdditionalMethod As String
ExtractedFields As String
IsQueryFilter As Boolean
FilterString As String
End Type
Now we can use a single variable instead of keeping track of multiple variables to represent a single concept.
One way a Filter can be generated is by using a Range.
' Generates a Filter from a given Range of input data.
Private Function GenerateFilter(ByRef source As Range) As Filter
With GenerateFilter
.Field = CStr(source)
.Operator = CStr(source.Offset(0, 1))
.Criteria = source.Offset(0, 2)
.AdditionalMethod = CStr(source.Offset(0, 3))
.ExtractedFields = CStr(source.Offset(0, 4))
.IsQueryFilter = CBool(source.Offset(0, 5))
.FilterString = GenerateFilterString(GenerateFilter)
End With
End Function
Just as a single concept can be declared as a Type, a group of things can be declared as an Array (or a Collection, Dictionary, ...). This is useful, as it lets us decouple the logic from a specific Range.
' Generates a Filter for each row of a given Range of input data.
Private Function GenerateFilters(ByRef source As Range) As Filter()
Dim filters() As Filter
Dim filterRow As Range
Dim i As Long
ReDim filters(0 To source.Rows.Count)
i = 0
For Each filterRow In source.Rows
filters(i) = GenerateFilter(filterRow)
i = i + 1
Next
GenerateFilters = filters()
End Function
We now have a function that can return an Array of Filters from a given Range - and, as long as the columns are laid down in the right order, the code will work just fine with any Range.
With all of the data in a convenient package, it's easy enough to assemble the FilterString.
' Generates a FilterString for a given Filter.
Private Function GenerateFilterString(ByRef aFilter As Filter) As String
Dim temp As String
temp = " "
With aFilter
If .AdditionalMethod <> "" Then temp = temp & .AdditionalMethod & " "
If .IsQueryFilter Then
temp = temp & "[" & .Field & "]"
Else
temp = temp & .Field
End If
temp = temp & " " & .Operator & " "
If VarType(.Criteria) = vbString Then
temp = temp & "'" & .Criteria & "'"
Else
temp = temp & .Criteria
End If
End With
GenerateFilterString = temp
End Function
The data can then be merged to strings that can be used in queries regardless of how many Filters of either type are present in the specified Range.
' Merges the FilterStrings of Filters that have IsQueryString set as True.
Private Function MergeQueryFilterStrings(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
temp = " WHERE"
For i = 0 To UBound(filters)
If filters(i).IsQueryFilter Then temp = temp & filters(i).FilterString
Next
MergeQueryFilterStrings = temp
End Function
' Merges the FilterStrings of Filters that have IsQueryString set as False.
Private Function MergeRecordFilterStrings(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
For i = 0 To UBound(filters)
If Not filters(i).IsQueryFilter Then _
temp = temp & filters(i).FilterString
Next
MergeRecordFilterStrings = temp
End Function
' Merges the ExtractedFields of all Filters.
Private Function MergeExtractedFields(ByRef filters() As Filter) As String
Dim temp As String
Dim i As Long
temp = ""
For i = 0 To UBound(filters)
If filters(i).ExtractedFields <> "" Then _
temp = temp & "[" & filters(i).ExtractedFields & "],"
Next
If temp = "" Then
temp = "*"
Else
temp = Left(temp, Len(temp) - 1) ' Remove dangling comma.
End If
MergeExtractedFields = temp
End Function
With all of that done, we can finally plug a single Range in and get the generated strings out. It would be trivial to change filterRange or generate Filters from multiple Ranges.
Public Sub TestStringGeneration()
Dim filters() As Filter
Dim filterRange As Range
Set filterRange = Range("A1:A10")
filters = GenerateFilters(filterRange)
Debug.Print MergeQueryFilterStrings(filters)
Debug.Print MergeRecordFilterStrings(filters)
Debug.Print MergeExtractedFields(filters)
End Sub
TL;DR
Split code to reusable Functions & Subs
Favor sending data as arguments
Avoid hard-coding
Group data that represent a single concept
Use Arrays or other data structures over multiple variables

Why does my script seem to randomly truncate strings?

The script below basically takes a source table and a target table and copies all the records. It works fairly well, but one recurring issue I'm having is that it seems to truncate some strings. Here is one of the error outputs:
Error Number: -2147217900 Description: [Microsoft][ODBC SQL Server
Driver][SQL S erver]Unclosed quotation mark after the character string
'STRINGSEGMENT^urn:uuid:e9 e91fe151-5w4c-12e1-bac5-25b3a0'.
INSERT INTO TableName VALUES ('23189','23189','','','1^^','','12/5/2013 3:37:2 2
PM','fieldvalue','','somethinghere','somethinghere','12/5/2013 9:37:22
AM','123456','1234568798','STRINGSEGMENT^urn:uuid:
e91fe151-5w4c-12e1-bac5-25b3a0
Query is 584 characters long
If you look at the source data, the string that is truncated looks something like this:
STRINGSEGMENT^urn:uuid:e91fe151-5w4c-12e1-bac5-25b3a0004b00^STRINGSEGMENT
So it's cutting it off after the 53rd character (highlighted). The entire length of tSQL is only 584 characters long.
Why is this happening?
WScript.Echo "Setting Vars..."
Dim sConnect, tConnect, resultSet, r
Dim sDSN, sUserName, sPassWord
Dim tDSN, tUserName, tPassWord
Dim value
sDSN = "mydsn"
sUsername = "myusername"
sPassword = "mypassword"
tDSN = "LOCAL"
tUsername = "myusername"
tPassword = "mypassword"
sTable = "sourceTable"
tTable = "targetTable"
sSQL = "" 'see below
sDSN = "DSN=" & sDSN & ";UID=" & sUsername & ";PWD=" & sPassword & ";"
tSQL = "Select TOP 1 ID FROM " & tTable & " ORDER BY ID Desc"
tDSN = "DSN=" & tDSN & ";UID=" & sUsername & ";PWD=" & sPassword & ";"
Set sConnect = CreateObject("ADODB.Connection")
WScript.Echo "Opening connection to source..."
sConnect.Open sDSN
Set tConnect = CreateObject("ADODB.Connection")
WScript.Echo "Opening connection to target..."
tConnect.Open tDSN
WScript.Echo "Finding Current Record..."
Set r = tConnect.Execute(tSQL)
On Error Resume Next
r.MoveFirst
if r.eof Then currentRecord = 1
Err.Clear
Do While Not r.eof
currentRecord = r("ID") + 1
r.MoveNext
Loop
r.Close
sSQL ="Select * FROM " & sTable & " WHERE ID >= " & currentRecord
WScript.Echo "Beginning shadow at record " & currentRecord & "..."
Set resultSet = sConnect.Execute(sSQL)
resultSet.MoveFirst
Do While Not resultSet.eof
On Error Resume Next
tSQL = "INSERT INTO " & tTable & " VALUES ('"
For i = 0 To resultSet.fields.Count - 1
if NOT IsNull(resultSet(i)) Then
value = replace(resultSet(i),"'","")
'somewhere around here
else
value = ""
End If
tSQL = tSQL & value
if i < resultSet.fields.Count - 1 Then
tSQL = tSQL & "','"
end if
Next
tSQL = tSQL & "')"
'when the error occurs, the line above doesn't seem to be processed but the line below obviously is...
tConnect.Execute(tSQL)
If (Err.Number <> 0) Then
WScript.Echo "Error Number: " & Err.Number & " Description: " & Err.Description
WScript.Echo tSQL
WScript.Echo "Query is " & Len(tSQL) & " characters long"
WScript.StdIn.ReadLine
Err.Clear
End If
tSQL = ""
resultSet.MoveNext
Loop
resultSet.Close
sConnect.Close
Set sConnect = Nothing
tConnect.Close
Set tConnect = Nothing
WScript.Quit(0)
I don't know why this is happening, but here is my workaround. I will not accept this as an answer, just wanted to document.
Function allowed(n)
allowed = true
if n = 13 Then allowed = false
if n = 14 Then allowed = false
End Function
Function sanitize(v,i) 'v=value i=index
mystr = ""
if allowed(i) Then
if Not IsNull(v) Then
mystr = replace(v,"'","")
End If
end if
sanitize = mystr
End Function
Basically I'm just manually excluding the columns that have a problem. Notice that I identified a second one. What's really curious is that columns 12 and 13 have identical data in the source database, but column 12 copies fine.

VBA ADODB update recordset

I am trying to read the contents of two different tabs in a worksheet and compare them by using ADODB and querying techniques VBA.
Below you can find my code:
stCon = "Provider=Microsoft.Jet.OLEDB.4.0;" _
& "Data Source=" & wbBook.FullName & ";" _
& "Extended Properties=""Excel 8.0;HDR=YES;IMEX=1;Readonly = False"";"
' MsgBox (stCon)
'here is SQL code to gather data including our calculation from two tables within the workbook
'stSQL = "SELECT [Recon_Daily_Xml_report$].RECTYPEGLEDGER, [GL_Activity_totals$].TRXNTYPE, ([Recon_Daily_Xml_report$].Amount_Abs - [GL_Activity_totals$].BILLINGAMT) as Amount_Diff ,"
'stSQL = stSQL & " ([Recon_Daily_Xml_report$].NUMOFENTRIES - [GL_Activity_totals$].NUMOFTRXNS) as Count_Diff "
'stSQL = stSQL & " FROM [Recon_Daily_Xml_report$], [GL_Activity_totals$]"
'stSQL = stSQL & " WHERE Lower([Recon_Daily_Xml_report$].RECTYPEGLEDGER) = Lower([GL_Activity_totals$].TRXNTYPE)"
'stSQL = stSQL & " ORDER BY [Recon_Daily_Xml_report$].RECTYPEGLEDGER ASC"
stSQL = "SELECT LCASE([GL_Activity_totals$].TRXNTYPE),Sum(ABS([GL_Activity_totals$].BILLINGAMT)),Sum([GL_Activity_totals$].NUMOFTRXNS) "
stSQL = stSQL & " FROM [GL_Activity_totals$] "
stSQL = stSQL & " Group By [GL_Activity_totals$].TRXNTYPE "
stSQL = stSQL & " ORDER BY [GL_Activity_totals$].TRXNTYPE ASC"
'MsgBox (stSQL)
Set cnt = New ADODB.Connection
Set rst = New ADODB.Recordset
cnt.Open stCon
'rst.Open stSQL, cnt, 1, 3
rst.Open stSQL, cnt, adOpenStatic, adLockOptimistic
'rst.Open strSQL, cnt, adOpenStatic, adLockOptimistic
With rst
Do While Not .EOF
If rst.Fields.Item(0).Value <> "" Then
strString = Replace(rst.Fields.Item(0).Value, " ", " ")
rst.Update
rst.Fields.Item(0) = strString
End If
.MoveNext
Loop
End With
This specific code gives me back an error suggesting that I cannot update the field in the recordset I want to update when reading it. The error I am currently getting is:
Run-time error '-2147217911 Cannot update. Database or object is read-only.
Tried to change the way i open the recordset by using 1,3 option but again i was getting the same error.
Can anyone help with this?
The issue is with
LCASE([GL_Activity_totals$].TRXNTYPE)
and with the GROUP BY.
In this case, rst.Fields.Item(0) is an expression, not a table value. You can't update expressions. Also, since you're using GROUP BY, the recordset is not linked to any particular record for access to edit. You could accomplish the same task purely in SQL
cnt.Execute("UPDATE [GL_Activity_totals$] " & _
" SET [GL_Activity_totals$].TRXNTYPE = Substitute([GL_Activity_totals$].TRXNTYPE,' ', ' ') " & _
" WHERE NOT [GL_Activity_totals$].TRXNTYPE IS NULL " & _
" AND [GL_Activity_totals$].TRXNTYPE <> '';")

Resources