Звучит так, как будто вы ищете условный UNION:
select id, name, date
from the_table
union all
select 0, '', null
where not exists (select * from the_table);
Вторая часть объединения возвращает постоянные значения, но только если the_table
не содержит строк.
Я обычно просто пытаюсь перенести все свои вызовы Регулярного выражения в их собственной функции с понятным именем и некоторыми основные комментарии. Мне нравится думать о Регулярных выражениях как о языке только для записи, читаемом только тем, который записал это (Если это не действительно просто). Я полностью ожидаю, что кто-то должен был бы, вероятно, полностью переписать выражение, если бы они должны были изменить его намерение, и это должно, вероятно, к лучшему поддержать обучение Регулярного выражения.
Используйте Экспресс, который дает иерархическую, английскую разбивку regex.
Или
Эта подсказка от Darren Neimke:
.NET позволяет образцам регулярного выражения быть созданными со встроенными комментариями через RegExOptions. Параметр компилятора IgnorePatternWhitespace и (? #...), синтаксис встраивается в каждой строке строки образца.
Это позволяет, чтобы комментарии psuedo-code-like были встроены в каждую строку, и имеет следующее влияние на удобочитаемости:
Dim re As New Regex ( _
"(?<= (?# Start a positive lookBEHIND assertion ) " & _
"(#|@) (?# Find a # or a @ symbol ) " & _
") (?# End the lookBEHIND assertion ) " & _
"(?= (?# Start a positive lookAHEAD assertion ) " & _
" \w+ (?# Find at least one word character ) " & _
") (?# End the lookAHEAD assertion ) " & _
"\w+\b (?# Match multiple word characters leading up to a word boundary)", _
RegexOptions.Multiline Or RegexOptions.IgnoreCase Or RegexOptions.IgnoreWhitespace _
)
Вот другой пример.NET (требует RegexOptions.Multiline
и RegexOptions.IgnorePatternWhitespace
опции):
static string validEmail = @"\b # Find a word boundary
(?<Username> # Begin group: Username
[a-zA-Z0-9._%+-]+ # Characters allowed in username, 1 or more
) # End group: Username
@ # The e-mail '@' character
(?<Domainname> # Begin group: Domain name
[a-zA-Z0-9.-]+ # Domain name(s), we include a dot so that
# mail.somewhere is also possible
.[a-zA-Z]{2,4} # The top level domain can only be 4 characters
# So .info works, .telephone doesn't.
) # End group: Domain name
\b # Ending on a word boundary
";
Если Ваш RegEx применим к типичной проблеме, другая опция состоит в том, чтобы зарегистрировать его и отправить RegExLib, где он будет оценен и прокомментирован. Ничто не бьет много пар глаз...
Другой инструмент RegEx является Регулятором
Ну, вся цель в жизни PCRE/x модификатор состоит в том, чтобы позволить Вам писать regexes больше четко, как в этом тривиальном примере:
my $expr = qr/
[a-z] # match a lower-case letter
\d{3,5} # followed by 3-5 digits
/x;
Некоторые люди используют REs для неправильных вещей (я ожидаю первого ТАК вопрос о том, как обнаружить действительную программу C++ с помощью единственного РЕ).
Я обычно нахожу, что, если я не могу соответствовать своему РЕ в 60 символах, это более обеспечено быть частью кода, так как это почти всегда будет более читаемо.
В любом случае я всегда документирую в коде, чего РЕ, как предполагается, достигает в мельчайших подробностях. Это вызвано тем, что я знаю, на основе горького опыта, как трудно это для кого-то еще (или даже меня, шесть месяцев спустя), чтобы войти и попытаться понять.
Я не полагаю, что они являются злыми, хотя я действительно верю некоторым людям, которые используют их, являются злыми (не рассмотрение Вас, Michael Ash :-). Они - большой инструмент, но, как цепная пила, Вы отключите свои участки, если Вы не будете знать, как использовать их правильно.
ОБНОВЛЕНИЕ: На самом деле я только что перешел по ссылке к тому чудовищу, и это должно проверить m/d/y даты формата между годами 1600 и 9999. Это - классический случай того, где полноценный код был бы более читаемым и удобным в сопровождении.
Вы просто разделяете его на три поля и проверяете отдельные значения. Я почти считал бы это преступлением достойный завершения, если бы один из моих фаворитов купил это мне. Я, конечно, передал бы их обратно для записи этого правильно.
Вот тот же regex, разломанный на удобоваримые части. В дополнение к тому, чтобы быть более читаемым, некоторые sub-regexes могут быть полезными самостоятельно. Также значительно легче изменить позволенные разделители.
#!/usr/local/ActivePerl-5.10/bin/perl
use 5.010; #only 5.10 and above
use strict;
use warnings;
my $sep = qr{ [/.-] }x; #allowed separators
my $any_century = qr/ 1[6-9] | [2-9][0-9] /x; #match the century
my $any_decade = qr/ [0-9]{2} /x; #match any decade or 2 digit year
my $any_year = qr/ $any_century? $any_decade /x; #match a 2 or 4 digit year
#match the 1st through 28th for any month of any year
my $start_of_month = qr/
(?: #match
0?[1-9] | #Jan - Sep or
1[0-2] #Oct - Dec
)
($sep) #the separator
(?:
0?[1-9] | # 1st - 9th or
1[0-9] | #10th - 19th or
2[0-8] #20th - 28th
)
\g{-1} #and the separator again
/x;
#match 28th - 31st for any month but Feb for any year
my $end_of_month = qr/
(?:
(?: 0?[13578] | 1[02] ) #match Jan, Mar, May, Jul, Aug, Oct, Dec
($sep) #the separator
31 #the 31st
\g{-1} #and the separator again
| #or
(?: 0?[13-9] | 1[0-2] ) #match all months but Feb
($sep) #the separator
(?:29|30) #the 29th or the 30th
\g{-1} #and the separator again
)
/x;
#match any non-leap year date and the first part of Feb in leap years
my $non_leap_year = qr/ (?: $start_of_month | $end_of_month ) $any_year/x;
#match 29th of Feb in leap years
#BUG: 00 is treated as a non leap year
#even though 2000, 2400, etc are leap years
my $feb_in_leap = qr/
0?2 #match Feb
($sep) #the separtor
29 #the 29th
\g{-1} #the separator again
(?:
$any_century? #any century
(?: #and decades divisible by 4 but not 100
0[48] |
[2468][048] |
[13579][26]
)
|
(?: #or match centuries that are divisible by 4
16 |
[2468][048] |
[3579][26]
)
00
)
/x;
my $any_date = qr/$non_leap_year|$feb_in_leap/;
my $only_date = qr/^$any_date$/;
say "test against garbage";
for my $date (qw(022900 foo 1/1/1)) {
say "\t$date ", $date ~~ $only_date ? "matched" : "didn't match";
}
say '';
#comprehensive test
my @code = qw/good unmatch month day year leap/;
for my $sep (qw( / - . )) {
say "testing $sep";
my $i = 0;
for my $y ("00" .. "99", 1600 .. 9999) {
say "\t", int $i/8500*100, "% done" if $i++ and not $i % 850;
for my $m ("00" .. "09", 0 .. 13) {
for my $d ("00" .. "09", 1 .. 31) {
my $date = join $sep, $m, $d, $y;
my $re = $date ~~ $only_date || 0;
my $code = not_valid($date);
unless ($re == !$code) {
die "error $date re $re code $code[$code]\n"
}
}
}
}
}
sub not_valid {
state $end = [undef, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
my $date = shift;
my ($m,$d,$y) = $date =~ m{([0-9]+)[-./]([0-9]+)[-./]([0-9]+)};
return 1 unless defined $m; #if $m is set, the rest will be too
#components are in roughly the right ranges
return 2 unless $m >= 1 and $m <= 12;
return 3 unless $d >= 1 and $d <= $end->[$m];
return 4 unless ($y >= 0 and $y <= 99) or ($y >= 1600 and $y <= 9999);
#handle the non leap year case
return 5 if $m == 2 and $d == 29 and not leap_year($y);
return 0;
}
sub leap_year {
my $y = shift;
$y = "19$y" if $y < 1600;
return 1 if 0 == $y % 4 and 0 != $y % 100 or 0 == $y % 400;
return 0;
}
Я нашел, что хороший метод должен просто разбить процесс соответствия в несколько фаз. Это, вероятно, не выполняется как быстро, но у Вас есть добавленная премия также способности сказать на более прекрасном гранулярном уровне, почему соответствие не происходит.
Другой маршрут должен использовать LL или LR-анализ. Некоторые языки не являются выразимыми как регулярные выражения, вероятно, даже с non-fsm расширениями perl.
Я учился избегать всех кроме самого простого regexp. Я далеко предпочитаю другие модели, такие как строковое сканирование Значка или парсинг Haskell combinators. В обеих из этих моделей можно написать пользовательский код, который имеет те же полномочия и состояние как встроенная строковая операция в секунду. Если бы я программировал в Perl, то я, вероятно, подстроил бы некоторый парсинг combinators в Perl---, я сделал его для других языков.
Очень хорошая альтернатива должна использовать Грамматики, разбирающие выражение, поскольку Roberto Ierusalimschy сделал со своим пакетом LPEG, но в отличие от синтаксического анализатора combinators это что-то, что Вы не можете сделать на скорую руку днем. Но если кто-то уже сделал ШТЕПСЕЛИ для Вашей платформы, это - очень хорошая альтернатива регулярным выражениям.
Ничего себе, это ужасно. Похоже, что это должно работать, по модулю неизбежная ошибка, имеющая дело с 00 как два года цифры (это должен быть високосный год одна четверть времени, но без века у Вас нет способа знать то, чем это должно быть). Существует большое дублирование, которое должно, вероятно, быть факторизовано в sub-regexes, и я создал бы три sub-regexes для трех основных случаев (который является моим следующим проектом сегодня вечером). Я также использовал другой символ для разделителя, чтобы избежать необходимости выходить из наклонных черт вправо, изменил односимвольное чередование в классы символов (который счастливо позволяет нам избежать необходимости выходить из периода), и измененный \d
кому: [0-9]
начиная с бывших соответствий любой символ цифры (включая U+1815
MONGOLIAN DIGIT FIVE
: ᠕) в Perl 5.8 и 5.10.
Предупреждение, непротестированный код:
#!/usr/bin/perl
use strict;
use warnings;
my $match_date = qr{
#match 29th - 31st of all months but 2 for the years 1600 - 9999
#with optionally leaving off the first two digits of the year
^
(?:
#match the 31st of 1, 3, 5, 7, 8, 10, and 12
(?: (?: 0? [13578] | 1[02] ) ([/-.]) 31) \1
|
#or match the 29th and 30th of all months but 2
(?: (?: 0? [13-9] | 1[0-2] ) ([/-.]) (?:29|30) \2)
)
(?:
(?: #optionally match the century
1[6-9] | #16 - 19
[2-9][0-9] #20 - 99
)?
[0-9]{2} #match the decade
)
$
|
#or match 29 for 2 for leap years
^
(?:
#FIXME: 00 is treated as a non leap year
#even though 2000, 2400, etc are leap years
0?2 #month 2
([/-.]) #separtor
29 #29th
\3 #separator from before
(?: #leap years
(?:
#match rule 1 (div 4) minus rule 2 (div 100)
(?: #match any century
1[6-9] |
[2-9][0-9]
)?
(?: #match decades divisible by 4 but not 100
0[48] |
[2468][048] |
[13579][26]
)
|
#or match rule 3 (div 400)
(?:
(?: #match centuries that are divisible by 4
16 |
[2468][048] |
[3579][26]
)
00
)
)
)
)
$
|
#or match 1st through 28th for all months between 1600 and 9999
^
(?: (?: 0?[1-9]) | (?:1[0-2] ) ) #all months
([/-.]) #separator
(?:
0?[1-9] | #1st - 9th or
1[0-9] | #10th - 19th or
2[0-8] #20th - 28th
)
\4 #seprator from before
(?:
(?: #optionally match the century
1[6-9] | #16 - 19
[2-9][0-9] #20 - 99
)?
[0-9]{2} #match the decade
)
$
}x;
Некоторые люди, сталкиваясь с проблемой, думают, что "Я знаю, я буду использовать регулярные выражения". Теперь у них есть две проблемы. — Jamie Zawinski в comp.lang.emacs.
Сохраните регулярные выражения столь простыми, как они могут возможно быть (KISS). В Вашем примере даты я, вероятно, использовал бы одно регулярное выражение для каждого типа даты.
Или еще лучше, заменил его библиотекой (т.е. анализирующей дату библиотекой).
Я также предпринял бы шаги, чтобы гарантировать, что входной источник имел некоторые ограничения (т.е. только один тип строк даты, идеально ISO 8601).
Кроме того,
Править:
"усовершенствованные конструкции приводят к проблемам maintainance"
Моя исходная точка была то, что, если используется правильно это должно привести к более простым выражениям, не более трудным. Более простые выражения должны уменьшить обслуживание.
Я обновил текст выше для высказывания так же.
Я указал бы, что регулярные выражения едва квалифицируют как усовершенствованные конструкции в и себя. Быть знакомым с определенной конструкцией не делает это усовершенствованной конструкцией, просто незнакомая. Который не изменяет то, что регулярные выражения мощны, компактны и - если используется правильно - изящный. Во многом как скальпель это находится полностью в руках того, который владеет им.
Я думаю, что ответ на поддержание регулярного выражения не так с конструкциями regex или комментарием.
Если бы для меня определили задачу с отладкой примера, Вы дали, я сел бы infront regex отладчика (как тренер Regex) и шаг через регулярное выражение на данных, которые это, должен обработать.
Я не ожидаю, что регулярные выражения будут читаемы, таким образом, я просто оставлю их, как они и переписывают в случае необходимости.
Я мог все еще работать с ним. Я просто использовал бы Регулятор. Одной вещью, которую это позволяет Вам делать, является сохранение regex наряду с данными тестирования для него.
Конечно, я мог бы также добавить комментарии.
Вот то, что произвел Экспресс. Я никогда не использовал его прежде, но теперь, Регулятор без работы:
// using System.Text.RegularExpressions; /// /// Regular expression built for C# on: Thu, Apr 2, 2009, 12:51:56 AM /// Using Expresso Version: 3.0.3276, http://www.ultrapico.com /// /// A description of the regular expression: /// /// Select from 3 alternatives /// ^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$ /// Beginning of line or string /// Match expression but don't capture it. [(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2)] /// Select from 2 alternatives /// (?:(?:0?[13578]|1[02])(\/|-|\.)31)\1 /// Match expression but don't capture it. [(?:0?[13578]|1[02])(\/|-|\.)31] /// (?:0?[13578]|1[02])(\/|-|\.)31 /// Match expression but don't capture it. [0?[13578]|1[02]] /// Select from 2 alternatives /// 0?[13578] /// 0, zero or one repetitions /// Any character in this class: [13578] /// 1[02] /// 1 /// Any character in this class: [02] /// [1]: A numbered capture group. [\/|-|\.] /// Select from 3 alternatives /// Literal / /// - /// Literal . /// 31 /// Backreference to capture number: 1 /// (?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2) /// Return /// New line /// Match expression but don't capture it. [(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2] /// (?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2 /// Match expression but don't capture it. [0?[13-9]|1[0-2]] /// Select from 2 alternatives /// 0?[13-9] /// 0, zero or one repetitions /// Any character in this class: [13-9] /// 1[0-2] /// 1 /// Any character in this class: [0-2] /// [2]: A numbered capture group. [\/|-|\.] /// Select from 3 alternatives /// Literal / /// - /// Literal . /// Match expression but don't capture it. [29|30] /// Select from 2 alternatives /// 29 /// 29 /// 30 /// 30 /// Backreference to capture number: 2 /// Return /// New line /// Match expression but don't capture it. [(?:1[6-9]|[2-9]\d)?\d{2}] /// (?:1[6-9]|[2-9]\d)?\d{2} /// Match expression but don't capture it. [1[6-9]|[2-9]\d], zero or one repetitions /// Select from 2 alternatives /// 1[6-9] /// 1 /// Any character in this class: [6-9] /// [2-9]\d /// Any character in this class: [2-9] /// Any digit /// Any digit, exactly 2 repetitions /// End of line or string /// ^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$ /// Beginning of line or string /// Match expression but don't capture it. [0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))] /// 0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))) /// 0, zero or one repetitions2 /// [3]: A numbered capture group. [\/|-|\.] /// Select from 3 alternatives /// Literal / /// - /// Literal . /// 29 /// Backreference to capture number: 3 /// Match expression but don't capture it. [(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))] /// Match expression but don't capture it. [(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)] /// Select from 2 alternatives /// (?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26]) /// Match expression but don't capture it. [1[6-9]|[2-9]\d], zero or one repetitions /// Select from 2 alternatives /// 1[6-9] /// 1 /// Any character in this class: [6-9] /// [2-9]\d /// Any character in this class: [2-9] /// Any digit /// Match expression but don't capture it. [0[48]|[2468][048]|[13579][26]] /// Select from 3 alternatives /// 0[48] /// 0 /// Any character in this class: [48] /// [2468][048] /// Any character in this class: [2468] /// Any character in this class: [048] /// [13579][26] /// Any character in this class: [13579] /// Any character in this class: [26] /// (?:(?:16|[2468][048]|[3579][26])00) /// Return /// New line /// Match expression but don't capture it. [(?:16|[2468][048]|[3579][26])00] /// (?:16|[2468][048]|[3579][26])00 /// Match expression but don't capture it. [16|[2468][048]|[3579][26]] /// Select from 3 alternatives /// 16 /// 16 /// [2468][048] /// Any character in this class: [2468] /// Any character in this class: [048] /// [3579][26] /// Any character in this class: [3579] /// Any character in this class: [26] /// 00 /// End of line or string /// ^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$ /// Beginning of line or string /// Match expression but don't capture it. [(?:0?[1-9])|(?:1[0-2])] /// Select from 2 alternatives /// Match expression but don't capture it. [0?[1-9]] /// 0?[1-9] /// 0, zero or one repetitions /// Any character in this class: [1-9] /// Match expression but don't capture it. [1[0-2]] /// 1[0-2] /// 1 /// Any character in this class: [0-2] /// Return /// New line /// [4]: A numbered capture group. [\/|-|\.] /// Select from 3 alternatives /// Literal / /// - /// Literal . /// Match expression but don't capture it. [0?[1-9]|1\d|2[0-8]] /// Select from 3 alternatives /// 0?[1-9] /// 0, zero or one repetitions /// Any character in this class: [1-9] /// 1\d /// 1 /// Any digit /// 2[0-8] /// 2 /// Any character in this class: [0-8] /// Backreference to capture number: 4 /// Match expression but don't capture it. [(?:1[6-9]|[2-9]\d)?\d{2}] /// (?:1[6-9]|[2-9]\d)?\d{2} /// Match expression but don't capture it. [1[6-9]|[2-9]\d], zero or one repetitions /// Select from 2 alternatives /// 1[6-9] /// 1 /// Any character in this class: [6-9] /// [2-9]\d /// Any character in this class: [2-9] /// Any digit /// Any digit, exactly 2 repetitions /// End of line or string /// /// /// public static Regex regex = new Regex( "^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.)31)\\1|\r\n(?:(?:0?[13-9]"+ "|1[0-2])(\\/|-|\\.)(?:29|30)\\2))\r\n(?:(?:1[6-9]|[2-9]\\d)?\\d"+ "{2})$|^(?:0?2(\\/|-|\\.)29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0["+ "48]|[2468][048]|[13579][26])|\r\n(?:(?:16|[2468][048]|[3579][2"+ "6])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))\r\n(\\/|-|\\.)(?:0?[1-9"+ "]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$", RegexOptions.CultureInvariant | RegexOptions.Compiled );
Недавно я опубликовал вопрос о комментировании регулярных выражений со встроенными комментариями Были полезные ответы, в частности, от @mikej
См. Сообщение Мартина Фаулера на ComposedRegex для получения дополнительных идей по улучшение читаемости регулярных выражений. В резюмируя, он выступает за разрушение сложное регулярное выражение на более мелкие части которой можно присвоить значимую переменную имена. например,