如何强制表列中的值匹配正则表达式“[axyto0-9\s]{0,2}[\s0-9]{0,10}”?

Mar*_*ith 13 sql-server regular-expression check-constraints

我有一张如下表

CREATE TABLE dbo.DemoTable
(
Value VARCHAR(12)
)
Run Code Online (Sandbox Code Playgroud)

我想限制它只包含Value匹配以下模式的行

[axyto0-9\s]{0,2}[\s0-9]{0,10}

  • 字符串的开始
  • 出现在下面列表中的单个字符在 0 到 2 次之间,尽可能多次
    • 列表“axyto”中的单个字符(区分大小写)
    • 介于“0”和“9”之间的字符
    • 一个“空白字符”
  • 匹配下面列表中出现的单个字符 0 到 10 次,尽可能多
    • 一个“空白字符”
    • 介于“0”和“9”之间的字符
  • 字符串结束

如何在 SQL Server 中执行此操作?(受 SO 上这个问题的启发

Mar*_*ith 23

TSQL 中的字符串处理很差。

PATINDEX支持一组有限的通配符,但不支持字符类,例如\s匹配空格或量词。使用本机 TSQL 表达式对这个相对简单的正则表达式进行验证并非不可能。12 的最大长度由数据类型强制执行,并且使用varchar意味着我的代码页中只有 6 个空格字符。

所以下面的方法应该有效

  • 前 2 个字符必须匹配 NOT LIKE CONCAT('%[^aotxy0123456789',char(9),char(10),char(11),char(12),char(13),char(32),char(160),']%')
  • 从第三个字符开始必须匹配 NOT LIKE CONCAT('%[^0123456789',char(9),char(10),char(11),char(12),char(13),char(32),char(160),']%')

这已经相当混乱(我什至不确定它目前是否正确)并且对于更复杂的模式完全不可行。

正则表达式支持可通过使用 CLR 获得,也被称为Java 语言扩展启用的可能性之一。

这扩展了 TSQL 的表面积,以更好地处理涉及正则表达式、字符串处理和 NLP 支持的用例。

Microsoft 站点上已经提供了将正则表达式与这些技术结合使用的示例(CLRJava

This answer为这些不是一个选项的情况提供了解决方案(可能被策略禁用或使用这些不可用的Azure SQL数据库)。

这利用了XML 模式正则表达式

使用包含正则表达式的模式约束创建 XML 模式

CREATE XML SCHEMA COLLECTION dbo.PatternValidatorSchema
AS '<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="test">
        <xs:simpleType>
            <xs:restriction base="xs:string">
                <xs:pattern value="[axyto0-9\s]{0,2}[\s0-9]{0,10}"/>
            </xs:restriction>
        </xs:simpleType>
    </xs:element>
</xs:schema>';
Run Code Online (Sandbox Code Playgroud)

添加检查约束,以便验证插入/更新

ALTER TABLE dbo.DemoTable 
 ADD CONSTRAINT CK_ValidateValueMatchesPattern 
 CHECK (CAST(ISNULL('<test>' + REPLACE(REPLACE(REPLACE(Value, '&','&amp;'), '<','&lt;'), '>','&gt;') + '</test>','') AS XML(dbo.PatternValidatorSchema)) IS NOT NULL)
Run Code Online (Sandbox Code Playgroud)

这种方法对于一般验证不是很灵活,因为任何失败都会引发错误,因此它不能用于以基于集合的方式确定好行和坏行,但为了在发现单个错误值时中止插入它工作正常。

检查约束并不是真正检查任何有价值的东西。它会导致调用到类型化 XML 的转换,因为任何失败都会导致抛出错误。

插入一些行

INSERT INTO dbo.DemoTable
VALUES      (''),
            ('ax1234567890'),
            ('to123456'),
            ('AX1234567890'); 
Run Code Online (Sandbox Code Playgroud)

错误

Msg 6926, Level 16, State 1, Line 37

XML 验证:无效的简单类型值:“AX1234567890”。位置:/*:test[1]

尝试的最终值违反了表达式,因为它是大写的,因此插入被阻止。错误值有助于显示在错误中(尽管消息的其余部分可能会引起混淆)

表现

不幸的是,尽管付出了潜在的重大性能损失。TABLOCK在我的机器上通过提示将 1000 万个有效值插入表中时,在没有检查约束的情况下花费了大约2.6 秒

使用 CLR 函数在检查约束中调用以下将增加到12 秒

using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;

public partial class UserDefinedFunctions
{
    private static readonly Regex regexObj = new Regex(@"\A[axyto0-9\s]{0,2}[\s0-9]{0,10}\z", RegexOptions.Compiled);

    [SqlFunction(DataAccess = DataAccessKind.None, 
                 IsDeterministic = true, 
                 IsPrecise = true, 
                 SystemDataAccess = SystemDataAccessKind.None)]
    public static SqlBoolean PatternValidator([SqlFacet(MaxSize = 12)]SqlString valueToCheck)
    {    
        return new SqlBoolean(regexObj.IsMatch(valueToCheck.Value));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用以下检查约束,本机 TSQL 尝试需要 17 秒

ALTER TABLE [dbo].[DemoTable]  WITH CHECK ADD  CONSTRAINT [CK_ValidateValueMatchesPattern] CHECK  (
LEFT(Value,2)  COLLATE Latin1_General_100_BIN2 NOT LIKE CONCAT('%[^aotxy0123456789',char(9),char(10),char(11),char(12),char(13),char(32),char(160),']%')
      AND SUBSTRING(Value,3,10) COLLATE Latin1_General_100_BIN2 NOT LIKE CONCAT('%[^aotxy0123456789',char(9),char(10),char(11),char(12),char(13),char(32),char(160),']%')
)
Run Code Online (Sandbox Code Playgroud)

使用类型化 XML 方法需要 4 分钟

这是每行 24 微秒的惩罚,而不是没有约束,因此需要评估这是否值得支付或不考虑预期的插入/更新量。

在此处输入图片说明

仅通过更改检查约束以生成非类型化 XML 后的比较,插入该数量的行需要 1 分 25 秒,因此类型化 XML 确实增加了一些显着的开销。