web-dev-qa-db-ja.com

数字以外がLIKE [0-9]なのはなぜですか?

私のサーバーのデフォルトの照合順序は、次のクエリで決定されるLatin1_General_CI_ASです。

SELECT SERVERPROPERTY('Collation') AS Collation;

この照合で、述語LIKE '[0-9]'を使用して文字列内の数字以外の文字を照合できることを発見して驚いた。

なぜデフォルトの照合でこれが起こるのですか?これが役立つケースは考えられません。バイナリ照合を使用してこの動作を回避できることはわかっていますが、デフォルトの照合を実装する奇妙な方法のようです。

数字をフィルタリングすると、数字以外の文字が生成されます

考えられるすべての1バイト文字値を含む列を作成し、値を数字一致述語でフィルタリングすることにより、動作を示すことができます。

次のステートメントは、現在のコードページのコードポイントごとに1つずつ、256行の一時テーブルを作成します。

WITH P0(_) AS (SELECT 0 UNION ALL SELECT 0),
P1(_) AS (SELECT 0 FROM P0 AS L CROSS JOIN P0 AS R),
P2(_) AS (SELECT 0 FROM P1 AS L CROSS JOIN P1 AS R),
P3(_) AS (SELECT 0 FROM P2 AS L CROSS JOIN P2 AS R),
Tally(Number) AS (
  SELECT -1 + ROW_NUMBER() OVER (ORDER BY (SELECT 0))
  FROM P3
)
SELECT Number AS CodePoint, CHAR(Number) AS Symbol
INTO #CodePage
FROM Tally
WHERE Number >= 0 AND Number <= 255;

各行には、コードポイントの整数値とコードポイントの文字値が含まれます。すべての文字値が表示可能であるわけではありません-一部のコードポイントは厳密に制御文字です。以下は、SELECT CodePoint, Symbol FROM #CodePageの出力の選択的なサンプルです。

0   
1   
2   
...
32   
33  !
34  "
35  #
...
48  0
49  1
50  2
...
65  A
66  B
67  C
...
253 ý
254 þ
255 ÿ

LIKE述語を使用し、「0」から「9」までの文字の範囲を指定して、Symbol列をフィルタリングして数字を検索できると思います。

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0-9]';

それは驚くべき出力を生成します:

CodePoint   Symbol
48  0
49  1
50  2
51  3
52  4
53  5
54  6
55  7
56  8
57  9
178 ²
179 ³
185 ¹
188 ¼
189 ½
190 ¾

48から57までのコードポイントのセットは、私が期待するものです。驚いたのは、上付き文字と分数の記号も結果セットに含まれていることです。

指数と分数を数値と考えるのは数学的な理由があるかもしれませんが、それらを数字と呼ぶのは間違っているようです。

回避策としてバイナリ照合を使用する

期待する結果を得るために、対応するバイナリ照合順序Latin1_General_BINを強制できることを理解しています。

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0-9]' COLLATE Latin1_General_BIN;

結果セットには、コードポイント48から57のみが含まれます。

CodePoint   Symbol
48  0
49  1
50  2
51  3
52  4
53  5
54  6
55  7
56  8
57  9

[0-9]は、数字に一致するように定義されたある種の正規表現ではありません。

LIKEパターンの範囲は、照合のソート順に従って、開始文字と終了文字の間の文字に一致します。

SELECT CodePoint,
       Symbol,
       RANK() OVER (ORDER BY Symbol COLLATE Latin1_General_CI_AS) AS Rnk
FROM   #CodePage
WHERE  Symbol LIKE '[0-9]' COLLATE Latin1_General_CI_AS
ORDER  BY Symbol COLLATE Latin1_General_CI_AS 

戻り値

CodePoint            Symbol Rnk
-------------------- ------ --------------------
48                   0      1
188                  ¼      2
189                  ½      3
190                  ¾      4
185                  ¹      5
49                   1      5
50                   2      7
178                  ²      7
179                  ³      9
51                   3      9
52                   4      11
53                   5      12
54                   6      13
55                   7      14
56                   8      15
57                   9      16

デフォルトの照合では、これらの文字は0の後、9の前にソートされるため、これらの結果が得られます。

照合は、01の間の分数が正しい順序で実際に数学的な順序で並べ替えられるように定義されているように見えます。

範囲ではなくセットを使用することもできます。 2²に一致しないようにするには、CS照合が必要になります

SELECT CodePoint, Symbol
FROM #CodePage
WHERE Symbol LIKE '[0123456789]' COLLATE Latin1_General_CS_AS
22
Martin Smith

Latin1はコードページ1252で、 178は 'SUPERSCRIPT TWO' です。これはUnicodeです superscript :is 文字 "2" as superscriptです。 Unicode Technical Standard#10 によると、2と等しいはずです。 8.1照合折りたたみ を参照してください。

互換性(三次)の同等物全角文字や上付き文字などを代表的な文字にマップする

上付き文字2が2と異なる場合のバグです。 「しかし、私の列はUnicodeではない」と言う前に、安心してください: [〜#〜] msdn [〜#〜] (Windows照合順序を参照)に従って、すべての文字列比較ディスク上での表現がCHARの場合でも、Unicodeの規則に従って並べ替えが行われます。

あなたの例の他の文字については、VULGAR FRACTION ONE QUARTERなどは、どの数値とも比較しませんが、Markがすでに示したように、0から9の間で適切にソートします。

そしてもちろん、コードページを変更すると、異なる結果が得られます。例えば。 Greek_CS_ASコードページ1253 )コード178、179、189の文字を取得します。

6
Remus Rusanu