2012-04-03 32 views
1

这是我的第一个问题“Porting “SQL” export to T-SQL”的后续。将一个完整的“INSERT INTO xxx VALUES”文件转换为大容量插入文件可解析的文件

我正在与第三方程序,我无法控制,我不能改变。这一计划将它导出的内部数据库中,以一组每.sql一个用的格式:

INSERT INTO [ExampleDB] ([IntField] , [VarcharField], [BinaryField]) 
VALUES 
(1 , 'Some Text' , 0x123456), 
(2 , 'B' , NULL), 
--(SNIP, it does this for 1000 records) 
(999, 'E' , null); 
(1000 , 'F' , null); 

INSERT INTO [ExampleDB] ([IntField] , [VarcharField] , BinaryField) 
VALUES 
(1001 , 'asdg', null), 
(1002 , 'asdf' , 0xdeadbeef), 
(1003 , 'dfghdfhg' , null), 
(1004 , 'sfdhsdhdshd' , null), 
--(SNIP 1000 more lines) 

这种模式继续下去,直到.sql文件已经达到了文件大小的出口过程中设置,导出文件被分组EXPORT_PATH\%Table_Name%\Export#.sql其中#是一个从1开始的计数器。

目前我有大约1.3GB的数据,我有1MB块导出(跨26个表1407个文件,除5个表外只有一个文件,最大的表有207个文件)。

现在我只是有一个简单的C#程序,读取每个文件到RAM然后调用ExecuteNonQuery。问题是我平均60秒/文件,这意味着它将花费大约23小时来完成整个出口。

我假设如果我一些如何可以格式化文件加载与BULK INSERT而不是INSERT INTO它可以更快。是否有任何简单的方法来做到这一点,或者我必须写一些种类的查找&替换并保持我的手指交叉,它不会在某些角落案件失败并炸毁我的数据。

关于如何加快插入的任何其他建议也将不胜感激。


UPDATE:

我结束了与parse and do a SqlBulkCopy method去。它从1个文件/分钟。到1个文件/秒。

+0

确保交易正在使用 - 我假设每个文件只有一个“INSERT INTO”,但是......也就是说,确保问题是由于首先不使用TDS引起的。由于大多数工具(包括批量数据/合并)都了解CSV,因此将数据转化为CSV可能最容易。同时确保选择的群集不会愚蠢,并在插入时抖动IO。 – 2012-04-03 22:26:15

+0

@pst在每个文件中有多个插入,每1000行有一个'INSERT INTO',就好像你尝试插入的不止一个那样你会得到一个错误'INSERT语句中的行值表达式的数量超过最大允许的1000个行值的数量.'。我提取的问题是*是否有任何简单的方法来转换为CSV或我必须写一些种类的查找和替换,并保持我的手指交叉,它不会在某些角落的情况下失败,炸毁我的数据。 – 2012-04-03 22:31:22

+0

@pst你能否详细说明交易如何帮助加速交易?我应该为每个文件做一个事务还是有一个打开的事务,然后在解析所有文件时提交它?另外我将如何检查IO Thrashing? – 2012-04-03 22:34:37

回答

1

嗯,这里是我的 “解决方案”,帮助把数据转换成一个DataTable或以其他方式(在LINQPad运行):

var i = "(null, 1 , 'Some''\n Text' , 0x123.456)"; 
var pat = @",?\s*(?:(?<n>null)|(?<w>[\w.]+)|'(?<s>.*)'(?!'))"; 
Regex.Matches(i, pat, 
     RegexOptions.IgnoreCase | RegexOptions.Singleline).Dump(); 

比赛应该每个值组运行一次​​(例如(a,b,etc))。解析结果(例如转换)留给调用者,我还没有测试[很多]。我会建议创建正确类型的DataTable 第一个 - 尽管可能将所有内容“作为字符串”传递给数据库? - 然后使用列中的信息来帮助提取过程(可能使用type converters)。对于捕获:n为空,w是字(例如数字),s是字符串。

快乐编码。

+0

谢谢,这段代码让我走上正轨。我实际上是从附带的xml文件生成SQL中的目标数据表,因此在C#中创建数据表也不会有问题。 – 2012-04-03 23:26:06

+0

什么是'.Dump()'? – 2012-04-03 23:31:41

+0

@ScottChamberlain它是LINQPad添加的扩展方法,用于显示结果。 (这应该作为LINQPad中的“C#语句”上下文运行)。我已经添加了主要答案的链接。 – 2012-04-04 00:32:03

1

显然你的数据总是用圆括号括起来并以左括号开头。您可能希望将此规则用于splitRemoveEmptyEntries)中的每一行并将其加载到DataTable中。然后,您可以使用SqlBulkCopy一次全部复制到数据库中。

这种方法不一定是故障安全的,但它肯定会更快。

编辑:这里是你如何能得到的架构为每个表的方式:

private static DataTable extractSchemaTable(IEnumerable<String> lines) 
{ 
    DataTable schema = null; 
    var insertLine = lines.SkipWhile(l => !l.StartsWith("INSERT INTO [")).Take(1).First(); 
    var startIndex = insertLine.IndexOf("INSERT INTO [") + "INSERT INTO [".Length; 
    var endIndex = insertLine.IndexOf("]", startIndex); 
    var tableName = insertLine.Substring(startIndex, endIndex - startIndex); 
    using (var con = new SqlConnection("CONNECTION")) 
    { 
     using (var schemaCommand = new SqlCommand("SELECT * FROM " tableName, con)) 
     { 
      con.Open(); 
      using (var reader = schemaCommand.ExecuteReader(CommandBehavior.SchemaOnly)) 
      { 
       schema = reader.GetSchemaTable(); 
      } 
     } 
    } 
    return schema; 
} 

然后你只需要遍历文件中的每一行,检查它是否与(开始,拆分线通过Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)。然后你可以将结果数组添加到创建的模式表中。

事情是这样的:

var allLines = System.IO.File.ReadAllLines(path); 
DataTable result = extractSchemaTable(allLines); 
for (int i = 0; i < allLines.Length; i++) 
{ 
    String line = allLines[i]; 
    if (line.StartsWith("(")) 
    { 
     String data = line.Substring(1, line.Length - (line.Length - line.LastIndexOf(")")) - 1); 
     var fields = data.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 
     // you might need to parse it to correct DataColumn.DataType 
     result.Rows.Add(fields); 
    } 
} 
+0

建议将数据存入DataTable对象的最佳方法是什么?我试图尽早做到这一点,但是我很难找出从文本行到DataRow的正确方法。 – 2012-04-03 23:05:44

+0

@ScottChamberlain:编辑我的答案。刚刚看到,您可以简单地从文件名中获取表名,以便您可以跳过该部分。但棘手的部分是从String []创建DataRow。也许你需要一些辅助方法进行转换,或者我错过了一个简单的方法。 – 2012-04-04 00:14:14