Access VBA looping through collection, Making SQL statement for each item - vba

I am trying to write code that makes a collection of associate IDs (Associates is the name of my collection). There are 10 associates at any given time, but the collection will change based on who did what work this month. So once the collection has been made, I want to loop through it and make an SQL statement for each item. Some thing kind of like this:
For Each Item In Associates
qryTopSQL = "SELECT TOP " & QA# & _
" Date, ID, [L#], Deal, RndNum FROM tbl_Data WHERE Date Between #" & _
StartDate & "# And #" & EndDate & "# AND ID = " & Associates() & _
" ORDER BY RndNum"
Next Item
So I end up with however many SQL strings, but I'm having problems with this:
Am I writing the ID = " & Associates() & " part correctly?
How will it name these different strings so that I may access them later?
Once it makes these, I'd like to do a UNION ALL query for all the SQL strings. How would I do this?
Please help if you can, it's greatly appreciated. I'm new to collections and arrays and I don't understand some of the things I've found online.
EDIT for an update:
I tried this:
j = 1
k = 1
For Each Item In Associates
If j = 1 And k = 1 Then
qryTopString1 = "SELECT * FROM qryTopSQL_" & k
Else
qryTopString2 = " UNION ALL SELECT * FROM qryTopSQL_" & k
End If
j = j + 1
k = k + 1
Next Item
'
Set qryTopUnionqdef = CurrentDb.CreateQueryDef("qryTopSQLUnion", qryTopString1 & qryTopString2)
But the resulting query is a union between the first and last TopSQLs, and none in the middle. Clearly the loop at this point it the problem but I can't figure out what to do thus far.

In Access there are two ways to create query objects: VBA queries (in code) or stored queries (using ribbon, wizard, or navigation bar).
Essentially, you want to do both. So in order to migrate your VBA SQL strings into actual stored query objects, you must use QueryDefs. Below is how to iterate to dynamically create the 10 Associates queries and one union query.
Dim qryTopqdef As QueryDef, qryTopUnionqdef As QueryDef, findqdf As QueryDef
Dim i as Integer, j as Integer
' DELETE QUERIES IF EXIST
For each findqdf in CurrentDb.Querydefs
If Instr(findqdf.Name, "qryTopSQL") > 0 Then
db.QueryDefs.delete(findqdf.Name)
End if
Next findqdf
' INDIVIDUAL 10 QUERIES
i = 1
For Each Item In Associates
qryTopSQL = "SELECT TOP " & QA# & _
" Date, ID, [L#], Deal, RndNum FROM tbl_Data WHERE Date Between #" & _
StartDate & "# And #" & EndDate & "# AND ID = " & Item & _
" ORDER BY RndNum"
' QUERY NAMES ARE SUFFIXED BY THE ITERATOR COUNT
Set qryTopqdef = CurrentDb.CreateQueryDef("qryTopSQL_" & i, qryTopSQL)
i = i + 1
Next Item
' UNION QUERY
j = 1
For Each Item In Associates
If j = 1 Then
qryTopSQL = "SELECT TOP " & QA# & _
" Date, ID, [L#], Deal, RndNum FROM tbl_Data WHERE Date Between #" & _
StartDate & "# And #" & EndDate & "# AND ID = " & Item & _
" ORDER BY RndNum"
Else
' UNIONS ARE SIMPLY STACKS OF SELECT STATEMENTS OF SAME COLUMN NUMBER AND DATA TYPE
' TOGETHER JOINED BY THE UNION OR UNION ALL CLAUSE
qryTopSQL = qryTopSQL & " UNION SELECT TOP " & QA# & _
" Date, ID, [L#], Deal, RndNum FROM tbl_Data WHERE Date Between #" & _
StartDate & "# And #" & EndDate & "# AND ID = " & Item & _
" ORDER BY RndNum"
End if
j = j + 1
Next Item
Set qryTopUnionqdef = CurrentDb.CreateQueryDef("qryTopSQLUnion", qryTopSQL)
' UNINTIALIZE OBJECTS
Set qryTopqdef = nothing
Set qryTopUnionqdef = nothing
Also - see this SO post on collections vs arrays

Related

VBA ACCESS - SQL statement which Counting between 2 columns which are variables

I need a macro in VBA Access. I have a table with all dates of the years like columns (and also the dates are the names of the fields). I've made a form where the user selects two dates, and the macro would count all the data between these 2 columns.
For the example, I put two fixed dates. The problem is I need count between the 2 columns, and the columns can change depending the input of the user. The table is EVOLUTIVO_ASISTENCIA and the field can change depends the user selection. Ihe following code EVOLUTIVO_ASISTENCIA.[" & INICIO_MES_VAR1 & "] is the field "01-01-2023" of the EVOLUTIVO_ASISTENCIA table, but the syntax is wrong and does not function. Can anyone help me?
The code:
Private Sub BUSQUEDA()
Dim CONTEO As String
Dim VAR1 As String
Dim INICIO_MES_VAR1 As Date, TERMINOS_MES_VAR1 As Date
INICIO_MES_VAR1 = Format("01-01-2023", "dd-mm-yyyy")
TERMINOS_MES_VAR1 = Format("31-01-2023", "dd-mm-yyyy")
VAR1 = "VAR1"
CONTEO = "SELECT COUNT(*) FROM EVOLUTIVO_ASISTENCIA " & _
"WHERE EVOLUTIVO_ASISTENCIA.[NOMBRES]='" & VAR1 & "' " & _
** "BETWEEN EVOLUTIVO_ASISTENCIA.[" & INICIO_MES_VAR1 & "] AND EVOLUTIVO_ASISTENCIA.[" & TERMINOS_MES_VAR1 & "]"**
DoCmd.RunSQL CONTEO
End Sub
You don't run a select query, you open it as a recordset. So try:
Private Sub BUSQUEDA()
Dim Records As DAO.Recordset
Dim CONTEO As String
Dim VAR1 As String
Dim INICIO_MES_VAR1 As String
Dim TERMINOS_MES_VAR1 As String
Dim ASISTENCIA_CONTEO As Long
INICIO_MES_VAR1 = "01-01-2023"
TERMINOS_MES_VAR1 = "31-01-2023"
VAR1 = "VAR1"
CONTEO = "SELECT COUNT(*) FROM EVOLUTIVO_ASISTENCIA " & _
"WHERE EVOLUTIVO_ASISTENCIA.[NOMBRES]='" & VAR1 & "' " & _
"BETWEEN EVOLUTIVO_ASISTENCIA.[" & INICIO_MES_VAR1 & "] AND EVOLUTIVO_ASISTENCIA.[" & TERMINOS_MES_VAR1 & "]"
Set Records = CurrentDb.OpenRecordset(CONTEO)
' Read/list/print records.
' Retrieve the value of the first and only field of the first and only record.
ASISTENCIA_CONTEO = Records(0).Value
' Close when done.
Records.Close
End Sub

MS-Access Dynamically Convert Variable Row Values into Variable Column Values Using VBA

Original code link: MS Access - Convert rows values into columns values
I have a follow up to a question where the answer didn't completely resolve, but got super close. It was asked at the original code link above. It's the single page on the net that actually addresses the issue of transposing multiple values in a one-to-many relationship set of columns to a single row for each related value in a dynamic manner specifically using VBA. Variations of this question have been asked about a dozen times on this site and literally none of the answers goes as far as Vlado did (the user that answered), which is what's necessary to resolve this problem.
I took what Vlado posted in that link, adjusted it for my needs, did some basic cleanup, worked through all the trouble-shooting and syntax problems (even removed a variable declared that wasn't used: f As Variant), and found that it works almost all the way. It generates the table with values for the first two columns correctly, iterates the correct number of variable count columns with headers correctly, but fails to populate the values within the cells for each of the related "many-values". So close!
In order to get it to that point, I have to comment-out db.Execute updateSql portion of the Transpose Function; 3rd to last row from the end. If I don't comment that out, it still generates the table, but it throws a Run-Time Error 3144 (Syntax error in UPDATE statement) and only creates the first row and all the correct columns with correct headers (but still no valid values inside the cells). Below is Vlado's code from the link above, but adjusted for my field name needs, and to set variables at the beginning of each of the two Functions defined. The second Function definitely works correctly.
Public Function Transpose()
Dim DestinationCount As Integer, i As Integer
Dim sql As String, insSql As String, fieldsSql As String, updateSql As String, updateSql2 As String
Dim db As DAO.Database, rs As DAO.Recordset, grp As DAO.Recordset
Dim tempTable As String, myTable As String
Dim Var1 As String, Var2 As String, Var3 As String, Var4 As String
tempTable = "Transposed" 'Value for Table to be created with results
myTable = "ConvergeCombined" 'Value for Table or Query Source with Rows and Columns to Transpose
Var1 = "Source" 'Value for Main Rows
Var2 = "Thru" 'Value for Additional Rows
Var3 = "Destination" 'Value for Columns (Convert from Rows to Columns)
Var4 = "Dest" 'Value for Column Name Prefixes
DestinationCount = GetMaxDestination
Set db = CurrentDb()
If Not IsNull(DLookup("Name", "MSysObjects", "Name='" & tempTable & "'")) Then
DoCmd.DeleteObject acTable, tempTable
End If
fieldsSql = ""
sql = "CREATE TABLE " & tempTable & " (" & Var1 & " CHAR," & Var2 & " CHAR "
For i = 1 To DestinationCount
fieldsSql = fieldsSql & ", " & Var4 & "" & i & " INTEGER"
Next i
sql = sql & fieldsSql & ")"
db.Execute (sql)
insSql = "INSERT INTO " & tempTable & " (" & Var1 & ", " & Var2 & ") VALUES ("
Set grp = db.OpenRecordset("SELECT DISTINCT " & Var1 & ", " & Var2 & " FROM " & myTable & " GROUP BY " & Var1 & ", " & Var2 & "")
grp.MoveFirst
Do While Not grp.EOF
sql = "'" & grp(0) & "','" & grp(1) & "')"
db.Execute insSql & sql
Set rs = db.OpenRecordset("SELECT * FROM " & myTable & " WHERE " & Var1 & " = '" & grp(0) & "' AND " & Var2 & " = '" & grp(1) & "'")
updateSql = "UPDATE " & tempTable & " SET "
updateSql2 = ""
i = 0
rs.MoveFirst
Do While Not rs.EOF
i = i + 1
updateSql2 = updateSql2 & "" & Var3 & "" & i & " = " & rs(2) & ", " ' <------- MADE CHANGE FROM (3) to (2)
rs.MoveNext
Loop
updateSql = updateSql & Left(updateSql2, Len(updateSql2) - 1) & " WHERE " & Var1 & " = '" & grp(0) & "' AND " & Var2 & " = '" & grp(1) & "'"
db.Execute updateSql ' <-- This is the point of failure
grp.MoveNext
Loop
End Function
Public Function GetMaxDestination()
Dim rst As DAO.Recordset, strSQL As String
myTable = "ConvergeCombined" 'Value for Table or Query Source with Rows and Columns to Transpose
Var1 = "Source" 'Value for Main Rows
Var2 = "Thru" 'Value for Additional Rows
Var3 = "Destination" 'Value for Columns (Convert from Rows to Columns)
strSQL = "SELECT MAX(CountOfDestination) FROM (SELECT Count(" & Var3 & ") AS CountOfDestination FROM " & myTable & " GROUP BY " & Var1 & ", " & Var2 & ")"
Set rst = CurrentDb.OpenRecordset(strSQL)
GetMaxDestination = rst(0)
rst.Close
Set rst = Nothing
End Function
Sample Table:
Sample Data:
Add a Debug.Print updateSql before that Execute line and will see improper syntax in SQL statement. Need to trim trailing comma from updateSql2 string. Code is appending a comma and space but only trims 1 character. Either eliminate space from the concatenation or trim 2 characters.
Left(updateSql2, Len(updateSql2) - 2)
Concatenation for updateSql2 is using Var3 instead of Var4.
Source field is a number type in ConvergeCombined and this triggers a 'type mismatch' error in SELECT statement to open recordset because of apostrophe delimiters Var1 & " = '" & grp(0) & "' - remove them from two SQL statements.
Also, Source value is saved to a text field in Transposed, make it INTEGER instead of CHAR in the CREATE TABLE action.
So with the help of a friend I figured it out. It turns out I needed two Functions because the one-to-many relationships go both directions in my case. I explain below what needs to happen in comments for this to work. Essentially I went with the second comment under the question I posed (pre-defining field names in static tables because there is a limited number of fields that any person will need - it can't exceed 256 fields anyway, but it isn't always practical to use more than a dozen or so fields - this way allows for both and at the same time to simplify the code significantly).
This solution actually works - but it's dependent on having tables (or queries in my situation) labeled ConvergeSend and ConvergeReceive. Also, it's important to note that the instances where the Destination is single and the Source is plural, the table or query (ConvergeSend/ConvergeReceive) must have the Destination value as a column TO THE LEFT of the iterated Source columns. This is also true (but reverse naming convention) for the other table/query (the Source column must be TO THE LEFT of the iterated Destination columns).
' For this code to work, create a table named "TransposedSend" with 8 columns: Source, Destination1, Destination2,...Destination7; OR however many you need
' Save the table, Edit it, change all field values to Number and remove the 0 as Default Value at the bottom
' Not changing the field values to Number causes the Insert Into function to append trailing spaces for no apparent reason
Public Function TransposeSend()
Dim i As Integer
Dim rs As DAO.Recordset, grp As DAO.Recordset
CurrentDb.Execute "DELETE * FROM TransposedSend", dbFailOnError
CurrentDb.Execute "INSERT INTO TransposedSend (Source) SELECT DISTINCT Source FROM ConvergeSend GROUP BY Source", dbFailOnError
Set grp = CurrentDb.OpenRecordset("SELECT DISTINCT Source FROM ConvergeSend GROUP BY Source")
grp.MoveFirst
Do While Not grp.EOF
Set rs = CurrentDb.OpenRecordset("SELECT Source, Destination, [Destination App Name] FROM ConvergeSend WHERE Source = " & grp(0))
i = 0
rs.MoveFirst
Do While Not rs.EOF
i = i + 1
CurrentDb.Execute "UPDATE TransposedSend SET Destination" & i & " = '" & rs(1) & "', [Destination" & i & " App Name] = '" & rs(2) & "'" & " WHERE Source = " & grp(0)
rs.MoveNext
Loop
grp.MoveNext
Loop
End Function
' For this code to work, create a table named "TransposedReceive" with 8 columns: Destination, Source1, Source2,...Source7; OR however many you need
' Save the table, Edit it, change all field values to Number and remove the 0 as Default Value at the bottom
' Not changing the field values to Number causes the Insert Into function to append trailing spaces for no apparent reason
Public Function TransposeReceive()
Dim i As Integer
Dim rs As DAO.Recordset, grp As DAO.Recordset
CurrentDb.Execute "DELETE * FROM TransposedReceive", dbFailOnError
CurrentDb.Execute "INSERT INTO TransposedReceive (Destination) SELECT DISTINCT Destination FROM ConvergeReceive GROUP BY Destination", dbFailOnError
Set grp = CurrentDb.OpenRecordset("SELECT DISTINCT Destination FROM ConvergeReceive GROUP BY Destination")
grp.MoveFirst
Do While Not grp.EOF
Set rs = CurrentDb.OpenRecordset("SELECT Destination, Source, [Source App Name] FROM ConvergeReceive WHERE Destination = " & grp(0))
i = 0
rs.MoveFirst
Do While Not rs.EOF
i = i + 1
CurrentDb.Execute "UPDATE TransposedReceive SET Source" & i & " = '" & rs(1) & "', [Source" & i & " App Name] = '" & rs(2) & "'" & " WHERE Destination = " & grp(0)
rs.MoveNext
Loop
grp.MoveNext
Loop
End Function

VBA Excel - Importing from Access and Summing Data

I am using a userform in excel to allow users to import claims data from an Access database and paste it into a destination in the workbook.
The code below allows the user to import claim numbers for each of the last 5 years for a particular policy number.
The access database currently summarizes claims data so that all the claims for policy Y in year Y are on one row. However, I need to change the code so that the individual claim amounts can be pulled in and then adjusted based on parameters set out in the userform (i.e. capping claims at 100,000) and then summarized so that all the (adjusted claims) are on a single row for each year.
I have included an image of what the database structure looked like before and what it looks like now. I would like to include something that loops over all the claims in an underwriting year and sums up the total.
database
Without getting into too much detail, I would like know how to summarize the data after I have adjusted them. Do I need another loop in the code below?
Public Const RawdataDB = "N:\***\Rawdata DB.accdb"
Private Sub CommandButton1_Click()
Dim dbRawData As Database
Dim rTemp As Recordset
Dim sSQL As String
Dim YearTemp As Integer
Dim i As Integer
i = 1
Do Until i = 6
YearTemp = Year(Range("RenewalDate")) - i
Set dbRawData = OpenDatabase(RawdataDB, False, False, "MS Access;PWD=*****")
sSQL = "SELECT Galway_Claims.* FROM Galway_Claims WHERE (Galway_Claims.PolicyNo=" & Range("PolicyNoNew") & " AND Galway_Claims.Year=" & Range("UWYear") - 1 & " AND Galway_Claims.HistoricYear=" & i & ");"
Set rTemp = dbRawData.OpenRecordset(sSQL)
Controls("ClaimNos" & i).Value = Format(rTemp!ClaimNosD, "0.0")
i = i + 1
Loop
rTemp.Close
End Sub
The easiest here seems to sum everything in access: Make another query:
sSQL = "SELECT Sum(Galway_Claims.ClaimAmts) as theSum FROM Galway_Claims WHERE (Galway_Claims.PolicyNo=" & Range("PolicyNoNew") & " AND Galway_Claims.Year=" & Range("UWYear") - 1 & " AND Galway_Claims.HistoricYear=" & i & ");"
And then read this in like before:
sSQL = "SELECT Galway_Claims.* FROM Galway_Claims WHERE (Galway_Claims.PolicyNo=" & Range("PolicyNoNew") & " AND Galway_Claims.Year=" & Range("UWYear") - 1 & " AND Galway_Claims.HistoricYear=" & i & ");"
Set rTemp = dbRawData.OpenRecordset(sSQL)
Controls("ClaimNos" & i).Value = Format(rTemp!ClaimNosD, "0.0")
sSQL = "SELECT Sum(Galway_Claims.ClaimAmts) as theSum FROM Galway_Claims WHERE (Galway_Claims.PolicyNo=" & Range("PolicyNoNew") & " AND Galway_Claims.Year=" & Range("UWYear") - 1 & " AND Galway_Claims.HistoricYear=" & i & ");"
Set rTempSum = dbRawData.OpenRecordset(sSQL)
Controls("ClaimNos" & i).Value = Format(rTempSum!theSum, "0.0")
...

Given the VB.net code, combine multiple queries into 1

Given this code below which returns a first recordset (rs) based on a date range with some values that are then used in the second recordset (rs2) to sum up a cost. Further explanation is below the code:
strSQL = "SELECT job, suffix, isnull(qty_scrapped,0),isnull(qty_released,0), isnull(price,0),co_num FROM vwDashboardsQuality "
strSQL &= " WHERE trans_date >= '" & dtpStartDate.Value & "' AND trans_date <= '" & dtpEndDate.Value & "' "
rs = conn.Execute(strSQL)
While Not rs.EOF
strCONUM = Trim("" & rs("co_num").Value)
strSelectString = "SELECT ISNULL(a_cost,0) FROM jobmatl WHERE job='" & rs("job").Value & "' AND suffix = " & Format(rs("suffix").Value)
rs2 = conn.Execute(strSelectString)
While Not rs2.EOF
dblSumActualMaterialCost = dblSumActualMaterialCost + CDbl(rs2(0).Value)
rs2.MoveNext()
End While
rs2.Close()
rs2 = Nothing
rs.MoveNext()
End While
rs.Close()
rs = Nothing
I want to combine the queries into a single query so I am not hitting the database through the second recordset (rs2) just to sum up something that I know can be done in a single query.
Any tips would be helpful. Thank you in advance.
It looks like you're just needing to do an inner join on the two queries to get one result set.
See if this works. If so, you can eliminate the second query and second inner loop.
strSQL = "SELECT d.job, d.suffix, isnull(d.qty_scrapped,0), isnull(d.qty_released,0)," _
& " isnull(d.price,0), d.co_num, ISNULL(m.a_cost,0)" _
& " FROM vwDashboardsQuality d" _
& " INNER JOIN jobmatl m" _
& " ON d.job = m.job" _
& " AND d.suffix = m.suffix" _
& " WHERE trans_date >= '" & dtpStartDate.Value & "'" _
& " AND trans_date <= '" & dtpEndDate.Value & "'"
You can paste this in Management Studio, replacing dates as applicable to check the results.
SELECT d.job, d.suffix, isnull(d.qty_scrapped,0), isnull(d.qty_released,0), isnull(d.price,0), d.co_num,
ISNULL(m.a_cost,0)
FROM vwDashboardsQuality d
INNER JOIN jobmatl m
ON d.job = m.job
AND d.suffix = m.suffix
WHERE trans_date >= '2015-09-29'
AND trans_date <= '2015-09-30'
From your code I see that you are at the end just running a SUM on all values for jobmatl.a_cost that fulfill a condition set by the where clause. So why not doing everything on the same query? And you will save yourself all the unnecessary iterations on the result set, you are loosing previous CPU time and resources there.
Also, you are not using all other values on the first query, why getting them on the first place? I removed them from the following query.
SELECT SUM(j.a_cost)
FROM vwDashboardsQuality vDQ
INNER JOIN jobmatl j
ON vDQ.job = j.job
AND vDQ.suffix = j.suffix
WHERE vDQ.trans_date >= #startdate
AND vDQ.trans_date <= #enddate;

Get Next Number MS Access

I am trying to create a Work Order System for a company and I am limited to using MS Access. I am wanting to code in a Work Order ID column. This column will be based on 2 combobox options:
BuildingName
TargetDepartment
I need some VBA code to query the WOID column in the table to retrieve the next number. The conditions will be as the below example:
WOID BuildingName TargetDepartment
BUILDA-DEPTA-1 BUILDA DEPTA
BUILDA-DEPTB-1 BUILDA DEPTB
BUILDA-DEPTA-2 BUILDA DEPTA
The VBA code would query the WOID column, and find out if there is a work order for the same building and department and then increment the number at the end by 1. But if there is no WOID that matches the buildingname and targetdepartment, it would create the first entry for that.
So if it finds a matching buildingname and targetdepartment: MaxNumber +1
If it doesn't find a matching buildingname and targetdepartment: 1
Thanks for the help!
You can do this using DLookUp :
where_condition = "[WOID] Like '" & Me.[BuildingNameCombo] & "-" & Me.[TargetDepartmentCombo] & "-*'"
existing_woid = Nz(DLookUp("[WOID]","[TableName]", where_condition),"")
If(existing_woid = "") Then
next_id = 1
Else
next_id = DMax("Mid([WOID], InStrRev([WOID],""-"")+1)","[TableName]", where_condition) + 1
End If
woid = Me.[BuildingNameCombo] & "-" & Me.[TargetDepartmentCombo] & "-" & next_id
You can do it in one line as well, but I think it is better to see the way of thinking behind this.
Edit (with record locking)
Dim s as String, rs as Recordset
s = " Select [WOID] From [TableName] " & _
" Where [WOID] Like '" & Me.[BuildingNameCombo] & "-" & Me.[TargetDepartmentCombo] & "-*'" & _
" Order By 1 Desc"
'This will restrict table access
Set rs = CurrentDb.OpenRecordset(s, dbOpenDynaset, dbDenyRead + dbDenyWrite)
If rs.RecordCount > 0 Then
next_ind = Mid(rs(0), InStrRev(rs(0), "-") + 1) + 1
Else
next_ind = 1
End If
rs.AddNew
rs.Fields("WOID") = Me.[BuildingNameCombo] & "-" & Me.[TargetDepartmentCombo] & "-" & next_ind
rs.Update
rs.Close
Set rs = Nothing