SQL 使用 VBA 和 ADO 记录集进行查询 - 第二次执行 运行 时,代码执行的时间要少得多
SQL Query using VBA and ADO record set - Code takes far less time to execute when run the second time
我有一个 VBA 代码,其中涉及循环 SQL 查询,写为 Function
。当我第一次 运行 代码时,需要将近 155 秒(考虑到计算的复杂性,这是可以理解的),当我第二次 运行 时,大约需要 19 秒。我想知道为什么?另外我怎样才能让它 运行 更快? SQL服务器版本是2008.
FunctionX()
下面,随后在 For
和 Do while
循环中使用,所以我知道每次 运行 时都会创建一个新的连接。
Option Explicit
Public
Public conn As Object 'connection
________________________________________
Sub CreateConnection ()
Set conn = CreateObject("ADODB.connection")
Dim connstring As String
connstring = "Driver={SQL Server};Server=172.20.1.172;Database=WindAccess_dat_MRK;Uid=MRK_user;Pwd=MRK010usr;"
conn.Open connstring
conn.CommandTimeout = 120
End Sub
____________________________________
FunctionX(ActiveConnection as Object, arg1,arg2,....)
Dim SQLqryfunc1 As String
Dim SQLqryfunc2 As String
Dim SQLqryfunc3 As String
Dim fValue As Variant
Set wb = ThisWorkbook
Dim recset As Object
Set recset = CreateObject("ADODB.Recordset")
Dim ItemString1 As String
Dim ItemString2 As String
Dim ItemString3 As String
Dim ItemString4 As String
Dim ItemString5 As String
Dim StartZeit As String
Dim EndZeit As String
Select Case ChooseZeit
Case 1
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd 23:50:ss.000")
ItemString3 = " AND HAL150.WTUR_AVG.MaxPwrSetpoint >=" & MaxPwrSetpoint_AvgVal
ItemString4 = " AND HAL150.WTUR_MIN.MaxPwrSetpoint >=" & MaxPwrSetpoint_MinVal
ItemString5 = " AND HAL150.WTUR_AVG.W >=" & WAvg_Min
Case 2
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd hh:mm:ss.000")
ItemString3 = ""
ItemString4 = ""
ItemString5 = ""
End Select
Select Case Item
Case 1 ' 1= m
ItemString1 = " count(case when WindSpeed_Avg"
ItemString2 = " then 1 end)"
Case 2 ' 2= p
ItemString1 = " sum(case when WindSpeed_Avg"
ItemString2 = " then Leistung_Avg end)"
End Select
SQLqryfunc1 = "SELECT" _
& ItemString1 _
& " >=" _
& myWindSpeedFrom _
& " and" _
& " WindSpeed_Avg" _
& " <" _
& myWindSpeedTo _
& ItemString2 _
& " as '13.48 bis 14'" _
SQLqryfunc2 = " from" _
& " (" _
& " SELECT Time_Stamp, LDName as MONr,HAL150.WTUR_AVG.W as Leistung_Avg," _
& " WdSpd as WindSpeed_Avg, HAL150.WTUR_AVG.MaxPwrSetpoint as MaxPwrSetpoint_Avg,HAL150.WTUR_MIN.MaxPwrSetpoint as MaxPwrSetpoint_Min" _
& " FROM WSKD.RegCtrPeriodBlock" _
& " INNER JOIN HAL150.WNAC_AVG " _
& " ON WSKD.RegCtrPeriodBlock.idPBlock = HAL150.WNAC_AVG.idPBlock" _
& " INNER JOIN HAL150.WTUR_AVG" _
& " ON WSKD.RegCtrPeriodBlock.idPBlock = HAL150.WTUR_AVG.idPBlock" _
& " INNER JOIN WSKD.Components" _
& " ON WSKD.RegCtrPeriodBlock.IdLD = WSKD.Components.IdLD" _
& " INNER JOIN HAL150.WTUR_MIN" _
& " ON WSKD.RegCtrPeriodBlock.idPBlock = HAL150.WTUR_MIN.idPBlock" _
& " WHERE Time_Stamp BETWEEN" _
& " '" & StartZeit & "'" _
& " AND" _
& " '" & EndZeit & "'" _
& " AND" _
& " LDName in" _
& " (" & "'" & MONr & "'" & ")" _
& ItemString3 _
& ItemString4 _
& ItemString5 _
& " )" _
& " tbl"
SQLqryfunc3 = SQLqryfunc1 & SQLqryfunc2
'Debug.Print SQLqryfunc3
recset.Open SQLqryfunc3, myActiveConnection
fValue = recset.Getrows
Functionx = fValue(0, 0)
'Debug.Print Functionx
End Function
第二次执行您的查询时,它们可能被 SQL 服务器缓存。
使您的查询更快的最容易实现的目标是从该函数获取样本输出,然后通过 SQL Server Management Studio 启用执行计划 运行 它:https://docs.microsoft.com/en-us/sql/relational-databases/performance/display-an-actual-execution-plan?view=sql-server-ver15。它将为您提供一些提示,说明可以将哪些索引添加到某些列以加快查询速度。如果您对数据库具有创建权限,则可以创建一些索引来加快速度。您还可以查看查询的哪些部分最昂贵。
此外,动态 SQL 在 SQL 服务器上很难,正是因为它每次都必须提出一个新的优化执行计划,并且不能将其保存很长时间。它再次变慢的原因是因为它第一次创建的优化执行计划从缓存中弹出。如果输入来自用户,SQL 像这样 & " WHERE Time_Stamp BETWEEN" _ & " '" & StartZeit & "'" _
的连接也会让你面临 SQL 注入攻击。因此,如果您可以在查询中使用参数而不是使用 ADODB,就像在此示例中的答案一样: 这将使您的代码更安全。
另一个优化代码的想法可能是将条件处理移到 SQL 服务器上。例如这里的案例:
Select Case ChooseZeit
Case 1
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd 23:50:ss.000")
ItemString3 = " AND HAL150.WTUR_AVG.MaxPwrSetpoint >=" & MaxPwrSetpoint_AvgVal
ItemString4 = " AND HAL150.WTUR_MIN.MaxPwrSetpoint >=" & MaxPwrSetpoint_MinVal
ItemString5 = " AND HAL150.WTUR_AVG.W >=" & WAvg_Min
Case 2
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd hh:mm:ss.000")
ItemString3 = ""
ItemString4 = ""
ItemString5 = ""
End Select
不要有两个可能的 WHERE 子句,而是让您的 VBA 创建一个包含这两个子句的 SQL 脚本,并让 SQL 服务器进行区分。例如,输出中的 WHERE 子句可能如下所示:
...
WHERE
...
AND
(
(
2 = 1 -- ChooseZeit == 2 in VB so that's where the 2 comes from.
AND HAL150.WTUR_AVG.MaxPwrSetpoint >= 4 -- I just picked a random number here.
AND HAL150.WTUR_MIN.MaxPwrSetpoint >= 2 -- I just picked a random number here.
AND HAL150.WTUR_AVG.W >= 3 -- I just picked a random number here.
)
OR
(
2 = 2 -- ChooseZeit == 2 in VB so that's where the 2 comes from.
)
)
...
通过以这种方式构建的 WHERE 子句,您可以为 SQL 服务器提供一个常量查询结构来构建优化路径。如果您使用参数,您现在还可以更轻松地将 VB 创建的脚本查询移动到可以在 SQL 服务器上编译的存储过程中,以获得更多收益。
我有一个 VBA 代码,其中涉及循环 SQL 查询,写为 Function
。当我第一次 运行 代码时,需要将近 155 秒(考虑到计算的复杂性,这是可以理解的),当我第二次 运行 时,大约需要 19 秒。我想知道为什么?另外我怎样才能让它 运行 更快? SQL服务器版本是2008.
FunctionX()
下面,随后在 For
和 Do while
循环中使用,所以我知道每次 运行 时都会创建一个新的连接。
Option Explicit
Public
Public conn As Object 'connection
________________________________________
Sub CreateConnection ()
Set conn = CreateObject("ADODB.connection")
Dim connstring As String
connstring = "Driver={SQL Server};Server=172.20.1.172;Database=WindAccess_dat_MRK;Uid=MRK_user;Pwd=MRK010usr;"
conn.Open connstring
conn.CommandTimeout = 120
End Sub
____________________________________
FunctionX(ActiveConnection as Object, arg1,arg2,....)
Dim SQLqryfunc1 As String
Dim SQLqryfunc2 As String
Dim SQLqryfunc3 As String
Dim fValue As Variant
Set wb = ThisWorkbook
Dim recset As Object
Set recset = CreateObject("ADODB.Recordset")
Dim ItemString1 As String
Dim ItemString2 As String
Dim ItemString3 As String
Dim ItemString4 As String
Dim ItemString5 As String
Dim StartZeit As String
Dim EndZeit As String
Select Case ChooseZeit
Case 1
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd 23:50:ss.000")
ItemString3 = " AND HAL150.WTUR_AVG.MaxPwrSetpoint >=" & MaxPwrSetpoint_AvgVal
ItemString4 = " AND HAL150.WTUR_MIN.MaxPwrSetpoint >=" & MaxPwrSetpoint_MinVal
ItemString5 = " AND HAL150.WTUR_AVG.W >=" & WAvg_Min
Case 2
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd hh:mm:ss.000")
ItemString3 = ""
ItemString4 = ""
ItemString5 = ""
End Select
Select Case Item
Case 1 ' 1= m
ItemString1 = " count(case when WindSpeed_Avg"
ItemString2 = " then 1 end)"
Case 2 ' 2= p
ItemString1 = " sum(case when WindSpeed_Avg"
ItemString2 = " then Leistung_Avg end)"
End Select
SQLqryfunc1 = "SELECT" _
& ItemString1 _
& " >=" _
& myWindSpeedFrom _
& " and" _
& " WindSpeed_Avg" _
& " <" _
& myWindSpeedTo _
& ItemString2 _
& " as '13.48 bis 14'" _
SQLqryfunc2 = " from" _
& " (" _
& " SELECT Time_Stamp, LDName as MONr,HAL150.WTUR_AVG.W as Leistung_Avg," _
& " WdSpd as WindSpeed_Avg, HAL150.WTUR_AVG.MaxPwrSetpoint as MaxPwrSetpoint_Avg,HAL150.WTUR_MIN.MaxPwrSetpoint as MaxPwrSetpoint_Min" _
& " FROM WSKD.RegCtrPeriodBlock" _
& " INNER JOIN HAL150.WNAC_AVG " _
& " ON WSKD.RegCtrPeriodBlock.idPBlock = HAL150.WNAC_AVG.idPBlock" _
& " INNER JOIN HAL150.WTUR_AVG" _
& " ON WSKD.RegCtrPeriodBlock.idPBlock = HAL150.WTUR_AVG.idPBlock" _
& " INNER JOIN WSKD.Components" _
& " ON WSKD.RegCtrPeriodBlock.IdLD = WSKD.Components.IdLD" _
& " INNER JOIN HAL150.WTUR_MIN" _
& " ON WSKD.RegCtrPeriodBlock.idPBlock = HAL150.WTUR_MIN.idPBlock" _
& " WHERE Time_Stamp BETWEEN" _
& " '" & StartZeit & "'" _
& " AND" _
& " '" & EndZeit & "'" _
& " AND" _
& " LDName in" _
& " (" & "'" & MONr & "'" & ")" _
& ItemString3 _
& ItemString4 _
& ItemString5 _
& " )" _
& " tbl"
SQLqryfunc3 = SQLqryfunc1 & SQLqryfunc2
'Debug.Print SQLqryfunc3
recset.Open SQLqryfunc3, myActiveConnection
fValue = recset.Getrows
Functionx = fValue(0, 0)
'Debug.Print Functionx
End Function
第二次执行您的查询时,它们可能被 SQL 服务器缓存。
使您的查询更快的最容易实现的目标是从该函数获取样本输出,然后通过 SQL Server Management Studio 启用执行计划 运行 它:https://docs.microsoft.com/en-us/sql/relational-databases/performance/display-an-actual-execution-plan?view=sql-server-ver15。它将为您提供一些提示,说明可以将哪些索引添加到某些列以加快查询速度。如果您对数据库具有创建权限,则可以创建一些索引来加快速度。您还可以查看查询的哪些部分最昂贵。
此外,动态 SQL 在 SQL 服务器上很难,正是因为它每次都必须提出一个新的优化执行计划,并且不能将其保存很长时间。它再次变慢的原因是因为它第一次创建的优化执行计划从缓存中弹出。如果输入来自用户,SQL 像这样 & " WHERE Time_Stamp BETWEEN" _ & " '" & StartZeit & "'" _
的连接也会让你面临 SQL 注入攻击。因此,如果您可以在查询中使用参数而不是使用 ADODB,就像在此示例中的答案一样: 这将使您的代码更安全。
另一个优化代码的想法可能是将条件处理移到 SQL 服务器上。例如这里的案例:
Select Case ChooseZeit
Case 1
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd 23:50:ss.000")
ItemString3 = " AND HAL150.WTUR_AVG.MaxPwrSetpoint >=" & MaxPwrSetpoint_AvgVal
ItemString4 = " AND HAL150.WTUR_MIN.MaxPwrSetpoint >=" & MaxPwrSetpoint_MinVal
ItemString5 = " AND HAL150.WTUR_AVG.W >=" & WAvg_Min
Case 2
StartZeit = Format(InputDateErsterTag, "yyyy-mm-dd hh:mm:ss.000")
EndZeit = Format(InputDateLetzterTag, "yyyy-mm-dd hh:mm:ss.000")
ItemString3 = ""
ItemString4 = ""
ItemString5 = ""
End Select
不要有两个可能的 WHERE 子句,而是让您的 VBA 创建一个包含这两个子句的 SQL 脚本,并让 SQL 服务器进行区分。例如,输出中的 WHERE 子句可能如下所示:
...
WHERE
...
AND
(
(
2 = 1 -- ChooseZeit == 2 in VB so that's where the 2 comes from.
AND HAL150.WTUR_AVG.MaxPwrSetpoint >= 4 -- I just picked a random number here.
AND HAL150.WTUR_MIN.MaxPwrSetpoint >= 2 -- I just picked a random number here.
AND HAL150.WTUR_AVG.W >= 3 -- I just picked a random number here.
)
OR
(
2 = 2 -- ChooseZeit == 2 in VB so that's where the 2 comes from.
)
)
...
通过以这种方式构建的 WHERE 子句,您可以为 SQL 服务器提供一个常量查询结构来构建优化路径。如果您使用参数,您现在还可以更轻松地将 VB 创建的脚本查询移动到可以在 SQL 服务器上编译的存储过程中,以获得更多收益。