• ベストアンサー

VBで配列の差分を取りたいのですが

VB2005を使用しています。 エクセルに2つのシートには更新前と更新後のデータがあって、 それの差分を取るために一度2次元配列に確保してから処理を行い たいのですが、配列を入れ子にして順にループ、比較させると時間がかかりすぎてしまいます。 どうしたらよいのでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
回答No.2

>配列を入れ子にして順にループ せっかくエクセルを利用しているのであれば、エクセルに比較部分を処理したらよいと思います。 「EXACT」というエクセルの文字列比較関数を利用しましょう。 それと、エクセルをループで読み取るだけでも時間がかると思いますが、データベースとしてエクセルブックを扱い、相違部分だけのデータを抽出できます。 下の例は ※ブック1に二つのシート「変更前」「変更後」後が存在している ※「変更前」「変更後」のそれぞれのA列を比較する ※比較結果を、別のエクセルブック2として保存する という処理になっています。 もし「変更前」「変更後」が別のブックである場合は、以下のコードを理解し、改造を加えてください。 Private Const DEF_ブックネタパス As String = "C:\Book1.xls" Private Const DEF_ブック結果パス As String = "C:\Book2.xls" Private Const DEF_シート変更前 As String = "変更前" Private Const DEF_シート変更後 As String = "変更後" Sub Main()   Dim l_dtb As DataTable = エクセル比較()   Dim l_strMsg As String = ""   If l_dtb.Columns.Count.Equals(0) Then     'カラムが存在しないということは、比較対象のデータ件数が0件     l_strMsg = "データが存在しない"   Else     Dim l_ary As New System.Collections.ArrayList     l_ary.Add(String.Format("データ相違数数{0}件", l_dtb.Rows.Count))     For Each l_drw As DataRow In l_dtb.Rows       Dim l_strWK As String       l_strWK = String.Format("{0}行目", l_drw.Item("F1").ToString())       l_strWK &= vbTab & String.Format("変更前「{0}」", l_drw.Item("F2").ToString())       l_strWK &= vbTab & String.Format("変更後「{0}」", l_drw.Item("F3").ToString())       l_ary.Add(l_strWK)     Next     l_strMsg = Join(CType(l_ary.ToArray(GetType(String)), String()), vbCrLf)   End If   MsgBox(l_strMsg) End Sub Function エクセル比較() As DataTable   Dim l_xlsApp As Object = CreateObject("Excel.Application")   l_xlsApp = CreateObject("Excel.Application")   'データの件数を取得する   Dim l_int件数 As Integer = 件数取得(l_xlsApp)   '比較情報ブックを作成する   比較作成(l_xlsApp, l_int件数)   'エクセルを閉じる   l_xlsApp.Quit()   Return 結果読み込み() End Function Function 件数取得(ByVal p_xlsApp As Object) As Integer   Dim l_xlsBook As Object = p_xlsApp.Workbooks.Open(DEF_ブックネタパス)   Dim l_xlsSheet1 As Object = l_xlsBook.Worksheets(DEF_シート変更前)   Dim l_xlsSheet2 As Object = l_xlsBook.Worksheets(DEF_シート変更後)   'シートのデータ件数の多い方を件数とする   Dim l_int件数 As Integer = System.Math.Max(データ範囲行(l_xlsSheet1), データ範囲行(l_xlsSheet2))   l_xlsBook.Close()   Return l_int件数 End Function Function データ範囲行(ByVal p_xlsCells As Object) As Integer   Dim l_xlsCellsTop As Object = p_xlsCells.Cells(1)   Dim l_xlsCellsBottom As Object = l_xlsCellsTop.SpecialCells(11)   Return l_xlsCellsBottom.Row End Function Sub 比較作成(ByVal p_xlsApp As Object, ByVal p_int件数 As Integer)   '結果ブックを削除する   If System.IO.File.Exists(DEF_ブック結果パス) Then     Kill(DEF_ブック結果パス)   End If   '件数が存在しない場合、処理を行わない   If p_int件数.Equals(0) Then     Exit Sub   End If   '比較用のワークシート   Dim l_xlsBook As Object = p_xlsApp.Workbooks.Add   '比較用のワークシート   Dim l_xlsSheet As Object = l_xlsBook.Worksheets(1)   Dim l_xlsRangeA As Object = l_xlsSheet.Cells(1, 1)   Dim l_xlsRangeB As Object = l_xlsSheet.Cells(1, 2)   Dim l_xlsRangeC As Object = l_xlsSheet.Cells(1, 3)   Dim l_xlsRangeD As Object = l_xlsSheet.Cells(1, 4)   'A1~D1   Dim l_xlsRangeFil1 As Object = l_xlsSheet.Range(l_xlsRangeA, l_xlsRangeD)   'A1~Dのデータ件数分   Dim l_xlsRangeFil2 As Object = l_xlsSheet.Range(l_xlsRangeA, l_xlsRangeD.Offset(p_int件数))   'ブックのファイル情報   Dim l_intFileInfo As New System.IO.FileInfo(DEF_ブックネタパス)   'ブックファイル名   Dim l_strBookName As String = l_intFileInfo.Name   'ブックファイルパス   Dim l_strBookPath As String = l_intFileInfo.FullName.Substring(0, l_intFileInfo.FullName.Length - l_intFileInfo.Name.Length)   '関数を作成するワーク   Dim l_strRep() As String = New String() {l_strBookPath, l_strBookName, DEF_シート変更前, DEF_シート変更後}   '比較を行うエクセル関数文字列   Dim l_str変更前セル As String = String.Format("'{0}[{1}]{2}'!RC[-1]", New String() {l_strBookPath, l_strBookName, DEF_シート変更前})   Dim l_str変更後セル As String = String.Format("'{0}[{1}]{2}'!RC[-2]", New String() {l_strBookPath, l_strBookName, DEF_シート変更後})   Dim l_str関数書式 As String = "=IF(ISNUMBER({0}),TEXT({0}, ""@""),T({0}))"      'A1に行番号   l_xlsRangeA.FormulaR1C1 = "=Row()"   'B1に変更前シートの情報   l_xlsRangeB.FormulaR1C1 = String.Format(l_str関数書式, l_str変更前セル)   'C1に変更後シートの情報   l_xlsRangeC.FormulaR1C1 = String.Format(l_str関数書式, l_str変更後セル)   'D1に比較結果   l_xlsRangeD.FormulaR1C1 = "=EXACT(RC[-2], RC[-1])"   'オートフィル機能により、比較結果をデータ件数分反映する   l_xlsRangeFil1.AutoFill(l_xlsRangeFil2)   '結果を保存する   l_xlsBook.Close(True, DEF_ブック結果パス) End Sub Public Function 結果読み込み() As DataTable   Dim l_dtbRet As New DataTable   If IO.File.Exists(DEF_ブック結果パス) Then     'ブックをデータベースとして接続する文字列     Dim l_strCn As String = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties='Excel 5.0;HDR=No;'", DEF_ブック結果パス)     '比較結果がFALSEのものだけを抽出(WHERE句をはずすと、全件取得)     Dim l_strSQL As String = "SELECT * FROM [Sheet1$] WHERE F4 = FALSE"     Dim l_fil As New System.Data.OleDb.OleDbDataAdapter(l_strSQL, l_strCn)     l_fil.Fill(l_dtbRet)   End If   Return l_dtbRet End Function

noname#68570
質問者

お礼

返事が遅くなり申し訳ありません。 大変参考になりました。 >せっかくエクセルを利用しているのであれば、エクセルに比較部分を処理したらよいと思います。 確かにおっしゃられている通り、EXCELのワークシート関数とか操作を上手く利用したほうが格段に効率が良いですね。 結局、同じシートにデータを貼り付けてソートしてフィルタして実現できました。 テーブルとして使えるのも始めて知りました。 ありがとうございます。

その他の回答 (1)

回答No.1

vbは、特に最近の.netのvbは知らないのですが・・・。 vba風で良ければこんなのはどうでしょう。 内容が詳しくわからないのですが、 >エクセルに2つのシートには更新前と更新後のデータがあって、 とあるので、同じブック内の2つのシートだとして、比較結果のシートを作ります。 2つのシートの同じ位置のデータを比較するとします。 book1.xlsのsheet1とsheet2の(1,1)-(10,10)の範囲を比較するとします。 vbで、formにButton1を配置します。 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'bookを開く Dim wb As Object wb = CreateObject("Excel.Application").Workbooks.Open(Filename:="c:\book1.xls") '新しいシートを作って差分の計算式を代入(ここでは(1,1)-(10,10)の範囲 Dim ws As Object ws = wb.Worksheets.Add() ws.Range(ws.Cells(1, 1), ws.Cells(10, 10)).FormulaR1C1 = "=Sheet1!RC-Sheet2!RC" '各種操作 '配列に入れても良いが、そのままws.Cells(row, col).valueでアクセスできる '例えば結果表示 MessageBox.Show(ws.Cells(1, 1).value) '後始末 ws = Nothing wb.Close(savechanges:=False) wb = Nothing End Sub

noname#68570
質問者

お礼

回答していただきありがとうございました。 私の質問の内容がちょっとわかりずからったですね。 もうしわけございません。

関連するQ&A