SQLiteでは、独特な方法で動的な型づけを行っている。CAST式の挙動を把握するために、いくつかのパターンで返される値を整理したいと思う。
SQLiteのCAST式の書き方
CAST式の書き方は、他のSQL標準のDBMSと変わらない。以下のようなものである。
CAST(列名 AS 型名)
型名に指定できる値は、SQL準拠のDBMSがサポートしているものであればなんでも良い。中には良くないものもあるかもしれないが、SQLiteでは型アフィニティという仕組みにより、自動で型名を判断し、SQLiteで使われている4つのストレージクラス(≒データ型)に変換する。
INTEGER
、REAL
、TEXT
、BLOB
がそれで、変換できないデータはBLOB
に分類されることになっている(分類の例は公式ドキュメントのここにある)。
データ型別に見るCAST式の返り値
CAST式の変換処理については、公式ドキュメントのここに記載がある。
ただ、自分の目で確かめた方が324929(ミニヨクツク)と思うので、以下のようなテーブルを用意してCAST式の挙動を確かめたい。
.header ON
.nullvalue (NULL)
.mode table
CREATE TABLE cast_test(original_value, sample_value);
-- 整数
INSERT INTO cast_test VALUES("1",1);
-- 整数に変換できる小数
INSERT INTO cast_test VALUES("1.0",1.0);
-- 整数に変換できない小数
INSERT INTO cast_test VALUES("1.1",1.1);
-- 整数にも小数にも変換できる文字列
INSERT INTO cast_test VALUES("'1.0'",'1.0');
-- 整数に変換できず、小数に変換できる文字列
INSERT INTO cast_test VALUES("'1.1'",'1.1');
-- 整数にも小数にも変換できない文字列
INSERT INTO cast_test VALUES("'壱'",'壱');
-- 文字と数が混ざった文字列1
INSERT INTO cast_test VALUES("'壱1'",'壱1');
-- 文字と数が混ざった文字列2
INSERT INTO cast_test VALUES("'壱1'",'壱1.0');
-- 文字と数が混ざった文字列3
INSERT INTO cast_test VALUES("'1壱'",'1壱');
-- 文字と数が混ざった文字列4
INSERT INTO cast_test VALUES("'1.0壱'",'1.0壱');
-- バイナリ
INSERT INTO cast_test VALUES("X'53514C697465'",X'53514C697465');
-- 空文字
INSERT INTO cast_test VALUES("''",'');
-- NULL
INSERT INTO cast_test VALUES("NULL",NULL);
ほいでは、さっそく実験を始める。
INTEGERへのキャスト結果
SELECT original_value,CAST(sample_value AS INTEGER) FROM cast_test;
+------------+-------------------------------+
| original_v | CAST(sample_value AS INTEGER) |
+------------+-------------------------------+
| 1 | 1 |
+------------+-------------------------------+
| 1.0 | 1 |
+------------+-------------------------------+
| 1.1 | 1 |
+------------+-------------------------------+
| '1.0' | 1 |
+------------+-------------------------------+
| '1.1' | 1 |
+------------+-------------------------------+
| '壱' | 0 |
+------------+-------------------------------+
| '壱1' | 0 |
+------------+-------------------------------+
| '壱1' | 0 |
+------------+-------------------------------+
| '1壱' | 1 |
+------------+-------------------------------+
| '1.0壱' | 1 |
+------------+-------------------------------+
| X'53514C69 | 0 |
| 7465' | |
+------------+-------------------------------+
| '' | 0 |
+------------+-------------------------------+
| NULL | (NULL) |
+------------+-------------------------------+
だいたい予想通りの結果である。先頭が数字で始まる文字はその数字が取得され、文字から始まるものは0
が返される点に注意したい。
また、小数点以下は切り捨てられて整数になる。例えば1.5
だろうが1.9999999
だろうが1
に丸められる。
REALへのキャスト結果
SELECT original_value,CAST(sample_value AS REAL) FROM cast_test;
+------------+----------------------------+
| original_v | CAST(sample_value AS REAL) |
+------------+----------------------------+
| 1 | 1.0 |
+------------+----------------------------+
| 1.0 | 1.0 |
+------------+----------------------------+
| 1.1 | 1.1 |
+------------+----------------------------+
| '1.0' | 1.0 |
+------------+----------------------------+
| '1.1' | 1.1 |
+------------+----------------------------+
| '壱' | 0.0 |
+------------+----------------------------+
| '壱1' | 0.0 |
+------------+----------------------------+
| '壱1' | 0.0 |
+------------+----------------------------+
| '1壱' | 1.0 |
+------------+----------------------------+
| '1.0壱' | 1.0 |
+------------+----------------------------+
| X'53514C69 | 0.0 |
| 7465' | |
+------------+----------------------------+
| '' | 0.0 |
+------------+----------------------------+
| NULL | (NULL) |
+------------+----------------------------+
小数点以下が切り捨てられない以外は、INTEGER
と同じ結果である。
NUMERICへのキャスト結果
SELECT original_value,CAST(sample_value AS NUMERIC) FROM cast_test;
+------------+-------------------------------+
| original_v | CAST(sample_value AS NUMERIC) |
+------------+-------------------------------+
| 1 | 1 |
+------------+-------------------------------+
| 1.0 | 1.0 |
+------------+-------------------------------+
| 1.1 | 1.1 |
+------------+-------------------------------+
| '1.0' | 1 |
+------------+-------------------------------+
| '1.1' | 1.1 |
+------------+-------------------------------+
| '壱' | 0 |
+------------+-------------------------------+
| '壱1' | 0 |
+------------+-------------------------------+
| '壱1' | 0 |
+------------+-------------------------------+
| '1壱' | 1 |
+------------+-------------------------------+
| '1.0壱' | 1 |
+------------+-------------------------------+
| X'53514C69 | 0 |
| 7465' | |
+------------+-------------------------------+
| '' | 0 |
+------------+-------------------------------+
| NULL | (NULL) |
+------------+-------------------------------+
NUMERIC
は、SQLiteの型アフィニティの1つで、浮動小数点数に解釈できるものはREAL
に、整数に解釈できるものはINTEGER
になる。はずだが、'1.0壱'
が1
になっている。
試しにSELECT CAST('1.1壱'AS NUMERIC);
を打ってみると、1.1
が返った。
イヤイヤながら、CAST式の説明をちゃんと読んでみた。NUMERIC
でキャストすると、「入力テキストが整数のように見え(小数点も指数もない)、値が64ビット符号付き整数に収まるほど小さい場合にはINTEGER
と解釈される」ようである。
小数点はあるが、1.0
は整数と等価として解釈されるらしい。
TEXTへのキャスト結果
SELECT original_value,CAST(sample_value AS TEXT) FROM cast_test;
+------------+----------------------------+
| original_v | CAST(sample_value AS TEXT) |
+------------+----------------------------+
| 1 | 1 |
+------------+----------------------------+
| 1.0 | 1.0 |
+------------+----------------------------+
| 1.1 | 1.1 |
+------------+----------------------------+
| '1.0' | 1.0 |
+------------+----------------------------+
| '1.1' | 1.1 |
+------------+----------------------------+
| '壱' | 壱 |
+------------+----------------------------+
| '壱1' | 壱1 |
+------------+----------------------------+
| '壱1' | 壱1.0 |
+------------+----------------------------+
| '1壱' | 1壱 |
+------------+----------------------------+
| '1.0壱' | 1.0壱 |
+------------+----------------------------+
| X'53514C69 | SQLite |
| 7465' | |
+------------+----------------------------+
| '' | |
+------------+----------------------------+
| NULL | (NULL) |
+------------+----------------------------+
文字列になるだけなので、大体予想通りである。
ここに来て、ようやくBLOB値(X'53514C69
)が置換された。この値は公式ドキュメントのリテラル値の項から拝借したものである。
BLOB
からTEXT
へのキャストに関しては、"BLOB値をTEXTにキャストするには、BLOBを構成するバイト列を、データベースのエンコーディングを使用してエンコードされたテキストとして解釈"とある。
また、対応するバイト列がエンコーディングにない場合は、空文字が返るようだ。
BLOBへのキャスト結果
SELECT original_value,CAST(sample_value AS BLOB) FROM cast_test;
+------------+----------------------------+
| original_v | CAST(sample_value AS BLOB) |
+------------+----------------------------+
| 1 | 1 |
+------------+----------------------------+
| 1.0 | 1.0 |
+------------+----------------------------+
| 1.1 | 1.1 |
+------------+----------------------------+
| '1.0' | 1.0 |
+------------+----------------------------+
| '1.1' | 1.1 |
+------------+----------------------------+
| '壱' | 壱 |
+------------+----------------------------+
| '壱1' | 壱1 |
+------------+----------------------------+
| '壱1' | 壱1.0 |
+------------+----------------------------+
| '1壱' | 1壱 |
+------------+----------------------------+
| '1.0壱' | 1.0壱 |
+------------+----------------------------+
| X'53514C69 | SQLite |
| 7465' | |
+------------+----------------------------+
| '' | |
+------------+----------------------------+
| NULL | (NULL) |
+------------+----------------------------+
試しにBLOB
にキャストしてみたら、TEXT
と結果が同じである。エンコーディングによって違いが出るそうだが、そもそもエンコーディングの知識が曖昧なのでよくわからない。
NULLへのキャスト結果
sqlite> SELECT original_value,CAST(sample_value AS NULL) FROM cast_test;
Parse error: near "NULL": syntax error
SELECT original_value,CAST(sample_value AS NULL) FROM cast_test;
error here ---^
NULL
へのキャストはパースエラーが出た。
SOMETHINGへのキャスト結果
SELECT original_value,CAST(sample_value AS SOMETHING) FROM cast_test;
+------------+---------------------------------+
| original_v | CAST(sample_value AS SOMETHING) |
+------------+---------------------------------+
| 1 | 1 |
+------------+---------------------------------+
| 1.0 | 1.0 |
+------------+---------------------------------+
| 1.1 | 1.1 |
+------------+---------------------------------+
| '1.0' | 1 |
+------------+---------------------------------+
| '1.1' | 1.1 |
+------------+---------------------------------+
| '壱' | 0 |
+------------+---------------------------------+
| '壱1' | 0 |
+------------+---------------------------------+
| '壱1' | 0 |
+------------+---------------------------------+
| '1壱' | 1 |
+------------+---------------------------------+
| '1.0壱' | 1 |
+------------+---------------------------------+
| X'53514C69 | 0 |
| 7465' | |
+------------+---------------------------------+
| '' | 0 |
+------------+---------------------------------+
| NULL | (NULL) |
+------------+---------------------------------+
謎の型へのキャストは、NUMERIC
へのキャストと同じ結果になった。この辺は型アフィニティに沿って解釈されるようだ。例えばINT
もINTEGER
と同じ結果になる。