照合順序 (コレーション)

文字列の比較・並べ替えの規則。言語や文化圏によって異なるソート順を定義する。

照合順序 (コレーション) とは、文字列の比較や並べ替えに使用される規則の集合です。同じ文字でも言語や文化圏によってソート順が異なるため、国際化対応のシステムでは正しいコレーション設定が不可欠です。たとえばドイツ語では「ö」は「o」とほぼ同じ位置に並びますが、スウェーデン語では「å」「ä」「ö」が「z」の後ろに続く独立した文字として扱われます。

データベースでは、テーブルやカラムごとにコレーションを指定できます。MySQL の utf8mb4_unicode_ci は大文字小文字を区別しない比較を行い、utf8mb4_bin はバイナリ比較を行います。コレーションの選択を誤ると、検索結果の漏れやソート順の不具合が発生します。たとえば utf8mb4_bin では「A」と「a」が別の文字として扱われるため、ユーザー名の検索で大文字小文字の違いにより見つからないケースが起こり得ます。名前に含まれる ci は大文字小文字を区別しない、ai はアクセントを区別しない、csas はそれぞれ区別する、という意味です。ただし同じ ci でも中身は同一ではありません。utf8mb4_0900_ai_ci は UCA (Unicode 照合アルゴリズム) 9.0.0 の重みに基づき、UCA 4.0.0 に基づく utf8mb4_unicode_ci より新しい規則です。両者は末尾の空白の扱いも異なり、新しい側は NO PAD として「a」と「a 」を別の値と判定します。既存テーブルのコレーションを移行すると、それまで同じ値と見なされていた行が別物になり、ユニーク制約や検索結果が変わる点に注意が必要です。

日本語のソートは特に複雑です。ひらがな・カタカナの同一視 (「あ」と「ア」)、濁点・半濁点の扱い (「は」「ば」「ぱ」の順序)、漢字をどう並べるかなど、多くの規則が絡み合います。Unicode の CLDR (Common Locale Data Repository) がこれらの規則をロケール別に整備しており、ICU ライブラリを通じて各プログラミング言語から利用できます。かなの種類の違いは重みの 4 段目で判定される扱いのため、既定の設定ではひらがなとカタカナが同じものとして並びます。MySQL の日本語向けコレーションも同様で、utf8mb4_ja_0900_as_cs はカタカナとひらがなを同一視し、区別したい場合は 4 段の重みを使う utf8mb4_ja_0900_as_cs_ks を選びます。さらに厄介なのが漢字です。氏名や書名を読み順に並べたくても、漢字の並びから読みは一意に決まりません (「山崎」は「やまざき」とも「やまさき」とも読みます)。漢字のまま並べると「山崎」が「川口」より前に来ますが、読みのカナで並べれば「川口」が先です。読み順が要件なら、カナの読みを別項目として保持し、その列で並べるのが確実です。

JavaScript では Intl.Collator を使ってロケールに応じた文字列比較ができます。ここで例として押さえておきたいのが new Intl.Collator('ja').compare('あ', 'ア') の結果で、これは 0 (等しい) を返します。前述のとおりかなの違いは最も下の重みで扱われるため、強度の指定を上げても区別されない実装があります。ひらがなとカタカナを別扱いにしたいなら、比較の前にどちらかへ寄せて正規化するなど、コレーションの外側で対処します。単純な String.localeCompare() でも同様の比較ができますが、大量のデータをソートする場合は Intl.Collator のインスタンスを 1 つ作って再利用するほうがパフォーマンスに優れます。{ numeric: true } を渡すと「file2」が「file10」より前に並ぶ数値順の比較になり、連番を含む名前のソートで役立ちます。

文字数カウントの観点では、コレーションは文字の等価性に影響します。「は」と「ば」を同一視するかどうか、全角数字「1」と半角数字「1」を同じとみなすかどうかは、コレーション設定によって変わります。JavaScript の Intl.Collator は日本語ロケールで「1」と「1」を等しいと判定しますが、MySQL の utf8mb4_bin では別の文字です。同じデータでも、重複の判定件数がアプリ側と DB 側で食い違うのはこのためです。集計や重複排除を行う前に、どの差異を無視するかを決め、無視すると決めた差異は保存時に正規化しておくと、後段の数え方がぶれません。

この記事を共有