質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

Q&A

解決済

1回答

892閲覧

980MBのCSVについて条件に沿ったデータのみ抽出する際、一定行以降で貼り付け時にエラーが発生する

kktok

総合スコア23

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

0グッド

0クリップ

投稿2021/04/21 01:11

編集2021/04/21 04:33

経緯

列数が236行、行数がExcelで開く限りでは表示できない量の巨大なCSVについて、
条件に沿ったデータ行のみを抽出するファンクションを作成しています。

①列によっては改行文字が含まれるためFileSystemObjectのReadメソッドを用いて一文字ずつ読み込んでいく
②1行分の処理が終わった時点で1次元配列に格納していた行について条件に合致した場合のみ多次元配列に格納する
③多次元配列に格納した回数が5000回に達した時点で一度CSVに貼り付ける

という処理をDoループで繰り返すことで実現しようとしていました。

問題

30000行を超えた時点でそれまで出てこなかった

アプリケーション定義またはオブジェクト定義のエラーです

といった内容のエラーが発生し貼り付けが不可能となってしまいました。

30000行前後であれば行数的にはExcelで開く分にも問題ない程度のデータ量であることから
原因が分からず、お教えいただけますと幸いです。

VBA

1'inTSはobjFSO.OpenTextFileで読み込んだ対象データ、targetColNameは判定列の名前、targetKeyArr()は集計対象外となる条件群、targetFolderPath はCSVのパス 2Sub trimCsvOrg2(ByRef inTS As Variant, ByVal targetColName As String, ByRef targetKeyArr() As String, targetFolderPath As String) 3 4 Dim currentRow As Long, currentColumn As Long, indexChara As Long 5 Dim lngQuote As Long 6 Dim strTarget As String 7 Dim targetColumn As Long 8 Dim isAdd As Boolean: isAdd = True 9 Dim strArr() As String 10 Dim strRec As String 11 Dim test1 As Long 12 Dim resultCSV As Workbook 13 Dim strResults() As String 14 Dim dimenNum As Long 15 Dim startR As Long: startR = 1 16 Dim countThrough As Long: countThrough = 0 17 Dim countColumn As Long: countColumn = 100000 18 19 Set resultCSV = returnWB(targetFolderPath) 20 21 currentRow = 1 'シートの1行目から出力 22 currentColumn = 0 '列位置はupdateConditionsでカウントアップ 23 lngQuote = 0 'ダブルクォーテーションの数 24 targetColumn = 0 '列番号の初期化 25 strTarget = "" 26 27 Do While Not inTS.AtEndOfStream 28 29 On Error GoTo err2 30 31 strRec = CStr(inTS.Read(1)) 32 33 Select Case strRec 34 35 Case vbLf, vbCr '「"」が偶数なら改行、奇数ならただの文字 36 37 If lngQuote Mod 2 = 0 Then 38 39 strRec = strRec & CStr(inTS.Read(1)) '改行としてのCrが出てきたらLfも読み込んで捨てる 40 41 '行が変わる時の処理。追加判定が真なら1次元配列を2次元配列に突っ込む 42 If isAdd Then 43 Call pushValueToArr(strTarget, strArr) '成型した文字列を配列に格納 44 Call pushArrToMDArr(strResults, strArr, dimenNum, countColumn) '2次元配列に1次元配列を突っ込む 45 If currentRow = 1 Then countColumn = UBound(strArr) 46 47 '5000次元までいった時点でシートに差し込む 48 If dimenNum = 4999 Then 49 50 startR = pushArrToCells(resultCSV.Worksheets(1), startR, dimenNum, strResults) 51 dimenNum = 0 52 53 End If 54 55 Else 56 57 countThrough = countThrough + 1 58 59 End If 60 61 Erase strArr '1次元配列をリセットする 62 63 Call updateConditions(currentColumn, strTarget, lngQuote) '列が変わる時の処理 64 currentRow = currentRow + 1 65 isAdd = True 66 currentColumn = 0 67 lngQuote = 0 68 69 Else 70 71 strTarget = strTarget & strRec 72 73 End If 74 75 Case "," '「"」が偶数なら区切り、奇数ならただの文字 76 77 If lngQuote Mod 2 = 0 Then 78 79 If targetColumn = 0 Then Call getTargetColumnIndex(currentColumn, targetColumn, strTarget, targetColName) '項目行なら判定行のインデックスを探す 80 If currentRow > 1 And (currentColumn + 1) = targetColumn Then isAdd = Not isMemberInArr(targetKeyArr, strTarget) 81 If isAdd Then Call pushValueToArr(strTarget, strArr()) '成型した文字列を配列に格納 82 Call updateConditions(currentColumn, strTarget, lngQuote) '列が変わる時の処理 83 84 Else 85 86 strTarget = strTarget & strRec 87 88 End If 89 90 Case """" '「"」のカウントをとる 91 92 lngQuote = lngQuote + 1 93 strTarget = strTarget & strRec 94 95 Case Else 96 97 strTarget = strTarget & strRec 98 99 End Select 100 101 Loop 102 103 '最終セルの処理 104 If currentColumn > 0 And strTarget <> "" Then 105 106 If (currentColumn + 1) = targetColumn Then isAdd = isMemberInArr(targetKeyArr, strTarget) 107 If isAdd Then Call pushValueToArr(strTarget, strArr) '成型した文字列を配列に格納 108 Call updateConditions(currentColumn, strTarget, lngQuote) '列が変わる時の処理 109 110 End If 111 112 If Not Not strResults Then 113 114 Call pushArrToCells(resultCSV.Worksheets(1), startR, dimenNum, strResults) 115 116 End If 117 118 resultCSV.Save 119 120err2: 121 MsgBox (err.Description) 122 123 124End Sub 125 126'対象列の値なら配列に追加する 127'①現在の列インデックス②対象の文字列③ダブルクォーテーションの数④対象列名⑤対象列インデックス⑥文字列配列 128Sub updateConditions(ByRef currentColumn As Long, ByRef strTarget As String, ByRef lngQuote As Long) 129 130 currentColumn = currentColumn + 1 131 lngQuote = 0 132 133 strTarget = "" 134 135End Sub 136'項目列が判定列ならインデックスを返す 137Sub getTargetColumnIndex(ByVal currentColumn As Long, ByRef targetColumn As Long, ByVal strTarget As String, ByVal targetColName As String) 138 139 If editStrIsBlank(strTarget) = targetColName Then 140 141 targetColumn = currentColumn + 1 142 143 End If 144 145End Sub 146 147'空白なら""を、それ以外なら"を取った値に編集する 148Function editStrIsBlank(ByVal strTarget As String) 149 150 If Left(strTarget, 1) = """" And Right(strTarget, 1) = """" Then 151 152 If Len(strTarget) <= 2 Then 153 154 editStrIsBlank = "" 155 Exit Function 156 157 Else 158 159 editStrIsBlank = Mid(strTarget, 2, Len(strTarget) - 2) 160 Exit Function 161 162 End If 163 164 End If 165 166 editStrIsBlank = strTarget 167 168End Function 169 170'文字列の"を削除、最大数を増やして1次元配列に値を入れる 171Sub pushValueToArr(ByVal strTarget As String, ByRef strArr() As String) 172 173 strTarget = Replace(strTarget, """""", """") '前後の「"」を削除 174 175 strTarget = editStrIsBlank(strTarget) '空白なら""を、それ以外なら"を取った値に編集する 176 177 If Not Not strArr Then '配列が初期化済みなら上限数を増やして追加する 178 179 ReDim Preserve strArr(UBound(strArr) + 1) 180 181 Else 182 183 ReDim strArr(0) 184 185 End If 186 187 strArr(UBound(strArr)) = strTarget 188 189End Sub 190 191'1次元配列の値をすべて2次元配列に入れる(呼び出し元は最後にtransposeする)。1列目以降は次元数の横方向固定 192Sub pushArrToMDArr(strResults() As String, strArr() As String, ByRef dimenNum As Long, ByVal countColumn As Long) 193 194 Dim temp As Variant 195 Dim errA As Variant 196 197 If countColumn = 100000 Then 198 199 countColumn = UBound(strArr) 200 201 End If 202 203 If Not Not strResults Then '配列が初期化済みなら上限数を増やして追加する 204 205 dimenNum = dimenNum + 1 206 207 End If 208 209 ReDim Preserve strResults(countColumn, dimenNum) 210 211 212 For i = 0 To countColumn 213 214 If i > UBound(strArr) Then 215 216 strResults(i, dimenNum) = "" 217 218 Else 219 220 strResults(i, dimenNum) = strArr(i) 221 222 End If 223 224 Next i 225 226End Sub 227 228'指定要素が配列のメンバーかどうか返す 229Function isMemberInArr(targetKeyArr() As String, strTarget As String) As Boolean 230 231 Dim result As Variant 232 233 result = Filter(targetKeyArr(), strTarget) 234 235 isMemberInArr = (UBound(result) <> -1) 236 237End Function 238 239'ワークブックを作る 240Function returnWB(path As String) As Workbook 241 242 Dim wbResult As Workbook 243 Set wbResult = Workbooks.Add 244 wbResult.SaveAs fileName:=path & "編集済データ.csv", _ 245 FileFormat:=xlCSV 246 247 Set returnWB = wbResult 248 249End Function 250 251'2次元配列をシート内に突っ込む 252Function pushArrToCells(ByRef ws As Worksheet, ByVal startR As Long, ByVal dimenNum As Long, ByRef strResults() As String) As Long 253 254 With ws 255 256 .Range(.Cells(startR, 1), .Cells(dimenNum + startR, UBound(strResults) + 1)).value = WorksheetFunction.Transpose(strResults) '結果データを張り付ける 257 258 End With 259 260 Erase strResults 261 262 pushArrToCells = startR + dimenNum + 1 263 264End Function 265

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sazi

2021/04/21 03:44

> 行数がExcelで開く限りでは表示できない量の巨大なCSV それをVBAで貼り付けてもエクセルでは開けないサイズに編集しようとしたら同じことではないでしょうか。 編集できたとしてもエクセルでは開けない事が分かっていて、その結果を使って何をしたいのですか?
hatena19

2021/04/21 03:48

> という処理をDoループで繰り返すことで実現しようとしていました。 提示のコードには Doループはないですけど。 再帰のような感じですが、そうでもないですし。 ちゃんと動作するコードを提示してください。
kktok

2021/04/21 04:36

sazi様 意図が伝わりづらく失礼しました。 こちら、他の方が作ったツールで集計を行うにあたり、 独自の集計を行いたいためCSVをそのツールに通す前に集計外となるデータを省いたCSVを作成したい、というのが目的です。 元のツール作成者にお願いすることが難しかったため、別でツールを作成することになった次第です。
kktok

2021/04/21 04:37

hatena19様 エラーの発生するコードのみ載せてしまい失礼いたしました。 コード範囲を拡大させていただきましたので、改めてご確認ください。
guest

回答1

0

ベストアンサー

他の方が作ったツールで集計を行うにあたり、独自の集計を行いたいためCSVをそのツールに通す前に集計外となるデータを省いたCSVを作成したい

上記の意図としてはCSVの加工ですね。
加工するのにエクセルシートを使おうとされていますが、そもそもエクセルでそのCSVを開けないなら、シートで編集するのではなく、ファイルI/Oなど別な方法で行うのが得策だと思います。

テキストファイルを操作する(開く)
テキストファイルを操作する(書き込む)
上記以外にも参考になる記事は沢山あります。

投稿2021/04/21 05:07

編集2021/04/21 05:23
sazi

総合スコア25327

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kktok

2021/04/21 05:27

ご回答くださりありがとうございます。 ファイルI/Oを使用することも考え作成したのですが、 実行完了までに1時間弱かかってしまうので ある程度のデータを配列内でまとめておいて一括で貼り付けることで 時間短縮できないか、という考えでした。
sazi

2021/04/21 05:47

ファイル10万行でI/O使って1時間弱ですか!? 余程の加工処理しない限りそこまで時間かからないと思うんですけどね。 ACESSとか別なDBにインポートして、加工してからエクスポートという方法を取った方が、少しは短縮できるのではないかと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問