この記事は Ruby on Rails Advent Calendar の 7日目の記事です。
この記事では、Ruby on Rails がサポートしている PostgreSQL の型を紹介した上で
その中でも特にマイナーな ltree 型について詳しく紹介します。
PostgreSQL で使える型
実は、Ruby on Rails では PostgreSQL 特有の多数の型を利用可能です。
例として、下記のような型が利用可能です。
型 | 概要 |
---|---|
daterange | 日付の範囲 |
numrange | Numeric 型の範囲 |
tsrange | Timestamp (Ruby の DateTime相当)の範囲 |
tstzrange | タイムゾーンつき Timestamp の範囲 |
int4range | integer(32bit 整数)の範囲 |
int8range | bigint(64bit 整数)の範囲 |
binary | 巨大なバイナリ文字列。内部的には bytea 型 |
xml | XML 型 |
tsvector | テキスト検索に最適化された文字列の型 |
hstore | キー、値の組合せを意味する型 |
inet | IPv4 アドレスまたはネットワーク |
cidr | IPv4 ネットワーク |
macaddr | Macアドレス |
uuid | RFC4122 で定義された Universally Unique Identifier |
json | JSON 型。入力されたテキストを内部的にそのまま保存する |
jsonb | JSON 型。入力されたテキストを解析して、バイナリ形式で保存する |
ltree | Label Tree を表現するデータ型 |
citext | 大文字、小文字を区別しないテキスト型 |
point | 平面における座標点。幾何データ型の1つ。 |
line | 無限長の直線。幾何データ型の1つ。 |
lseg | 線分。幾何データ型の1つ。 |
box | 矩形。幾何データ型の1つ。 |
path | 経路。幾何データ型の1つ。 |
polygon | 多角形。幾何データ型の1つ。 |
circle | 円。幾何データ型の1つ。 |
bit | 固定長のビット列データ型。limit: 5 なら長さ5のビット列。 |
bit_varying | 可変長のビット列データ型。 limit: 7 なら最大長 7 のビット列 |
money | 貨幣金額を固定精度の小数点で格納するデータ型 |
全部を網羅的に説明すると大変ですので、上記の中で特に誰にも知られてなさそうな ltree データ型を例に紹介します。
ltree
ltree とは
ltree とは、階層的なラベルデータを表現するデータ型です。
下記の例では、Top
が最上位で、 Top.Science
Top.Science.Astronomy
などがその子孫の
Label として存在しています。このように .
区切りで英数字の Label を並べることで、Label Path を表現します。
Ruby on Rails での ltree の利用
まず、ltree を利用可能なように準備します
$ bin/rails dbconsole
psql> CREATE EXTENSION ltree;
次にテーブルを作成します
$ bin/rails g model test path:ltree
インデックスを作るように編集します。
class CreateTests < ActiveRecord::Migration[5.0]
def change
create_table :tests do |t|
t.ltree :path
t.timestamps
end
add_index :tests, :path, using: :gist, name: "index_tests_path_gist"
add_index :tests, :path, using: :btree, name: "index_tests_path_btree"
end
end
t.ltree
と指定することで、ltree
型の列 path
を定義しています。
今回は GiSTインデックスと B-Tree インデックスの両方を設定しているので、インデックスの名前が重複しないうに、あえて name を指定しています。
$ bin/rake db:migrate:up VERSION=20161206150631
Running via Spring preloader in process 27159
== 20161206150631 CreateTests: migrating ======================================
-- create_table(:tests)
-> 0.0587s
-- add_index(:tests, :path, {:using=>:gist, :name=>"index_tests_path_gist"})
-> 0.0049s
-- add_index(:tests, :path, {:using=>:btree, :name=>"index_tests_path_btree"})
-> 0.0405s
== 20161206150631 CreateTests: migrated (0.1044s) =============================
次にレコードを追加します。
%w(
Top
Top.Science
Top.Science.Astronomy
Top.Science.Astronomy.Astrophysics
Top.Science.Astronomy.Cosmology
Top.Hobbies
Top.Hobbies.Amateurs_Astronomy
Top.Collections
Top.Collections.Pictures
Top.Collections.Pictures.Astronomy
Top.Collections.Pictures.Astronomy.Stars
Top.Collections.Pictures.Astronomy.Galaxies
Top.Collections.Pictures.Astronomy.Astronauts
).each do |path|
Test.create(path: path)
end
下記は、Top.Science の子孫のみを抽出する例です。
> bin/rails console
irb> Test.where("path <@ ?", "Top.Science").map &:path
Test Load (0.6ms) SELECT "tests".* FROM "tests" WHERE (path <@ 'Top.Science')
=> ["Top.Science", "Top.Science.Astronomy", "Top.Science.Astronomy.Astrophysics", "Top.Science.Astronomy.Cosmology"]
下記は、 Astronomy を経由する場合のみを抽出する例です。
irb> Test.where("path ~ ?", "*.Astronomy.*").map &:path
Test Load (1.2ms) SELECT "tests".* FROM "tests" WHERE (path ~ '*.Astronomy.*')
=> ["Top.Science.Astronomy", "Top.Science.Astronomy.Astrophysics", "Top.Science.Astronomy.Cosmology", "Top.Collections.Pictures.Astronomy", "Top.Collections.Pictures.Astronomy.Stars", "Top.Collections.Pictures.Astronomy.Galaxies", "Top.Collections.Pictures.Astronomy.Astronauts"]
簡単に、 Astronomy を経由するレコードを一度に取得することができました。
このように ltree を使うと、階層構造を簡単に扱うことができて、便利です。
まとめ
今回、Ruby on Rails では多数の PostgreSQL 特有の型が扱えることを紹介しました。
さらに、PostgreSQL 特有の型の中でも特にマイナーな ltree 型を例に詳しく使い方を紹介しました。
ltree 型を使うことで、階層的なデータに対して、子孫や祖先、共通の経路などの条件で、簡単に該当するレコードを取得できます。
参考文献
PostgreSQL ドキュメント 付録 F. 追加で提供されるモジュール F21 ltree
Rails Guide: ActiveRecord and PostgreSQL