[PostgreSQL] あんたは一体、何 length だい?
データベースにおける「文字数」の認識を調べてみました。
最初は仕事でもよく使っている PostgreSQL で検証します。
なおエンコーディング・照合は ja_JP.UTF-8
とします。
対象者
- データベースの設計をしている
- 文字コードの仕組みが気になる
- 絵文字おもしれー
みたいな方には興味がある内容かなと思います。
やってみた
Docker で postgres:16-alpine コンテナを起動し、接続クライアントには DBeaver を用いて実施しました。
確認した環境
-- SELECT VERSION()
PostgreSQL 16.2 on x86_64-pc-linux-musl, compiled by gcc (Alpine 13.2.1_git20231014) 13.2.1 20231014, 64-bit
JIS 水準シリーズ
まずは日本語ではおなじみのJIS第1水準などなど、日本語関連を調べてみる。
SELECT
-- ASCII
length('A') AS ascii,
-- ひらがな
length('あ') AS hiragana,
-- 半角カナ
length('ア') AS hankaku_kana,
-- 漢字
length('来') AS kanji,
-- JIS第2水準
length('來') AS jis2,
-- JIS第3水準
length('俠') AS jis3,
-- JIS第4水準
length('丂') AS jis4
結果は全部 1 文字として認識した。
ascii | hiragana | hankaku_kana | kanji | jis2 | jis3 | jis4 |
---|---|---|---|---|---|---|
1 | 1 | 1 | 1 | 1 | 1 | 1 |
※あとの画面ショットは省きます。
正規化形式シリーズ
ここが本命です。Windows/Linux ではおなじみの NFC と、UTF-8-MAC と言われたりする NFD での違いを見ます。ぱっと見で違いが分かりにくいのでバイト列ろ 16 進数でコメントに併記します。
SELECT
-- ASCII - 41
length('A') AS ascii_a,
-- 片仮名
length('ト') AS kana_to,
-- NFC / 正規化形式C / Windows や Linux
length('ド') AS nfc_do,
-- NFD / 正規化形式D / macOS UTF-8-MAC
length('ド') AS nfd_do,
-- NFC オングストローム
length('Å') AS nfc_a,
-- NFD オングストローム
length('Å') AS nfd_a
結果は、、、NFD だと 2 文字としてカウントしていますね。
ascii_a | kana_to | nfc_do | nfd_do | nfc_a | nfd_a |
---|---|---|---|---|---|
1 | 1 | 1 | 2 | 1 | 2 |
余談:オングストローム (長さの単位)
日本語な UTF-8 バイト列だとよく E3 などから始まる 3 バイトがメインになってくるので、違う例として極小単位の オングストローム (wikipedia) に登場してもらいました。
1 オングストローム = 0.1 ナノメートル = 100 ピコメートル。見えねぇ。
🐘 絵文字シリーズ
しっかり巷では市民権を得た絵文字について。
多用するとおじさん構文などと言われ兼ねないですが、、実験です。多用します。
SELECT
-- 絵文字(単文字)
length('🐘') AS emoji_elephant,
-- 絵文字(色変更) - 金髪
length('👱🏽') AS emoji_blond_haired,
-- 絵文字(組合せ)
length('👩👨👧👦') AS emoji_zero_width_joiner
結果は、7 文字と認識するケースも。
emoji_elephant | emoji_blond_haired | emoji_zero_width_joiner |
---|---|---|
1 | 2 | 7 |
まとめ
改めて結果を表にまとめます。
文字 | NOTE | length() | Unicode | UTF-8 バイト列 |
---|---|---|---|---|
A | ASCII | 1 | U+0041 | 41 |
あ | 平仮名 | 1 | U+3042 | E3 81 82 |
ト | 片仮名(NFC) | 1 | U+30C8 | E3 83 88 |
ド | 片仮名(NFC) | 1 | U+30C9 | E3 83 89 |
ド | 片仮名(NFD) | 2 | U+30C8 U+3099 | E3 83 88 E3 82 99 |
Å | 単位(NFC) | 1 | U+00C5 | C3 85 |
Å | 単位(NFD) | 2 | U+0041 U+030A | 41 CC 8A |
🐘 | 絵文字(単文字) | 1 | U+1F418 | F0 9F 90 98 |
👱🏽 | 絵文字(色変更) | 2 | U+1F46A U+1F3FD | F0 9F 91 B1 F0 9F 8F BD |
👩👨👧👦 | 絵文字(組合せ) | 7 | U+1F469 U+200D U+1F468 U+200D U+1F467 U+200D U+1F466 | F0 9F 91 A9 E2 80 8D F0 9F 91 A8 E2 80 8D F0 9F 91 A7 E2 80 8D F0 9F 91 A6 |
Word Joiner
組み合わせで作られた絵文字に登場する E2 80 8D
は単語結合子 (word joiner) と呼ばれる制御文字だそう。Unicode コードポイントでは U+200D
となる。
絵文字の仕様
「UTS #51 Unicode Emoji」として詳細にレポートがある。
NFD
Normalization Form Canonical Decomposition (NFD) では「ド」が「ト」+結合用の「゛」という風にコードポイントが分かれているのが分かる。
結合用については compart.com というサイトの表現分かりやすかった。
PostgreSQL は Unicode コードポイント数
まとめの表からも分かる通りですが、公式マニュアルで「文字数」と説明しているのは「Unicode で表したコードポイントの個数」ということになる。「見かけ上の文字数」ではない点に注意したい。
日本語翻訳版:
何に影響するか?
非ASCIIを許容する文字処理・マルチバイト処理において、とくに文字制限の要件があるとハマりやすいポイントになりそう。
-
length()
で文字数判定して分岐したり弾いたりする -
right()
やleft()
substring()
で文字列をカットする- 文字が分断されて文字化け、表示時に違う文字とくっついて認識される
- 100 文字まで許容だから
varchar(100)
としてカラム定義した- データ挿入・更新時に 100 文字に満たない状態で長さエラーで失敗する
- 例:
ERROR: value too long for type character varying
おわりに
ひとくちに「文字数」と表現しても、それを処理系(今回は PostgreSQL)がどう認識しているかをじっくり調べてみて色々と面白い発見になりました。特に Unicode の制御系が学びになった。これはこれで1つの記事になりそう。
MySQL や SQL Server では違う結果になるんでしょうか、折を見て調べたいですね。
それではまた!
Discussion