【環境】
CentOS 6/PostgreSQL 8.4
CentOS 7/PostgreSQL 9.2
例えば、文字列型のカラムにて、文字列と数値が混じったデータの場合にソートすると、次のようになる。
db=> SELECT * FROM sample_data ORDER BY os;
os
-----------------
Mac OS X 10.12
Mac OS X 10.13
Mac OS X 10.9
Windows NT 10.0
Windows NT 6.3
いい感じにソートするには、少し工夫する。
db=> SELECT * FROM sample_data
ORDER BY
SUBSTRING(os FROM '^[A-Za-z ]+'),
TO_NUMBER(SUBSTRING(os FROM '[0-9.]+$'), '9999999999');
os
-----------------
Mac OS X 10.9
Mac OS X 10.12
Mac OS X 10.13
Windows NT 6.3
Windows NT 10.0
考え方としては、以下の通り。
・SUBSTRING を使い、ソートするカラムの先頭から英字を抜き出して、ソートの第一条件とする。
・SUBSTRING を使い、ソートするカラムの後ろから数値とドット(小数点)を抜き出して、to_numberで適当な桁数(上記では10桁)のnumeric型にキャストして、ソートの第二条件とする。
ソートの条件をSELECTで抽出してみると、どうなっているか分かりやすい。
db=> SELECT
os,
SUBSTRING(os FROM '^[A-Za-z ]+'),
TO_NUMBER(SUBSTRING(os FROM '[0-9.]+$'), '9999999999')
FROM
sample_data;
os | substring | to_number
------------------+-------------+-----------
Windows NT 10.0 | Windows NT | 100
Windows NT 6.3 | Windows NT | 63
Mac OS X 10.12 | Mac OS X | 1012
Mac OS X 10.9 | Mac OS X | 109
Mac OS X 10.13 | Mac OS X | 1013
ここに、'Mac OS X 10.12.1' というデータが入ってくると、うまくいかない(´・ω・`)
db=> SELECT * FROM sample_data
ORDER BY
SUBSTRING(os FROM '^[A-Za-z ]+'),
TO_NUMBER(SUBSTRING(os FROM '[0-9.]+$'), '9999999999');
os
------------------
Mac OS X 10.9
Mac OS X 10.12
Mac OS X 10.13
Mac OS X 10.12.1
Windows NT 6.3
Windows NT 10.0
バージョンの区切りになっているドットの出現回数が違うのが原因。
この場合、出現回ごとに桁数を揃えて(ドットで区切って0詰めする)、ソート条件に追加すると意図したようにソートされる。
db=> SELECT * FROM sample_data
ORDER BY
SUBSTRING(os FROM '^[A-Za-z ]+'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 1), 3, '0'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 2), 3, '0'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 3), 3, '0');
os
------------------
Mac OS X 10.9
Mac OS X 10.12
Mac OS X 10.12.1
Mac OS X 10.13
Windows NT 6.3
Windows NT 10.0
試しに、'Mac OS X 11.0' や 'Mac OS X 9.99' という架空のデータを入れて試してみる。
db=> SELECT * FROM sample_data
ORDER BY
substring(os from '^[A-Za-z ]+'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 1), 3, '0'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 2), 3, '0'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 3), 3, '0');
os
------------------
Mac OS X 9.99
Mac OS X 10.9
Mac OS X 10.12
Mac OS X 10.12.1
Mac OS X 10.13
Mac OS X 11.0
Windows NT 6.3
Windows NT 10.0
いい感じでソートされる。
ソート条件がどうなっているか、SELECTで抽出してみると、桁毎に0詰めになっている事が確認できる。
db=> SELECT
os,
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 1), 3, '0'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 2), 3, '0'),
LPAD(SPLIT_PART(SUBSTRING(os FROM '[0-9.]+$'), '.', 3), 3, '0')
FROM sample_data;
os | lpad | lpad | lpad
------------------+------+------+------
Windows NT 10.0 | 010 | 000 | 000
Windows NT 6.3 | 006 | 003 | 000
Mac OS X 10.12 | 010 | 012 | 000
Mac OS X 10.9 | 010 | 009 | 000
Mac OS X 10.13 | 010 | 013 | 000
Mac OS X 10.12.1 | 010 | 012 | 001
Mac OS X 11.0 | 011 | 000 | 000
Mac OS X 9.99 | 009 | 099 | 000
但し、前半に文字列があり、後半に数字(ドットも可)がある、という前提で、バージョン部分が「x.y.z」というフォーマットしか判別できない。
使える場面が限られるけど、とりあえずメモ。
UserAgentから取り出したOSのソートには使えそう。
参考;
https://www.postgresql.jp/document/9.2/html/functions-string.html
https://www.postgresql.jp/document/9.2/html/functions-formatting.html