CSV ファイルを マルチテーブル・インサート を使ってインポートする
大量データをDBに取り込む時、Oracleではマルチテーブル・インサートという機能があります。
CSVファイルを1レコードずつ読み込んでDBにINSERT、とかよくありますよね。
現行システムがここで時間かかっていたので、対応したいと思います。
※PL/SQL or SQL*Loader の方が早いけど、今回は可能な限り修正コストを抑えたいので、コード修正だけで対応できるこの方法で。
構文はこんな感じ。
insert all
1つのSQL文で複数の表にINSERTする絶品テクニック(1/2) − @IT
into sample_table (id, name, value) values (1, 'TKISHIMO', 0)
into sample_table2 (id, name, value) values (2, 'MHAYASHI', 0)
select * from dual;
では、何件ずつ突っ込めば一番早いんだ?検証してみる。
using Oracle.DataAccess.Client; using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; namespace MultiInsert { class Program { static void Main(string[] args) { var connStr = new OracleConnectionStringBuilder(); connStr.UserID = "UID"; connStr.Password = "PWD"; connStr.DataSource = "SID"; using (var conn = new OracleConnection(connStr.ToString())) { try { conn.Open(); //create Execute(String.Format("create table Test({0})", GetNames(true)), conn); var insert = string.Format("insert into Test({0}) values({1})", GetNames(false), GetValue()); var sqls = new List<string>(); for (int i = 0; i < 50000; i++) sqls.Add(insert); var sw = new Stopwatch(); sw.Start(); foreach (var sql in sqls) Execute(sql, conn); sw.Stop(); Console.WriteLine(" 1件ずつinsert : {0}", sw.Elapsed); sw.Reset(); sw.Start(); MultiExecute(sqls, conn, 10); sw.Stop(); Console.WriteLine(" 10件ずつinsert : {0}", sw.Elapsed); sw.Reset(); sw.Start(); MultiExecute(sqls, conn, 100); sw.Stop(); Console.WriteLine(" 100件ずつinsert : {0}", sw.Elapsed); sw.Reset(); sw.Start(); MultiExecute(sqls, conn, 500); sw.Stop(); Console.WriteLine(" 500件ずつinsert : {0}", sw.Elapsed); //delete Execute("drop table Test", conn); Console.WriteLine("Press any key..."); Console.ReadLine(); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } private static void MultiExecute(List<string> sqls, OracleConnection conn, int p) { var sb = new StringBuilder(); var array = new string[p]; for (int i = 0; i < sqls.Count; i += p) { sqls.CopyTo(i, array, 0, p); sb.AppendLine("insert all"); foreach (var item in array) sb.AppendLine(item.Replace("insert ", "")); sb.AppendLine("select * from dual"); Execute(sb.ToString(), conn); sb.Clear(); } } static string GetNames(bool isCreate) { var list = new List<String>(); var type = isCreate ? "VARCHAR2(50)" : ""; for (int i = 0; i < 100; i++) list.Add(String.Format("V_{0:000} {1}", i, type)); return String.Join(",", list); } static string GetValue() { var list = new List<String>(); for (int i = 0; i < 100; i++) list.Add("'test string'"); return String.Join(",", list); } static void Execute(string sql, OracleConnection conn) { using (var cmd = new OracleCommand(sql, conn)) { cmd.ExecuteNonQuery(); } } } }
結果はこんな感じ。
1件ずつinsert : 00:01:15.7948220 10件ずつinsert : 00:00:13.7392012 100件ずつinsert : 00:00:06.7394553 500件ずつinsert : 00:01:04.8971647 Press any key...
多すぎても良くないみたい。
だが、1件ずつループでINSERTするよりも断然早い。