web-dev-qa-db-ja.com

アクセント記号付きソート

なぜこれら2つのSELECTステートメントは異なるソート順になるのですか?

USE tempdb;
CREATE TABLE dbo.OddSort 
(
    id INT IDENTITY(1,1) PRIMARY KEY
    , col1 NVARCHAR(2)
    , col2 NVARCHAR(2)
);
GO
INSERT dbo.OddSort (col1, col2) 
VALUES (N'e', N'eA')
    , (N'é', N'éB')
    , (N'ë', N'ëC')
    , (N'è', N'èD')
    , (N'ê', N'êE')
    , (N'ē', N'ēF');
GO

SELECT * 
FROM dbo.OddSort 
ORDER BY col1 COLLATE Latin1_General_100_CS_AS;
╔════╦══════╦══════╗
║id║col1║col2║
╠════╬══════╬ ══════╣
║1║e║eA║
║2║é║éB║
║4║è║èD║-id 3である必要がありますか? 
║5║ê║êE║
║3║ë║ëC║
║6║ē║ēF║
╚════╩═══ ═══╩══════╝
SELECT * 
FROM dbo.OddSort 
ORDER BY col2 COLLATE Latin1_General_100_CS_AS;
╔════╦══════╦══════╗
║id║col1║col2║
╠════╬══════╬ ══════╣
║1║e║eA║
║2║é║éB║
║3║ë║ëC║
║4 è║èD║
║5║ê║êE║
║6║ē║ēF║
╚════╩══════╩═══ ═══╝
19
Aram

この質問はデータベースにそれほど関係しているわけではありませんが、Unicodeの処理と規則については詳しく説明しています。

https://docs.Microsoft.com/en-us/sql/t-sql/statements/windows-collat​​ion-name-transact-sqlLatin1_General_100_CS_ASの意味:「照合はLatin1 General辞書の並べ替え規則を使用し、コードページ1252にマップします」とCS = Case SensitiveおよびAS = Accent Sensitiveを追加します。

Windowsコードページ1252とUnicode( http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT )の間のマッピングは、すべての文字に対して同じ値を示しています(Microsoftのマッピングに存在しないマクロンを除いて、この場合はどうなるかわからない)を扱うため、今のところ、Unicodeツールと用語に集中できます。

まず、すべての文字列について、何を扱っているかを正確に教えてください。

0065  LATIN SMALL LETTER E
0041  LATIN CAPITAL LETTER A
00E9  LATIN SMALL LETTER E WITH ACUTE
0042  LATIN CAPITAL LETTER B
00EB  LATIN SMALL LETTER E WITH DIAERESIS
0043  LATIN CAPITAL LETTER C
00E8  LATIN SMALL LETTER E WITH Grave
0044  LATIN CAPITAL LETTER D
00EA  LATIN SMALL LETTER E WITH CIRCUMFLEX
0045  LATIN CAPITAL LETTER E
‎0113  LATIN SMALL LETTER E WITH MACRON
0046  LATIN CAPITAL LETTER F

Unicode照合アルゴリズムは次のとおりです: https://www.unicode.org/reports/tr10/

一部のルールは状況依存であるため、並べ替えは1つの文字だけに依存できないことを説明しているセクション1.3「状況依存の感度」をご覧ください。

1.8の次の点にも注意してください。

照合順序は文字列のプロパティではありません。照合順序は、通常、連結または部分文字列操作では保持されません。

デフォルトでは、アルゴリズムは3つの完全にカスタマイズ可能なレベルを使用します。ラテン文字の場合、これらのレベルはおおよそ次のように対応します。

alphabetic ordering
diacritic ordering
case ordering.

ただし、アルゴリズム自体は少し密度が高くなります。その要点は次のとおりです。簡単に説明すると、Unicode照合アルゴリズムは入力Unicode文字列と文字のマッピングデータを含む照合要素テーブルを受け取ります。これは、符号なし16ビット整数の配列であるソートキーを生成します。そのようにして生成された2つ以上のソートキーをバイナリ比較して、それらが生成された文字列間の正しい比較を行うことができます。

ここでは、ラテン語の特定の並べ替え規則を表示できます。 http://developer.mimer.com/collat​​ions/charts/latin.htm 以上、MS SQLの場合は特に: http:/ /collat​​ion-charts.org/mssql/mssql.0409.1252.Latin1_General_CS_AS.html

e文字の場合、次のように表示されます。

e EéÉèêÊëË

これは、col1で注文したときの結果を説明しています。ただし、ēはコードページ1252に存在しないため、それが何をするのかまったくわかりません。

または、手動でUnicodeアルゴリズムを実行する場合、 http://www.unicode.org/Public/UCA/latest/allkeys.txt でDUCETのキー値を使用します。

ステップ1:正規化形式Dなので、各ケースは次のようになります。

e => U+0065
é => U+0065 U+0301
ë => U+0065 U+0308
è => U+0065 U+0300
ê => U+0065 U+0302
ē => U+0065 U+0304

ステップ2、照合配列を生成する(ファイルallkeys.txtで検索)

e => [.1D10.0020.0002]
é => [.1D10.0020.0002] [.0000.0024.0002]
ë => [.1D10.0020.0002] [.0000.002B.0002]
è => [.1D10.0020.0002] [.0000.0025.0002]
ê => [.1D10.0020.0002] [.0000.0027.0002]
ē => [.1D10.0020.0002] [.0000.0032.0002]

ステップ3、フォームのソートキー(各レベルで、各照合配列内の各値を取得し、区切り文字として0000を入力して、次のレベルで再び開始する)

e => 1D10 0000 0020 0000 0002
é => 1D10 0000 0020 0024 0000 0002 0002
ë => 1D10 0000 0020 002B 0000 0002 0002
è => 1D10 0000 0020 0025 0000 0002 0002
ê => 1D10 0000 0020 0027 0000 0002 0002
ē => 1D10 0000 0020 0032 0000 0002 0002

手順4、並べ替えキーの比較(各値を1つずつ単純にバイナリ比較):4番目の値はそれらすべてを並べ替えるのに十分であるため、最終的な順序は次のようになります。

e
é
è
ê
ë
ē

col2での注文と同じ方法で:

ステップ1:NFD

eA => U+0065 U+0041
éB => U+0065 U+0301 U+0042
ëC => U+0065 U+0308 U+0043
èD => U+0065 U+0300 U+0044
êE => U+0065 U+0302 U+0045
ēF => U+0065 U+0304 U+0046

ステップ2:照合配列

eA => [.1D10.0020.0002] [.1CAD.0020.0008]
éB => [.1D10.0020.0002] [.0000.0024.0002] [.1CC6.0020.0008]
ëC => [.1D10.0020.0002] [.0000.002B.0002] [.1CE0.0020.0008]
èD => [.1D10.0020.0002] [.0000.0025.0002] [.1CF5.0020.0008]
êE => [.1D10.0020.0002] [.0000.0027.0002] [.1D10.0020.0008]
ēF => [.1D10.0020.0002] [.0000.0032.0002] [.1D4B.0020.0008]

ステップ3:フォームのソートキー

eA => 1D10 1CAD 0000 0020 0020 0000 0002 0008
éB => 1D10 1CC6 0000 0020 0024 0020 0000 0002 0002 0008
ëC => 1D10 1CE0 0000 0020 002B 0020 0000 0002 0002 0008
èD => 1D10 1CF5 0000 0020 0025 0020 0000 0002 0002 0008
êE => 1D10 1D10 0000 0020 0027 0020 0000 0002 0002 0008
ēF => 1D10 1D4B 0000 0020 0032 0020 0000 0002 0002 0008

手順4:並べ替えキーを比較する:2番目の値はそれらすべてを並べ替えるのに十分であり、実際には既に昇順であるため、最終的な順序は確かに次のとおりです。

eA
éB
ëC
èD
êE
ēF

更新:ソロモンルッツキーの3番目のケースを追加します。これは、新しいルールを有効にするスペースがあるため、よりトリッキーです(「無視できないケース」を選択しました)。

ステップ1、NFD:

è 1 => U+0065 U+0300 U+0020 U+0031
ê 5 => U+0065 U+0302 U+0020 U+0035
e 2 => U+0065 U+0020 U+0032
é 4 => U+0065 U+0301 U+0020 U+0034
ē 3 => U+0065 U+0304 U+0020 U+0033
ë 6 => U+0065 U+0308 U+0020 U+0036

ステップ2、照合配列を作成する:

è 1 => [.1D10.0020.0002] [.0000.0025.0002] [*0209.0020.0002] [.1CA4.0020.0002]
ê 5 => [.1D10.0020.0002] [.0000.0027.0002] [*0209.0020.0002] [.1CA8.0020.0002]
e 2 => [.1D10.0020.0002] [*0209.0020.0002] [.1CA5.0020.0002]
é 4 => [.1D10.0020.0002] [.0000.0024.0002] [*0209.0020.0002] [.1CA7.0020.0002]
ē 3 => [.1D10.0020.0002] [.0000.0032.0002] [*0209.0020.0002] [.1CA6.0020.0002]
ë 6 => [.1D10.0020.0002] [.0000.002B.0002] [*0209.0020.0002] [.1CA9.0020.0002]

ステップ3、フォームのソートキー:

è 1 => 1D10 0209 1CA4 0000 0020 0025 0020 0020 0000 0002 0002 0002 0002
ê 5 => 1D10 0209 1CA8 0000 0020 0027 0020 0020 0000 0002 0002 0002 0002
e 2 => 1D10 0209 1CA5 0000 0020 0020 0020 0000 0002 0002 0002
é 4 => 1D10 0209 1CA7 0000 0020 0024 0020 0020 0000 0002 0002 0002 0002
ē 3 => 1D10 0209 1CA6 0000 0020 0032 0020 0020 0000 0002 0002 0002 0002
ë 6 => 1D10 0209 1CA9 0000 0020 002B 0020 0020 0000 0002 0002 0002 0002

手順4、並べ替えキーの比較:

基本的に3番目の値は順序を決定し、実際には最後の桁のみに基づいているため、順序は次のようになります。

è 1
e 2
ē 3
é 4
ê 5
ë 6

2回目の更新は、Unicodeバージョンに関するSolomon Rutzkyのコメントに基づいています。

今回は最新のUnicodeバージョン、つまりバージョン10.0に関するallkeys.txtデータを使用しました

代わりにUnicode 5.1を考慮する必要がある場合、これは次のようになります: http://www.unicode.org/Public/UCA /5.1.0/allkeys.txt

上記のすべての文字について、照合配列は次のようになりました。

e => [.119D.0020.0002.0065]
é => [.119D.0020.0002.0065] [.0000.0032.0002.0301]
ë => [.119D.0020.0002.0065] [.0000.0047.0002.0308]
è => [.119D.0020.0002.0065] [.0000.0035.0002.0300]
ê => [.119D.0020.0002.0065] [.0000.003C.0002.0302]
ē => [.119D.0020.0002.0065] [.0000.005B.0002.0304]

そして:

eA => [.119D.0020.0002.0065] [.1141.0020.0008.0041]
éB => [.119D.0020.0002.0065] [.0000.0032.0002.0301] [.1157.0020.0008.0042]
ëC => [.119D.0020.0002.0065] [.0000.0047.0002.0308] [.116F.0020.0008.0043]
èD => [.119D.0020.0002.0065] [.0000.0035.0002.0300] [.1182.0020.0008.0044]
êE => [.119D.0020.0002.0065] [.0000.003C.0002.0302] [.119D.0020.0008.0045]
ēF => [.119D.0020.0002.0065] [.0000.005B.0002.0304] [.11D5.0020.0008.0046]

そして:

è 1 => [.119D.0020.0002.0065] [.0000.0035.0002.0300] [*0209.0020.0002.0020] [.1138.0020.0002.0031]
ê 5 => [.119D.0020.0002.0065] [.0000.003C.0002.0302] [*0209.0020.0002.0020] [.113C.0020.0002.0035]
e 2 => [.119D.0020.0002.0065] [*0209.0020.0002.0020] [.1139.0020.0002.0032]
é 4 => [.119D.0020.0002.0065] [.0000.0032.0002.0301] [*0209.0020.0002.0020] [.113B.0020.0002.0034]
ē 3 => [.119D.0020.0002.0065] [.0000.005B.0002.0304] [*0209.0020.0002.0020] [.113A.0020.0002.0033]
ë 6 => [.119D.0020.0002.0065] [.0000.0047.0002.0308] [*0209.0020.0002.0020] [.113D.0020.0002.0036]

次に、次のソートキーを計算します。

e => 119D 0000 0020 0000 0002 0000 0065
é => 119D 0000 0020 0032 0000 0002 0002 0000 0065 0301
ë => 119D 0000 0020 0047 0000 0002 0002 0000 0065 0308
è => 119D 0000 0020 0035 0000 0002 0002 0000 0065 0300
ê => 119D 0000 0020 003C 0000 0002 0002 0000 0065 0302
ē => 119D 0000 0020 005B 0000 0002 0002 0000 0065 0304

そして:

eA => 119D 1141 0000 0020 0020 0000 0002 0008 0000 0065 0041
éB => 119D 1157 0000 0020 0032 0020 0000 0002 0002 0008 0000 0065 0301 0042
ëC => 119D 116F 0000 0020 0047 0020 0000 0002 0002 0008 0000 0065 0308 0043
èD => 119D 1182 0000 0020 0035 0020 0000 0002 0002 0008 0000 0065 0300 0044
êE => 119D 119D 0000 0020 003C 0020 0000 0002 0002 0008 0000 0065 0302 0045
ēF => 119D 11D5 0000 0020 005B 0020 0000 0002 0002 0008 0000 0065 0304 0046

そして:

è 1 => 119D 0209 1138 0000 0020 0035 0020 0020 0000 0002 0002 0002 0002 0000 0065 0300 0020 0031
ê 5 => 119D 0209 113C 0000 0020 003C 0020 0020 0000 0002 0002 0002 0002 0000 0065 0302 0020 0035
e 2 => 119D 0209 1139 0000 0020 0020 0020 0000 0002 0002 0002 0000 0065 0020 0032
é 4 => 119D 0209 113B 0000 0020 0032 0020 0020 0000 0002 0002 0002 0002 0000 0065 0301 0020 0034
ē 3 => 119D 0209 113A 0000 0020 005B 0020 0020 0000 0002 0002 0002 0002 0000 0065 0304 0020 0033
ë 6 => 119D 0209 113D 0000 0020 0047 0020 0020 0000 0002 0002 0002 0002 0000 0065 0308 0020 0036

これもまた、次の3つのソート結果を提供します。

e
é
è
ê
ë
ē

そして

eA
éB
ëC
èD
êE
ēF

そして

è 1
e 2
ē 3
é 4
ê 5
ë 6
13
Patrick Mevzek

ここで見られる動作は、一般的な意味で、 nicode照合アルゴリズム(UCA) が複雑なマルチレベルのソートを可能にするという事実によるものです。すなわち:

  1. 並べ替えは比較ではありません:

    2つの文字列が同じか異なるかを判断することは、非常に簡単です(特定のロケール/言語と一連の機密性が与えられている場合)。ただし、2つ以上の文字列の順序を決定することは非常に複雑になる可能性があります。

  2. 並べ替えは一連の手順で行われ、各手順は文字ごとではなく文字列全体に適用されます。

    1. 標準:基本文字のソート(アクセントや大文字小文字の違いに関係なく)
    2. アクセントを区別する場合は、アクセント/分音記号の重みを適用します
    3. 大文字と小文字を区別する場合は、ケーシングウェイトを適用します

_col1_(1文字)で並べ替えると、すべての文字が「e」であるため、すべての文字が同じ重みを持つと最初に判断されます。次に、アクセント/分音記号の重みを適用します。大文字と小文字の違いはないので、3番目のステップでは何も変更されません。したがって、唯一の違いはステップ2にあります。そのため、_col1_に基づいてこれらの行に優先順位があります。

_col2_(2文字)で並べ替えると、並べ替えの重みを決定するために両方の文字が使用されるため(たとえば、(ea "、"eb "など)。次に、アクセント/分音記号の重みを適用します。大文字と小文字の違いはないので、3番目のステップでは何も変更されません。したがって、今回はステップ1と2に違いがあります。ただし、ステップ2の重みが考慮される前に、ステップ1の違いはすでに各文字列に適用されているため、ステップ2の重みは順序に影響を与えません。 2つ以上の行のステップ1の重みが同じである場合にのみ適用されます。

質問からのサンプルコードの以下の適応は、うまくいけば、上記のソート動作を示しています。照合が大文字と小文字を区別する影響を示すために、いくつかの行と列を追加しました(元のサンプルデータはすべて小文字であるため)。

[〜#〜]セットアップ[〜#〜]

_USE [tempdb];

-- DROP TABLE dbo.OddSort;
CREATE TABLE dbo.OddSort
(
    id INT IDENTITY(1,1) PRIMARY KEY,
    col1 NVARCHAR(5) COLLATE Latin1_General_100_CS_AS,
    col2 NVARCHAR(5) COLLATE Latin1_General_100_CS_AS,
    col3 NVARCHAR(5) COLLATE Latin1_General_100_CS_AS
);
GO

INSERT dbo.OddSort (col1, col2, col3)
VALUES (N'e', N'eA', N'e A')
     , (N'ê', N'êE', N'ê E')
     , (N'é', N'éH', N'é H')
     , (N'ë', N'ëC', N'ë C')
     , (N'E', N'EG', N'E G')
     , (N'Ë', N'ëh', N'ë h')
     , (N'è', N'èD', N'è D')
     , (N'é', N'éB', N'é B')
     , (N'ë', N'ëH', N'ë H')
     , (N'ē', N'ēF', N'ē F');
_

テスト1

_SELECT [id], [col1], UNICODE([col1]) AS [CodePoint]
FROM dbo.OddSort 
ORDER BY col1;
_

戻り値:

_id    col1    CodePoint
1     e       101
5     E       69
8     é       233
3     é       233
7     è       232
2     ê       234
4     ë       235
9     ë       235
6     Ë       203
10    ē       275
_

上記の結果からわかること:

  1. コードポイントはソート順を決定していません
  2. アクセント記号のない文字は、アクセント記号付きの文字の前にソートされます(同じ文字内:fは、これらすべての後に引き続き続きます)。明らかに、アクセントの重みは、ケースの重みの前に適用されます。
  3. 小文字は、同じアクセント(または非アクセント)文字(つまり、e)内で大文字の前にソートされ、次に[〜#〜] e [〜#〜]、そしてëthenË)。この調整はほとんどのWindows照合で使用されますが、ほとんどのSQL Server照合は最初に大文字でソートされます。

テスト2

_SELECT [id], [col2]
FROM dbo.OddSort 
ORDER BY col2;
_

戻り値:

_id    col2
1     eA
8     éB
4     ëC
7     èD
2     êE
10    ēF
5     EG
3     éH
6     ëh
9     ëH
_

上記の結果からわかること:

  1. 第1レベルの並べ替えは、基本的な文字です。アクセント/発音区別符号の場合、ëC(id = 4)、ēF(id = 10)、および[〜#〜] eg [〜#〜](id = 5)行は、その場所にありません。大文字と小文字を区別する場合、[〜#〜] eg [〜#〜](id = 5)行は、その場所にありません。
  2. セカンドレベルの並べ替えは、アクセント/分音記号です。これにより、最後の3行がéH->ëh->である理由が説明されますëHの代わりにëh->éH->ëH(つまり、ID 3-> 6-> 9の代わりに6-> 3-> 9)。
  3. 3番目のレベルの並べ替えは、まさにケーシングです。これが、最後の2行がëh->ëHである理由です。小文字が最初にソートされます。

テスト3

_SELECT [id], [col3]
FROM dbo.OddSort 
ORDER BY col3;
_

戻り値:

_id    col3
1     e A
8     é B
4     ë C
7     è D
2     ê E
10    ē F
5     E G
3     é H
6     ë h
9     ë H
_

上記の結果からわかること:

  1. ソート順はテスト2とまったく同じです。ここでのテスト値の唯一の違いは、各文字の間にスペースがあり、コンテキストルールの可能性が排除されていることです。したがって、質問の_col2_の並べ替え順が異なる理由は、「マルチレベル比較」によるものであり、not「コンテキスト感度によるものです。 」.

その他の注意事項:

  1. 正確なルールを取得することに関しては、それは本来あるべきほど簡単ではありません。これらの規則の具体的な説明を取得する際の問題は、Unicodeの並べ替え規則は、明確に文書化されていますが、推奨事項であるということです。これらの推奨事項を実装するかどうかは、Microsoftなどのベンダー次第です。マイクロソフトは、Unicodeのドキュメントに記載されているとおりに推奨事項を実装しなかったため、そこに断線があります(HTML仕様もCSS仕様も完全に実装されておらず、ベンダー間でも同じように実装されていないのと同じです)。次に、異なるバージョンのWindows照合順序があり(SQL Server 2008に付属するバージョン_100_を使用しています)、現在のバージョンのUnicodeよりも古いUnicodeバージョンまたは- ICU Collat​​ion demo が使用しています。たとえば、SQL Server 2008の "照合順序とUnicodeサポート"ドキュメント)のSQL Server 2008 Collat​​ionsセクションの新機能 __100__シリーズの照合順序の「新機能」について、2つの非常に興味深い点を示します。

    1. Unicode 5.0ケーステーブル。

      Unicode 5.0は2006年7月に公開されました(まあ、そのときに文字データベースがリリースされ、完全な仕様は2006年後半に続きました)。現在のバージョンは10.0で、2017年6月に公開されました。過去4年間のリリースパターンを考えると、バージョン11.0は2018年半ばにリリースされる可能性があります。

    2. 同等に比較されるはずだった、以前は重み付けされていなかった文字に重み付けが追加されました。

      これらの重みは、Unicode標準で定義されている可能性が高いものであり、この実装ではありません。


    それでも、上にリンクされているUCAドキュメントは、開始するのに適した場所です。

  2. Windows/.NET/SQL Serverで使用されるソートキーは、Unicode標準(@Patrickの回答を参照)に示されているものと同じではないか、または [〜#〜] icu [〜#〜] に実装されています。 。 Windows/.NET/SQL Serverの使用状況を確認するには、 CompareInfo.GetSortKeyメソッド を試してください。これらの値を渡してソートキーを取得するために、SQLCLR UDFを作成しました。 .NET Framework 4.5-4.6.1がインストールされたWindows 10でSQL Server 2017を使用しているため、.NETはUnicode 6.0.0を使用する必要があることに注意してください。また、これらの文字列にはLevel4は使用されていません。

    _CHAR    L1     L2     L3     L4
    e      0E21
    E      0E21           12
    ë      0E21    13
    Ë      0E21    13     12
    _

    テスト1のこれらの並べ替えキーを見て、レベルが_ORDER BY_句内の複数の列のように並べ替えられている(L3はL2の同じ値内で並べ替えられ、L2は同じ値内で並べ替えられている)ことを理解すると、質問で指摘されている動作の理由は、実際にはUnicodeのマルチレベルのソート機能です。同様に:

    _CHAR       L1         L2       L3       L4
    EG      0E210E25              1212
    éH      0E210E2C      0E      0212
    ëh      0E210E2C      13
    ëH      0E210E2C      13      0212
    _

    テスト2の並べ替えキーのいくつかを見ると、最初に基本文字が並べ替えられ(L1)、次にアクセントが並べ替えられ(L2)、次に大文字小文字が並べ替えられます(L3)。

  3. データ型はNVARCHARであるため、Unicodeコードポイントと並べ替えアルゴリズムのみを考慮し、テスト1ではUNICODE()関数を使用しています。コードページはほとんどの照合順序で指定されていますが、 VARCHARデータのみに関係します。つまり、コードページ1252は_Latin1_General*_一連の照合順序によって指定されますが、ここでは無視できます。

  4. デフォルトのUnicode照合要素テーブル(DUCET)( バージョン5.0. は__100__シリーズ照合にマップする必要があります)に記載されている重みは、米国英語では問題ありませんが、他のロケール/言語では問題ありません。 。他の言語では、DUCETでstartを実行してから、Common Locale Data Repository(CLDR)プロジェクトで定義されているロケール固有のオーバーライドルールを適用する必要があります。私の知る限りでは、バージョン1.4/1.4.1は2006年にリリースされました。これらのオーバーライドを取得するには、CLDR 1.4「コア」ファイルを http://unicode.org/Public/cldr/1.4.0からダウンロードします。 /core.Zip 次に、そのZipファイルでcollat​​ionフォルダーに移動し、使用されているロケールに対応するXMLファイルを見つけます。これらのファイルにはオーバーライドのみが含まれており、照合規則の完全なセットではありません。

16
Solomon Rutzky