MySQLと「令和」その2

MySQLでは異なる字が等しいと見なされることがあるということを書きました。

tmtms.hatenablog.com

この動きはMySQLが独自に変なことをしているわけではなく、Unicodeの規則に従っています。

MySQL 8.0 のデフォルトのCollationは Unicode 9.0.0Unicode 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:

MySQL 5.6 の日本語マニュアルでは:

SQL 標準では、LIKE は文字ごとに一致を実行するため、= 比較演算子とは異なる結果が生成される可能性があります。

文字数(コードポイント数)が異なる合字や異体字セレクタでは =LIKE が異なるのはまあわかるんですが、 「」(U+4EE4)と「」(U+F9A8)は同じ1文字なのに LIKE では一致しなくなるのが謎…。

(って書いておけば誰かがソース読んで解説してくれるんじゃないかと期待)