How to run a double loop in Access VBA - sql

i'm learning Access by few days and i'm having some problems running into a double loop. I'm doing something wrong because i'm not expert with Access. It seems i'm stuck in an infinite loop (or some other error). My goal is to retrieve some data from the same table. Here's an example:
Table1
Field1 Field2
AAA 1
BBB 2
CCC 3
CCC 4
AAA 5
BBB 6
i want to retrieve all the records in field 2 where Field1= x (but not repeating the action if the same record is found)
Here's my code:
Dim strSQL as String, rs as DAO.Recordset, rs2 as Dao.Recordset, result as String
strSQL = "SELECT * FROM Table1"
strSQL2 = "SELECT DISTINCT Field1 FROM Table1"
Set rs = Currentdb.OpenRecordset(strSQL)
Set rs2 = Currentdb.OpenRecordset(strSQL2)
rs2.movefirst
While not rs2.EOF
rs.movefirst
result = ""
While not rs.EOF
If rs.Fields("Field1") = rs2.Fields("Field1") Then
result = result & rs.fields("Field2") & " "
rs.movenext
End if
Wend
rs2.movenext
Debug.print result
Wend
Set rs=Nothing
Set rs2 = Nothing
The result i want should be:
Result(first loop) = 1 5
Result(second loop) = 2 6
Result(third loop) = 3 4
EDIT:
i'm running loops because i need to generate emails with the data i found. I know how to generate emails but i'm only stacked with this loop. An example of emails should be like this:
number of emails = number of unique values in [table1].[field1] (so 3 in the before mentioned example)
for each email a list of all records in field2 that has the value-in-loop in field1.

Assuming Field2 actually contains unique number values, consider:
TRANSFORM Max(Table1.Field2) AS MaxOfField2
SELECT Table1.Field1
FROM Table1
GROUP BY Table1.Field1
PIVOT DCount("*","Table1","Field1='" & Field1 & "' AND Field2<" & Field2)+1;
However, if there are too many values for a CROSSTAB to handle or if you really need a single string of values from Field2 for each value in Field1, use VBA to concatenate data. One version http://allenbrowne.com/func-concat.html

Another option using the getstring method of an ado recordset.
a) Paste into a new query in sql view and save: select field1,field2list(field1) from table1
b) In a vba module, paste field2list function:
Function field2list(field1) As String
Dim rs As New adodb.Recordset
Dim ColumnSeparator
Dim RowSeparator
Dim sql
ColumnSeparator = ""
RowSeparator = ","
sql = "select field2 from table1 where field1='" & field1 & "'"
Set rs = CurrentProject.Connection.Execute(sql)
If rs.RecordCount > 0 Then
field2list = rs.GetString(, -1, ColumnSeparator, RowSeparator)
End If
End Function
c) Open query created in step a.

Related

MS Access line chart Create Values for X-Axis

I have a table in MS Access that is structured like this (example)
part number
time of testing
cleanliness class A
cleanliness class B
B
2021-06-12 15:22:22.00
20
30
A
2021-06-14 13:04:22.00
400
50
A
2021-06-14 13:28:28.00
200
60
A
2021-06-14 14:17:5.00
300
11
B
2021-06-17 09:25:7.00
18
5
B
2021-06-17 09:37:7.00
21
17
A
2021-06-25 11:53:18.00
150
70
C
2021-06-26 18:01:01.00
210
30
As you can see, the cleanliness of different parts is tested in no particular order.
My goal is to create a line chart of the cleanliness of each cleanliness class that contains the part numbers I choose of a list that contains all the part numbers while also choosing a timeframe.
time of testing
At first I have two textboxes in which you can put Start and End dates. With the following code I do a query which selects only the rows between these dates.
Private Sub Befehl11_Click()
'Dates
Dim Anfang As Variant
Dim Ende As Variant
Text5.SetFocus
Anfang = Text5.Text
Text7.SetFocus
Ende = Text7.Text
Dim dbs As DAO.Database
Dim qdf As QueryDef
Dim strSQL As String
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("DatumGefiltert")
Application.RefreshDatabaseWindow
'SELECT-Statement bauen Build Select Statement
strSQL = "SELECT * FROM dbo_Cleanliness WHERE Format(dbo_Cleanliness.Date_of_Analysis,'yyyy-MM-dd hh:mm:ss') >= Format("""
strSQL = strSQL & Anfang & " 00:00:00"""
strSQL = strSQL & ",""yyyy-MM-dd hh:mm:ss"") AND Format(dbo_Cleanliness.Date_of_Analysis,'yyyy-MM-dd hh:mm:ss') <= Format("""
strSQL = strSQL & Ende & " 23:59:00"" , ""yyyy-MM-dd hh:mm:ss"")"
Text9.SetFocus
Text9.Text = strSQL
qdf.SQL = strSQL
End Sub
List
The next step is to create a list where a can choose several part numbers.
First I created a query which only selects the column "part number" with SELECT DISTINCT
| part number |
| :--|
|A |
|B |
|C|
Then I created a listbox in a form with that query as source and enabled multiselect
Listbox
With a button the following code is run to build and execute a query
Private Sub Befehl4_Click()
Dim ctlSource As Control
Dim strItems As String
Dim intCurrentRow As Integer
'My listbox
Set ctlSource = Liste2
'The objects of the WHERE clause
For intCurrentRow = 0 To ctlSource.ListCount - 1
If ctlSource.Selected(intCurrentRow) Then
strItems = strItems & " " & "Nummer = " & ctlSource.Column(0, intCurrentRow) & " Or "
End If
Next intCurrentRow
'Get rid of the last OR
strItems = Left(strItems, Len(strItems) - 4)
'Build the Query
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Dim strSQL As String
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("myQuery2")
Application.RefreshDatabaseWindow
strSQL = "SELECT * FROM DatumGefiltert "
strSQL = strSQL & "WHERE "
strSQL = strSQL & strItems
'Order By part number(Nummer) and time of testing(Datum)
strSQL = strSQL & " ORDER BY Nummer, Datum"
'RUN Query
qdf.SQL = strSQL
'CLEAR the variables
' qdf.Close
' Set qdf = Nothing
' Set dbs = Nothing
End Sub
If I select A and B in the listbox I want to get.
part number
time of testing
cleanliness class A
cleanliness class B
A
2021-06-14 13:04:22.00
400
50
A
2021-06-14 13:28:28.00
200
60
A
2021-06-14 14:17:5.00
300
11
A
2021-06-25 11:53:18.00
150
70
B
2021-06-12 15:22:22.00
20
30
B
2021-06-17 09:25:7.00
18
5
B
2021-06-17 09:37:7.00
21
17
Now I want to create a line chart for each of the cleanliness classes that contains both the part numbers.
Desired Chart
My problem now is:
If I would use the date of analysis as values for the x-Axis the lines wouldn´t be conected
My idea would be to add a new column with the amount of times the part has been tested.
part number
time of testing
cleanliness class A
cleanliness class B
Test number
A
2021-06-14 13:04:22.00
400
50
1
A
2021-06-14 13:28:28.00
200
60
2
A
2021-06-14 14:17:5.00
300
11
3
A
2021-06-25 11:53:18.00
150
70
4
B
2021-06-12 15:22:22.00
20
30
1
B
2021-06-17 09:25:7.00
18
5
2
B
2021-06-17 09:37:7.00
21
17
3
These values in the column test number could be used as the X-Axis.
But unfortunately I don´t know how to do this?
Or is there maybe a simpler way to achieve my Linechart overall?
Thanks in advance! If something is unclear please ask for clarification.
Greetings arijon
I have Access 2010 and can use only classic MSGraph, not ModernChart.
Following assumes there is a unique ID field (if there isn't, can easily add autonumber type) otherwise use [time of testing] field for unique identifier.
Build and save Query1 (replace Data with your table or query name):
SELECT ID, [part number], [time of testing], [cleanliness class A] AS Cleanliness,
"A" AS Class,
DCount("*","Data","[part number]='" & [part number] & "' AND ID<=" & [ID]) AS [Test Number]
FROM Data
UNION
SELECT ID, [part number], [time of testing], [cleanliness class B], "B",
DCount("*","Data","[part number]='" & [part number] & "' AND ID<=" & [ID])
FROM Data;
A correlated subquery can be used instead of DCount() to calculate group sequence number, review Access query counter per group
Build a form or report with RecordSource:
SELECT "A" AS Class FROM Data UNION SELECT "B" FROM Data;
Create a textbox named tbxClass and bind it to Class field. Label caption Cleanliness Class:
Create chart with RowSource:
PARAMETERS [tbxClass] Text ( 255 );
TRANSFORM Sum(Cleanliness) AS Clean
SELECT [Test Number] FROM Query1
WHERE [Class]=[tbxClass]
GROUP BY [Test Number]
PIVOT [part number];
I wasn't able to set form for ContinuousView (error "can't view a form as continuous if it contains ... a bound chart ...") - never encountered that before but never used a CROSSTAB query with dynamic parameter as RowSource. If you want the two graphs viewed at same time, then instead of dynamic parameter in WHERE clause, build two graph objects and use static criteria for Class field WHERE Class="A".
However, dynamic parameterized CROSSTAB RowSource works just fine for a report.
Strongly advise not to use spaces nor punctuation/special characters in naming convention, better would be: TimeOfTesting or TestTime.

While Loop in VBA Access

I have delete and append functions that build Table1 based on inputs from the user. Therefore Table1 has a different number of records appending to it for every user.
My SQL code works to find the dates, but it only does it once, I need to loop the SQL code for the length of the table. I'm not great at coding, I tried a while statement, not sure if I can use variable Z in the criteria for that, but I want it to run until the due_date in the record with the smallest ID value has been filled.
Here's what I tried:
Private Sub Command7_Click()
Y = DMax("ID", "Table1", BuildCriteria("Due_date", dbDate, "Null"))
A = DMin("ID", "Table1", BuildCriteria("Due_date", dbDate, "Not Null"))
X = DMin("ID", "Table1")
Z = DLookup("Due_date", "Table1", BuildCriteria("ID", dbLong, CStr(X)))
B = DLookup("Duration", "Table1", BuildCriteria("ID", dbLong, CStr(Y)))
C = DLookup("Due_date", "Table1", BuildCriteria("ID", dbLong, CStr(A)))
E = DateAdd("d", -B, C)
Dim SQL As String
SQL = "UPDATE Table1 " & _
"SET " & BuildCriteria("Due_date", dbDate, CStr(E)) & " " & _
"WHERE " & BuildCriteria("ID", dbLong, CStr(Y))
While Z Is Null
DoCmd.RunSQL SQL
End While
End Sub
To illustrate:
Before Running SQL
After running SQL once
After clicking several times
The goal would be to click once and the whole table fills
Your variable Z contains the result returned by the DLookup function when evaluated as the fourth line of the definition of your sub Command7_Click; the value of this variable will not change unless the variable is redefined.
The intent of your code is somewhat obscured by the use of your BuildCriteria function, so it is difficult to advise the best way to write the code...
Edit: BuildCriteria is a new one for me - thanks to #Andre for pointing this out.
Since the content of your SQL statement is static, there should be no need for a loop, as nothing is changing within the loop - the SQL statement will update all records which meet your criteria and will do nothing for every subsequent iteration (unless, that is, the value to which you are updating the records also fulfils the selection criteria).
EDIT
Based on your additional explanations & screenshots, you could approach the task by iterating over a recordset sorted by your ID field and successively calculating the appropriate Due_date for each record - something like:
Private Sub Command7_Click()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim dat As Date
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("select * from Table1 order by ID desc")
With rst
If Not .EOF Then
.MoveFirst
Do Until .EOF
If Not IsNull(!Due_date) Then
dat = !Due_date
Else
dat = DateAdd("d", -!Duration, dat)
.Edit
!Due_date = dat
.Update
End If
.MoveNext
Loop
End If
.Close
End With
Set rst = Nothing
Set dbs = Nothing
End Sub
Though based on your screenshots, it seems that you are trying to use Access like an Excel spreadsheet.
Consider actually no For loop, no BuildCriteria and even no VBA SQL. Save the update query as an MS Access action query object that is run on button click.
Specifically, you would need several domain functions --DLookUp, DSum, and DMax-- where you calculate a running sum of duration days (i.e., a correlated aggregate computation) and then DateAdd the result to the DueDate of the corresponding maximum ID with no missing DueDate.
SQL
UPDATE myTable d
SET d.DueDate = DateAdd("d", -1 * DSum("Duration", "DueDateDuration", "ID >= " & d.ID),
DLookUp("DueDate", "DueDateDuration", "ID = " &
DMax("ID", "DueDateDuration", "DueDate IS NOT NULL")
)
)
WHERE d.DueDate IS NULL;
VBA
Private Sub Command7_Click()
DoCmd.OpenQuery "mySavedUpdateQuery" ' WITH WARNINGS
CurrentDb.Execute "mySavedUpdateQuery" ' WITHOUT WARNINGS
End Sub
To demonstrate on sample data:
Before Update (mytable)
ID Item Duration DueDate
2674 Issue 1 2/18/2019
2675 Shipping 1 2/19/2019
2678 Completed 0 2/20/2019
2679 Issue 1
2680 Shipping 10
2681 Other 6
2682 Buy Off 6
2683 Punch List 3
2684 Completed 0 3/29/2019
After Update (mytable)
ID Item Duration DueDate
2674 Issue 1 2/18/2019
2675 Shipping 1 2/19/2019
2678 Completed 0 2/20/2019
2679 Issue 1 3/3/2019
2680 Shipping 10 3/4/2019
2681 Other 6 3/14/2019
2682 Buy Off 6 3/20/2019
2683 Punch List 3 3/26/2019
2684 Completed 0 3/29/2019

Determining if Spreadsheet Entries Match Database Column Entries

One aspect of my project involves comparing the part number entered by the operator to a predetermined list of part numbers in a column in a database. Right now, my program is telling me that every part number entered in the spreadsheet (50+) does not match any in the database, which I've verified to be incorrect. I've checked that both the spreadsheet part number and the database part number are of string datatype. I've doublechecked that my looping logic is good, and to me seems like it should work. To the best of my knowledge there are no hidden characters in either the database cells or in the spreadsheet cells. I'm completely stumped at this point as to why my program doesn't detect any matches between the spreadsheet and the database. Below is the Sub containing the code for checking that the part numbers match:
Sub CheckPN()
'Connect to the E2 database
Call SetPNConnection
'Open a recordset
Set PNRecordset = New ADODB.Recordset
PNRecordset.Open "EstimRpt", PNConnection, adOpenKeyset, adLockOptimistic, adCmdTable
PNSQLCmd = "SELECT DISTINCT [PartNo] FROM EstimRpt;"
'Loop through data, comparing part numbers to E2 database part number records
TotalBadPNCount = 0
With PNRecordset
For DataRowCount = 2 To TrackingLastRow
PNCount = 0
Part_Number = Tracking.Sheets("Operator Data").Range("A" & DataRowCount).Value
'MsgBox "The datatype for " & Part_Number & " is " & VarType(Part_Number) & "."
Do Until .EOF
'MsgBox "The datatype for " & .Fields("PartNo").Value & " is " & VarType(.Fields("PartNo").Value) & "."
If Part_Number = .Fields("PartNo").Value Then
'If .Fields("PartNo").Value = Part_Number Then
MsgBox Part_Number & " is a match."
PNCount = PNCount + 1
End If
.MoveNext
Loop
If PNCount < 1 Then
MsgBox "The P/N " & Part_Number & " entered in cell A" & DataRowCount & " is incorrect. Please correctly enter the P/N and re-run the program."
TotalBadPNCount = TotalBadPNCount + 1
End If
Next DataRowCount
If TotalBadPNCount >= 1 Then
Exit Sub
End If
End With
PNRecordset.Close
Set PNRecordset = Nothing
PNConnection.Close
Set PNConnection = Nothing
End Sub
On a side note, I'd like to have the entire program stop executing if a part number doesn't match, not just the immediate sub. Currently, just this sub exits upon no part number matches.
Thanks for the help on both of these issues.
Jordan
I'd suggest not using a loop to compare records from your user-submitted dataset to your permanent table. Instead, load the user-submitted dataset into a temporary table in your DB, and use SQL to compare the 2 tables.
You can try something along these lines:
'Load spreadsheet into temp table
<your code here>
'open recordset in order to compare PartNos
Dim db As Database
Set db = CurrentDb
Dim rs As Recordset
sSQL = "select count(*) as [count] from temp " _
& " where temp.PartNo not in (select distinct EstimRpt.PartNo from EstimRpt)"
Set rs = db.OpenRecordset(sSQL)
ctRecords = rs![Count]
'if records are found in temp table that do not exist
'in the perm table, then end execution of everything.
if ctRecords > 0 then
End
else
'run the rest of your code
<your code here>
end if
'Drop temp table
<your code here>
I found my problem at long last. The comparing records between database and spreadsheet does work now. I had to make the following changes to my code:
Instead of:
Do Until .EOF
I needed:
Do Until .EOF = True
I also needed to add the following just after the For Loop declaration:
.MoveFirst
Now my code loops correctly.

how to update a table using another table

I have an access 2007 database with two tables main and main1 both tables have the same design and the same fields (85 fields)
I want to update one of them from the other one , is there an easy way to do that? I know I can use update query but I see in this case I have to specify each field in both tables in the query design and that is hard for 85 fields.
So what should I do in this case ?
UPDATE main
SET main.ID = [main1]![ID],
main.eng1job = [main1]![eng1job],
main.[eng1job-s] = [main1]![eng1job-s],
main.[eng1job-q] = [main1]![eng1job-q];
Using VBA
dim rs1 as Recordset
dim rs2 as Recordset
dim i as Integer
dim c as Integer
set rs1 = CurrentDb.OpenRecordset("main")
set rs2 = CurrentDb.OpenRecordset("main1", dbOpenDynaset)
c = rs1.Fields.Count - 1
Do Until rs1.EOF
rs2.AddNew
For i = 0 To c
rs2(i) = rs1(i)
Next
rs2.Update
rs1.MoveNext
Loop

Access 2007 VBA code returns only the first record of the loop

I have two tables, I am trying to get data from table B into table A. If The ID for table A is not found in table B then leave it null.
Eg.
AID Sttl BID Sttl
4 88 3
20 92 2
88 3 100 8
92 2 500 10
800
The code I currently have only return the first similar record in table A. It does not return the next matching record (eg. it returns 3 for AID 88 but does not return anything for AID92). What am i missing?
Dim db As dao.Database
Dim rs1 As dao.Recordset
Dim rs2 As dao.Recordset
Set db = CurrentDb()
Set rst1 = db.OpenRecordset(cstrFromtbl, dbOpenDynaset)
Set rst2 = db.OpenRecordset(cstrTotbl, dbOpenDynaset)
Do While Not rs2.EOF
If rs2.Fields("A.ID") = rs1.Fields("B.ID") Then
rs2.Edit
rs2.Fields("Sttl") = rs1.Fields("Sttl")
rs2.Update
Else
rs2.Edit
rs2.Fields("Sttl") = Null
rs2.Update
End If
rs2.MoveNext
Loop
Set rs1 = Nothing
Set rs2 = Nothing
Set dbs = Nothing
End Function
I could be wrong but it looks like you are recreating SQL in VBA. The SQL below is simpler and will probably run much, much faster.
UPDATE tablea
INNER JOIN tableb
ON tablea.idfield = tableb.idfield
SET tablea.destinationfield = tableb.sourcefield
What many forgot, you can't be sure about the order of a table except you sort it. Therfore you have to do a search, and check if something was found.
Do While Not rs2.EOF
rs1.FindFirst("ID=" & rs2.Fields("A.ID"))
rs2.Edit
If rs1.NoMatch Then
rs2.Fields("Sttl") = Null
Else
rs2.Fields("Sttl") = rs1.Fields("Sttl")
EndIF
rs2.Update
rs2.MoveNext
Loop