第51回nds postgresqlのデータ型 #nds51

57
PostgreSQL ののののの #nds51 @civic

Upload: civicpg

Post on 05-Apr-2017

242 views

Category:

Technology


6 download

TRANSCRIPT

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

使用例

本の情報 booksCREATE TABLE books( id integer PRIMARY KEY, name text, price integer, info jsonb);

INSERT INTO books VALUES(1, 'book1', 45745, '{"format": "text", "tags": ["english","game","kids"]}'::jsonb),

検索SELECT * FROM books WHEREinfo @> '{"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 2014

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

JavaApplication

JDBC Driverfor PostgreSQL

JDBCAPI

EclipseLinkJPA

PostgreSQL 特有の機能はネイティブなところまで

どうやって使うのか• ネイティブ対応しているレイヤーを使用

• JDBC for PostgreSQL• テキスト経由で受け渡し→ SQL で型変換• PostgreSQL方言

• ライブラリ側の対応次第

具体的に考えてみる• jsonb 型を Java / Python から利用

CREATE TABLE json_test( id integer, info jsonb)

id info1 {"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

PythonApplication

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