关于如何在 Oracle 数据库中使用 VBA 执行 SQL 脚本的建议

Posted

技术标签:

【中文标题】关于如何在 Oracle 数据库中使用 VBA 执行 SQL 脚本的建议【英文标题】:Suggestions on How to execute SQL script using VBA in Oracle Database 【发布时间】:2020-02-05 18:44:37 【问题描述】:

这是我想要做的:

    从工作表中提取 SQL 脚本(脚本有 cmets 和查询) 将其分配给一个字符串变量,该变量具有工作表中单元格范围(脚本粘贴的位置)的值 通过将字符串变量传递给我之前作为记录集建立的 ADODB 连接来执行脚本 将在 Oracle 数据库中执行的脚本的结果粘贴到新工作表中

到目前为止我所取得的成就:

    数据库连接成功 我可以将范围值分配给变量,但不能分配给字符串(错误:类型不匹配) 如果我将变量更改为变体,则无法将其作为记录集执行。 (错误:参数类型错误、超出可接受范围或相互冲突)

我知道我使用的方法并不容易,因此我需要有关如何实现这一目标的建议。

Private Sub RunValidation_Click()
Dim ws As Worksheet
Dim sheet As Variant
Dim StrSQL As Variant
Dim sheetnumber As Integer
Dim irow As Integer
Dim rs As ADODB.Recordset
Dim elementcount As Integer

Call OptimizeCode_Begin
Call Start_DBConnect

irow = ScriptExecutor.Range("A" & Rows.count).End(xlUp).row
elementcount = irow - 13

StrSQL = ScriptExecutor.Range("A14: A" & irow).Value


Set rs = New ADODB.Recordset
rs.Open StrSQL, cn, adOpenDynamic, adLockReadOnly, adCmdText

If Not rs.EOF Then
rs.MoveFirst
End If

i = 1
sheetnumber = Application.Sheets.count - i
Set ws = Sheets.Add(After:=Sheets(Sheets.count))
ws.name = "Extracts-" & sheetnumber

Sheets("Extracts-" & sheetnumber).Range("A2").CopyFromRecordset rs

rs.Close
Set rs = Nothing

End Sub

【问题讨论】:

SQL 脚本是否包含在第 14 行以下的 A 列中,每个单元格是 1 行脚本,所以完整的脚本是所有与 CRLF 连接的单元格? @CDP1802 - 是的,脚本在 A 列中,从第 14 行开始,但其中包含 cmets 和查询。每个单元格不是一行脚本,有些行可能为空。 CRLF 没有加入它们。我也会添加脚本的图像。 我的意思是,如果所有行都用回车符/换行符连接成一个字符串,那会是你试图执行的 SQL 吗?这将是一个接一个地执行 3 个 select 语句。 【参考方案1】:

尝试使用 CRLF 连接范围中的每一行以创建一个字符串。从范围分配的数组是具有多行的单列。 JOIN 函数需要单行多列数组,因此需要转置函数。

   Dim StrSQL As String, arLines As Variant
   arLines = ScriptExecutor.Range("A14: A" & irow).Value
   StrSQL = Join(Application.Transpose(arLines), vbCrLf)

如果您必须忽略 --cmets 包括与语句在同一行的那些(以及空行),那么一次构建字符串可能是最简单的方法。

Dim cell As Range, sLine As String, StrSQL As String
With ScriptExecutor.Range("A14: A" & irow)
    For Each cell In .Cells
        sLine = Trim(cell.Value)

        ' remove any comments --
        i = InStr(1, sLine, "--", vbTextCompare)
        If i > 0 Then
             sLine = Left(sLine, i - 1)
        End If

        If len(sLine) = 0 Then
            ' skip blank lines
        Else
            If Len(StrSQL) > 0 Then sLine = vbCrLf & sLine
            StrSQL = StrSQL & sLine
        End If
    Next
End With
Debug.Print StrSQL

在同一个脚本中使用多个查询会获得多个记录集,因此请尝试使用 .nextRecordSet 方法。

Set ws = Sheets.Add(After:=Sheets(Sheets.Count))
ws.Name = "Extracts-" & sheetnumber

Set rs = oCon.Execute(sql)

iRow = 2
Do Until rs Is Nothing
    With ws
        .Range("A" & iRow).CopyFromRecordset rs
        iRow = .Range("A" & Rows.Count).End(xlUp).Row + 2
    End With
    Set rs = rs.nextRecordSet
Loop

【讨论】:

@CDP1802 - 感谢您的解决方案。在实施您的解决方案后,我至少能够将变量传递到记录集中以执行,但是,现在,当我执行这些语句'Set rs = New ADODB.Recordset rs.Open StrSQL,cn,adOpenDynamic,adLockOptimistic,adCmdText'时,我得到一个错误:“在获取或执行和获取之前定义未完成”。事实证明,我将不得不删除 cmets,所以我想知道是否有办法让 cmets 保持原样并尝试在我的代码中处理它? @siddhant.jain 我很惊讶 Oracle 没有忽略注释行,是在 Unix 上吗?反正我已经更新了我的代码 @CDP1802-我实现了您的解决方案,但它似乎不适用于所有查询。我想知道为什么。示例(抱歉不知道如何添加图像): -- 未处于最终状态的网络请求数 select count(web_request_id) from TOLL_DBA.Web_request where web_request_status not in ('A', 'J'); -- 网络用户数 = 从 TOLL_DBA.Web_user 中选择计数 (*);从 toll_dba.account 中选择 count(account_id);按 account_type 从 toll_dba.account 组中选择 account_type, count(account_type); @siddhant.jain 您是否尝试过单独运行查询。 ?他们是否在像 SQL Developer 这样的 Oracle 客户端中工作?我真的只能帮助 VBA,我不是 Oracle DBA。 @CDP1802- 是的,如果我只是复制所有查询并将其作为脚本在 SQL Developer 中运行,那么一切正常。我先在 SQL Developer 中执行查询,然后粘贴到工作表中,这样就不会出现语法错误。【参考方案2】:

我可以通过稍微不同的方法来解决上述问题。唯一的前提是用户必须从脚本中删除 cmets。

脚本如下所示: Sample Script.

代码如下:

Private Sub RunValidation_Click()
Dim ws As Worksheet
Dim sheet As Variant
Dim sheetnumber As Integer
Dim irow As Integer
Dim rs As ADODB.Recordset
Dim fld As ADODB.field
Dim elementcount As Integer
Dim sqlscript As Variant
Dim StrSQL As String
Dim commands As Variant
Dim cmd() As Variant
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim results As String
Dim rownum As Integer
Dim col As Integer

On Error GoTo UserForm_Initialize_Err

If ScriptExecutor.TextUser = vbNullString Then
    MsgBox ("Please enter User ID.")
    GoTo UserForm_Initialize_Exit
End If

If ScriptExecutor.TextPwd = vbNullString Then
    MsgBox ("Please enter Password.")
    GoTo UserForm_Initialize_Exit
End If

Call OptimizeCode_Begin
Call Start_DBConnect

' Figuring out the last row with data
irow = ScriptExecutor.Range("A" & Rows.count).End(xlUp).row
elementcount = irow - 13

' Assigning range to a Variant variable
sqlscript = ScriptExecutor.Range("A14: A" & irow).Value

'Converting into String
StrSQL = Join(Application.Transpose(sqlscript), vbCrLf)

' Break the script into semi-colon
commands = Split(StrSQL, ";")

' Transfer values from array with empty values to array with empty values in the end
ReDim cmd(0 To 0)
j = 0

For i = LBound(commands) To UBound(commands)
    If commands(i) <> "" Then
        j = j + 1
        cmd(UBound(cmd)) = commands(i)
        ReDim Preserve cmd(0 To UBound(cmd) + 1)
    End If
Next i

'remove that empty array field at the end
If UBound(cmd) > 0 Then
    ReDim Preserve cmd(0 To UBound(cmd) - 1)
End If


Set rs = New ADODB.Recordset

' Open new sheet to paste results
k = 2
sheetnumber = Application.Sheets.count - k
Set ws = Sheets.Add(After:=Sheets(Sheets.count))
ws.name = "Extracts-" & sheetnumber

' Copy results in new sheet with field names
rownum = 1
For i = LBound(cmd) To UBound(cmd)
rs.Open cmd(i), cn, adOpenDynamic, adLockOptimistic, adCmdText
rs.MoveFirst
    col = 1
    For Each fld In rs.Fields
    With ws.Cells(rownum, col)
    .Value = fld.name: .HorizontalAlignment = xlLeft: .VerticalAlignment = xlTop: .EntireColumn.AutoFit: .Font.Bold = True: .Borders.Color = vbBlack
    End With
    col = col + 1
    Next

    rownum = ws.Range("A" & Rows.count).End(xlUp).row + 1
    With ws.Range("A" & rownum)
    .CopyFromRecordset rs:
    .Borders.Color = vbBlack
    rownum = ws.Range("A" & Rows.count).End(xlUp).row + 2
    End With

rs.Close
Next

Set rs = Nothing

UserForm_Initialize_Exit:
On Error Resume Next
Call OptimizeCode_End
Call End_DBConnect

Exit Sub

UserForm_Initialize_Err:
MsgBox Err.number & vbCrLf & Err.Description, vbCritical, "Error!"

Resume UserForm_Initialize_Exit
End Sub

目前这似乎工作得很好,但我正在用不同的场景对其进行测试。

【讨论】:

由于您是单独运行 select 语句,将它们连接成一个字符串,然后用分号分割对我来说似乎过于复杂,尤其是在之后构建数组时。我会简单地扫描工作表(忽略空白行和注释行)并逐行构建 SQL 语句。当您遇到行尾时;然后执行该查询,理想情况下作为一个单独的子程序,称为 getResults(SQL) 。将 SQL 设置回 "" 并继续扫描。 @CDP1802 - 谢谢,我会调查一下。

以上是关于关于如何在 Oracle 数据库中使用 VBA 执行 SQL 脚本的建议的主要内容,如果未能解决你的问题,请参考以下文章

关于ORACLE列转行的问题

访问 VBA、链接到 Oracle 并运行 SQL

linux上自动执行oracle

Access 2003 - VBA - 加入两种类型(字符串 = 日期)

Oracle 数据库中的 VBA ADODB DATE 字段类型

如何在 VBA 中自动执行电源查询?