VBA - looping multiple record sets - vba

I think I have a fairly simple question to answer. The below code works perfectly as is, with an end result of populating 2 of my userform's combo boxes with field data from an Access data base. I still have several more combo boxes to fill with access data. I am looking for a way to loop through multiple SQL statements in a single record set rather than needing to create a new record set for each SQL query. As always, much appreciated.
Const conStrAccess As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data
Source=C:\Users\Andy\Desktop\Database\IATC.accdb;Persist Security
Info=False;"
Const providerSQL As String = "SELECT DISTINCT [Provider Name] FROM
tblProvider ORDER BY [Provider Name];"
Const employeeSQL As String = "SELECT DISTINCT [Employee Name] FROM
tblEmployee ORDER BY [Employee Name];"
Dim aConn As ADODB.Connection
Dim providerData As ADODB.Recordset
Dim employeeData As ADODB.Recordset
Set aConn = New ADODB.Connection
Set providerData = New ADODB.Recordset
Set employeeData = New ADODB.Recordset
aConn.ConnectionString = conStrAccess
aConn.Open
aConn.ConnectionString = conStrAccess
aConn.Open
providerData.Open providerSQL, aConn, adOpenStatic, adLockReadOnly
providerData.MoveFirst
With Me.cbxProvider
.Clear
Do
.AddItem providerData![Provider Name]
providerData.MoveNext
Loop Until providerData.EOF
End With
employeeData.Open employeeSQL, aConn, adOpenStatic, adLockReadOnly
employeeData.MoveFirst
With Me.cbxEmployee
.Clear
Do
.AddItem employeeData![Employee Name]
employeeData.MoveNext
Loop Until employeeData.EOF
End With

Consider not using any recordsets at all as MS Access form comboboxes and listboxes can use tables and queries as rowsources:
Dim var As Variant, varList As Variant
varList = Array("Provider", "Employee")
For Each var in varList
sql = "SELECT DISTINCT [" & var & " Name] " _
& " FROM tbl" & var & " ORDER BY [" & var & " Name];"
With Me.Form.Controls("cbx" & var)
.RowSourceType = "Table/Query"
.RowSource = sql
.Requery
End With
Next var

In programming a useful rule of thumb is "don't repeat yourself" ("DRY"). If you find you're writing the same code over and over with consistent variations, then you should refactor that code out into a separate method, with some parameters to manage the variations.
Untested:
Sub Main()
Const conStrAccess As String = "Provider=Microsoft.ACE.OLEDB.12.0;Data " & _
"Source=C:\Users\Andy\Desktop\Database\IATC.accdb;Persist Security Info=False;"
Const providerSQL As String = "SELECT DISTINCT [Provider Name] FROM tblProvider ORDER BY [Provider Name]"
Const employeeSQL As String = "SELECT DISTINCT [Employee Name] FROM tblEmployee ORDER BY [Employee Name]"
Dim aConn As New ADODB.Connection
aConn.Open conStrAccess
FillListBox aConn, providerSQL, Me.cbxProvider
FillListBox aConn, employeeSQL, Me.cbxEmployee
'...more lists...
aConn.Close
End Sub
'Fill a combobox from a single-field SQL query
Sub FillComboBox(con As ADODB.Connection, SQL As String, cb)
Dim rs As New ADODB.Recordset
rs.Open SQL, con, adOpenStatic, adLockReadOnly
With cb
.Clear
Do While Not rs.EOF
.AddItem rs.Fields(0).Value
rs.MoveNext
Loop
End With
rs.Close
End Sub

Related

Access VBA loop through SQL stored procedure recordset into subform

I am working on a solution for a SQL database with Access form for data entry. In this application, I have customers, and am working to provide a sub-form that will show any possible duplicates.
In another post, I have found a solution for a stored procedure, which will identify 3 types of duplicates (exact, ones with a "difference" factor between 3 columns, and ones with the same name, where one address is null and one is not). This stored procedure also looks for one of the dupes, in each of the 3 types, to be the ID of the current customer I am evaluating.
That solution is here: Stack Overflow Post 64932557
Now, on the Access side, I made a private function on the main Customer's form, where I run the stored procedure, pass the ID parameter, and then seek to only show the subform and subform tab/page if there are results. All of that seems to work, but then when I loop through the recordset output from the stored procedure, I need to map that to the unbound fields of the subform. It does this for 1 of the duplicates, but not for all the results.
I am testing with a record that has 3 duplicates (including it's own record being returned). I only get 1 record on the continuous subform, and I should get 2 if not 3, if it will include it's own record.
I run this function as part of my navigation on the form, as the user goes to the next record, previous, uses a combo to jump to a record, or brings up a form to search for a record and then goes to that record.
Private Function FindDuplicates()
Dim cmd As New ADODB.Command
Dim conn As ADODB.Connection
Dim prm As ADODB.Parameter
Dim strConn As String
Dim strSQL As String
Dim rs As ADODB.Recordset
Dim dRecs As Integer
strConn = "Provider=sqloledb;Server=MySQLServerName;Database=MyDBName;Trusted_Connection=yes;"
Set conn = New ADODB.Connection
conn.Open strConn
Set cmd = New ADODB.Command
cmd.CommandText = "sp_FindMyDuplicates"
cmd.CommandType = adCmdStoredProc
cmd.ActiveConnection = conn
Set prm = cmd.CreateParameter("CID", adInteger, adParamInput)
cmd.Parameters.Append prm
cmd.Parameters("CID").Value = Me.ID
'Execute the stored procedure
Set rs = cmd.Execute
dRecs = -1
With rs
' Debug.Print .RecordCount & " is record count"
If (rs.EOF = True) And (rs.BOF = True) Then
Me.pgDuplicates.Visible = False
Else
Me.pgDuplicates.Visible = True
If Not .BOF And Not .EOF Then
While (Not .EOF)
dRecs = dRecs + 1
'Debug.Print "customer ID: " & rs.Fields("ID") & " customer name: " & rs.Fields("FirstName")
Me.frmCustomers_subDuplicates.Form.txtFirst = rs.Fields("FirstName")
Me.frmCustomers_subDuplicates.Form.txtLast = rs.Fields("LastName")
Me.frmCustomers_subDuplicates.Form.txtAddress1 = rs.Fields("Add1")
Me.frmCustomers_subDuplicates.Form.txtAddress2 = rs.Fields("Add2")
Me.frmCustomers_subDuplicates.Form.txtCity = rs.Fields("City")
Me.frmCustomers_subDuplicates.Form.txtState = rs.Fields("State")
Me.frmCustomers_subDuplicates.Form.txtZip = rs.Fields("Zip")
.MoveNext
Wend
Me.frmCustomers_subDuplicates.Form.txtDuplicateCount = dRecs & " Duplicates Found"
End If
End If
.Close
End With
'Close the connection
conn.Close
End Function
Anyone see why I am not getting all the records in the recordset?
You are initializing dRecs with -1 instead of 0. So it will display one less.
It also seems that you are assigning the values to the same textboxes all the time, without adding new lines in the subform.
Insert the line
Me.frmCustomers_subDuplicates.SetFocus
before the loop and insert the line
DoCmd.GoToRecord , , acNewRec
after the line dRecs = dRecs + 1 to always insert a new record in the subform.
I ended up getting it worked out. Thank you all for your helpful comments and suggestions.
Private Function FindDuplicates()
Dim cmd As New ADODB.Command
Dim conn As ADODB.Connection
Dim prm As ADODB.Parameter
Dim strConn As String
Dim strSQL As String
Dim rs As ADODB.Recordset
Dim dRecs As Integer
If Not Me.NewRecord Then
strConn = "Provider=sqloledb;Server=ServerName;Database=DatabaseName;Trusted_Connection=yes;"
Set conn = New ADODB.Connection
conn.Open strConn
Set cmd = New ADODB.Command
cmd.CommandText = "sp_FindMyDuplicates"
cmd.CommandType = adCmdStoredProc
cmd.ActiveConnection = conn
Set prm = cmd.CreateParameter("CID", adInteger, adParamInput)
cmd.Parameters.Append prm
cmd.Parameters("CID").Value = Me.ID
'Execute the Stored Procedure
cmd.Execute
If DCount("ID", "tblCustomerDupesTemp", "ID = " & Me.ID) = 0 Then
Me.pgDuplicates.Visible = False
Else
Me.pgDuplicates.Visible = True
Me.frmCustomer_subDuplicates.Form.Filter = "[ID] <> " & Me.ID & " And [AnchorID] = " & Me.ID
Me.frmCustomer_subDuplicates.Form.FilterOn = True
Me.frmCustomer_subDuplicates.Form.txtDuplicateCount = CStr(Me.frmCustomer_subDuplicates.Form.CurrentRecord) & " of " & _
DCount("ID", "tblCustomerDupesTemp", "ID <> " & Me.ID) & " Duplicate Customer(s)"
Me.frmCustomer_subDuplicates.Form.Requery
End If
End If
End Function

Is it possible to send off info row by row from Access to QuickBooks?

Currently I have the following code that allows me to insert values into specific fields in QuickBooks.
I am trying to add fields from a table into QuickBooks row by row:
See picture ex:
Example:
At the end of each row there is a column for sending off the entries to QuickBooks. How can I modify my code to have this function work?
Public Sub exampleInsert()
Const adOpenStatic = 3
Const adLockOptimistic = 3
Dim oConnection
Dim oRecordset
Dim sMsg
Dim sConnectString
Dim sSQL
sConnectString = "DSN=Quickbooks Data;OLE DB Services=-2;"
sSQL = "Insert into customer (Name, FullName, CompanyName) values ('Testing VB', 'Full Name', 'Test Company Name')"
Set oConnection = CreateObject("ADODB.Connection")
Set oRecordset = CreateObject("ADODB.Recordset")
oConnection.Open sConnectString
oConnection.Execute (sSQL)
sMsg = sMsg & "Record Added"
MsgBox sMsg
Set oRecordset = Nothing
Set oConnection = Nothing
End Sub
UPDATE:
I added:
sConnectString = "DSN=Quickbooks Data;OLE DB Services=-2;"
sSQL = "Insert into customer (Name, CompanyName) Select Num, Description From TestTable"
Set oConnection = CreateObject("ADODB.Connection")
Set oRecordset = CreateObject("ADODB.Recordset")
oConnection.Open sConnectString
oConnection.Execute (sSQL)
sMsg = sMsg & "Record Added"
MsgBox sMsg
But I get the error "Invalid table name: TestTable" how can I get this SQL script to see my Access table?
To add the form's current record values to your queries, you just pull the value (e.g. Me.txtDescription). I would recommend you use the ADODB.Command object, so you can parameterize your SQL and avoid SQL injection:
Option Explicit
Const adOpenStatic As Integer = 3
Const adLockOptimistic As Integer = 3
Const CONNECTION_STRING As String = "DSN=Quickbooks Data;OLE DB Services=-2;"
Private Sub Command10_Click()
Dim cn As ADODB.Connection
Dim cmd As ADODB.Command
Dim param As ADODB.Parameter
Dim sMsg As String
' set up ADODOB connection
Set cn = New ADODB.Connection
cn.Open CONNECTION_STRING
' set up ADODB command object
Set cmd = New ADODB.Command
cmd.ActiveConnection = cn
' note that we're using unnamed parameters,
' with the ? symbol
cmd.CommandText = _
"INSERT INTO customer " & _
"(Name, CompanyName) " & _
"VALUES " & _
"(?, ?)"
' add form values as command parameters
cmd.Parameters.Append cmd.CreateParameter( _
Type:=adVarChar, _
Size:=255, _
value:=Me.txtNumber)
cmd.Parameters.Append cmd.CreateParameter( _
Type:=adVarChar, _
Size:=255, _
value:=Me.txtDescription)
' now that we have the command set up with its params,
' we can just execute it:
cmd.Execute
sMsg = "Record Added"
MsgBox sMsg
Set param = Nothing
Set cmd = Nothing
cn.Close: Set cn = Nothing
End Sub
Of course, you'll have to use the actual names of your textboxes.
Also, please notice a couple of additional modifications I a made to your original code:
I have Option Explicit defined. You may already have this in your code, but if not, you need it. That way, any variables used have to be declared. For more information, see the Microsoft Docs
I moved your ADODB constants outside your sub. Ideally, you'd either use early binding and add the ADODB library reference (so you don't need to define these yourself), or add them in a separate module, so you can use them in any of your forms.
I also added your connection string as a constant outside your sub. Again, this should probably be in a separate module (e.g. modConstants) you can easily refer to from anywhere in your project.
I improved the indentation of your code.
I explicitly added the types for your declarations (Dim sSQL as String rather than just Dim sSQL). Note that if you declare a variable without a type, it defaults to the Variant type, instead of String (which you want). See Microsoft Docs for more information.

Using VBA Combobox value in SQL Query returning type mismatch

I have a user form that contains a list of agents. When you click on an agent and click a button to mark them as not here, a popup comes up that requires you to pick a reason they aren't here.
This popup also contains a box that should display the current amount of attendance points the agent has. The code to pull that info from the SQL table where it's stored is below.
When it runs I get a type mismatch error on the .additem rs![Five9 Extension] line. The column on the SQL table is a varchar and I just need it to display a number so I'm not really sure what the issue is.
Dim Cn As ADODB.Connection
Dim Server_Name As String
Dim Database_Name As String
Dim SQLStr As String
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
Server_Name = "SDL02-VM25"
Database_Name = "PIA"
SQLStr = "select [Five9 Extension] from dbo.[Master Staffing List] Where [Agent Name] ='" & MainPage.AgentName.Selected(itemIndex) & "'"
Set Cn = New ADODB.Connection
Cn.Open "Driver={SQL Server};Server=" & Server_Name & ";Database=" & Database_Name & ""
rs.Open SQLStr, Cn, adOpenStatic
With ReasonPopup.CurPoints
.Clear
Do
.AddItem rs![Five9 Extension]
rst.MoveNext
Loop Until rst.EOF
End With
rs.Close
Cn.Close
Set rs = Nothing
Set Cn = Nothing
Exit Sub
Bang operator implicit default member calls aside (rs![Field Name] is shorthand for rs.Fields("Field Name").Value), it seems the query isn't returning what you think it does:
SQLStr = "select [Five9 Extension] from dbo.[Master Staffing List] Where [Agent Name] ='" & MainPage.AgentName.Selected(itemIndex) & "'"
ListBox.Selected(index) returns a Boolean, so the query you're sending is something like
select [Five9 Extension] from dbo.[Master Staffing List] Where [Agent Name] ='True'
....Which I would expect to yield a grand total of 0 rows.
Question: what would happen if [Agent Name] were to be Jake O'Neil? That's right, a syntax error with the query. Now what if [Agent Name] were to be Robert'; DROP TABLE [Master Staffing List];--? That's right, very bad things. This is called a SQL injection vulnerability, and it plagues database-queryuing code all over the world, whenever people concatenate WHERE clauses with user inputs. It's not only a matter of database security, it's also a cause of easily avoidable bugs.
Let's fix this. Use an ADODB.Command, and in your SQL string remove the single quotes and replace the parameter concatenation with a question mark:
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
Set cmd.ActiveConnection = Cn
cmd.CommandText = "SELECT [Five9 Extension] FROM dbo.[Master StaffingList] WHERE [Agent Name] = ?"
cmd.CommandType = adCmdText
cmd.Parameters.Append cmd.CreateParameter(Type:=adVarChar, Value:=selectedName)
Set rs = cmd.Execute
Now for consuming the recordset, you can't assume that there will be rows - so you make a Do While loop that doesn't enter if rs.EOF is True:
Do While Not rs.EOF
'...consume recordset...
rs.MoveNext
Loop
Now, only selectedName needs to be figured out. Use the listbox' ListIndex property to do that:
Dim selectedName As String
With MainPage.AgentName
Debug.Assert .MultiSelect = fmMultiSelectSingle 'wheels come off otherwise
selectedName = .List(.ListIndex)
End With

How to export SQL statement results to an Excel File

I have an Access DataBase and a form in Excel VBA. All the data I input into the DB is input through the VBA form.
This DB contains all the benefits cards we already received this year in the company. But the same employee can ask for the card twice or more, so we'll have more than one record on the DB for him.
What I need is when the number of records is greater than one, the SQL statement result should appear in a Excel report.
I use the SELECT (*) COUNT statement to know when there is more than one record that is compatible with the search criterion. But I can't make the result appear in an Excel file.
Here is my code:
Public Function Relatorio()
Dim sql As String
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim rel As String
Set cn = New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & enderecoDB & ";Jet OLEDB:Database"
cn.Open
Set rs = New ADODB.Recordset
sql = "INSERT INTO OPENROWSET('Microsoft.ACE.OLEDB.12.0', 'Excel 12.0;Database=" & enderecoDB & ";', 'SELECT * FROM [Planilha1$]') SELECT * FROM controle WHERE BP = " & controlectform.nmbpbox.Value & ";"
rs.Open sql, cn
End Function
When I run this code it gives me a message saying something like:
Can't locate the OPENROWSET Table exit
I'm not able to install new programs, so I need to do this using only Excel VBA and the Access DB.
How can I make this work?
I don't believe Access supports the OPENROWSET, dynamic table you're working with there. I have a lot of old projects that do this though, so here's my method
Public Function Relatorio()
Dim sql As String
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim rel As String
Set cn = New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & enderecoDB & ";Jet OLEDB:Database"
cn.Open
Set rs = New ADODB.Recordset
dim path_To_XLSX
dim name_of_sheet
path_To_XLSX = "c:\temp\output.xlsx"
name_of_sheet = "Planilha1"
sql = sql = "SELECT * INTO [Excel 12.0;Database=" & path_To_XLSX & "]." & name_of_sheet & " FROM controle WHERE BP = '" & controlectform.nmbpbox.Value & "';"
rs.Open sql, cn
'If this application is in an unsecure environment, use the following code instead! This is to prevent a SQL injection, security concern here.
'As it is an Access Database, this is likely overkill for this project
'Create Command Object.
Set Cmd1 = New ADODB.Command
Cmd1.ActiveConnection = cn
cmd1.CommandText = "SELECT * FROM controle INTO [Excel 12.0;Database=" & path_To_XLSX & "]." & name_of_sheet & " WHERE BP = ?"
' Create Parameter Object.
Set Param1 = Cmd1.CreateParameter(, adInteger, adParamInput, 5) 'use adVarchar for strings(versus adInteger), https://www.w3schools.com/asp/met_comm_createparameter.asp
Param1.Value = controlectform.nmbpbox.Value
Cmd1.Parameters.Append Param1
Set Param1 = Nothing
Set Rs = Cmd1.Execute()
End Function
I had this challenge so many years ago that I cant remember but this link ring the bell. check if it help.
https://stackoverflow.com/a/28889774/382588
try { connw.Open(); OleDbCommand command; command = new OleDbCommand( "Update Deliveries " + "SET Deliveries.EmployeeID = ?, Deliveries.FIN = ?, Deliveries.TodaysOrders = ? , connw); command.Parameters.Add(new OleDbParameter("#EMPID", Convert.ToDecimal(empsplitIt[1]))); command.Parameters.Add(new OleDbParameter("#FIN", truckSplit[1].ToString())); command.Parameters.Add(new OleDbParameter("#TodaysOrder", "R")); catchReturnedRows = command.ExecuteNonQuery();//Commit connw.Close(); } catch (OleDbException exception) { MessageBox.Show(exception.Message, "OleDb Exception"); }
you can use this, to print the actual SQL.
Private Sub Command2_Click()
Dim db As Database
Dim qr As QueryDef
Set db = CurrentDb
For Each qr In db.QueryDefs
TextOut (qr.Name)
TextOut (qr.SQL)
TextOut (String(100, "-"))
Next
End Sub
Public Sub TextOut(OutputString As String)
Dim fh As Long
fh = FreeFile
Open "C:\Users\rs17746\Desktop\Text_Files\sample.txt" For Append As fh
Print #fh, OutputString
Close fh
End Sub
Here is one more version for you. This will export the results of each query, each to a separate text file.
Private Sub Command0_Click()
Dim qdf As QueryDef
Dim strFileName As String
For Each qdf In CurrentDb.QueryDefs
If Left(qdf.Name, 1) <> "~" Then
'you need to figure out TransferText command. Maybe
'you won't be lazy and expect people to read it to
'you and tutor you on how it works.
strFileName = qdf.Name
'Docmd.TransferText ....
DoCmd.TransferText transferType:=acExportDelim, TableName:=strFileName, FileName:="C:\test\" & strFileName & ".txt", hasfieldnames:=True
End If
Next qdf
MsgBox "Done"
End Sub

String truncated at 255 chars when got from a MS Access SQL query

I'm having problems with a String var in which I put a SQL query I've saved in MS Access. It works like this:
I generate a list through a SELECT query
Using a menu at a Userform with Excel VBA, I choose columns to order the list
As I will have to order many lists in many ways, I decided to create a Sub ordenar(ByVal sqlLista As String, ByVal sqlOrd As String).
sqlLista contains the name of the SELECT query that generates the list I want to order; sqlOrd contains the ORDER BY <col1>... piece of query:
sqlLista = "ListaAbonos"
ListaAbonos (Access query) =
SELECT Left('0000',4-Len(c.nro_cliente)) & c.nro_cliente & ' - ' & IIF(IsNull(razon_social),apellido & ' ' & nombre, razon_social), a.cod_localidad & '/' & cod_cobrador, cod_abono, descripcion, tel_verificacion, fecha_alta, a.direccion, s.nombre_servicio, ts.valor, a.estado
FROM ((abonos AS a
INNER JOIN servicios AS s ON a.cod_servicio = s.cod_servicio)
INNER JOIN tarifas_servicio AS ts ON a.cod_servicio = ts.cod_servicio)
INNER JOIN clientes AS c ON a.nro_cliente = c.nro_cliente;
sqlOrd = "ORDER BY..."
[NOTE: I ran the entire query (SELECT...ORDER BY...) and IT WORKS PROPERLY]
DECLARATIONS
Public Sub ordenar(ByVal sqlLista As Variant, ByVal sqlOrd As Variant)
Dim cs As String
Dim sPath As String
Dim sql As String
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim qd As DAO.QueryDef
CONNECTION TO DB:
sPath = "C:\Users\Ezequiel\Documents\ZEN.accdb"
cs = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & sPath & ";Persist Security Info=False;"
Set cn = New ADODB.Connection
cn.Open cs
Set rs = New ADODB.Recordset
With rs
.CursorLocation = adUseClient
.CursorType = adOpenStatic
.LockType = adLockOptimistic
End With
THIS IS THE IMPORTANT PART OF THE CODE:
Set qd = CurrentDb.QueryDefs(sqlLista)
sql = Left(qd.sql, Len(qd.sql) - 1) & " " & sqlOrd
Set rs = cn.Execute(sql)
When I execute the Sub, I get an error
Characters found after end of sql statement
Inspecting "sql" and "qd.sql" I found that the query had been truncated:
sql = qd.sql = "SELECT Left('0000',4-Len(c.nro_cliente)) & c.nro_cliente & ' - ' & IIF(IsNull(razon_social),apellido & ' ' & nombre, razon_social), a.cod_localidad & '/' & cod_cobrador, cod_abono, descripcion, tel_verificacion, fecha_alta, a.direccion, s.nombre_serv
Note that the ORDER BY doesn't even appear as it's beyond the first 255 chars.
What's the problem? Thanks!