Совпадения

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

Табличные функции отчасти похожи на функции, описанные выше, но имеют два существенных отличия. Во-первых, атрибуты, используемые в этом методе, должны полностью декларировать структуру возвращаемой таблицы. Во вторых, существует два таких метода. Первый метод возвращает счетный объект вместо фактического результата работы функции. Второй прогоняется с целью заполнения счетными объектами полей каждой строки. Каждое значение, получаемое от нумератора, должно соответствовать одной строке набора результатов. Интерфейс ICollection в.NET Framework обеспечивает выполнение IEnumerable, что означает возможность возвращения любого набора первым методом. Класс Regex содержит метод Matches, возвращающий объект MatchCollection, который можно использовать. Проблема с объектом MatchCollection состоит в том, что строка должна быть обработана полностью до возвращения результата методом Matches. SQL Server включает в себя оптимизацию, которая зависит от осуществляемой при необходимости обработки, так что вместо возвращения всего набора наперед, лучше использовать написанный самостоятельно нумератор, который возвращает каждое совпадение по требованию. Это решение на самом деле зависит от того, как используется функция, и должно быть надежно проверено до оптимизации нумератора.

На рис. 2 показан код нумератора. Класс MatchNode возвращает отдельное совпадение в строке, отслеживая его положение в наборе возвращенных совпадений. Класс MatchIterator является счетным и управляет обработкой регулярного выражения. Он использует новое выходное ключевое слово для значительно более легкой процедуры создания нумератора, чем это было в предыдущих версиях оболочки. Каждое совпадение, обнаруженное внутри строки, он будет возвращать по требованию.

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

Копировать код

internal class MatchNode

{

private int _index;

public int Index { get{ return _index; } }

private string _value;

public string Value { get { return _value; } }

public MatchNode(int index, string value)

{

_index = index;

_value = value;

}

}

internal class MatchIterator: IEnumerable

{

private Regex _regex;

private string _input;

public MatchIterator(string input, string pattern)

{

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

_input = input;

}

public IEnumerator GetEnumerator()

{

int index = 0;

Match current = null;

do

{

current = (current == null)?

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

if (current.Success)

{

yield return new MatchNode(++index, current.Value);

}

}

while (current.Success);

}

}

Код на рис. 3 описывает табличную функцию CLR UDF. Метод RegexMatches возвращает новый класс MatchIterator. Арибут SqlFunctionAttribute метода RegexMatches также включает в себя несколько дополнительных свойств. Свойство TableDefinition устанавливается для определения таблицы функции. Свойство FillRowMethodName устанавливается для названия метода, который должен вызываться на каждой итерации возвращаемого счетного объекта. В данном случае этот метод называется FillMatchRow.

Figure 3 Табличная функция CLR UDF для совпадений

[SqlFunction(FillRowMethodName = "FillMatchRow",

TableDefinition = "[Index] int,[Text] nvarchar(max)")]

public static IEnumerable RegexMatches(SqlChars input, SqlString pattern)

{

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

}

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

public static void FillMatchRow(object data,

out SqlInt32 index, out SqlChars text)

{

MatchNode node = (MatchNode)data;

index = new SqlInt32(node.Index);

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

}

Для каждой итерации класса MatchIterator класс MatchNode передается методу FillMatchRow в качестве первого аргумента. Остальные параметры метода FillMatchRow должны декларироваться как выходные параметры и должны соответствовать определению таблицы, определенной в первой функции. Функция FillMatchRow попросту использует свойства MatchNode для заполнения полей данными.

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

declare @text nvarchar(max), @pattern nvarchar(max)

select

@text = N'Here are four words.',

@pattern = '\w+'

select count(distinct [Text])

from dbo.RegexMatches(@text, @pattern)

Приведенный пример является достаточно исчерпывающим. Он демонстрирует определенный потенциал использования данной функции, а путем удаления явных ключевых слов он возвращает полное количество слов в строке. Многие веб-узлы ограничивают ввод текста некоторой его произвольной длиной. Вместо этого с помощью данного типа теста в сочетании с новой нотацией nvarchar(max) становится возможным ограничение ввода с помощью подсчета слов. Этот тип запроса может использоваться для различных нужд аналитической обработки, однако функция RegexMatches может быть также использована и для более общих задач. К сожалению, этот тип запроса также представляет собой излишне усердное использование регулярных выражений. Операция разбиения, выполняемая в данном случае с помощью выражения "\w+", могла бы быть легко выполнена с помощью метода String.Split, который работает гораздо быстрее. Регулярные выражения – это мощное средство, но прежде чем их использовать, следует понять, насколько оправдано их применение: в наличии могут быть и более простые инструменты, которые в отдельных случаях можно использовать с большей эффективностью.

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

declare @pattern nvarchar(max), @list nvarchar(max)

select @pattern = N'[^,]+', @list = N'2,4,6'

select d.* from [Data] d

inner join dbo.RegexMatches(@list, @pattern) re

on d.[ID] = re.[Text]

Этот шаблон совпадает с любой группой символов, не содержащей запятые. Для таблицы с именем Data, содержащей столбец ID с целыми значениями, этот запрос будет возвращать каждую идентифицированную в списке запись. Он становится более полезным при рассмотрении особенностей неявного приведения типов в SQL Server. Этот же запрос можно использовать для целочисленных данных, а также для данных типа "дата/время", GUID и с плавающей запятой. В других обеспечивающих большую гибкость методах обработки списка значений требуется применение различных функций и сохраненных процедур. Эта функция применима для списков, не использующих запятую в качестве разделителя. Она также может обрабатывать списки, использующие в качестве разделителя пробел, точку с запятой, клавишу табуляции, возврат каретки или любые другие идентифицируемые символы.


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



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