excel vba:暂停参数化 SQL 查询以完成?

发布于 2024-10-18 17:50:41 字数 1498 浏览 3 评论 0原文

我有一个工作簿,其中几个数据源根据某些下拉菜单/用户操作将参数传递回 SQL 查询。这可以保持工作簿精简、改进计算等 - 将所有项目级详细信息本地保存在工作簿中是不切实际的。

我的 VBA 的某些元素取决于对来自这些参数化查询的数据的评估。这里出现了问题 - 在评估宏中的所有内容之前,VBA 不会等待参数传递回查询。

我很好奇是否有人对以编程方式“暂停”VBA 执行直到提要刷新的最佳实践有任何想法或建议。我现在的工作是将我的 VBA 分为两部分,将依赖于更改数据的任何内容放入单独的函数中,并使用 application.ontime 暂停 X 秒。

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

这是 90% 的解决方案,但还不够理想。时间长度是任意的 - 在非常慢的连接上它不够长,而在快速连接上它又不必要地慢。

理想情况下,应该有某种方式等待 Excel 准备就绪然后继续。与使用 MS Internet Controls 库时类似,您可以用来

Do Until .document.ReadyState = "complete"

暂停执行,直到 IE 返回就绪状态。有更优雅的解决方案的策略吗?

编辑:下面,添加代码并解释 SQL 查询的工作原理:

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)

?是传递相关学生 ID 的参数 - MS 查询使用该参数的单元格值。它所查找的单元格仅根据选择的学生进行查找:

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

(iferror 只是传递任意数字,以防止在以某种方式选择不正确的值时弹出令人讨厌的对话框)。

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).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

对不⑦ 2024-10-25 17:50:41

您的错误是使用 MS-Query。使用 ADODB 编写数据库调用代码,并等待 ADODB.Command 对象的 Execute 方法。

...如果这就是你实际上正在做的事情。这里有一定量的猜测,但看起来查询的状态(而不是它嵌入的工作表)就是您需要的信息。

这段代码异步调用 SQL 查询 - 它在概念上类似于命令对象,(我认为)这就是你实际上正在做的 - 并且粗略的“睡眠”循环可以用进度条或轮询标志和代码来替换其他地方的计算。

仅供参考,ADO 对象的状态和状态属性可能会令人困惑。一般来说,0 表示关闭,1 表示打开(对于返回打开连接或数据集的对象),大于 1 的值对应于等待或执行。

当然,您可以只同步调用查询。

我可以为您提供“DataConnection”的代码,但您最好前往 ConnectionStrings.com 获取该代码。

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

[更新]

自从我发布这个答案以来,我学到了一些东西:

如果您知道 MS-Access 数据库中的命令、行集返回函数或命名查询的名称,请不要像这样调用它:

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

通过以下方式调用命令名称,并使用 adCmdStoredProc 常量告诉数据库引擎这是一个命名命令:

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

它运行得快得多。

在 MSDN 上查找 CommandTypeEnum,并使用最适合您的内容:

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

对命名表使用 adCmdTable,看看是否可以对于“视图”对象,它比 adCmdStoredProc 效果更好 - 我发现它在数据库引擎之间有所不同。

[/更新]

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]

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文