MySQLでは異なる字が等しいと見なされることがあるということを書きました。
この動きはMySQLが独自に変なことをしているわけではなく、Unicodeの規則に従っています。
MySQL 8.0 のデフォルトのCollationは Unicode 9.0.0のUnicode Collation Algorithm(UCA) に従っています。
文字にはそれぞれ Weight という値が設定されていてソートに使用されています。この値が同じなら等しい文字とみなされます。
Collation
utf8mb4 のデフォルトの Collation は utf8mb4_0900_ai_ci
という名前ですが、これは次のような意味です。
名前の要素 | 意味 |
---|---|
utf8mb4 |
charset名 |
0900 |
Unicodeバージョン9.0.0 |
ai |
アクセントの違いを無視 (Accent Insensitive) |
ci |
大文字小文字の違いを無視 (Case Insensitive) |
ai
は「a
」と「á
」が等しくなります。日本語の場合は「は
」「ぱ
」「ば
」が等しくなります。
ai
ではなく as
であれば異なる文字として扱われます。
ci
は「A
」と「a
」が等しくなります。日本語の場合は「あ
」「ぁ
」が等しくなります。
ci
ではなく cs
であれば異なる文字として扱われます。
utf8mb4 の Collation は次のようなものがあります。
utf8mb4_0900_ai_ci
- アクセント/大文字小文字の違いを無視。
utf8mb4_0900_as_ci
- アクセントが異なれば異なる文字。大文字小文字の違いは無視。
utf8mb4_0900_as_cs
- アクセント/大文字小文字が異なれば異なる文字。
utf8mb4_bin
- UnicodeのCollationを無視。すべて異なる文字。
以上を踏まえて、この前の記事であげた例を見てみます。
「令」と「令」
「令和
」(U+4EE4 U+548C)と「令和
」(U+F9A8 U+548C)が等しいと見なされるやつです。
Collationによって次のように評価されます。
Collation | = |
---|---|
utf8mb4_0900_ai_ci |
真 |
utf8mb4_0900_as_ci |
真 |
utf8mb4_0900_as_cs |
真 |
utf8mb4_bin |
偽 |
「令
」(U+F9A8)のWeightは DUCET(https://www.unicode.org/Public/UCA/9.0.0/allkeys.txt) に次のように定義されています。
F9A8 ; [.FB40.0020.0002][.CEE4.0000.0000] # CJK COMPATIBILITY IDEOGRAPH-F9A8
「令
」(U+4EE4)は定義されていませんが、http://www.unicode.org/reports/tr10/tr10-34.html#Implicit_Weights に計算方法が載っています。
これに従って計算すると、次のようになります。
[.FB40.0020.0002][.CEE4.0000.0000]
「令
」(U+4EE4)と「令
」(U+F9A8)のWeightは同じ値になるので、utf8mb4_bin
以外は等しいと評価されます。
合字
「㍻
」と「平成
」が等しいと見なされるやつです。
別に元号だけに限らず、「㌠
」と「サンチーム
」も同じです。
Collationによって次のように評価されます。
Collation | = |
---|---|
utf8mb4_0900_ai_ci |
真 |
utf8mb4_0900_as_ci |
真 |
utf8mb4_0900_as_cs |
偽 |
utf8mb4_bin |
偽 |
「㍻
」のWeightは DUCET(https://www.unicode.org/Public/UCA/9.0.0/allkeys.txt) に次のように定義されています。
337B ; [.FB40.0020.001C][.DE73.0000.0000][.FB40.0020.001C][.E210.0000.0000] # SQUARE ERA NAME HEISEI
「平
」と「成
」は定義されてませんが「令
」と同じく計算すると次のようになります。「㍻
」と似てますがちょっと異なります。
平成 [.FB40.0020.0002][.DE73.0000.0000][.FB40.0020.0002][.E210.0000.0000]
Unicode では大文字小文字を区別しない場合(ci)は [ ]
の中の3番目の数字を無視して評価することになってます。
そうすると次のようにまったく同じ値となります。
㍻ [.FB40.0020][.DE73.0000][.FB40.0020][.E210.0000] 平成 [.FB40.0020][.DE73.0000][.FB40.0020][.E210.0000]
ということで、ci の Collation では「㍻=平成
」となるのでした。
異体字セレクタ
「令和」(U+4EE4 U+548C)と「令󠄂和」(U+4EE4 U+E0102 U+548C) が等しいと見なされるやつです。
Collationによって次のように評価されます。
Collation | = |
---|---|
utf8mb4_0900_ai_ci |
真 |
utf8mb4_0900_as_ci |
真 |
utf8mb4_0900_as_cs |
真 |
utf8mb4_bin |
偽 |
U+E0102 は DUCET に次のように定義されています。
E0102 ; [.0000.0000.0000] # VARIATION SELECTOR-19
UCA ではすべてが 0 の値は無視することになってるので、異体字セレクタの U+E0102 は無視され、utf8mb4_bin
以外は等しいと評価されます。
LIKE
上のように =
での評価は UCA に従ってることで説明できるのですが、LIKE
での評価はよくわかりませんでした。
LIKE
ではすべての Collation で偽になります。
Collation | LIKE |
---|---|
utf8mb4_0900_ai_ci |
偽 |
utf8mb4_0900_as_ci |
偽 |
utf8mb4_0900_as_cs |
偽 |
utf8mb4_bin |
偽 |
MySQL のマニュアルには次のように書いてあります。
Per the SQL standard, LIKE performs matching on a per-character basis, thus it can produce results different from the = comparison operator:
SQL 標準では、LIKE は文字ごとに一致を実行するため、= 比較演算子とは異なる結果が生成される可能性があります。
文字数(コードポイント数)が異なる合字や異体字セレクタでは =
と LIKE
が異なるのはまあわかるんですが、
「令
」(U+4EE4)と「令
」(U+F9A8)は同じ1文字なのに LIKE
では一致しなくなるのが謎…。
(って書いておけば誰かがソース読んで解説してくれるんじゃないかと期待)