Преобразуйте файл, полный INSERT INTO xxx VALUES, во что-то, что массовая вставка может проанализировать

Это продолжение моего первого вопроса «Перенос экспорта «SQL» в T-SQL».

Я работаю со сторонней программой, которую не могу контролировать и не могу изменить. Эта программа экспортирует свою внутреннюю базу данных в набор из .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,3 ГБ данных, и я экспортирую их фрагментами по 1 МБ (1407 файлов в 26 таблицах, во всех таблицах, кроме 5, есть только один файл, в самой большой таблице 207 файлов).

Прямо сейчас у меня есть простая программа на C#, которая считывает каждый файл в оперативную память, а затем вызывает ExecuteNonQuery. Проблема в том, что я усредняю ​​60 секунд на файл, что означает, что для всего экспорта потребуется около 23 часов.

Я предполагаю, что если бы я каким-то образом мог отформатировать файлы для загрузки с помощью BULK INSERT вместо INSERT INTO, это могло бы работать намного быстрее. Есть ли какой-нибудь простой способ сделать это или мне нужно написать что-то вроде «Найти и заменить» и держать пальцы скрещенными, чтобы он не вышел из строя в каком-то крайнем случае и не взорвал мои данные.

Любые другие предложения о том, как ускорить вставку, также будут оценены.


ОБНОВЛЕНИЕ:

В итоге я выбрал анализ и метод SqlBulkCopy. Пошло от 1 файла/мин. до 1 файла/сек.


person Scott Chamberlain    schedule 03.04.2012    source источник
comment
Убедитесь, что транзакции используются — я предполагаю, что на файл приходится только один INSERT INTO, но… то есть убедитесь, что проблема вызвана тем, что сначала не используется TDS. Может быть, проще всего сначала взять данные и преобразовать их в CSV, поскольку большинство инструментов (включая массовые данные/слияние) понимают CSV. Также убедитесь, что выбранный кластер не является глупым и не перегружает ввод-вывод при вставках.   -  person    schedule 04.04.2012
comment
@pst есть более одной вставки в файл, INSERT INTO на 1000 строк, как если вы попытаетесь вставить больше, чем это, вы получите ошибку The number of row value expressions in the INSERT statement exceeds the maximum allowed number of 1000 row values.. Мой конкретный вопрос: Есть ли простой способ конвертировать в CSV или мне нужно написать что-то вроде «Найти и заменить» и держать пальцы скрещенными, чтобы он не дал сбой в каком-то крайнем случае и не взорвал мои данные..   -  person Scott Chamberlain    schedule 04.04.2012
comment
@pst, можете ли вы уточнить, как транзакции помогут ускорить его? должен ли я выполнить одну транзакцию для каждого файла или иметь одну открытую транзакцию, а затем зафиксировать ее, когда все файлы будут проанализированы? Кроме того, как я могу проверить IO Thrashing?   -  person Scott Chamberlain    schedule 04.04.2012
comment
Просто убедитесь, что кластер не нужно постоянно обновлять (например, резервные ключи обычно увеличиваются, а не случайным образом). Я бы уже написал конвертер в CSV. SQL — относительно простой синтаксис. Основные варианты значений: это число (начинается с цифры и может быть шестнадцатеричным), нуль (в любом регистре) или строка (начинается с ' и заканчивается знаком ' not, за которым следует другим '). Написание должно занять около 10 минут.   -  person    schedule 04.04.2012
comment
Что касается транзакций, то они не кажутся проблемой: они были бы, если бы после каждой отдельной вставки (но при пакетах в 1000 это сведено к минимуму). Хотя, возможно, хотелось бы, чтобы транзакция не стала слишком большой, но я не уверен, каковы окончательные соображения относительно размеров транзакций, так как мои большие вставки составляют всего около 50 000 записей. вовремя.   -  person    schedule 04.04.2012


Ответы (2)


Что ж, вот мое «решение», помогающее преобразовать данные в 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 -- хотя, может быть, можно передать все "в виде строки" в базу данных? -- а затем используйте информацию в столбцах для помощи в процессе извлечения (возможно, с помощью преобразователи типов). Для захватов: n — это null, w — это слово (например, число), s — это строка.

Удачного кодирования.

person Community    schedule 03.04.2012
comment
Спасибо, этот фрагмент направляет меня на правильный путь. На самом деле я генерирую целевые таблицы данных в SQL из сопровождающего файла xml, поэтому создание таблиц данных в C # также не будет проблемой. - person Scott Chamberlain; 04.04.2012
comment
@ScottChamberlain Это метод расширения, добавленный LINQPad для отображения результата. (Это должно выполняться как контекст операторов C# в LINQPad). Я добавил ссылку в основной ответ. - person ; 04.04.2012

Очевидно, ваши данные всегда заключены в скобки и начинаются с левой скобки. Вы можете использовать это правило для split(RemoveEmptyEntries) каждого из этих строк и загрузить его в 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);
    }
}
person Tim Schmelter    schedule 03.04.2012
comment
Как лучше всего порекомендовать получить данные в объект DataTable. Я пытался сделать это раньше, но у меня возникли проблемы с определением правильного пути перехода от строки текста к DataRow. - person Scott Chamberlain; 04.04.2012
comment
@ScottChamberlain: отредактировал мой ответ. Только что увидел, что вы можете просто получить имя таблицы из имени файла, чтобы пропустить эту часть. Но самое сложное — создать DataRow из String[]. Возможно, вам нужны какие-то вспомогательные методы для преобразования, или я пропустил простой способ здесь. - person Tim Schmelter; 04.04.2012