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

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

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

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

VB

VB(ビジュアルベーシック)はマイクロソフトによってつくられたオブジェクト指向プログラミング言語のひとつで、同社のQuickBASICが拡張されたものです。VB6の進化版といわれています。

Oracle

Oracleは、米オラクルが取り扱うリレーショナルデータベース管理システムです。メインフレームからPCまで、多様なプラットフォームに対応しています。

Q&A

解決済

3回答

4549閲覧

VB6、ADOでOracleのデータをCSV化しようとすると、メモリ不足で止まる

退会済みユーザー

退会済みユーザー

総合スコア0

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

VB

VB(ビジュアルベーシック)はマイクロソフトによってつくられたオブジェクト指向プログラミング言語のひとつで、同社のQuickBASICが拡張されたものです。VB6の進化版といわれています。

Oracle

Oracleは、米オラクルが取り扱うリレーショナルデータベース管理システムです。メインフレームからPCまで、多様なプラットフォームに対応しています。

0グッド

0クリップ

投稿2019/12/27 09:01

編集2020/01/07 07:40

はじめに

お世話になっております。
今回は大量のデータベースをCSV化することについて質問いたします。

OracleデータベースをVB6でCSV化する必要がありました。
(OracleをPostgreに移行したいけど、方法がCSVをpsqlで読み込むしか考えつかなかったため)
ので、ササっと以下のように作りました。

VB6

1 'データ出力 2 strSQL = "" 3 strSQL = strSQL & "SELECT * " 4 strSQL = strSQL & "FROM " & strSchema & "." & strTable & " " 5 6 Set objData = Po_DataBase.DbCreateDynaset(strSQL, ORADYN_DEFAULT) 7 8 'ファイルオープン 9 Set FSO = CreateObject("Scripting.FileSystemObject") 10 Set Ts = FSO.OpenTextFile(strOutput & strTable & "_" & Format(DateTime.Date, "yyyyMMdd") & ".csv", ForWriting, True) 11 12 'ファイル出力 13 With Ts 14 Dim i As Integer 15 Dim j As Integer 16 objData.MoveFirst 17 Do While objData.EOF = False 18 strOut = "" 19 For j = 0 To intFldCount 20 strOut = strOut & CORA20_FieldsValue(objData, objDataFld, j) & "," 21 Next 22 23 strOut = Left(strOut, Len(strOut) - 1) 24 25 .WriteLine strOut 26 27 'ここでエラーが発生 28 objData.movenext 29 Loop 30 End With

何がダメだったか

一応上記のプログラムは動きました。
ある程度のデータ数ならCSVとして吐き出すことができます。
しかし、出力データ数が226,482KBを超えたところでエラーが発生して、
途中のデータまでしかCSV化できませんでした。
イメージ説明

要望

今回は半分うまくいっているので、言語や手法を変えずにできる方法を探しています。
よろしければご教授願います。

追記1

エラー箇所にErr.Descriptionを仕込んでみたら
「メモリ不足です」と表示されました。
この場合、メモリを増築するなどで対処できますでしょうか?
もしくは、SQL部分をチューニングしたり、分割出力(やり方はわからないです)などで回避できるものでしょうか?

追記2

エラーの発生個所は、上記コードに追記しています。
FieldValueは以下のようになっています。

VB6

1Public Function CORA20_FieldsValue(ByRef Ho_DBObject As Object _ 2 , Ho_Fields() As Object _ 3 , Hn_Idx As Integer) As String 4 5 Dim Ls_Ret As String 6 7 On Error GoTo CORA20_FieldsValue_ERR 8 9 '/* 戻り値クリア 10 If Not Ho_Fields(Hn_Idx) Is Nothing Then 11 If Pb_ADO_ConnectFlg = True Then 12 Ls_Ret = CStr(IIf(Ho_Fields(Hn_Idx).Type = 202, vbNullString, 0)) 13 Else 14 Ls_Ret = CStr(IIf(Ho_Fields(Hn_Idx).OraIDataType = 1, vbNullString, 0)) 15 End If 16 Else 17 Ls_Ret = vbNullString 18 End If 19 20 'Null値が存在する場合は必ず IsNullを使用する。 21 If Not IsNull(Ho_Fields(Hn_Idx).Value) Then 22 If Pb_ADO_ConnectFlg = True Then '/* ADO 23 Dim Ls_Value As String 24 Ls_Value = CStr(Ho_Fields(Hn_Idx).Value) 25 If Len(Trim(Ls_Value)) = 0 Then 26 Ls_Value = vbNullString 27 End If 28 Ls_Ret = Ls_Value 29 Else 30 Ls_Ret = CStr(Ho_Fields(Hn_Idx).Value) 31 End If 32 Else 33 If Pb_ADO_ConnectFlg = True Then '/* ADO 34 Ls_Ret = IIf(Ho_Fields(Hn_Idx).Type = 202, vbNullString, 0) 35 36 Else '/* Oracle Object for OLE 37 Ls_Ret = IIf(Ho_Fields(Hn_Idx).OraIDataType = 1, vbNullString, 0) 38 End If 39 End If 40 41 CORA20_FieldsValue = Ls_Ret 42 43 Exit Function 44 45CORA20_FieldsValue_ERR: 46 If Err.Number <> 0 And Err.Number <> 3021 Then 47 Call CORA20_ORAErr(Po_DataBase, "CORA20_FieldsValue") 48 Call CMSG00_ORAErr("CORA20_FieldsValue") 49 End If 50 51 CORA20_FieldsValue = Ls_Ret 52End Function

接続コードは以下のようになっています。

VB6

1Function CORA00_DB_Connect(ByRef Ho_DBObject As Object _ 2 , ByVal Hs_DatabaseName As String _ 3 , ByVal Hs_UserName As String _ 4 , ByVal Hs_Password As String) As Integer 5 6 Dim Ls_Connect As String 7 8 'VBのエラーとして判断するためエラー処理を行う。 9 On Error GoTo CORA00_DB_Connect_Err 10 11 CORA00_DB_Connect = Ret_NG 12 13 '/* 接続形態チェック 14 Pb_ADO_ConnectFlg = CheckAdoConnection 15 16 If Pb_ADO_ConnectFlg = True Then '/* ADO 17 Set Ho_DBObject = New clsADO 18 19 'ここを通っている 20 Ls_Connect = "Provider=OraOLEDB.Oracle; Data Source=" & Hs_DatabaseName & ";" _ 21 & "User Id=" & Hs_UserName & ";" _ 22 & "Password=" & Hs_Password & ";" 23 '/* クライアントカーソル使用 24 Ho_DBObject.CursorLocation = adUseClient 25 26 '/* 接続文字列セット 27 Ho_DBObject.ConnectionString = Ls_Connect 28 29 '/* DB接続 30 Ho_DBObject.ConnectDatabase Ho_DBObject.ConnectionString, Hs_UserName, Hs_Password 31 Else '/* Oracle Object for OLE 32 33 Ls_Connect = Trim$(Hs_UserName) & "/" & Hs_Password 34 35 'Lo_OraSessionオブジェクトは、アプリケーション内で使われる 36 'OraDatabaseおよびOraConnection、OraDynasetオブジェクト 37 'の集合を管理します。 38 'OralceをObjectとして認識します。 39 Dim Lo_OraSession As Object 40 Set Lo_OraSession = CreateObject("OracleInProcServer.XOraSession") 41 42 Set Ho_DBObject = Lo_OraSession.OpenDatabase(Hs_DatabaseName$, Ls_Connect$, ORADB_DEFAULT) 43 End If 44 45 CORA00_DB_Connect = Ret_OK 46 47 Exit Function 48 49CORA00_DB_Connect_Err: 50 51 Call CORA20_ORAErr(Po_DataBase, "ORA000_DB_Connect") 52 Call CMSG00_ORAErr("ORA000_DB_Connect") 53 54End Function

追記3

YAmaGNZさんの案を採用させていただきました。
データを1~10000行、10001~20000行…という感じに分割して出力する方法に変更しようと思います。
早速ROWNUMを試しましたが、以下のようになってしまいます。
イメージ説明

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

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

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

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

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

YAmaGNZ

2019/12/27 09:44 編集

どこでどういうエラーが発生しているのでしょうか?
KOZ6.0

2019/12/27 16:16

・DbCreateDynaset って oo4o ではないですか? ・ADO 使っているならプロバイダーは何を使っていますか? ・ODBC プロバイダーの場合、ODBC は何を使っていますか? ・CORA20_FieldsValue は何をやっているんでしょう?
退会済みユーザー

退会済みユーザー

2019/12/29 01:38

エラーは上記プログラムのファイル出力部分で起きてるみたいです 実行する環境はvbが入っていないので、デバックできず詳しい場所は分かりません
退会済みユーザー

退会済みユーザー

2019/12/29 01:41

自分はADOも詳しくわかっていません 上からいただいたプログラムと、ADO.datファイルを同じ場所に置いたら動いたので、ADOと思っていました fieldvalueでは、オブジェクト内の値を取ってきてるだけです
YAmaGNZ

2019/12/29 01:49

VBが入ってなくてデバッグできないのであれば、デバッグできるようなログを出力するなりしましょう。
KOZ6.0

2020/01/06 13:28

・どの行でエラーが発生しているか特定できたのでしょうか? ・oo4o か ADO かは接続のコードを見ればわかるはずですので記載してください。 ・端折っているコード(CORA20_FieldsValue) も記載してください。
guest

回答3

0

'ここでエラーが発生
objData.movenext

ということでいいですか?

接続は ADO でプロバイダは Oracle OLEDB を使っているようですね。

問題になりそうなのは、

Ho_DBObject.CursorLocation = adUseClient

の部分で、おそらくクライアントカーソルを使用しています。

クライアントカーソルを使用すると、キャッシュを作るので、メモリ不足になっていると予想されます。

サーバーカーソルを使ってみてください。
サーバーカーソルを使うには

Ho_DBObject.CursorLocation = adUseServer

とします。

DbCreateDynaset は oo4o でなく自前の Function なので、その中で CursorLocation を指定している箇所があれば、同様にしてください。

追記

フォームを使用して進捗表示等を行っている場合は、フォームの Enabled プロパティを False にして操作不能にした上で、ループ中に適宜 DoEvents をいれてください。
でないと固まります。

VB

1 count = count + 1 2 If (count Mod 100) = 0 Then 3 DoEvents 4 End If

こんな感じです。

追記2

こうなっていますか?
イメージ説明

追記3

サンプルです。(400 万件、460 MB の CSV 出力に成功)

VB

1Sub OutputCSV(ByVal con As ADODB.Connection, ByVal strSchema As String, ByVal strTable As String, ByVal strOutput As String) 2 Dim strFileName As String 3 Dim FSO As Scripting.FileSystemObject 4 Dim TS As Scripting.TextStream 5 6 Dim strSQL As String 7 Dim objData As ADODB.Recordset 8 Dim objDataFld As ADODB.Field 9 10 Dim count As Long 11 Dim strOut As String 12 13 Set FSO = New Scripting.FileSystemObject 14 strFileName = FSO.BuildPath(strOutput, strTable & "_" & Format(Now, "yyyyMMdd") & ".csv") 15 Set TS = FSO.OpenTextFile(strFileName, ForWriting, True) 16 17 strSQL = "SELECT * FROM " & strSchema & "." & strTable 18 Set objData = con.Execute(strSQL) 19 20 Do Until objData.EOF 21 strOut = "" 22 For Each objDataFld In objData.Fields 23 strOut = strOut & "," & objDataFld.value 24 Next 25 TS.WriteLine Mid(strOut, 2) 26 count = count + 1 27 If (count Mod 100) = 0 Then 28 DoEvents 29 End If 30 objData.MoveNext 31 Loop 32 objData.Close 33 TS.Close 34End Sub

使い方

VB

1Dim con As ADODB.Connection 2Set con = New ADODB.Connection 3con.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=(TNS名);User Id=(ユーザ名);Password=(パスワード);" 4con.CursorLocation = adUseServer 5con.Open 6 7OutputCSV con, スキーマ名1, テーブル名1, 出力フォルダ名1 8OutputCSV con, スキーマ名2, テーブル名2, 出力フォルダ名2

投稿2020/01/07 02:38

編集2020/01/07 08:58
KOZ6.0

総合スコア2626

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

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

退会済みユーザー

退会済みユーザー

2020/01/07 07:32

試してみましたが、やはりメモリ不足で止まりました。
KOZ6.0

2020/01/07 07:38

うーん、DbCreateDynaset ってどうなっているのでしょうか?
KOZ6.0

2020/01/07 07:40

あと、objData をウオッチしてみて CursorLocation がどうなっているかも確認してください。
guest

0

ベストアンサー

SELECTで取得している件数が多すぎるのではないでしょうか?
ROWNUMを利用したページング処理を行ってみてはどうでしょう

SQL

1SELECT * FROM 2 ( 3 SELECT a.*, ROWNUM num FROM (SELECT * FROM テーブル名) a 4 ) b 5WHERE b.num BETWEEN 開始位置 AND 終了位置

詳しくは「Oracle ページング処理」などで検索してみてください。

ただ、データベース移行なのであれば1回実行すればいいのでしょうから、Orlofskyさんの仰るようにツール等で出力したほうがいいとは思います。

投稿2020/01/07 01:55

YAmaGNZ

総合スコア10258

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

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

退会済みユーザー

退会済みユーザー

2020/01/07 07:31

データベース移行をこれから何件も行っていくので、専用のプログラムを作る事になりました。 30日間だけしか使えるものでなく、いつまでも使えるものを求めています。 >>RUWNUM この方法を試してみましたが、同じ行が何度も出てきたので上手くいきませんでした。 ROW_NUMBER()も試してみましたが、同様に重複行が発生して上手くいきませんでした。
退会済みユーザー

退会済みユーザー

2020/01/07 08:03

SQLDeveloperで最初試したのですが、出力が1日経っても終わらなかったので選考外としました。
退会済みユーザー

退会済みユーザー

2020/01/10 04:58

すみません。プログラムに間違いがありました。 ROWNUMの方式でうまく取得することができました。 ありがとうございます。
guest

0

今回は半分うまくいっているので、言語や手法を変えずにできる方法を探しています。

頑張ってください。

上記前提を無視できるなら、

30日間だけ無料で使える有償ソフトを使う。Oracle テーブルデータを出力する5つの方法 INSERT文に落とすのが手っ取り早そう。

sqlplusでcvsに落とす

投稿2019/12/27 09:33

Orlofsky

総合スコア16415

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問