やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

PostgreSQLでboolean型のカラムを持つレコードを取得するとt, fとなる

Rubyでpg gemを使って、postgresqlに接続してクエリの実行結果を見ていたところ、boolean型のカラムの戻り値がTRUEの場合にt、FALSEの場合にfとなる現象を確認しました。

CREATE TABLE test1 (a boolean, b text);
INSERT INTO test1 VALUES (TRUE, 'sic est');
INSERT INTO test1 VALUES (FALSE, 'non est');
SELECT * FROM test1;

 a |    b
---+---------
 t | sic est
 f | non est

最初はpg側で設定やオプションの指定をミスってるのかなと思ったのですが、そうではなくboolean型のカラムの戻り値がt, fとなるのはPostgreSQLの仕様のようでした。先ほど添付したSQLも以下のリンク先の公式ドキュメントから転載しました。

booleanのデータ型を出力する関数は例 8.2にあるように、常にtかfを出力します。

PostgreSQL 15.0文書: 論理値データ型

なぜboolean値をt, fと表示しているのか背景を調べてみたところ、メモリ領域を節約するためとのことでした。
tと表示するのではなくtrueと表示するためにはtと表示するのと比べて単純に4倍のメモリ領域が必要になります。内部では正しくboolean型の値を記録しているそうで、表示上のパフォーマンスを考慮した決定だったようです。

でもtrue, falseで取得したい

少なくともRubyでpg gem経由でデータを取得すると、boolean型の値はt, fとなってしまうため扱いが面倒です。

conn = PG.connect( dbname: 'samples' )
query = <<~EOS
  SELECT boolean_column FROM samples
EOS
res = conn.exec(query)

puts res.first
# { "boolean_column" => "t" }

GitHub - ged/ruby-pg: A PostgreSQL client library for Ruby

何か回避方法はないか調べてみたところ、明示的に型をキャストすることで、この問題を解決することが出来ました。 今回は文字列のtrue, falseにして欲しいので::textを使用して、text型にキャストさせます。

明示的なキャスト要求の構文は、CAST(x AS typename)、もしくは、x::typename式です。

CREATE CAST

conn = PG.connect( dbname: 'samples' )
query = <<~EOS
  SELECT boolean_column::text FROM samples
EOS
res = conn.exec(query)

puts res.first
# { "boolean_column" => "true" }

無事に期待していた値を取得することが出来ました。
他にも::booleanを試してみましたが、結果は変わらずt, fのままでした。
またCASE文を使って、明示的にboolean型に変換してみても、結果は変わりませんでした。

query = <<~EOS
  SELECT boolean_column::boolean FROM samples
EOS

# { "boolean_column" => "t" }
query = <<~EOS
  SELECT
    CASE
      WHEN boolean_column THEN TRUE
      ELSE FALSE
    END AS boolean_column
  FROM
    samples
EOS

# { "boolean_column" => "t" }

pg gemのexecが内部で呼び出しているPostgreSQLが提供しているPQexec関数を確認しましたが、boolean型の値をt, fとせずにそのまま表記させるようなオプション値はないようです。

www.postgresql.org

::textとするのが一番楽ですね。
Railsでこの現象に遭遇しないのはActiveRecordがパースする処理をしてくれているからのようです。

Rails/ActiveRecord/quoting.rb