Извлечение данных при совпадениях

Подобно тому, как происходит возвращение совпадений, из каждого совпадения можно извлекать данные. Использовать для этого SQL довольно затруднительно. Обычно этот вид задач выполняется приложениями, а не средствами базы данных, что вызывает определенные проблемы, так как каждое приложение, использующее базу данных, должно выполнять соответствующую обработку данных. При таких сценариях разумным является подход, позволяющий добиться требуемой функциональности с помощью сохраненных процедур.

Как и с реализацией функции RegexMatches, я предпочитаю использовать пользовательский счетный объект, возвращающий групповую информацию. Группировка представляет собой чуть более сложную процедуру, поскольку в пределах каждого совпадения должны выполняться итерации по группам. На рис. 4 класс GroupNode выглядит, практически, как класс MatchNode, за исключением того, что он также включает имя группы, которую представляет. Класс GroupIterator аналогичен классу MatchIterator, но включает в себя дополнительный цикл для возвращения к каждой группе. Теперь, когда у нас есть счетный объект, можно точно так же определить табличную функцию, как это было сделано для функции RegexMatches.

Figure 4 Пользовательский счетный объект для групп

internal class GroupNode

{

private int _index;

public int Index { get { return _index; } }

private string _name;

public string Name { get { return _name; } }

private string _value;

public string Value { get { return _value; } }

public GroupNode(int index, string group, string value)

{

_index = index;

_name = group;

_value = value;

}

}

internal class GroupIterator: IEnumerable

{

private Regex _regex;

private string _input;

public GroupIterator(string input, string pattern)

{

_regex = new Regex(pattern, UserDefinedFunctions.Options);

_input = input;

}

public IEnumerator GetEnumerator()

{

int index = 0;

Match current = null;

string[] names = _regex.GetGroupNames();

do

{

index++;

current = (current == null)?

_regex.Match(_input): current.NextMatch();

if (current.Success)

{

foreach(string name in names)

{

Group group = current.Groups[name];

if (group.Success)

{

yield return new GroupNode(

index, name, group.Value);

}

}

}

}

while(current.Success);

}

}

На рис. 5 функция RegexGroups определяется аналогично функции RegexMatches, за исключением того, что она возвращает дополнительный столбец данных, содержащий имя группы внутри совпадения. Теперь с помощью этой функции мы можем находить в пределах строки различные совпадения и извлекать из каждого совпадения конкретные фрагменты информации.

Figure 5 Табличная функция CLR UDF для групп

[SqlFunction(FillRowMethodName = "FillGroupRow", TableDefinition =

"[Index] int,[Group] nvarchar(max),[Text] nvarchar(max)")]

public static IEnumerable

RegexGroups(SqlChars input, SqlString pattern)

{

return new GroupIterator(new string(input.Value), pattern.Value);

}

[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters")]

public static void FillGroupRow(object data,

out SqlInt32 index, out SqlChars group, out SqlChars text)

{

GroupNode node = (GroupNode)data;

index = new SqlInt32(node.Index);

group = new SqlChars(node.Name.ToCharArray());

text = new SqlChars(node.Value.ToCharArray());

}

При работе с базами данных импортирование данных в различных форматах является распространенной задачей. Импортирование данных в файлы с форматом, где разделителями являются запятые, представляет собой значительно более рутинное дело, чем следовало бы. Большинство разработчиков создают приложения, которые обрабатывают каждую строку, извлекают из нее данные и затем для каждой строки запускают сохраненную процедуру. И хотя этот процесс нормально работает, я бы предложил другое решение. Что если бы мы могли передать весь файл сохраненной процедуре и позволить ей управлять всем процессом? Эта идея обычно рассматривается как слишком сложная для реализации, но благодаря функции RegexGroups можно осуществить этот ввод фактически с помощью единственного запроса. Рассмотрим, к примеру, следующие данные о заказчиках.

2309478,Janet Leverling,J

2039748,Nancy Davolio,N

0798124,Andrew Fuller,M

4027392,Robert King,L

Из каждой строки необходимо получить три различных представляющих интерес фрагмента информации: номер заказчика, состоящий из семи цифр, фамилию заказчика и тип заказчика, определяемый одной буквой. Все три фрагмента информации можно извлечь с помощью следующего выражения:

(?<CustomerNumber>\d{7}),(?<CustomerName>[^,]*),(?<CustomerType>[A-Z])\r?\n

В этой ситуации остается проблема, заключающаяся в том, что результаты, возвращенные функцией RegexGroups, не могут быть прямо использованы. Вместо курсора для итерации результатов можно использовать основные функциональные возможности SQL Server 2005. Поместив все это вместе в сохраненную процедуру, мы получим все, что нам необходимо. Сохраненная процедура, приведенная на рис. 6, может принять целый текстовый файл с разделителем-запятой, содержащий до 2 Гб данных в формате Unicode. Она обрабатывает весь файл целиком и каждую строку этого файла вставляет в таблицу Customer. Таким же образом может быть обработан любой текстовый файл с разделителями. В шаблон могут быть внесены некоторые несущественные изменения и добавлены управляющие последовательности для поддержки запятых в строках.

Figure 6 Обработка файла с запятыми в качестве разделителя

create proc ImportCustomers

(

@file nvarchar(max)

)

as

declare @pattern nvarchar(max)

set @pattern = N'(?<CustomerNumber>\d{7}),

(?<CustomerName>[^,]*),(?<CustomerType>[A-Z])\r?\n'

insert [Customer]

(

[CustomerNumber],

[CustomerName],

[CustomerType]

)

select

f.[CustomerNumber],

f.[CustomerName],

f.[CustomerType]

from dbo.RegExGroups(@file, @pattern) regex

pivot

(

max([Text])

for [Group]

in ([CustomerNumber], [CustomerName], [CustomerType])

) as f

Эта процедура также демонстрирует существование различных путей решения одной и той же задачи, причем не всегда регулярные выражения являются наилучшим выбором. В приведенном примере использование основных функциональных возможностей означает фактический отказ от всей работы, которую выполняла функция RegexGroups для возвращения данных в специальном групповом формате. Можно ввести данные напрямую в таблицу, использующую гораздо более простую и быструю табличную функцию (table-valued function, TVF), просто считывающую каждую строку, применить метод String.Split на запятых и возвратить каждую строку в обработанном виде.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: