(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Регулярные выражения облегчают поиск и выборку данных

Источник: MSDN Magazine
Дэвид Банистер

Оглавление

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

На самом деле гораздо более эффективное и элегантное решение предлагают регулярные выражения. Легко убедиться в том, как они полезны при сравнении текста для идентификации записей, хотя при этом они способны на гораздо большее. Я покажу, как решать разнообразные задачи, для которых средства SQL Server 2000 считались неудобными или неприменимыми, а теперь с появлением SQL Server 2005 стали возможными благодаря поддержке хостинга CLR.

Регулярные выражения для SQL - не новость. Компания Oracle встроила регулярные выражения еще в базу данных 10g, и многие решения для баз данных с открытым исходным кодом также используют различные библиотеки регулярных выражений. По сути, регулярные выражения могли бы применяться и в более ранних версиях SQL Server, но механизм их обработки был неэффективен.

С помощью хранимой процедуры sp_OACreate можно задействовать любой объект OLE Automation, реализующий регулярные выражения, но сначала нужно создать COM-объект, затем сделать минимум один вызов IDispatch и уничтожить этот объект. Для большинства задач это совершенно неэффективно и было причиной многих проблем с производительностью. Единственной альтернативой было создание расширенной хранимой процедуры. Однако сегодня существует SQLCLR, пользовательская CLR-функция, позволяющая создавать эффективный и менее подверженный ошибкам набор функций на основе Microsoft .NET Framework.

Пользовательские CLR-функции

Такие функции являются попросту статическими методами (общими функциями в Visual Basic), определенными внутри .NET-сборки. Чтобы использовать объекты SQLCLR, необходимо зарегистрировать сборку в SQL Server новым оператором CREATE ASSEMBLY, а затем создавать каждый объект, указывая на его реализацию в сборке. Для поддержки пользовательских CLR-функций усовершенствован оператор CREATE FUNCTION. При использовании SQL Server Project среда разработки Visual Studio 2005 берет на себя весь процесс регистрации. Этот тип проекта отличается от большинства проектов Visual Studio, поскольку при попытке отладки (или запуска без отладки) проект компилируется заново, и получаемая сборка, как и все определенные в ней объекты SQLCLR, развертываются и регистрируются в SQL Server. Затем интегрированная среда разработки запускает тестовый сценарий, предназначенный для данного проекта. Для упрощения отладки в SQL-сценарий и в .NET-код можно ставить точки прерывания.

Добавление функции аналогично добавлению нового класса к проекту любого другого типа. Вы просто включаете в проект новый элемент и при появлении запроса выбираете User-Defined Function (UDF). Новый метод добавляется к частичному классу, в котором содержатся все пользовательские функции. К новому методу также применяется атрибут SqlFunction. Он используется средой Visual Studio для создания SQL-операторов, необходимых при регистрации функции. Поля IsDeterministic, IsPrecise, DataAccess и SystemDataAccess атрибута SqlFunction используются SQL Server.

Поиск по шаблонам

Определить, соответствует ли строка шаблону, - простейшее применение регулярных выражений и, как видно в листинге 1, реализуется очень легко.

Листинг 1. Поиск строки по шаблону

public static partial class UserDefinedFunctions
{
    public static readonly RegexOptions Options =
        RegexOptions.IgnorePatternWhitespace /
        RegexOptions.Singleline;

    [SqlFunction]
    public static SqlBoolean RegexMatch(
        SqlChars input, SqlString pattern)
    {
        Regex regex = new Regex( pattern.Value, Options );
        return regex.IsMatch( new string( input.Value ) );
    }
}

Сначала я использую поле Options для сохранения параметров регулярного выражения применительно к функциям. В данном случае я выбрал RegexOptions.SingleLine и RegexOptions.IgnorePatternWhitespace. Первый из них устанавливает однострочный режим, а второй исключает пробелы, не помеченные Escape-символом, из регулярного выражения и разрешает отмечать комментарии знаком #. Другой параметр, который вам, возможно, понадобится после долгих раздумий, - RegexOption.Compiled. Этот параметр при интенсивном использовании регулярных выражений, пока их количество не слишком велико, дает заметный прирост производительности. Компилировать следует выражения, применяемые многократно. Но не используйте параметр Compiled с регулярными выражениями, к которым вы обращаетесь лишь изредка, поскольку это приведет к издержкам и использованию лишней памяти. Вероятно, вы захотите дополнить мою универсальную функцию RegexMatch еще одним параметром, определяющим, надо ли компилировать данное выражение; таким образом, в каждом конкретном случае можно было бы решать, насколько оправданы издержки ради увеличения производительности.

После задания параметров RegexOptions я определяю функцию RegexMatch, используя тип данных SqlChars вместо SqlString. Тип данных SqlString преобразуется в тип nvarchar(4,000), тогда как SqlChars преобразуется в тип nvarchar(max). Новая возможность задания максимального размера допускает работу со строками, длина которых превышает предел для SQL Server 2000, равный 8000 байт. Для большей гибкости nvarchar(max) в настоящей статье используется в как можно более обобщенном виде. Однако, если соответствующие строки содержат менее 4000 символов, эффективность при использовании nvarchar(4,000) может быть гораздо выше. Поэтому сначала оцените конкретные потребности и пишите код с их учетом.

Остальная часть кода данного метода довольно проста. Создается экземпляр Regex с определенными параметрами и заданным шаблоном, после чего для определения, соответствует ли шаблону введенное значение, применяется метод IsMatch. Теперь необходимо добавить к тестовому сценарию простой запрос:

select dbo.RegexMatch( N'123-45-6789', N'^\d{3}-\d{2}-\d{4}$' )

Шаблоном в этом выражении является простая проверка на номер в системе страхования США. Установите точку прерывания в новом запросе и, запустив отладчик, начинайте пошагово выполнять функцию. Эта функция позволяет делать массу различных проверок, но здесь мы рассмотрим лишь некоторые особенности, которые многие не принимают во внимание. Например, очень важно придерживаться соглашения об именовании, действующего в пределах базы данных, а написание запроса для проверки того, что все хранимые процедуры удовлетворяют нормативам данной организации, - дело непростое. Функция RegexMatch значительно упрощает эту задачу. Например, следующий тест запроса выполняет эту задачу:

select ROUTINE_NAME
from INFORMATION_SCHEMA.ROUTINES
where ROUTINE_TYPE = N'PROCEDURE'
  and dbo.RegexMatch( ROUTINE_NAME,
    N'^usp_(Insert/Update/Delete/Select)([A-Z][a-z]+)+$' ) = 0

Этот запрос проверяет, каждая ли хранимая процедура имеет префикс «usp_», за которым следует «Insert», «Update», «Delete» или «Select». Он также проверяет, начинается ли каждое слово в названии объекта с заглавной буквы. Сравните те строки с этой крайне упрощенной версией, использующей только встроенные функции:

select ROUTINE_NAME
from INFORMATION_SCHEMA.ROUTINES
where ROUTINE_TYPE = N'PROCEDURE'
    and ( LEN( ROUTINE_NAME ) < 11
    or LEFT( ROUTINE_NAME, 4 ) <> N'usp_'
    or SUBSTRING( ROUTINE_NAME, 5, 6 ) not in
        ( N'Insert', N'Update', N'Delete', N'Select' ) )

Хотя этот запрос в большей мере является кодом, в нем фактически не хватает нескольких функций, присутствующих в версии с регулярными выражениями. Во-первых, он нечувствителен к регистру букв, и поиск по шаблону может дать неверные результаты. Во-вторых, он не проверяет фактическое имя сущности, содержащееся в имени процедуры. В-третьих, все четыре строки, тестируемые в запросе, имеют длину в шесть символов, что позволило упростить код извлечением отдельной подстроки длиной в шесть символов и использовать ее во всех допустимых операциях сравнения. Это не проблема в данном примере, поскольку все имена команд состоят из шести символов, но представьте себе стандарт с более сложными командами вроде Get, List или Find. Этими командами легко манипулирует функция RegexMatch, поскольку они являются лишь дополнительными вариантами в списке.

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

CREATE TABLE [Account]
(
    [AccountNumber] nvarchar(20) CHECK (dbo.RegexMatch(
        [AccountNumber], '^[A-Z]{3,5}\d{5}-\d{3}$' ) = 1),
    [PhoneNumber] nchar(13) CHECK (dbo.RegexMatch(
        [PhoneNumber], '^\(\d{3}\)\d{3}-\d{4}$' ) = 1),
    [ZipCode] nvarchar(10) CHECK (dbo.RegexMatch(
        [ZipCode], '^\d{5}(\-\d{4})?$' ) = 1)
)

Содержимое столбца AccountNumber проверяется на соответствие произвольному соглашению о том, что оно должно начинаться с трех-пяти букв, за которыми следуют пять цифр, затем тире и еще три цифры. Как телефонные номера, так и почтовые индексы проверяются на соответствие стандартам США для форматов телефонных номеров и почтовых индексов. Функция RegexMatch предоставляет массу возможностей для SQL Server, но реализация регулярных выражений в .NET, как будет показано, дает гораздо больше.

Извлечение данных

Для извлечения данных из строки могут использоваться средства группирования регулярных выражений. Разработанная мной функция RegexGroup обеспечивает эту возможность для T-SQL:

[SqlFunction]
public static SqlChars RegexGroup(
    SqlChars input, SqlString pattern, SqlString name )
{
    Regex regex = new Regex( pattern.Value, Options );
    Match match = regex.Match( new string( input.Value ) );
    return match.Success ?
        new SqlChars( match.Groups[name.Value].Value ) :
        SqlChars.Null;
}

Она, как и функция RegexMatch, создает объект Regex. Однако, вместо того чтобы проводить проверку на соответствие, для первого же найденного во входной строке совпадения создается объект Match. Этот объект используется для извлечения указанной группы. Если во входной строке соответствия не обнаружено, функция возвращает null. Функция будет работать и в том случае, если вместо именованных групп вы предпочтете использовать нумерованные. Просто передавайте целое значение функции в SQL-коде, и оно будет неявно приводиться к типу nvarchar, в результате чего будет возвращаться соответствующая группа.

Для извлечения одних фрагментов данных из других можно использовать функцию RegexGroup в списке SELECT. Например, для поля, в котором содержится URL, можно легко провести его синтаксический разбор для получения отдельных частей. Данный запрос использует группирование для нахождения имен всех серверов в поле Url таблицы UrlTable:

select distinct dbo.RegexGroup( [Url],
    N'https?://(?<server>([\w-]+\.)*[\w-]+)', N'server' )
from [UrlTable]

Эта функция применима и в вычисляемых полях. Следующее определение таблицы обеспечивает разделение электронных адресов на почтовые ящики и домены:

CREATE TABLE [Email]
(
    [Address] nvarchar(max),
    [Mailbox] as dbo.RegexGroup( [Address],
        N'(?<mailbox>[^@]*)@', N'mailbox' ),
    [Domain] as dbo.RegexGroup( [Address],
        N'@(?<domain>.*)', N'domain' )

Поле почтового ящика будет возвращать почтовый ящик или имя пользователя этого адреса, а поле домена - домен.

Хранилище шаблонов

Все шаблоны (patterns), используемые этими функциями, - просто строки, и любая из них может храниться в таблице базы данных. Большинство баз данных с информацией международного характера имеет таблицу, представляющую страны. Добавив к такой таблице несколько дополнительных полей, можно хранить в ней шаблоны проверки на конкретную страну. Это позволяет ввести ограничение для адресной строки, автоматически изменяемое в зависимости от страны.

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

Совпадения

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

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

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

Листинг 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.

Листинг 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 )

Приведенный пример довольно прямолинеен. Он демонстрирует некоторые возможности применения данной функции, а если вы удалите ключевое слово distinct, функция будет возвращать число слов в строке. На многих веб-сайтах ввод текста ограничивают весьма произвольной длиной. С помощью показанной мной проверки в сочетании с новой нотацией 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 или типов данных с плавающей точкой. Другие способы обработки списка значений потребовали бы применения множества функций или усложнения хранимых процедур. Эта функция применима и для списков, где в качестве разделителя запятая не используется. Она позволяет обрабатывать списки, разделяемые пробелами, точками с запятой, табуляторами, символами «возврат каретки» или любыми другими идентифицируемыми символами.

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

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

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

Листинг 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 за исключением того, что она возвращает дополнительное поле данных, содержащее имя группы внутри совпадения. Теперь с помощью этой функции можно находить в пределах строки различные совпадения и извлекать из каждого совпадения нужные фрагменты информации.

Листинг 5. Табличная 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. Таким же образом может быть обработан любой текстовый файл с разделителями. Внеся в шаблон некоторые несущественные изменения, вы могли бы добавить управляющие последовательности для поддержки запятых в строках.

Листинг 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 для возврата данных в специальном группирующем формате. Можно было бы вставлять данные напрямую в таблицу, применяя гораздо более простую и быструю табличную функцию, просто считывающую каждую строку, метод String.Split (ориентируясь на запятые) и возвращать каждую строку.

Заключение

Хотя функции поиска совпадений очень мощные, они недостаточно полны. Есть масса вариантов поиска совпадений. Если база данных нечувствительна к регистру букв, могут потребоваться функции, которые обеспечивают нахождение совпадений и без учета регистра. Для уменьшения набора результатов может понадобиться критерий отбора. Многострочный режим позволяет для некоторых задач создавать более точные шаблоны поиска. Можно даже создать пользовательский тип для передачи соответствующих «настроечных» значений каждой из функций, чтобы при вызовах они использовали нужный вам набор параметров.

Помните и о том, что при обработке текста могут возникать некоторые проблемы, связанные с локализацией. Например, класс Regex из .NET Framework распознает гораздо больше букв, чем входит в латиницу, поэтому следует быть внимательнее при разработке шаблонов для глобальных баз данных.

И, конечно же, как неоднократно упоминалось в данной статье, поскольку регулярные выражения - чрезвычайно мощный инструмент, следует четко представлять, насколько необходима его мощь для решения стоящих перед вами задач. Некоторые задачи можно решить быстрее, используя более простые средства.

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

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

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 10.05.2007 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Windows Professional 10, Электронный ключ
Microsoft Office 365 Бизнес. Подписка на 1 рабочее место на 1 год
Microsoft 365 Business Standard (corporate)
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
Microsoft 365 Business Basic (corporate)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Работа в Windows и новости компании Microsoft
Windows и Office: новости и советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100