Question

I have a workbook where several of my data feeds pass a parameter back into the SQL query depending upon some dropdown menus/user actions. This keeps the workbook trim, improves calculation, etc - keeping all of the item-level detail locally in the workbook just isn't practical.

Some elements of my VBA depend upon evaluations of data that comes from these parameterized queries. Herein arises the problem - VBA doesn't wait for the parameter to be passed back to the query before evaluating everything in the macro.

I'm curious if anyone has any thoughts or advice about best practices for programmatically 'pausing' VBA execution until a feed refreshes. My work around right now is to chunk my VBA into two parts, throw anything that depends on the changed data into a separate function, and use application.ontime to pause for X seconds.

Application.OnTime Now + TimeSerial(0, 0, 10), "Restart"

this is a 90% solution but it's less than ideal. The length of time is arbitrary - on a really slow connection it's insufficiently long and on a fast one it is unnecessarily slow.

Ideally there would be some sort of way to wait until Excel was ready and then continue. Similar to how when using the MS Internet Controls library you can use

Do Until .document.ReadyState = "complete"

to pause execution until IE returns a ready state. Any strategies for a more elegant solution?

edit: per jon below, adding the code and explaining how the SQL query works:

select sts1.studentid, sts1.alphascore as testcycle, 
sts2.numscore as lexile, sts3.alphascore as gleq, sts4.numscore as nce

from ps.studenttestscore sts1
join ps.students stu on (sts1.studentid = stu.id)
join ps.studenttestscore sts2 on (sts1.studenttestid = sts2.studenttestid)
join ps.studenttestscore sts3 on (sts1.studenttestid = sts3.studenttestid)
join ps.studenttestscore sts4 on (sts1.studenttestid = sts4.studenttestid)

where (stu.id = ? ) and (sts1.testscoreid = 578) and (sts2.testscoreid = 575) 
and (sts3.testscoreid = 577) and (sts4.testscoreid = 576)

the ? is a parameter that passes the relevant student ID - MS query uses a cell value for that parameter. the cell that it looks to just has a lookup based on what student has been selected:

=IFERROR(INDEX(Stu!$B:$F,MATCH(Student!B2,Stu!$F:$F,0),1),999999)

(the iferror just passes an arbitrary number up to prevent nasty dialog boxes from popping up if an improper value somehow gets selected).

Was it helpful?

Solution

Your mistake is using MS-Query. Code up the database calls using ADODB, and wait for the Execute method of the ADODB.Command object.

...If that's what you're actually doing. There's a certain amount of guesswork here, but it looks as if the status of the query - not the sheet it's embedded in - is the information you need.

This code calls a SQL query asynchronously - it's conceptually similar to a command object, which is (I think) what you're actually doing - and the crude 'sleep' loop can be replaced by a progress bar, or code for polling flags and calculations elsewhere.

FYI, the state and status properties of ADO objects can be confusing. In general, zero means close, and 1 means open (for objects that return an open connection or a dataset) and values higher than 1 correspond to waiting, or executing.

You could, of course, just call the query synchronously.

I could give you code for 'DataConnection' but you're far better off going to ConnectionStrings.com for that.

Public Function FetchRecordSet(SQL As String, Optional CursorType As CursorTypeEnum = adOpenForwardOnly) As ADODB.Recordset
On Error Resume Next

Set FetchRecordSet = New ADODB.Recordset

With FetchRecordSet

    .CacheSize = 8
    Set .ActiveConnection = DataConnection
    .Open SQL, , CursorType, adLockReadOnly, adCmdText + adAsyncFetch

    Do While .State > 1
        Application.StatusBar = "Retrieving data... " 
        Sleep 250
    Loop

End With

Application.StatusBar = False

End Function





[Update]

I've learned something since I posted this answer:

If you know the name of a command, a rowset-returning function, or a named query in an MS-Access database, don't bother calling it like this:

    SQL = "SELECT * FROM MyQuery"
    .Open SQL, , CursorType, adLockReadOnly, adCmdText + adAsyncFetch

Call the command by name, and tell the database engine it's a named command using the adCmdStoredProc constant:

    SQL = "MyQuery"
    .Open SQL, , CursorType, adLockReadOnly, adCmdStoredProc + adAsyncFetch

It runs much, much faster.

Look up the CommandTypeEnum on MSDN, and use whatever works best for you:

https://msdn.microsoft.com/en-us/library/ms675946(v=vs.85).aspx

Use adCmdTable for named tables, and see whether that works better than adCmdStoredProc for 'view' objects - I've found that it varies between database engines.

[/Update]

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top