web-dev-qa-db-ja.com

テーブル列の値が正規表現「[axyto0-9 \ s] {0,2} [\ s0-9] {0,10}」に一致するようにするにはどうすればよいですか?

以下のようなテーブルがあります

CREATE TABLE dbo.DemoTable
(
Value VARCHAR(12)
)

Valueが次のパターンに一致する行のみを含むように制約したい

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

  • 文字列の始まり
  • 以下のリストにある1文字が0から2回、可能な限り多く
    • リスト「axyto」の1文字(大文字と小文字を区別)
    • 「0」から「9」までの範囲の文字
    • 「空白文字」
  • 以下のリストにある1文字と0〜10回、できるだけ多く一致します
    • 「空白文字」
    • 「0」から「9」までの範囲の文字
  • 文字列の終わり

SQL Serverでこれを行うにはどうすればよいですか? (SOに関するこの質問に触発された

13
Martin Smith

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),']%')と一致する必要があります
  • 3文字目以降はNOT LIKE CONCAT('%[^0123456789',char(9),char(10),char(11),char(12),char(13),char(32),char(160),']%')と一致する必要があります

これはすでにかなり厄介であり(私がそれが現在正しいかどうかさえもわかりません)、より複雑なパターンでは完全に実行不可能になります。

正規表現のサポートはCLRを使用することで利用でき、 the Java言語拡張 によって可能になる可能性の1つとしても呼び出されます。

これにより、TSQL表面積が拡張され、正規表現、文字列処理、およびNLPサポートを含むユースケースがより適切に処理されます。

これらのテクノロジーでの正規表現の使用例は、Microsoftサイトですでに公開されています( [〜#〜] clr [〜#〜]Java

この回答は、これらがオプションではない場合(おそらく、ポリシーによって無効にされているか、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>';
_

挿入/更新が検証されるようにチェック制約を追加する

_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)
_

このアプローチは一般的な検証にはあまり柔軟性がありません。失敗するとエラーがスローされるため、セットベースの方法で良行と不良行を特定することはできませんが、単一の不良値が見つかった場合に挿入を中止するためです。それは正常に動作します。

チェック制約は、実際に価値のあるものをチェックしていません。障害が発生するとエラーがスローされるため、型付きXMLへの変換が呼び出されるようにします。

行を挿入する

_INSERT INTO dbo.DemoTable
VALUES      (''),
            ('ax1234567890'),
            ('to123456'),
            ('AX1234567890'); 
_

エラー

メッセージ6926、レベル16、状態1、行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));
    }
}
_

以下のチェック制約を設定すると、ネイティブ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),']%')
)
_

タイプのXMLアプローチを使用すると、4分かかりました

これは、1行あたり24マイクロ秒のオーダーであり、制約がないため、ペナルティです。そのため、これが支払う価値のある価格か、予想される挿入/更新の量を考慮に入れていないかを評価する必要があります。

enter image description here

比較のために、チェック制約を変更して型なしXMLを生成した後、その数の行を挿入するのに1分25秒かかったので、型付きXMLはそれにかなりのオーバーヘッドを追加します。

23
Martin Smith