如何通过MS Access VBA保留多表合并后Excel单元格的数据类型
保留Access VBA左连接后Excel单元格的原始数据类型
问题概述
通过MS Access VBA基于「Unique Transaction ID」左连接5个Excel工作表,合并结果保存到新工作表后,无法保留原工作表的单元格数据类型。尝试用字典映射ID对应行来应用格式,但整列会被第一个遇到的数据类型覆盖,需要实现细粒度的单元格格式保留。
现有实现代码
左连接与数据导出代码
Dim ls_last_row As Long Dim cs_last_row As Long Dim e_sheet_last_row As Long Dim p_sheet_last_row As Long Set ls = objFile.Worksheets("2. sheet") Set cs = objFile.Worksheets("3. sheet") Set es = objFile.Worksheets("4. sheet") Set ps = objFile.Worksheets("5. sheet") l_sheet_last_row = ls.Cells.Find(What:="*", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues).Row cs_last_row = cs.Cells.Find(What:="*", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues).Row e_sheet_last_row = es.Cells.Find(What:="*", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues).Row p_sheet_last_row = ps.Cells.Find(What:="*", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues).Row ' 导入Sheet2到Access临时表 DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12Xml, "tmp_sheet2", filePath, True, "2. sheet!A1:AU" & loan_sheet_last_row ' 导入Sheet3到Access临时表 DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12Xml, "tmp_sheet3", filePath, True, "3. sheet!A1:I" & cs_last_row ' 导入Sheet4到Access临时表 DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12Xml, "tmp_sheet4", filePath, True, "4. sheet!A1:H" & e_sheet_last_row ' 导入Sheet5到Access临时表 DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel12Xml, "tmp_sheet5", filePath, True, "5. sheet!A1:D" & p_sheet_last_row ' 执行左连接SQL Dim strSql As String strSql = "SELECT sheet2.*, sheet3.*, sheet4.*, sheet5.* " _ & "FROM ((tmp_sheet2 AS sheet2 " _ & "LEFT JOIN tmp_sheet3 AS sheet3 ON CSTR(sheet2.[Unique Transaction ID]) = CSTR(sheet3.[Unique Transaction ID])) " _ & "LEFT JOIN tmp_sheet4 AS sheet4 ON CSTR(sheet2.[Unique Transaction ID]) = CSTR(sheet4.[Unique Transaction ID])) " _ & "LEFT JOIN tmp_sheet5 AS sheet5 ON CSTR(sheet2.[Unique Transaction ID]) = CSTR(sheet5.[Unique Transaction ID])" ' 创建合并结果表 DoCmd.SetWarnings False Dim tableName As String tableName = "joined_table" DoCmd.RunSQL "SELECT * INTO " & tableName & " FROM (" & strSql & ")" ' 删除重复的ID列 DoCmd.RunSQL "ALTER TABLE joined_table DROP COLUMN [sheet3_Unique Transaction ID]" DoCmd.RunSQL "ALTER TABLE joined_table DROP COLUMN [sheet4_Unique Transaction ID]" DoCmd.RunSQL "ALTER TABLE joined_table DROP COLUMN [sheet5_Unique Transaction ID]" ' 读取合并结果 Dim rs As Recordset Set rs = CurrentDb.OpenRecordset("SELECT * FROM " & tableName) ' 创建新工作表存储结果 Set newSheet = objFile.Worksheets.Add(After:=objFile.Worksheets(objFile.Worksheets.Count)) newSheet.Name = "2. Transaction Data" ' 写入表头 Dim headerRange As Range Dim i As Integer Set headerRange = newSheet.Range("A1") For i = 0 To rs.Fields.Count - 1 If i = 0 Then headerRange.Offset(0, i) = "Unique Transaction ID" Else headerRange.Offset(0, i) = rs.Fields(i).Name End If Next i ' 写入数据 headerRange.Offset(1, 0).CopyFromRecordset rs ' 构建Sheet2的ID-行号字典 Dim idDict As Object Set idDict = CreateObject("Scripting.Dictionary") For i = 2 To loan_sheet_last_row idDict.Add ls.Cells(i, 1).Value, i Next i ' 尝试应用Sheet2的格式 Dim j As Long For i = 2 To loan_sheet_last_row Dim id As Variant id = ls.Cells(i, 1).Value Dim matchRow As Variant If idDict.Exists(id) Then Dim rowNumber As Long rowNumber = idDict(id) For j = 1 To 47 Dim dataType As String dataType = TypeName(ls.Cells(i, j).Value) newSheet.Cells(rowNumber, j).NumberFormat = GetNumberFormat(dataType) Next j End If Next i ' 尝试应用Sheet3的格式 Dim k As Long For i = 2 To cs_last_row id = cs.Cells(i, 1).Value If idDict.Exists(id) Then rowNumber = idDict(id) For k = 2 To 9 dataType = TypeName(cs.Cells(i, k).Value) newSheet.Cells(rowNumber, k + 46).NumberFormat = GetNumberFormat(dataType) Next k End If Next i ' 尝试应用Sheet4的格式 Dim q As Long For i = 2 To e_sheet_last_row id = es.Cells(i, 1).Value If idDict.Exists(id) Then rowNumber = idDict(id) For q = 2 To 8 dataType = TypeName(es.Cells(i, q).Value) newSheet.Cells(rowNumber, q + 54).NumberFormat = GetNumberFormat(dataType) Next q End If Next i ' 尝试应用Sheet5的格式 Dim p As Long For i = 2 To p_sheet_last_row id = ps.Cells(i, 1).Value If idDict.Exists(id) Then rowNumber = idDict(id) For p = 2 To 4 dataType = TypeName(ps.Cells(i, p).Value) newSheet.Cells(rowNumber, p + 61).NumberFormat = GetNumberFormat(dataType) Next p End If Next i rs.Close Set rs = Nothing
格式映射函数
Function GetNumberFormat(dataType As String) As String '********************************************************************** ' Excel数据类型与VBA NumberFormat对应关系 ' General: General ' Number: 0 ' Currency: $#,##0.00;[Red]$#,##0.00 ' Accounting: _($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_) ' Date: m/d/yyyy ' Time: [$-F400]h:mm:ss am/pm ' Percentage: 0.00% ' Fraction: # ?/? ' Scientific: 0# ' String: @ ' Special: ;; ' Custom: #,##0_);[Red](#,##0) '********************************************************************** Select Case dataType Case "String" GetNumberFormat = "@" Case "Date" GetNumberFormat = "m/d/yyyy" Case "Currency" GetNumberFormat = "$#,##0.00;[Red]$#,##0.00" Case "Double" GetNumberFormat = "0" Case "Integer" GetNumberFormat = "0" Case "Accounting" GetNumberFormat = "_($* #,##0.00_);_($* (#,##0.00);_($* "" - ""??_);_(@_)" Case "Percentage" GetNumberFormat = "0.00%" Case Else GetNumberFormat = "General" End Select End Function
问题原因
- Access导入的类型统一:
DoCmd.TransferSpreadsheet导入Excel数据时,Access会自动推断列的统一数据类型,丢失原Excel中同一列内不同单元格的类型差异。 - 行号映射错位:现有字典仅映射Sheet2的ID与行号,但左连接后新工作表的行号与Sheet2不完全对应,导致格式应用位置错误。
- Excel列格式优先级:
CopyFromRecordset会自动为整列设置默认格式,后续单独设置的单元格格式可能被列格式覆盖,或因循环顺序导致同列格式被统一。
解决方案
方案1:直接在Excel中执行合并(推荐)
跳过Access中转,用Excel Power Query实现左连接,原生保留单元格格式:
Sub ExcelDirectJoin() Dim wb As Workbook Set wb = objFile ' 替换为你的目标工作簿对象 ' 创建Power Query合并查询 Dim qry As Object Set qry = wb.Queries.Add( _ Name:="TransactionJoin", _ Formula:= _ "let" & vbCrLf & _ " Source = Excel.CurrentWorkbook(){[Name=""2. sheet""]}[Content]," & vbCrLf & _ " #""合并3. sheet"" = Table.NestedJoin(Source, {""Unique Transaction ID""}, Excel.CurrentWorkbook(){[Name=""3. sheet""]}[Content], {""Unique Transaction ID""}, ""3. sheet"", JoinKind.LeftOuter)," & vbCrLf & _ " #""展开3. sheet"" = Table.ExpandTableColumn(#""合并3. sheet"", ""3. sheet"", List.RemoveItems(Table.ColumnNames(Excel.CurrentWorkbook(){[Name=""3. sheet""]}[Content]), {""Unique Transaction ID""}))," & vbCrLf & _ " #""合并4. sheet"" = Table.NestedJoin(#""展开3. sheet"", {""Unique Transaction ID""}, Excel.CurrentWorkbook(){[Name=""4. sheet""]}[Content], {""Unique Transaction ID""}, ""4. sheet"", JoinKind.LeftOuter)," & vbCrLf & _ " #""展开4. sheet"" = Table.ExpandTableColumn(#""合并4. sheet"", ""4. sheet"", List.RemoveItems(Table.ColumnNames(Excel.CurrentWorkbook(){[Name=""4. sheet""]}[Content]), {""Unique Transaction ID""}))," & vbCrLf & _ " #""合并5. sheet"" = Table.NestedJoin(#""展开4. sheet"", {""Unique Transaction ID""}, Excel.CurrentWorkbook(){[Name=""5. sheet""]}[Content], {""Unique Transaction ID""}, ""5. sheet"", JoinKind.LeftOuter)," & vbCrLf & _ " #""展开5. sheet"" = Table.ExpandTableColumn(#""合并5. sheet"", ""5. sheet"", List.RemoveItems(Table.ColumnNames(Excel.CurrentWorkbook(){[Name=""5. sheet""]}[Content]), {""Unique Transaction ID""}))" & vbCrLf & _ "in" & vbCrLf & _ " #""展开5. sheet""" ) ' 将查询结果加载到新工作表 wb.Sheets.Add(After:=wb.Sheets(wb.Sheets.Count)).Name = "2. Transaction Data" wb.Sheets("2. Transaction Data").ListObjects.Add(SourceType:=0, Source:= _ "OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=TransactionJoin;Extended Properties=""""" _ , Destination:=Range("$A$1")).QueryTable.Refresh BackgroundQuery:=False End Sub
方案2:修正Access中转后的格式逻辑
若必须使用Access中转,需调整映射和格式应用逻辑:
- 构建新工作表的ID-行号映射:
' 在CopyFromRecordset后执行 Dim newIdDict As Object Set newIdDict = CreateObject("Scripting.Dictionary") Dim newLastRow As Long newLastRow = newSheet.Cells(Rows.Count, 1).End(xlUp).Row For i = 2 To newLastRow newIdDict.Add newSheet.Cells(i, 1).Value, i Next i - 清除列默认格式:
' 先将所有列格式设为General,避免覆盖单元格格式 newSheet.Cells.NumberFormat = "General" - 直接复制原单元格格式:
以Sheet2为例,修改格式应用代码:
同理修改Sheet3、Sheet4、Sheet5的格式应用代码,注意调整新工作表的列偏移量(如Sheet3的列从48开始)。For i = 2 To l_sheet_last_row id = ls.Cells(i, 1).Value If newIdDict.Exists(id) Then rowNumber = newIdDict(id) For j = 1 To 47 ' 直接复制原单元格的NumberFormat,避免类型推断误差 newSheet.Cells(rowNumber, j).NumberFormat = ls.Cells(i, j).NumberFormat Next j End If Next i
关键优化点
- 直接复制原单元格的
NumberFormat属性,避免通过TypeName推断的误差。 - 先清除新工作表的列格式,确保单元格级格式生效。
- 基于合并后的新工作表构建ID-行号映射,避免行号错位。
内容的提问来源于stack exchange,提问作者ludwigf235
相关产品推荐
相关产品推荐

