【问题标题】:Execute a query once for an entire column instead of looping through all cells对整个列执行一次查询,而不是遍历所有单元格
【发布时间】:2019-11-01 23:25:26
【问题描述】:

我使用下面的循环遍历一列并对每个单元格值执行查询。鉴于此列中的单元格数量很容易超过 10'000 行,这不是一个非常快速的方法,因此我正在寻找另一种方法来提高性能。

我正在考虑用单元格的值填充一个数组,但是使用这种方法很可能仍然需要遍历所述数组并为每次迭代执行查询。

我不熟悉任何可能执行一次查询或至少显着提高此过程的性能的方法。有什么想法吗?

Public Function getdata(query As String) As ADODB.Recordset
Dim cnn As ADODB.Connection
Dim connstring As String
Set cnn = New ADODB.Connection

connstring = "Provider=SQLOLEDB;Data Source=noneofyourbusiness;Connect Timeout=180"
cnn.Open connstring

Set getdata = New ADODB.Recordset
    getdata.CursorLocation = adUseClient
getdata.Open query, connstring, 2, adLockReadOnly
End Function

Sub start()
'code...

For Each c In sht.Range("J3:J" & LRow)
    If Not c.Value = "" Then
        'Query
        Set rs = getdata("SELECT 'Checked' FROM astAssetTypes AT JOIN astAssetTypesUDFV UDFV ON UDFV.TableLinkId = AT.Id WHERE UDFV.Userfield13Id = '5029' AND AT.Code = '" & c.Value & "'")
        If Not rs.EOF Then
            sht.Cells(c.Row, "L").CopyFromRecordset rs
            With sht.Range(sht.Cells(c.Row, "A"), sht.Cells(c.Row, LCol)).Font
                .ThemeColor = xlThemeColorDark1
                .TintAndShade = -0.349986266670736
            End With
            rs.Close
        End If
    End If
Next c

'code...
End Sub 

【问题讨论】:

  • 有很多选项。不要将您的 Excel 行视为特别的东西。它们只是数据。如果你在 SQL 上有 dbo,你可以在那里链接/导入你的数据(可能是最快的)。如果没有,您能否将 Excel 和 SQL 数据加载到内存中,处理所有内容并将其转储出来。不要使用手动色调和阴影,使用条件格式。循环并不是让你变慢的原因,它不断循环回 SQL 并写入 Excel。如果(有 10k 行)这需要大约 30 秒以上,您需要进行优化。
  • @Gareth 请写一个答案,会有所帮助。至于 CF,我特别选择了:它需要是非动态格式
  • 您确实需要对该语句进行参数化。 SQL 注入仍然即使是现在在业界也是一个太大的问题,它不应该是。沙盒、开发工作、学校项目等并非“免疫”参数化;现在养成编写不仅仅是一个巨大的安全漏洞的代码的习惯,然后在将来你编写的东西如果它有一个巨大的漏洞就会“重要”时,它就不会成为问题。
  • @Larnu 我同意,但从未练习过保存 SQL 注入(到目前为止还没有必要,因为连接是只读的,并且仅由公司内的一小部分用户使用)。如果你给我一个保存方法,我会很乐意实现它

标签: sql sql-server excel vba ado


【解决方案1】:

方法一。

如果您在 SQL 上有 dbo,则创建一个临时表并在那里加载 Excel 数据。使用高效的字符串构建方法一次性完成(即使用 Mid 替换而不是常量连接)。或者使用集成直接加载数据。运行查询并返回数据。找出需要格式化的单元格并立即执行(循环使用 Union 以获得一个大范围)。

方法二。

使用客户端游标,从 SQL 加载所有数据并使用 rs.Filter 查找匹配记录。您可以将 Excel 数据加载到数组或断开连接的记录集中,然后将其放回。

重要的是不要不必要地写回 Excel。写入 Excel 的次数不应超过两次。

类似(代码未完全测试)

Dim rsLocal As ADODB.Recordset ' create a local, disconnected recordset
Set rsLocal = New ADODB.Recordset
rsLocal.CursorLocation = adUseClient
rsLocal.Fields.Append "L", adVarChar, 1024, adFldIsNullable ' change to suit your data
rsLocal.Open

Dim myRange As Range

rs.CursorLocation = adUseClient
'bring all the records back into memory
Set rs = GetData("SELECT 'Checked', AT.Code Code FROM astAssetTypes AT JOIN astAssetTypesUDFV UDFV ON UDFV.TableLinkId = AT.Id WHERE UDFV.Userfield13Id = '5029' AND AT.Code = '")


For Each c In sht.Range("J3:J" & lrow)
    rsLocal.AddNew
    If c.Value <> "" Then
        rs.Filter = "Code='" & c.Value & "'" 'use Filter to prevent lots of round trips
        If rs.RecordCount <> 0 Then
            rs.MoveFirst
            rsLocal("L") = rs("Code")

            'add the cells to the range as we go
            If myRange Is Nothing Then
                Set myRange = sht.Range(sht.cells(c.Row, "A"), sht.cells(c.Row, LCol))
            Else
                Set myRange = Union(sht.Range(sht.cells(c.Row, "A"), sht.cells(c.Row, LCol)), myRange)
            End If
        End If
    End If
    rsLocal.Update
Next

rsLocal.MoveFirst
sht.Range("L3").CopyFromRecordset rsLocal 'write all updates at once

With myRange.Font ' do all formatting at once
    .ThemeColor = xlThemeColorDark1
    .TintAndShade = -0.349986266670736
End With

【讨论】:

  • 已编辑。它不会完全解决您的问题(取决于您的 SQL 数据的大小),但应该有一些想法。
  • If myRange Is Nothing 请原谅,但在此行之前看不到对myRange 的分配
  • @JohnyL 因为范围从未初始化,所以默认为 Nothing。您可以将其显式设置为 Nothing,但它不应该是必需的。
【解决方案2】:
Sub start()

    Dim strCodes$, rng1 As Range, rng2 As Range, cell As Range

    '// Generate "IN" clause
    For Each c In sht.Range("J3:J" & LRow)
        If Len(c) > 0 Then
            strCodes = strCodes & "'" & c & "'" & IIf(c.Row = LRow, "", ",")
        End If
    Next

    'Query
    Set rs = getdata( _
        "SELECT 'Checked', AT.Code FROM astAssetTypes AT JOIN astAssetTypesUDFV UDFV ON UDFV.TableLinkId = AT.Id " & _
        "WHERE UDFV.Userfield13Id = '5029' AND AT.Code IN (" & strCodes & ");")
    While Not rs.EOF
        Set cell = sht.Columns("J:J").Find(rs("Code"), LookAt:=xlWhole)
        If Not cell Is Nothing Then
            If rng1 Is Nothing Then
                Set rng1 = sht.Cells(cell.Row, "L")
            Else
                Set rng1 = Union(rng1, sht.Cells(cell.Row, "L"))
            End If
            If rng2 Is Nothing Then
                Set rng2 = sht.Cells(cell.Row, "A").Resize(, LCol)
            Else
                Set rng2 = Union(rng2, sht.Cells(cell.Row, "A").Resize(, LCol))
            End If
        End If
        rs.MoveNext
    Wend

    '// Dump result
    rng1.Value = "Checked"
    With rng2.Font
            .ThemeColor = xlThemeColorDark1
            .TintAndShade = -0.349986266670736
        End With
    End With

End Sub

【讨论】:

  • 约翰尼看起来很棒。这看起来简单而快速。关于这两种方法中哪一种最快的想法?
  • 我一回到办公室就会这样做。需要更稳定的互联网连接来准确测试这两种方法
  • @TimStack 祝你好运! ;)
  • 请不要拒绝编辑-它现在设置为 SQL 代码格式,我已将其更改为 VBA。谢谢!一旦我比较了这两种方法@Gareth,就会选择答案
  • @TimStack 我错过了一个按钮)已批准!
猜你喜欢
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-03
  • 2013-06-19
  • 1970-01-01
  • 2011-01-01
相关资源
最近更新 更多