【问题标题】:Excel VBA ADODB RecordSet changes Field types from SQL ServerExcel VBA ADODB RecordSet 从 SQL Server 更改字段类型
【发布时间】:2020-03-03 11:54:34
【问题描述】:

我正在对 SQL Server 表运行一个简单查询:

SELECT * FROM MyTable

该表包含三列,类型为intbitdatetime2(0)。我正在使用以下代码:

Dim cnn As ADODB.Connection
Dim rst As ADODB RecordSet
Dim rngDest As Range
:
: ' etc
:
rst.Open "SELECT * FROM MyTable", cnn
rngDest.CopyFromRecordset rst

这很好用,除了它似乎字段类型错误:它说字段类型是 3、11、 202,根据文档,Integer(正确)、Boolean(我猜是正确的)和 NVarChar2 - 不正确。第三个字段应该是 date,当然(“Date”类型是 135)。

作为背景:我使用 SSMA 将数据从 Access 迁移到 SQL Server。该表现在具有我提到的三种列类型。另外,我知道 CopyFromRecordset 可能会导致字段类型错误是一个众所周知的问题,我最初认为这就是第三列(日期)作为字符串出现在 Excel 中的原因,但经过仔细检查,我可以看到当 VBA 读取 Recordset 时,它已经认为该字段是文本,在 CopyFromRecordset 行之前。我觉得如果我能让 VBA 以某种方式识别第三个字段应该是一个日期,那么我可以在内部对其进行转换。我不想专门为这个表创建解决方案,因为我有很多很多表和视图需要处理,所以我更愿意找到一种适用于所有这些表的方法。

如果重要的话,我使用的连接字符串是:

Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Data Source=[REDACTED];Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Use Encryption for Data=False;Tag with column collation when possible=False;Initial Catalog=[REDACTED];

有人可以帮忙吗?

【问题讨论】:

  • 您的日期数据看起来如何?
  • 它在数据库中看起来像这样:2020-03-02 14:05:11,当它从数据库中读取时,它是一个看起来与此相同的文本字段。我可以使用 DateValue(rst.Fields(2).Value 在 VBA 代码中截取它,但我觉得这有点过头了,我需要知道该字段确实是一个日期,.Type 属性应该告诉我,但它告诉我它是文本。
  • 你的回答很好。我添加了答案。有人提出了不同的解决方案。

标签: sql-server excel vba recordset


【解决方案1】:

Windows 附带的向后兼容的 SQLOLEDB 驱动程序没有在过去 20 年中引入的 SQL Server 数据类型的概念,例如 date。试用最新的 SQL Server OLEDB 驱动程序MSOLEDBSQL

我使用下面的 VbScript 运行了一个快速 ADO 测试,date 列返回 ADO 类型 133 (adDBDate)。

Set connection = CreateObject("ADODB.Connection")
connection.Open "Provider=MSOLEDBSQL;Data Source=.;Integrated Security=SSPI"
Set recordset = connection.Execute("SELECT CAST(SYSDATETIME() AS date);")
MsgBox recordset.Fields(0).Type
connection.Close

【讨论】:

  • 非常感谢您。我处于无法进行此类更改的环境中,但我会与支持人员取得联系,看看他们是否愿意这样做。
  • 新的 OLEDB 驱动程序与旧的驱动程序并排安装,因此如果操作系统兼容,应该不会有问题。不过,您需要使用新驱动程序测试应用代码。
  • 啊。那么我做的第一件事就是尝试它,错误消息是Provider cannot be found. It may not be properly installed.。我看看他们怎么说。
  • 进一步:我从here 运行了 Powershell 脚本,它不在结果输出中——但是当我在我的个人笔记本电脑上运行它时,它是。所以我需要和支持人员谈谈。
【解决方案2】:

这是我发现可行的解决方案(在等待支持人员更新驱动程序时): [TL;DR:使用rst.GetRows 将记录集读入变体varOrig;转置,然后插入 Excel 工作表;使用 .Value2 = 会导致 Excel 将其转换为正确的日期。 [我们必须转置,因为.GetRows,尽管它的名字,返回数据列。]]

希望这对以后遇到类似问题的其他人有用。

Public Sub RecordSetToRange(ByRef rst As ADODB.Recordset, ByRef lobDst As ListObject)

    Const strROUTINE As String = "RecordSetToRange"
    Dim iRow As Long
    Dim iCol As Long
    Dim varOrig As Variant
    Dim varTxp() As Variant

    ' -- Error Handling, Validation, Initialization.
    On Error GoTo ErrHandler

    ' -- Procedure.

    ' Headers first.
    With lobDst.HeaderRowRange.Cells(1)
        For iCol = 0 To rst.Fields.Count - 1
            .Offset(0, iCol).Value2 = rst.Fields(iCol).Name
        Next iCol
    End With
    ' Then data. Note that, despite the name of the function, GetRows returns columns of data, so we have
    ' to transpose it. Don't do anything if there is no data.
    If rst.RecordCount > 0 Then
        varOrig = rst.GetRows
        ReDim varTxp(UBound(varOrig, 2), UBound(varOrig, 1))
        For iRow = 0 To UBound(varOrig, 2)
            For iCol = 0 To UBound(varOrig, 1)
                varTxp(iRow, iCol) = varOrig(iCol, iRow)
            Next iCol
        Next iRow
        lobDst.DataBodyRange.Resize(UBound(varOrig, 2) + 1, UBound(varOrig, 1) + 1).Value2 = varTxp
    End If
    '
ErrHandler:        ' -- Error handling and Routine termination.
    If Err.Number <> 0 Then If DspErr(mstrMODULE, strROUTINE) Then Stop: Resume Else End
End Sub

【讨论】:

  • 如果记录集没有空数据,您可以直接写入单元格而无需循环。 lobDst.DataBodyRange.Resize(UBound(varOrig, 2) + 1, UBound(varOrig, 1) + 1)= application.transpose(varOrig)
  • 没错,谢谢,但是Application.Transpose 的行数限制为 65,536,我不知道我的数据是否会一直在该限制内。
【解决方案3】:

您的日期字段可能是字符串。所以你转换日期。以下示例将字符日期转换为日期数据。您需要根据您的角色类型进行不同的指定。

20130604060133 -> 2013/06/04 06:01:33

select
  convert(datetime,SUBSTRING( '20130604060133',1,8),112) +
  convert(datetime,
   ( substring( '20130604060133',9,2) + ':' +
    substring( '20130604060133',11,2)  + ':' +
    substring( '20130604060133',13,2)  )
    ,120) as myDate

问题似乎出在其他地方,但不清楚。但是有一种方法可以更改数据。见下文。

Dim cnn As ADODB.Connection
Dim rst As ADODB.Recordset
Dim rngDest As Range
:
: ' etc
:
rst.Open "SELECT * FROM MyTable", cnn
rngDest.CopyFromRecordset rst

Dim vDB
Dim rngDB As Range
Set rngDB = rngDest.CurrentRegion
vDB = rngDB
rngDB.NumberFormat = "General"
rngDB = vDB

【讨论】:

  • 谢谢,但我试过了,将格式更改为 General 并没有改变我的日期/时间。起作用的是使用 PasteSpecial 添加 0,但出于操作原因,我想避免使用剪贴板。
猜你喜欢
  • 2016-01-14
  • 2013-12-30
  • 2014-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多