2

我正在处理一个用 VB6 编写的遗留应用程序,它读取 Excel 电子表格并将它们插入数据库。
它在大多数情况下都有效,但如果电子表格中的数据不是从第一行开始,则第一个数据行会重复。
例如,电子表格中的前 3 行是空白的,前 4 行数据如下所示:

_|  A  |  B  |  C  |  D  |  E  |  F  |  G  |
1|     |     |     |     |     |     |     |
2|     |     |     |     |     |     |     |
3|     |     |     |     |     |     |     |
4|  99 |Text1|Text2|Text3|Text4|Text5|  77 |

应用程序连接到 Excel 电子表格并使用以下代码读取它:

Public Function obtainConnectionExcel(sql_conn, uid) As Variant
Dim cn As Object

Set cn = CreateObject("ADODB.Connection")
On Error Resume Next

cn.Provider = "Microsoft.ACE.OLEDB.12.0"
cn.Properties("Extended Properties").Value = "Excel 12.0;ReadOnly=True;HDR=No;IMEX=1"

If (Err <> 0) Then
    cn.Provider = "Microsoft.Jet.OLEDB.4.0"
    cn.Properties("Extended Properties").Value = "Excel 8.0;ReadOnly=True;HDR=No;IMEX=1"
End If

On Error Resume Next
cn.open getSpreadsheetPath(sql_conn, uid)
Set obtainConnectionExcel = cn
Exit Function
End Function
.....
Public Function extractAllData(parameters) As String

..... 'Variable declarations etc
On Error Resume Next
Set dbo_conn = obtainConnectionExcel(sql_conn, uid)
If Err <> 0 Then
    ....'logs error, goes to error handler
End If
On Error GoTo ErrorHandler

If (dbo_conn.State = 1) Then
    rownumber = 1

    Do While rownumber <= numberOfRowsToGet

        For x = lettercount To lettercount + lettercount_offset

            letter = Chr(x)
            sSql = "SELECT * FROM [" & worksheet & "$" & letter & rownumber & ":" & letter & rownumber & "]"
            On Error Resume Next
            Set rs = dbo_conn.execute(sSql)

            If (Not rs.EOF) Then
                'inserts the data into the db
            End If

        Next x

        rownumber = rownumber + 1
    Loop

    .... 'Post processing

Exit Function

....'Error handlers

End Function

那应该是相关的代码。问题发生在以下几行:

sSql = "SELECT * FROM [" & worksheet & "$" & letter & rownumber & ":" & letter & rownumber & "]"
        On Error Resume Next
        Set rs = dbo_conn.execute(sSql)

在读入数据时,无论我们使用的是 JET 还是 ACE,数据都是这样返回的:

_|  A  |  B  |  C  |  D  |  E  |  F  |  G  |
1|  99 |     |     |     |     |     |  77 |
2|  99 |     |     |     |     |     |  77 |
3|  99 |Text1|Text2|Text3|Text4|Text5|  77 |
4|  99 |Text1|Text2|Text3|Text4|Text5|  77 |

我尝试过连接到电子表格并以多种方式获取数据,但似乎没有任何效果 - 要么连接失败,要么数据只是空值。
我找到了一些解决方法 - 例如,如果我在单元格 A1 中输入空格字符,则问题不再出现。但是,我想要一个基于程序的解决方案,而不是告诉用户执行额外的步骤来避免这种情况。
它只复制第一行数据。如果单元格中的数据是数字,则将数据复制到该列中其上方的每个单元格中,如果是文本,则仅上升一级。
一个有趣的注意是,如果我更改电子表格以说所有数据都是文本,然后它会复制每个单元格,就好像它们是数字一样(即复制到上面的每个单元格,而不是单行)

总而言之,这很烦人——因为我在搜索这个问题时没有任何运气,我只能得出结论,我们做错了什么,或者很少有人为这种类型的测试数据而烦恼。

[编辑] 经过一番调查,我在解决这个问题上取得了一些进展——“提供者假定您的数据表从指定工作表上最上面、最左边的非空白单元格开始”(http://support .microsoft.com/default.aspx?scid=kb;en-us;257819)。如果我使用语句选择整个工作表,则可以确认这一点 - 它只返回数据块。
因此,当我选择该范围之外的任何单元格时,提供者不会执行返回 null 之类的明智操作,而是从该特定列的最上面的非空单元格返回数据。
我可以假设更改系统,使其简单地获取所有数据并假设最左上角的单元格是单元格 A1,但这会破坏与已经存在的数据的兼容性。
我现在需要的是一种方法来获取返回数据的单元格引用,以便我可以适当地对待它,或者一种强制这种情况不再发生的方法。

4

2 回答 2

1

关于与您的问题类似的问题的流行博客文章和线程(最初由 OneDayWhen 撰写)提到了注册表调整,该调整改变了 Excel 猜测单元格数据类型的方式。

我相信这种“猜测”行为可能是您问题的根源。

外部数据 - 混合数据类型

总之,使用 TypeGuessRows 让 Jet 检测是否存在“混合类型”情况,或者使用它来“欺骗”Jet 将某个特定数据类型检测为多数类型。如果检测到“混合类型”情况,请使用 ImportMixedTypes 告诉 Jet 使用多数类型或将所有值强制为“文本”(最多 255 个字符)。

其他人谈论修改您的连接字符串以包含 MaxScanRows=0 但这似乎不能解决问题。我了解您可能正在寻找便携式解决方案,但我无法找到这样的解决方法。

于 2011-08-30T18:37:53.443 回答
0

回答我自己的问题:看起来你不能。但是,您可以尝试围绕该问题编写代码。

JET 和 ACE 提供程序都将最左上角的非空单元格作为数据集的开始 (http://support.microsoft.com/default.aspx?scid=kb;en-us;257819)因此,当您尝试从该数据集开始之前出现的单元格中获取值时,而不是做一些明智的事情并返回 null,提供者会根据第一行数据返回一个猜测。

我找不到从提供者那里获取返回数据集的单元格引用的方法——它将所有内容标记为 F1、F2 等(“字段 1”、“字段 2”)

所以剩下两个解决方案:

1)一次抓取整个数据集,假设数据从 A1 开始,并使用该假设将其插入数据库。不幸的是,这将导致破坏与预先存在的数据的兼容性。

2)以编程方式计算单元格引用,并正确输入数据。我使用以下缩写代码做到了这一点

sSql = "SELECT * FROM [" & worksheet & "$]"
Set rs = dbo_conn.execute(sSql)

rownumber = 1
If Not rs.EOF Then
    oledata_array = rs.GetRows()
Else
    ReDim oledata_array(0, 0)
End If

Do While rownumber <= numberOfRowsToGet
    col_number = 1
    For x = lettercount To lettercount + lettercount_offset

        letter = Chr(x)
        sSql = "SELECT * FROM [" & worksheet & "$" & letter & rownumber & ":" & letter & rownumber & "]"
        On Error Resume Next
        Set rs = dbo_conn.execute(sSql)
        If Not rs.EOF Then
            If rs(0) <> "" Then
                If x < furthest_left Then
                    furthest_left = x
                End If
                If x > furthest_right Then
                    furthest_right = x
                End If
                If rownumber > bottom_of_set Then
                    bottom_of_set = rownumber
                End If
            Else
            End If
        End If

        col_number = col_number + 1

    Next x

    rs.MoveNext
    rownumber = rownumber + 1
Loop
rs.Close

top_of_set = bottom_of_set - UBound(oledata_array, 2)

If CLng(UBound(oledata_array, 1)) <> CLng(furthest_right - furthest_left) Then
    'log the fact that there is a discrepency, and continue
End If

'now have the co-ords of the "square" of data as it occurs in the spreadsheet

rownumber = 1
row_index = 0
Do While rownumber <= numberOfRowsToGet
    col_number = 1
    For x = lettercount To lettercount + lettercount_offset

        letter = Chr(x)

        'construct the first chunk of the sql insert string

        If (x <= furthest_right) And (x >= furthest_left) And (rownumber <= bottom_of_set) And (rownumber >= top_of_set) Then
            sSql = sSql & "'" & oledata_array(col_number - 1, row_index) & "'"
            col_number = col_number + 1
        Else
            sSql = sSql & "''"
        End If

        'finish the sql string and execute

    Next x
于 2011-09-12T16:35:48.483 回答