PostgreSQLで2バイト文字が含まれるカラムかを調査する方法
これは
PostgreSQLで文字列型のカラムに 2バイト文字 が含まれるかを調査する方法です。まさかこんな調査が必要になるとは思っていなかったのですが、必要になったのでやりました。
TL;DR
以下をやるだけです。
charの場合
以下のようなクエリで 0なら2バイト無し です。
select count(*) from 対象テーブル
where octet_length(対象カラム) > 対象カラムの桁数;
varcharの場合
以下のようなクエリで 0なら2バイト無し です。
select count(*) from 対象テーブル
where octet_length(対象カラム) <> char_length(対象カラム);
検証
テストテーブルで試して問題なかったので、100万件を超える本番テーブルでやってもきっちり2バイトあり/なしを切り分けられました。
以下は検証でやったテストテーブル作成とクエリでの参照手順です。
テストテーブルの作成&テストデータのInsert
難しいことは考えずに、適当なテーブルを作ります。環境構築がめんどいのでDockerでやりましょう。
docker pullで公式イメージをDL(バージョン指定がしたければお好みで)
# docker pull postgres:10
10: Pulling from library/postgres
0a4690c5d889: Already exists
723861590717: Already exists
db019468bdf4: Already exists
91cb81a60371: Already exists
a2a4ab07588d: Already exists
a7ccdc2a5f31: Already exists
93687df2bb93: Already exists
f00839cf3313: Already exists
722253fa84b2: Pull complete
5fc834e81b3c: Pull complete
b5bfe36fd72d: Pull complete
b2733b8e24de: Pull complete
9f032a1876d3: Pull complete
5f1457b9fa28: Pull complete
Digest: sha256:70aadcd012d4d59d2ffd4fadcce4aaf1308b0eaee618536565158caf58d33f6a
Status: Downloaded newer image for postgres:10
pullしたらrunします。Docker HubのコマンドそのままでOKです。
docker run --name some-postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres
docker psするとpostgresの起動を確認できます。簡単!
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1cec48407fbf postgres "docker-entrypoint.s…" 7 minutes ago Up 7 minutes 5432/tcp some-postgres
$
起動したpostgresさんにログインします。
docker exec -it some-postgres /bin/bash
root@1cec48407fbf:/# <-コンテナの中でプロンプトがでる
コンテナ内でpsql打っていきましょう。postgresユーザから始めるのを忘れすに。
su - postgres
psql
postgres=# が出たら準備OK
あとはテスト用のクエリを打っていきます。
create table morihaya (hoge char(10) , fuga varchar(10));
insert into morihaya (hoge , fuga) values ('a','a');
insert into morihaya (hoge , fuga) values ('aa','aa');
insert into morihaya (hoge , fuga) values ('あ','あ');
insert into morihaya (hoge , fuga) values ('ああ','ああ');
insert into morihaya (hoge , fuga) values ('あa','あa');
insert into morihaya (hoge , fuga) values ('漢字','漢字');
問題なくスイスイINSERTされるはずです。
postgres=# create table morihaya (hoge char(10) , fuga varchar(10));
CREATE TABLE
postgres=#
postgres=# insert into morihaya (hoge , fuga) values ('a','a');
INSERT 0 1
postgres=# insert into morihaya (hoge , fuga) values ('aa','aa');
INSERT 0 1
postgres=# insert into morihaya (hoge , fuga) values ('あ','あ');
INSERT 0 1
postgres=# insert into morihaya (hoge , fuga) values ('ああ','ああ');
INSERT 0 1
postgres=# insert into morihaya (hoge , fuga) values ('あa','あa');
INSERT 0 1
postgres=# insert into morihaya (hoge , fuga) values ('漢字','漢字');
INSERT 0 1
postgres=#
ここまでで、検証の準備は整ったはずです。
char
char型の検証をしていきます。
select length(hoge) , char_length(hoge) , octet_length(hoge) from morihaya;
以下のような結果になりました。
- hoge列はchar(10)で作成している
- 3行目以降から2バイト文字を挿入している
これらの条件からもうお分かりですね?
char(x)で宣言したx以上のoctet_lengthが戻る列が2バイト文字 ということが分かります。
postgres=# select length(hoge) , char_length(hoge) , octet_length(hoge) from morihaya;
length | char_length | octet_length
--------+-------------+--------------
1 | 1 | 10
2 | 2 | 10
1 | 1 | 12
2 | 2 | 14
2 | 2 | 12
2 | 2 | 14
(6 rows)
つまり、char(x)より上のoctet_lengthが1件でも含まれていれば、それは2バイト文字を含む列であると言えるのです。 よって以下のSQLでchar列の2バイト文字の有無を確認できます。
select count(*) from 対象テーブル
where octet_length(対象カラム) > 対象カラムの桁数;
varchar
一方でvarcharはどうでしょうか。
select length(fuga) , char_length(fuga) , octet_length(fuga) from morihaya;
以下のような結果になりました。
postgres=# select length(fuga) , char_length(fuga) , octet_length(fuga) from morihaya;
length | char_length | octet_length
--------+-------------+--------------
1 | 1 | 1
2 | 2 | 2
1 | 1 | 3
2 | 2 | 6
2 | 2 | 4
2 | 2 | 6
(6 rows)
一見すると法則性が無いように見えますが、そうではありません。
- 2行目までは英数字のみ(2バイト文字はない)
- 3行目以降は2バイト文字を含む
- char_lengthとoctet_lengthが一致したりしなかったり?
そう、2バイト文字がない場合は char_length と octet_length が一致 します。
これを利用して以下のクエリで2バイト文字の有無を調査することができます。
select count(*) from 対象テーブル
where octet_length(対象カラム) <> char_length(対象カラム);
余談
dockerあるあるですが、使い終わったコンテナは潔くGood Byしましょう。Let’s immutableですし、Not Pets But Cattleです。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1cec48407fbf postgres "docker-entrypoint.s…" 32 minutes ago Up 32 minutes 5432/tcp some-postgres
$
$
$ docker stop some-postgres
docker stop some-postgres
$ docker ps
参考
- ひしだま’s ホームページ - PostgreSQL 文字列メモ
- このブログの日付
2003-07-19何ですけど普通にめっちゃ助けられたので、基礎技術はずっと役立つっていうのを体感させて頂きました、感謝
- このブログの日付
- postgresql.org/docs/9.6/functions-string
- 公式にも一覧がある(ただし2バイト文字の記述がない)
- 日本語のpostgresql.org/docs/9.6/functions-string
- postgresの日本語ドキュメントは精度が高いことで有名(個人の観測)
- Slack: postgres-jp
- 心底震えるような強いエンジニアもいるし、ゆるふわなエンジニアもいるしで怖くないですよ。気軽にJoin!!