Joe*_*Joe 5 sql-server sql-server-2014 regex
我需要正确格式化一些欧洲地址。其中一个步骤是将第一个字母大写,但要避免一些特定的词,例如“on”、“on”、“von”、“van”、“di”、“in”、“sul”。因此,虽然我的技能稀缺,但我认为使用基于 RegEx 的函数是个好主意。
经过一番谷歌搜索后,我在这里找到了这个:
CREATE FUNCTION InitialCap
(
@String nvarchar(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE @Position INT;
SELECT
@String = STUFF(LOWER(@String),1,1,UPPER(LEFT(@String,1))) COLLATE Latin1_General_Bin,
@Position = PATINDEX('%[^A-Za-z][a-z]%',@String COLLATE Latin1_General_Bin);
WHILE @Position > 0
SELECT
@String = STUFF(@String,@Position,2,UPPER(SUBSTRING(@String,@Position,2))) COLLATE Latin1_General_Bin,
@Position = PATINDEX('%[^A-Za-z][a-z]%',@String COLLATE Latin1_General_Bin);
RETURN @String;
END
Run Code Online (Sandbox Code Playgroud)
这似乎是在寻找一个“非字母”+一个小写“字母”的序列
[^A-Za-z][a-z]
Run Code Online (Sandbox Code Playgroud)
好的,我想我已经了解它是如何工作的,并且我对其进行了修改以最好地满足我的需求。
我认为最好搜索一个空格或 ' 或 - 和一个小写字母,因此我将其更改为
[\s'-][\w]
Run Code Online (Sandbox Code Playgroud)
然后,经过多次尝试,我在 regexr.com 上构建了这个 RegEx,它似乎捕获了所需的序列:
[\s](?!di\s|in\s|sul\s|on\s|upon\s|von\s|uber\s|ueber\s)[\w]
Run Code Online (Sandbox Code Playgroud)
但是当我把它放到上面的函数中时,结果并不像预期的那样。
怎么了?
Sol*_*zky 10
SQL Server 内部不支持正则表达式。LIKE
并且PATINDEX
两者都支持非常有限的通配符,包括与 RegEx 语法相似的单字符范围匹配[...]
或排除[^...]
,并且在某种程度上功能相似,但肯定不是 RegEx。
如果您想要/需要 SQL Server 中的正则表达式,则需要使用 SQLCLR。您可以自己编写代码,也可以使用预先构建的函数,例如SQL#(我编写的)中可用的函数。大多数 RegEx 函数在免费版本中可用。我认为您可以使用RegEx_Matches返回不在排除列表中的单词结果集,然后将其与String_ToTitleCase4k函数(在免费版本中也可用)相结合来执行 InitCap。
例如:
DECLARE @Input NVARCHAR(MAX) =
N'santacroce sull''arno o''sullivan suLL sUlLiVan gsantacroce',
@Expression NVARCHAR(4000) =
N'[\s](?!di\s|in\s|sull\s|on\s|upon\s|von\s|uber\s|ueber\s)[\w]';
-- show matches for debugging
SELECT word.[StartPos],
word.[EndPos],
word.[Value] AS [Original],
SQL#.String_ToTitleCase4k(word.[Value], N'') AS [TitleCased]
FROM SQL#.RegEx_Matches(@Input, @Expression, 1, N'ignorecase') word;
SELECT @Input = STUFF(@Input,
word.[StartPos],
((word.[EndPos] - word.[StartPos]) + 1),
SQL#.String_ToTitleCase4k(word.[Value], N'')
COLLATE Latin1_General_100_BIN2)
FROM SQL#.RegEx_Matches(@Input, @Expression, 1, N'ignorecase') word;
SELECT @Input AS [Fixed];
Run Code Online (Sandbox Code Playgroud)
返回:
DECLARE @Input NVARCHAR(MAX) =
N'santacroce sull''arno o''sullivan suLL sUlLiVan gsantacroce',
@Expression NVARCHAR(4000) =
N'[\s](?!di\s|in\s|sull\s|on\s|upon\s|von\s|uber\s|ueber\s)[\w]';
-- show matches for debugging
SELECT word.[StartPos],
word.[EndPos],
word.[Value] AS [Original],
SQL#.String_ToTitleCase4k(word.[Value], N'') AS [TitleCased]
FROM SQL#.RegEx_Matches(@Input, @Expression, 1, N'ignorecase') word;
SELECT @Input = STUFF(@Input,
word.[StartPos],
((word.[EndPos] - word.[StartPos]) + 1),
SQL#.String_ToTitleCase4k(word.[Value], N'')
COLLATE Latin1_General_100_BIN2)
FROM SQL#.RegEx_Matches(@Input, @Expression, 1, N'ignorecase') word;
SELECT @Input AS [Fixed];
Run Code Online (Sandbox Code Playgroud)
它不能完全正确工作的原因是由于您的正则表达式不正确:
更新:
我能够通过将其更改为如下来修复您的正则表达式:
StartPos EndPos Original TitleCased
-------- ------ -------- ----------
11 12 s S
21 22 o O
32 33 s S
37 38 s S
46 47 g G
Fixed
-------------------------
santacroce Sull'arno O'sullivan SuLL SUlLiVan Gsantacroce
Run Code Online (Sandbox Code Playgroud)
与原版的主要区别:
\b
(字边界)而不是\s
(空白),因为它处理行/字符串的开头和结尾。它也不会捕获空格,\s
如果它在视觉上不明显,上面的每个匹配的字符串都以匹配的空格为前缀。虽然该空格不会影响替换,因为它仍然是一个空格,但它确实阻止了组中的第一个单词匹配,除非整个字符串前面有一些空格。在与地址一起使用的情况下,如果它们总是以数字开头,那么可能总会有前面的空格,但最好不要将其包含在匹配中。+
(一个或多个)量词添加到 the 中,\w
以便它不仅可以获取第一个字符\b
每个片段末尾的公共移到新的内部非捕获组之外来简化排除列表。这是非功能性差异。它只是让阅读和处理变得更容易。新输出:
\b(?!(?:di|in|sull|on|upon|von|uber|ueber)\b)\w+
Run Code Online (Sandbox Code Playgroud)
更新 2:
如果希望有一个可更新的排除词列表,而无需更新包含正则表达式的函数,那么通过执行以下操作很容易实现:
创建一个表来保存排除词:
StartPos EndPos Original TitleCased
-------- ------ -------- ----------
1 10 santacroce Santacroce
17 20 arno Arno
22 22 o O
24 31 sullivan Sullivan
38 45 sUlLiVan Sullivan
47 57 gsantacroce Gsantacroce
Fixed
-------------------------
Santacroce sull'Arno O'Sullivan suLL Sullivan Gsantacroce
Run Code Online (Sandbox Code Playgroud)填充/管理该表中的单词:
CREATE TABLE #ExcludeWords ([Word] NVARCHAR(50) NOT NULL);
Run Code Online (Sandbox Code Playgroud)在您执行此数据清理的任何代码中,从要排除的单词表中动态构建正则表达式:
INSERT INTO #ExcludeWords ([Word]) VALUES
(N'di'), (N'in'), (N'sull'), (N'on'), (N'upon'), (N'von'), (N'uber'), (N'ueber');
Run Code Online (Sandbox Code Playgroud)更新 3:
最初没有提供样本数据,所以两个答案都用一个简单的单词列表进行测试,其中一些是排除词。但是现在已经提供了一些测试用例,并且在使用其中一个测试用例进行测试时,我发现我的实现存在一些问题:
所以,我已经调整了上面的代码和测试用例以及结果来解决这些问题。主要区别是:
N'ignorecase'
REPLACE
与功能STUFF
,让我用每场比赛的起点和终点位置只替换一个项目请注意:
sul
而评论使用sull
。我已经调整了我的答案以使用sull
评论中提供的测试用例中显示的(两个“L”)。小智 1
恕我直言,我认为上述 UDF 无法满足您的要求。
我的方法是,
它会像这样工作。我有一个拆分字符串 UDF。您可以创建或下载自己的分割字符串函数。
declare @Excludeword table(word varchar(50))--this should be permanenet table
insert into @Excludeword VALUES ('on'), ('upon'),('von'),('van'),('di'),('in'),('sul')
DECLARE @String nvarchar(max)='santacroce sull''arno'--'caT UPON the wet floor'
SELECT stuff((
SELECT ' ' + CASE
WHEN ca.word = item
THEN ca.word
WHEN charindex('''', item) > 0
THEN STUFF(LOWER(item), charindex('''', item) + 1, 1, UPPER(substring(item, charindex('''', item) + 1, 1))) COLLATE Latin1_General_Bin
ELSE STUFF(LOWER(item), 1, 1, UPPER(LEFT(item, 1))) COLLATE Latin1_General_Bin
END
FROM dbo.DelimitedSplit8K(@String, ' ')
OUTER APPLY (
SELECT word
FROM @Excludeword E
WHERE e.word = item
) ca
WHERE item <> ''
FOR XML PATH('')
), 1, 1, '') Item
Run Code Online (Sandbox Code Playgroud)
输出, Cat on The Wet Floor
或Santacroce sull'Arno
或Cat upon The Wet Floor
您应该在问题本身中用适当的示例提及所有业务规则。
另外您打算如何使用它?就像您会传递一个变量还是您将处理整个表一样。
笔记 :
这只是粗略的想法。
如果这是您正在寻找的,那么可以根据您的要求定制 Split String,并且可以将一些内容封装在 UDf 本身中。