Nested Loop

CAMsk

Registered User.
Local time
Today, 14:14
Joined
Nov 29, 2016
Messages
23
Hi, I am working on an Access database for tracking stocks. The application is built on two tables. One is called tblSYMBOL with a single field SYMBOL (primary key) with a list of around 3000 stock symbols. The other table is named tblStockData with field MktDate as primary key and SYMBOL as a foreign key. The intent is these two keys are a composite key for tblStockData.

I have built a VBA module to test the logic. As I cycle thru stock symbols with the other loop (rs1), I want to hold that stock symbol fixed and cycle thru the date range (around 70 dates) in the inner loop (rs2) and do calculations. I cannot get the dates to cycle in the inner loop, rs2. I can bring up the first or the last date, but the only cycling that occurs in the inner loop, is to cycle thru symbols. I have tried tweaking the field order, properties, relationships, and nothing helps. I cannot figure out what I am missing. :banghead:

Here is the code:
Option Compare Database

Dim i As Integer
Dim j As Integer

Sub Two_Table_Nested_Loop()

Set dbs = CurrentDb
Set rs1 = dbs.OpenRecordset("tblSYMBOL", dbOpenDynaset)
Set rs2 = dbs.OpenRecordset("tblStockData", dbOpenDynaset)

'Outer Loop to cycle thru tblSymbol
With rs1
i = 5
'will replace with EOF after testing complete
'for now we will use a low value of i for testing
rs1.MoveLast
rs1.MoveFirst

Do While i > 0
Debug.Print "outer loop"; ";"
Debug.Print rs1.Fields("Symbol")
'use outer loop (rs1) to cycle thru symbols and inner loop (rs2) to cycle thru dates
'and perform calculations on each symbols price and volume data
j = 3

With rs2
rs2.MoveFirst
Do While j > 0
Debug.Print "inner loop"; ";"
Debug.Print rs2.Fields("MktDate")
rs2.MoveNext
j = j - 1
Loop
End With

i = i - 1
rs1.MoveNext
Loop

End With

rs1.Close
Set rs1 = Nothing
Set rs2 = Nothing
Set dbs = Nothing

End Sub

Example of tblSYMBOL:
tblSYMBOL
Symbol (primary key)
A
AA
AAAP
AAC
AAL
AAN

Example of tblStockData
tblStockData
MktDate Symbol
10/14/2016 A
10/14/2016 AA
10/14/2016 AAAP
10/14/2016 AAC
10/14/2016 AAL
10/14/2016 AAN
10/14/2016 AAOI
10/14/2016 AAON

There are 14 more fields of data to the right of Symbol. MktDate is the primary key and Symbol is the foreign key. My assumption is that selecting both of these fields as keys in the design view makes them part of the composite key. Do I need to make any entry on the Property Sheet to designate SYMBOL as a foreign key or that together they form a composite key?

Here is a partial look at the Immediate Window output from running the module.
outer loop;
A
inner loop;
10/14/2016
inner loop;
10/14/2016
inner loop;
10/14/2016
outer loop;
AA
inner loop;
10/14/2016
inner loop;
10/14/2016
inner loop;
10/14/2016
outer loop;
AAAP
inner loop;
10/14/2016
inner loop;
10/14/2016
inner loop;
10/14/2016
outer loop;


Some of you have helped me with an earlier rendition of this problem based on a single table. I have moved to the two table version as being conceptually simpler. All comments and questions appreciated and thanks for your help.
 
To be meaningful your inner loop needs to use data from the outer loop in order to select the appropriate records, so I would never expect to see this code in a working solution to this problem...
Code:
Set rs1 = dbs.OpenRecordset("tblSYMBOL", dbOpenDynaset)
Set rs2 = dbs.OpenRecordset("tblStockData", dbOpenDynaset)
The second recordset should always be opened inside the outer loop, and should be openend using data from the first recordset as a constraint.

Furthermore, to keep the operations even more clear, I would always write this code with the inner loop being a subroutine that is called repeatedly from the outer loop. In that case the separateness of your two distinct operations is more easily and more clearly maintained.

Hope this helps,
 
MarkK thanks, I will make these changes and test. Also like the idea of making the innner loop a sub, makes a lot of sense. I'll report back.
 
MarkK, I am still getting the behavior of Symbol cycling, but can't get the MktDate to advance under my control in the inner loop. I did move rs2 to within the outer loop, and attempted to constrain the inner loop to the current symbol using the With CurrentSymbol, with no luck. When I run and check the immediate window, the MktDate is not advancing with rs2.MoveNext. If I cange that command to rs2.MoveLast, the MktDate changes to the last value, but using rs2.Move 1 is not effective.

I may not have understood your suggestion to constrain the 2nd recordset based on the first. Here is the current code. I have not yet converted the inner loop to a subroutine.

Option Compare Database

Dim i As Integer
Dim j As Integer
Dim CurrentSymbol As Variant

Sub Two_Table_Nested_Loop()

Set dbs = CurrentDb
Set rs1 = dbs.OpenRecordset("tblSymbol", dbOpenDynaset)
With rs1
i = 5
rs1.MoveFirst
Do While i > 0
Debug.Print "outer loop"; ";"
Debug.Print i
Set rs2 = dbs.OpenRecordset("tblStockData2", dbOpenDynaset)
CurrentSymbol = rs1.Fields("Symbol")
Debug.Print rs1.Fields("Symbol")
Debug.Print rs2.Fields("MktDate")
'outer loop (rs1) cycle thru symbols
'inner loop (rs2) cycle thru dates for each symbol
'and perform calculations on each symbol's price and volume data
j = 2
'With rs1
With CurrentSymbol
Debug.Print CurrentSymbol
rs2.MoveFirst
Do While j > 0
Debug.Print "inner loop"; ";"
Debug.Print rs2.Fields("MktDate")
rs2.Move 1
j = j - 1
Loop
End With

i = i - 1
rs1.MoveNext
Loop

End With

rs1.Close
Set rs1 = Nothing
Set rs2 = Nothing
Set dbs = Nothing

End Sub

Thanks for your help/guidance. CAMsk.
 
One thing I might add is I created a link between the tables under Database Tools/Relationships. I have connected the primary key Symbol in tblSymbol to the foreign key Symbol in tblStockData. In this table, I also have MktDate as a primary key, so the tblStockData has a composite key. Creating this relationship did not change any behaviors that I could tell. I also created a tblStockData2 (in the above code), where I interchanged the fields, making the Symbol Field first going from left to right, instead of the MktDate field. This seemed to have no effect. Thanks.
 
In the inner loop you need to open the recordset using data from the outer loop, otherwise you don't really have a nested loop, so I would expect to see code something like...
Code:
Set rs2 = dbs.OpenRecordset( _
   "SELECT * FROM tblStockData2 " & _
   "WHERE Symbol = '" & rs1.Symbol & "' " & _
   "ORDER By datMarket")

   With rs2
      Do While Not .eof
[COLOR="Green"]         'do stuff with each row of rs2[/COLOR]
         .MoveNext
      Loop
      .Close
   End With
See how the inner loop is now controlled by the data in the outer loop?
 
I'm with Mark on this one. Synthesize a JOIN query between your symbol table and your stock data table so that you virtually see everything. Make the JOIN query do an ORDER BY SYMBOL and then by Date.

Now you need only ONE loop that merely tests whether the current record has the same symbol as the record you just processed. Thus your code EITHER (a) does the accumulation steps for the individual stock data records {when the symbol didn't change} or (b) it does whatever steps are required for summarization of the prior sequence, output of any findings, and a reset of the loop that processes the stock data table for a new symbol, or (c) it hits EOF and you do the end-of-symbol processing but don't have to reset the loop because you are done.

One loop, just make it sensitive to the symbol and test that before you decide what to do next. By the way, if you have a s**t-pot load of market data, doing it this way is slightly faster because the query is compiled but nested loops are based on EMULATED instructions. If the tables are big enough, the extra code starts to slow you down, but having the query do the sorting? You would never see a hiccup after the first record has been retrieved.
 
MarkK & The_Doc_Man, thanks and I now see what you were talking about. Will be revising and testing. CAMsk. I was going after this like the guy with a hammer and seeing everything as a nail.
 
Yep, been there, done that. Only for me it is a screwdriver so everything gets screwed.
 
MarkK, making progress with this module. Outer loop working as expected, symbols change in an orderly way. I made a correction in the query to "Order by MktDate", which I think you meant instead of datMarket. When I get into the inner loop, any attempt to move the pointer produces a "No current record" error. I have tried putting an rs2.MoveNext, tried .MoveLast, .MoveNext, tried to do a Debug.Print rs2.Fields("MktDate") (or any other field) and still get the "No current record". Here is the current code:

Option Compare Database

Sub MkK()

Dim i As Integer
Dim j As Integer
Dim CurrentSymbol As Variant

Set dbs = CurrentDb
Set rs1 = dbs.OpenRecordset("tblSymbol", dbOpenDynaset)
With rs1
i = 5
rs1.MoveFirst
'Do While Not EOF
Do While i > 0
Debug.Print "outer loop"; ";"
Debug.Print i
Debug.Print rs1.Fields("Symbol")
CurrentSymbol = rs1.Symbol
Debug.Print CurrentSymbol
Set rs2 = dbs.OpenRecordset("SELECT * FROM tblStockData2 " & _
"WHERE Symbol = ' " & rs1.Symbol & "' " & _
"ORDER by MktDate")

j = 5
With rs2
Do While j > 0
Debug.Print "inner loop"; ";"
Debug.Print rs1.Fields("Symbol")
.MoveNext
Debug.Print rs2.Fields("Current Price")

j = j - 1
Loop
.Close
End With
i = i - 1
rs1.MoveNext
Loop

End With

rs1.Close
Set rs1 = Nothing
Set rs2 = Nothing
Set dbs = Nothing

End Sub

I think this is close. Any suggestions? Thanks, CAMsk.
 
The_Doc_Man, I have some reading to do before I figure out how to do what you suggested. Aren't two loops required, one to advance to the next symbol, and one to cycle thru the dates? In terms of data, I have about 3,000 symbols, will keep around 210 trading days of data, and 14 fields. I expect the database to be around .25GB in size when complete. It will grow some if I store any calculations back in the data table. Also, will create queries and reports, so that will take a little space.
 
How do you know there are going to be 5 records in this recordset? What if there are none?
Code:
Set rs2 = dbs.OpenRecordset("SELECT * FROM tblStockData2 " & _
"WHERE Symbol = ' " & rs1.Symbol & "' " & _
"ORDER by MktDate")

j = 5
With rs2
Do While j > 0
Debug.Print "inner loop"; ";"
Debug.Print rs1.Fields("Symbol")
.MoveNext
Debug.Print rs2.Fields("Current Price")

j = j - 1
Loop

Compare this loop, which uses the records in the recordset to control the loop...
Code:
   With rs2
      Do While Not .eof
[COLOR="Green"]         'do stuff with each row of rs2[/COLOR]
         .MoveNext
      Loop
      .Close
   End With
 
MarkK, great catch, sorry I forogt to explain. I put in the low values of j and k to control the loops for testing. The intent is to use EOF when the code is proven to be implementing the correct logic. I will make the change and see if this fixes the issue.
 
When I change to Do While Not .eof (both loops), the process runs thru the entire symbol list from A to Z, but nothing ever prints from the inner loop, like everything within the 'With rs2...End With' is not even there. Here is a snippet. It should be printing "inner loop" when it passes thru that loop.

GOLD
outer loop;
-1234
GOOD
outer loop;
-1235
GOOG
outer loop;
-1236
GOOGL
outer loop;
-1237
GORO
outer loop;
-1238
GOV
outer loop;
-1239

I'll keep looking. This actually seems promising. Thanks.
 
Here, consider code like this. I'm not saying this is suitable for your purpose, but it gives you one way of thinking about nested loops...
Code:
Sub OuterLoop()
    With CurrentDb.OpenRecordset("tblSymbol")
        Do While Not .EOF
            Debug.Print "outer loop", .Fields("Symbol")
            InnerLoop .Fields("Symbol").Value
            .MoveNext
        Loop
        .Close
    End With
End Sub

Private Sub InnerLoop(Symbol As String)
    Const SQL As String = _
        "SELECT * " & _
        "FROM tblStockData2 " & _
        "WHERE Symbol = {0} " & _
        "ORDER by MktDate"
        
    With CurrentDb.OpenRecordset(Replace(SQL, "{0}", Symbol))
        Do While Not .EOF
            Debug.Print "Inner loop", .Fields("CurrentPrice").Value
            .MoveNext
        Loop
        .Close
    End With
End Sub
 
MarkK, haven't abandoned this, just been tied up. This looks very interesting and I will be getting into it shortly. Thanks for your continued assistance and patience. CAMsk.
 
If you upload an example database and explain what you are trying to do somebody could help you write a nice query that would be much faster than cycling through records with VBA. ;)
 
Static, I am close to having this run. Like to finish it, then I will take up the offer for a query.
 
MarkK, outer loop works well. I tested by not calling inner loop. When I call the inner loop, I get a Run-time error '3061', too few parameters, Expected 1. When I hit debug, this is the line that is highlighted in yellow by the compiler:

With CurrentDb.OpenRecordset(Replace(SQL, "{0}", Symbol))

I added a Debug.Print SQL just to see how the statement was evaluating. I had an error in the Fields, should be "Current Price", with a space.

Option Compare Database

Sub OuterLoop()
With CurrentDb.OpenRecordset("tblSymbol")
Do While Not .EOF
Debug.Print "outer loop", .Fields("Symbol")
InnerLoop .Fields("Symbol").Value
.MoveNext
Loop
.Close
End With
End Sub

Private Sub InnerLoop(Symbol As String)
Const SQL As String = _
"SELECT * " & _
"FROM tblStockData2 " & _
"WHERE Symbol = {0} " & _
"ORDER by MktDate"

Debug.Print SQL
With CurrentDb.OpenRecordset(Replace(SQL, "{0}", Symbol))
Do While Not .EOF
Debug.Print "Inner loop", .Fields("Current Price").Value
.MoveNext
Loop
.Close
End With
End Sub

Looks promising. Mystefied by the error message. Thanks, CAMsk
 
If symbol is a string it should be in quotes, so do something like...
Code:
    Const SQL As String = _
        "SELECT * " & _
        "FROM tblStockData2 " & _
        "WHERE Symbol = [COLOR="DarkRed"]'[/COLOR]{0}[COLOR="DarkRed"]'[/COLOR] " & _
        "ORDER by MktDate"
See the added quotes?
 

Users who are viewing this thread

Back
Top Bottom