PostgreSQLのデータ型
#nds51 @civic
自己紹介
• NDS管理人 @civic
• 最近は低温調理にハマっています
40歳超えたら
自己紹介は30秒以下にしろ!
今日話す内容
• PostgreSQL特有のデータ型の紹介
• プログラムからどのように利用するか
そういえば聞いたことがある・・・
「自分の知らない分野の話をすると非常に学びになる」
という
経緯
• 数値・文字列・日付ぐらいしか使ってないぞ
• ざっとドキュメントを眺めていた
• 使ったことのないデータ型がたくさんあるぞ
• 特にPostgreSQLはデータ型が多い
• もっとデータベースの機能を使ったほうが良いのでは
?
• 日付を8桁の文字で保持→DATE型
• ???→PostgreSQLが用意するデータ型
質問
• みなさんがメインで使っているRDBMS製品は?
• postgresql
• mysql
• sqlserver
• oracle
各種RDBMSが対応する
データ型を比較
データ型の対応を調べてみた
postgresql mysql sqlserver oracle
数値
文字列
日付
論理値
幾何データ
XML
JSON
バイナリ
....
思ったほど
大差なかった・・・
思ったより大差なかった・・・
• PostgreSQLにしかないと思っていた型
• 最近のバージョンではサポートしている
• 例:JSON
• postgresql 9.2
• mysql 5.7
• sqlserver 2016
PostgreSQL特有
ってほどでもないけど
面白そうなデータ型の紹介
PostgreSQL データ型
#nds51 @civic
特有ってほどでもないけど面白そうな
json / jsonb型
JSON(JavaScript Object Notation)
{
"foo": 123,
"bar": "Hello World",
"hoge": [1, 2, 3]
}
JSONデータ
json型
• postgresql 9.2以降
• mysql 5.7
• sqlserver 2016
• oracle チェック制約のみ
CREATE TABLE mytable(
id integer,
info json
);
jsonb型
• postgresql 9.4以降
• jsonをバイナリで保持
• 検索効率向上
• 各種関数・演算子のサポート
• インデックスサポート
CREATE TABLE mytable(
id integer,
info jsonb
);
使いどころ
使いどころ
• 入力によって柔軟な構造が必要なケース
• アドレス帳の追加属性とか?
JSONが使えるなら
1列でいいじゃん!
なんでもJSONにするのは危険
• 1つの列に複数の意味のデータを入れるべきではない
• 制約が使えない・外部キーが使えない
• クエリが複雑になる
• 検索や関連に使われないデータのみに使用したい
参考: PostgreSQLのアンチパターン : 何でもかんでもjsonに入れる
https://yakst.com/ja/posts/2445
使用例
本の情報 books
CREATE TABLE books(
id integer PRIMARY KEY,
name text,
price integer,
info jsonb
);
INSERT INTO books VALUES
(1, 'book1', 45745,
'{"format": "text", "tags": ["english","game","
検索
SELECT * FROM books
WHERE
info @> '{"format": "pdf"}'::jsonb;
formatがpdfの本を検索
@> 包含演算子
JSONパス・値を保持するか
感想
• textにjson入れるよりはJSON検証も行われる
• インデックスや各種関数、演算子が使えて便利
• id, key, valueだけのテーブル(EAV)よりマシか
• 外部キーは絶対マズい
• 検索条件に使うのは要件次第か
用法用量を守ろう
範囲型
範囲型
• 数値や日付の範囲を表すデータ型
• postgresql 9.2以降
• mysql
• sqlserver
• oracle
PostgreSQL特有!
start end
範囲型
• 数値
• int4range: 4バイト整数範囲
• int8range: 8バイト整数範囲
• numrange: 実数範囲
• 日付
• tsrange: 日付時刻範囲
• tszrange: 日付時刻範囲(タイムゾーンつき)
• daterange: 日付範囲
使いどころ
2列で持っていたデータを1列で
• from, to, start, end
• 開始よりも終了が大きいという整合性
境界が明確
• 3以上7以下(閉じた境界)
• '[3, 7]'::int4range
• 3以上7未満(開いた上限)
• '[3, 7)'::int4range
• 3以上(境界のない上限)
• '[3, ]'::int4range
3 7
3 7
3
重なりや含有をチェックする演算子
• 含有
'[10, 20]'::int4range @> 14
• 重なり
'[10, 20]'::int4range && '[15, 30]'::int4range
10 20
14
10 2015 30
インデックスもサポート
使用例
予約テーブル
• EXCLUDEの排他制約で予約の重複を防ぐ制約
CREATE TABLE reservation(
during daterange,
EXCLUDE USING GIST(during WITH &&)
);
INSERT INTO reservation
VALUES('[2017-03-10, 2017-03-12]');
INSERT INTO reservation
VALUES('[2017-03-11, 2017-03-13]');
--重なりがあるのでエラー
↑duringの重なりの排他
めっちゃ便利!
2つのデータ型を紹介しました
プログラムから
どう利用するか?
プログラミング言語から
• プログラミング言語からDBを使うときには様々な
ライブラリを通している
• DBアクセス用API
• ORマッパーなどの便利ライブラリ
• 標準APIは各種DBの最小公倍数的な機能
• JDBC APIで提供されるデータ型にJSONはない
Javaの例
PostgreSQL
Java
Application
JDBC Driver
for PostgreSQL
JDBC
API
EclipseLinkJPA
PostgreSQL特有の機能は
ネイティブなところまで
どうやって使うのか
• ネイティブ対応しているレイヤーを使用
• JDBC for PostgreSQL
• テキスト経由で受け渡し→SQLで型変換
• PostgreSQL方言
• ライブラリ側の対応次第
具体的に考えてみる
• jsonb型をJava / Pythonから利用
CREATE TABLE json_test(
id integer,
info jsonb
)
id info
1 {"a": 100, "b": "Hello"}
2 {"a": 200, "b": "World"}
Javaの場合
https://github.com/civic/postgresql-java-json
検証ケース
• SELECTとINSERTについて考える
• 低レベルAPI → JDBC API
• 高レベルAPI → JPA(永続化標準API)
JDBC API (低レベルAPI)
resultSet.getString("info");
// {"a": 100, "b": "Hello"}
SELECT
ResultSet#getStringで文字列として取得可能
JDBC API (低レベルAPI)
INSERT
PGobjectを使用。 ※postgresqlドライバの機能
import org.postgresql.util.PGobject;
PGobject pgobj =new PGobject();
pgobj.setValue(json_string); //json文字列
pgobj.setType("jsonb");
preparedStatement.setObject(1, pgobj);
Java的にはベンダー製パッケージのimportは
ちょっと抵抗がある
JDBC API (低レベルAPI)
INSERT
SQL文内でキャスト
PreparedStatement ps =conn.prepareStatement(
"INSERT INTO json_test(info) VALUES(jsonb(?))"
);
ps.setString(1, json_string); //json文字列
JPA (高レベルAPI)
SELECT / INSERT
Converterを作成すれば
SQLデータ型とJavaデータ型を相互変換できる
@Entity
@Table(name = "json_test")
public class JsonTest implements Serializable {
// ... 中略 ...
@Column(name = "info")
@Convert(converter = JsonbConverter.class)
private String info;
Pythonの場合
https://github.com/civic/postgresql-python-json
Pythonの例
PostgreSQL
Python
Application
psycopg2DB API
SQLAlchemy
PostgreSQL特有の機能は
ネイティブ対応なところまで
libpq
検証ケース
• SELECTとINSERTについて考える
• 低レベルAPI → psycopg2
• 高レベルAPI → SQLAlchemy
psycopg2 (低レベルAPI)
cur.execute(
"SELECT info FROM json_test WHERE id=1"
)
row = cur.fetchone()
print(row[0])
# {'b': 'Hello', 'a': 100}
SELECT
何もしなくても、dict(辞書型)として取得できる
psycopg2 (低レベルAPI)
INSERT
psycopg2.extras.Jsonを使用し、dict→Json
from psycopg2.extras import Json
#json objectで更新
cur.execute(
"INSERT INTO json_test(info) VALUES(%s)",
[
Json({"a":30, "b": "update"})
]
)
psycopg2 (低レベルAPI)
INSERT
SQL文内でキャスト。
import json
# dictを文字列化してSQLに渡す
cur.execute(
"INSERT INTO json_test(info) VALUES(jsonb(%s))",
[
json.dumps({"a":30, "b": "update"}) //dict→str
]
)
SQLAlchemy (高レベルAPI)
SELECT / INSERT
SQLAlchemyは至れり尽くせり。
各DBベンダー用方言(dialect)が用意されている。
from sqlalchemy.dialects.postgresql import JSONB
class JsonTest(Base):
__tablename__ = 'json_test'
id = Column(Integer, primary_key=True)
info = Column(JSONB) # JSONB型の列と宣言
...
まとめ
まとめ
• PostgreSQL特有のデータ型が便利そう
正規化を失わないように
• プログラミング言語からもpg特有の型は使えそう
• Java
• 標準APIのままでは使いにくい
• ベンダー実装の機能を使うか型変換で
• Python
• ダックタイピングできるのでJavaほど厳しくない
• SQLAlchemy便利
参考
• PostgreSQL 9.6.2文書
• PGCon2014 JSONB データ型を使ってみよう
• Oracle® Database SQL言語リファレンス
• MySQL 5.7 Reference Manual
• データ型 (Transact-SQL) - MSDN - Microsoft
• PostgreSQLのアンチパターン : 何でもかんでもjsonに入れる
• JPA 2.1 の 新機能 Converter まとめ
• Psycopg - PostgreSQL database adapter for Python

第51回NDS PostgreSQLのデータ型 #nds51

Editor's Notes

  • #6 例えば日付型を8桁の文字で保持するより、DATE型で保持したほうがよい ・不正な日付データ ・日付用の関数 ・データサイズ 同じように自分がよく知らなかったデータ型もどんどん使えばいいのでは?
  • #9 ポスグレにはこんなにたくさんのデータ型があるんだよ!って言いたかったんだけど
  • #13 あらためてタイトルの紹介
  • #16 PostgreSQLはいち早く対応したが、最近のバージョンではmysql, sqlserverでもjsonが使えるようです。 Oracleは文字列データとして扱うがJSONとして検証するためのCHECK制約だけがあるっぽい。
  • #17 こっちのほうがいいです 特殊なケースを除いてjson → jsonbでOK
  • #23 本の情報を保存しよう!
  • #25 1列に複数の情報は入れるべきではない。原則 柔軟な補足データを格納する場合に検討。 ・EAVパータン ・ジェイウォークパターン
  • #30 開始<終了という挿入ルールだったり、CHECK制約があったはず。
  • #32 betweenでやってたこと
  • #45 getStringでJSON文字列が取得できる
  • #46 JDBC APIにはJSON型はない。 JDBCの提供するインターフェース経由で使うのが通常。PostgreSQL提供のドライバの機能を使うしかない。 アプリケーションのどこでも書くのではなく、共通ライブラリなど限定された箇所で使うべき。
  • #47 文字列としてsetStringするので、標準APIのみで可能。
  • #48 JPAという仕様で、Converterが定義されているのでjsonb型ーStringの変換ができる
  • #50 Pythonの場合はORマッパー的なライブラリは標準APIになっていない
  • #53 Javaのときと同じようにPostgreSQL用ドライバが提供する機能でJson型をサポート
  • #54 Javaのときと同じようにJSON文字列を渡して、SQL文内で型変換する。
  • #55 カスタムデータタイプの機能が予めDBベンダー別に用意済み。