Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.
SQLAlchemy 入門
for Kobe Python Meetup #13
2017/09/15 Kobe Japan
Yasushi Masuda PhD
( @whosaysni )
Tech team, Core IT grp. IT Dept.
MonotaRO Co., LTD.
Pythonista since 2001 (2.0~)
• elaph...
アジェンダ
よくある誤解
SQLAlchemyを3行で



エンジンの基礎 (+hands-on)
SQL式を使う (+hands-on)
ORMの基礎 (+hands-on)
参考文献
オンラインドキュメント:

http://docs.sqlalchemy.org/en/
rel_1_1/



(古いけど)和訳:

http://omake.accense.com/
static/doc-ja/sqlalchem...
準備
sakila DB SQLite版
https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/
Sakila
• MySQL エンジニア
作のサンプルDB
• レ...
よくある誤解
[WRONG!] ORMライブラリですよね?

むしろ「DB操作フレームワーク」と考えましょう
[WRONG!] でもORMベースだよね?

SAのキモはコネクション管理とSQL式のフレームワークです。ORMがその上に乗ってます
...
DB
プログラム
SQL
の生成
クエリの

実行
データ

型変換
パラメタ付き
クエリ
DBドライバ
セットアップ
接続管理
(cache, pool)
結果データ
へのアクセス
値の

エスケープ
スキーマ名の
管理
データ型変換
SQL...
SQLAlchemyを
3行で
エンジン: DB接続を管理して、DB間のSQLの
違いや操作方法の違いを吸収する
SQL式: カラムやテーブルをPythonのオブジェ
クトで表現して、SQL文をPythonの式の組み
合わせで書ける
ORM: テーブルをPythonクラスに対...
SQLAlchemy のドキュメント
SQLAlchemy のドキュメント
SQLAlchemy Core
エンジン
スキーマ定義

SQL 式
SQLAlchemy ORM
O/Rマッパー
宣言的O/Rマッピング
セッション
Dialect
DBバックエンドごとの定義
エンジン Engine
DB接続を管理する仕組み
SQL式 SQL Expression

SQLをPythonの式で表現する仕組み
マッパー mapper

DBレコードをPythonオブジェクトに対応付ける仕組み
ダイアレクト(方言) Di...
エンジン
使ってみましょう
• エンジンを生成してみましょう
• SQLite版のSakila databaseに接続して、簡単なクエリを
実行してみましょう
準備
sakila DB SQLite版 http://bit.ly/2fdeeft
https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/sqlite-sakil...
エンジンを生成してみよう
from	sqlalchemy	import	create_engine

e	=	create_engine('sqlite:///sqlite-sakila.sq')

#	str(e)	してみましょう

#	di...
SQLite以外のDB
#	インメモリ

create_engine('sqlite://')

#	MySQL

create_engine(

			'mysql://scott:tiger@dbserv/dbname')

#	SQL	S...
ちなみにDB-APIでは
#	sqlite3

>>>	from	sqlite3	import	connect

>>>	con	=	connect('sqlite-sakila.sq')

#	mysql

>>>	from	MySQLdb	...
クエリを実行してみよう
>>>	e	=	create_engine('sqlite:///sqlite-sakila.sq')

>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.exe...
ちなみにDB-APIでは
#	DBAPIごとに挙動がまちまち

#	sqlite3

>>>	con.execute('select	*	from	film')

<sqlite3.Cursor	object	at	...>

#	mysql
...
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	for	row	in	res:

...					print(r...
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	for	row	in	res:

...					print(r...
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	for	row	in	res:

...					print(r...
ResultProxyにアクセスしよう
>>>	q	=	'select	title	from	film	limit	5'

>>>	res	=	e.execute(q)

>>>	list(res)		#	1レコード1タプルのリストになる

[...
update/insertを試そう
#	操作の前に、sqlite-sakila.sq	をバックアップ

してください!



>>>	res	=	e.execute('insert	into	film	(film_id,	
title,	lan...
パラメタクエリを使おう
#	非推奨(だけどよくある)書き方

param	=	1

e.execute('select	title	from	film	where	
film_id=%s'	%(param)).fetchall()

[('AC...
パラメタクエリを使おう
#	文字列挿入だとインジェクションできてしまう

>>>	param	=	'0	union	select	password	as	title	from	staff'

>>	e.execute('select	title...
パラメタクエリを使おう
e.execute('select	title	from	film	where	
film_id=?',	'0	union	select	password	as	title	
from	staff').fetchall(...
トランザクションを使おう
>>>	conn	=	e.connect()

>>>	trans	=	conn.begin()

>>>	trans

<sqlalchemy...RootTransaction	object	at	...>

#	...
トランザクションを使おう
>>>	conn	=	e.connect()

>>>	trans	=	conn.begin()

>>>	conn.execute('select	*	from	film	where	
film_id>9000')....
トランザクションを使おう
#	with	を使うと、成功なら	commit,	失敗なら	
rollback	をシンプルに書ける

>>>	with	e.begin()	as	conn:

...					conn.execute('update	...
ハンズオン: エンジン基礎
sqlite-sakila.sq	に

エンジンで接続

"DINOSAUR	SECRETARY"

という作品の	actor	
を検索しましょう
ここまでのまとめ
• create_engine() でエンジンを作れる
• engine.execute(query, *parameters) でクエリを実行
• 実行結果はResultProxyオブジェクト

アトリビュート、配列、辞書で...
SQL 式
エンジン: DB接続を管理して、DB間のSQLの
違いや操作方法の違いを吸収する
SQL式: カラムやテーブルをPythonのオブジェ
クトで表現して、SQL文をPythonの式の組み
合わせで書ける
ORM: テーブルをPythonクラスに対...
俺の考えたさいきょうの...
• SELECT foo, bar, baz ... が

select(foo, bar, baz ...)

みたいに書ければ便利じゃない?
• WHERE foo='blue' AND bar=42 が

w...
SQLAlchemyではこうなる
• select([col1, col2, ...], ...)
• column('foo')=='blue' & column('bar')==42
• t = table('mytable', colum...
書いてみよう
• SELECT 文を書いてみましょう
• SQLite版のSakila databaseに接続して、簡単なクエリを
実行してみましょう
こんなモデルで
film (作品)
-------------------------
film_id PK
title タイトル
description 説明
release_year 年
language_id 言語
original_lang...
こんなクエリを書いてみましょう
• 料金 (rental_rate) 3ドル以下で借りられる
• 作品 (film) のタイトル(title) と料金を知りたい
SQLで表すと...
film (作品)
-------------------------
film_id PK
title タイトル
description 説明
release_year 年
language_id 言語
original_l...
text()	を使ってみましょう
text()	は	SQL	式の一部を生SQLで書きたい時に使える

>>>	from	sqlalchemy.sql	import	select,	text

>>>	q	=	select([text('titl...
from	を指定して実行してみましょう
>>>	from	sqlalchemy.sql	import	select,	text

>>>	q	=	select([text('title')],

...												from_obj=...
テーブルやカラムを表す

オブジェクトを使いましょう
>>>	from	sqlalchemy.sql	import	column,	table

>>>	q	=	select([column('title')],

...											...
カラムとテーブルを結びつけましょう
>>>	film	=	table('film',

...														column('film_id'),

...														column('title'),

...			...
where	rental_rate	<	3.0	を表現しましょう
>>>	column('rental_rate')	<	3.0

<sqlalchemy...BinaryExpression	object	at	...>

>>>	cond	...
generativeインタフェースを使いましょう
>>>	q	=	film.select(film.c.rental_rate<3.0)

>>>	print(q)

SELECT	film.title,	film.rental_rate	

...
テーブル結合を表現しましょう
>>>	film_category	=	table('film_category',

...					column('film_id'),	column('category_id'))

...

>>>	on	...
ここまでのまとめ
• table() や column() を使ってテーブルやカラムを表せる
• カラム定義つきで tbl=table(...) を作ると、tbl.c.colname でカラムオブ
ジェクトにアクセスできる
• colname ...
ハンズオン:こんなクエリを書いてみましょう
• ジャンル名 (category.name) が Animation で
• 料金 (rental_rate) 3ドル以下で借りられる
• 作品 (film) のタイトル(title) と料金を知りたい
こんなモデルで
film (作品)
-------------------------
film_id PK
title タイトル
description 説明
release_year 年
language_id 言語
original_lang...
実現例

(他にも書き方があります。試してみて!)
>>>	category	=	table('category',

...																		column('category_id'),

...														...
スキーマ定義
エンジンチョットデキル
スキーマ定義チョットデキル
ORMチョットデキル
現在の習熟状況
今ココ
SQL式チョットデキル
エンジンチョットデキル
スキーマ定義チョットデキル
ORMチョットデキル
SQL式チョットデキル
SA expertへの道
上級編
エンジンの
仕組み
上級編
上級編
上級編
SQL式の
仕組み
mapper/sessionの
仕組み
SQL式...
エンジンチョットデキル
スキーマ定義チョットデキル
ORMチョットデキル
SQL式チョットデキル
SA expertへの道
上級編
エンジンの
仕組み
上級編
上級編
上級編
SQL式の
仕組み
mapper/sessionの
仕組み
SQL式...
スキーマ定義
スキーマ定義って何
• table() や column() との違い:

テーブルやスキーマの属性を設定できる

カラムのデフォルト値とか、インデクスとか、制約とか

定義に基づいてCREATE TABLEできる
• クエリを書くだけならいら...
使ってみましょう
• テーブル定義を書いてみましょう
• テーブル定義からSQL式を作ってみましょう
• CREATE TABLEを生成してみましょう
location (商品棚)
---------------------------------
location_id INTEGER 棚番号
store_id INTEGER 店舗ID
last_update DATETIME 最終更新
こ...
from	sqlalchemy	import	Column,	MetaData,	Table

from	sqlalchemy	import	TIMESTAMP,	INTEGER

location	=	Table(

				'locatio...
#	基本は	table()	で作ったものと同じ

>>>	str(location.select())

'SELECT	location.location_id,	location.store_id,	
location.last_updat...
なぜまぎらわしい同じインタフェースなのか
Visitable (base type)
ClauseElement
Selectable
FromClause
[ table() ]
ColumnElement
ColumnClause
[ co...
なぜまぎらわしい同じインタフェースなのか
Visitable (base type)
ClauseElement
Selectable
FromClause
[ table() ]
ColumnElement
ColumnClause
[ co...
>>>	location.exists(bind=e)		#	テーブル存在チェック

False

>>>	location.create(bind=e)		#	CREATE	TABLE	実行

>>>	location.exists(bind...
>>>	from	sqlalchemy	import	Index

>>>	f	=	Table('film',	MetaData(),	Column...)	

>>>	Index('k_title',	f.c.title)

>>>	

>>...
location (商品棚)
---------------------------------
location_id INTEGER 棚番号
store_id INTEGER 店舗ID
last_update DATETIME 最終更新
ハ...
ここまでのまとめ
• Table() や Column() でテーブルを定義すると、DDL を
実行できる

カラムの制約やインデクスも貼れる
• Table() や Column() は、 table(), column() と同じよう
に扱...
さて、やっと

ORM basics
ORMって何だろう
• オブジェクトとリレーショナル(DB) のマップ (対応付け)

DBの内容をデータオブジェクトとして扱えるよう頑張る存在
• ActiveRecord パターンでの実現が華やか
• 1テーブルを1クラス、1レコードを1オ...
SQLAlchemyのORMで覚えること
• ORMで頑張っているのはマッパー (mapper) とセッショ
ン (session)
• マッピングは古典的なのと近代的なのがある

裏側で使っている仕組みは同じ
• ORMでオブジェクトを操作す...
マッピング
Tableオブジェクト
Column foo
Column bar
Column baz
...
エンティティクラス
魔改造されたエンティティ
仕掛けの付いた foo
仕掛けの付いた bar
仕掛けの付いた baz
...
meth...
classic mapping

古いやつ昔からあるやつ

新しいやつを支えている

古の魔法 = よくわからんから避けられる



declarative mapping

新しいやつ 書きやすい 流行の宣言的API

古の魔法x現代の魔法=...
>>>	from	sqlalchemy.orm	import	mapper

>>>	actor_tbl	=	Table(...)

>>>	class	Actor(object):	pass

>>>	mapper(Actor,	actor_...
使ってみましょう
• 宣言的ORMの方法でテーブル定義を書いてみましょう
• セッションを作ってORMを操作しましょう
>>>	from	sqlalchemy.ext.declarative	import	
declarative_base

>>>	from	sqlalchmy	import	DateTime,	Integer,	
String

>>>	Ba...
#	ベースクラスを拡張するとORMで扱えるモデルクラスになる



class	Actor(Base):

				__tablename__	=	'actor'

				actor_id	=	Column(Integer,primary_k...
#	見かけは普通のクラス

>>>	Actor

<class	'__main__.Actor'>

#	定義したフィールドと得体の知れない属性が追加される

>>>	dir(Actor)

['__class__',	...	'_decl_c...
>>>	actor	=	Actor()

>>>	actor

>>>	vars(actor_obj)

{'_sa_instance_state':	
<sqlalchemy...InstanceState	object	at	...>}

...
セッションを理解しよう
• セッション=トランザクションのようなもの

エンジンのDB接続一つが対応している

通常、セッションの持続中は、他のプログラムは

セッションが捕まえている接続にアクセスできない
• オブジェクトの読み出し:SELE...
session
Engine
Program
query A
select A
record Aobject A
start

tracking A...
(updates) flag A as
"dirty"
reflect new/dirty
...
セッションを使ってみましょう
#	セッションを作るクラスを	sessionmaker	で作る

#	使うエンジンが一定ならこのとき指定する

>>>	from	sqlalchemy.orm	import	sessionmaker

>>>	Se...
クエリを生成してみましょう
>>>	query	=	session.query(Actor)

>>>	query

<sqlalchemy.orm.query.Query	object	at	...>

#	str(query)	してみましょ...
セッション中でオブジェクトを取り出そう
#	first(),	get(pk),	スライスなどの操作で

#	インスタンス	(の列)	を返す

>>>	my_actor	=	query.first()

>>>	my_actor

<...Act...
セッション中でオブジェクトを取り出そう
>>>	q	=	session.query(Actor)

>>>	q.first()

<...Actor	object	at	...>

>>>	q.all()		#	WARNING:	SELECTs...
フィルタでクエリを絞込みしよう
>>>	q	=	session.query(Actor)

>>>	q1	=	q.filter(Actor.first_name=='PENELOPE')

>>>	str(q1)

'SELECT	...	nF...
オブジェクトを追加・更新しよう
#	新規オブジェクトを	session	に	add()	すると

#	INSERT	対象になる

>>>	session	=	Session()

>>>	actor_a	=	Actor(first_name='...
ここまでのまとめ
• SAのORMは、宣言的 (declarative) にモデルを定義する
ことで使える
• ORM 下のオブジェクトを操作するにはセッションを使う
• DBレコードからオブジェクトを生成して取り出すには、
セッションでクエリ...
ハンズオン: ORM
• category テーブルをORMで定義
してみましょう
• セッションを作って、Category
のモデルのクエリを実行しましょ
う
• Nature というカテゴリを追加し
ましょう。
• NatureをGeogr...
以上です。
おつかれさまでした!
おまけ:今日お話していないこと
• エンジン: コネクション管理・コネクションプールの話

SQLiteでマルチスキーマDBぽく使う話
• スキーマ定義: マイグレーションの話

inspection/reflection でDBからスキーマ定義...
Próxima SlideShare
Cargando en…5
×

PlaySQLAlchemy: SQLAlchemy入門

6.645 visualizaciones

Publicado el

神戸Pythonの会での SQLAlchemy 入門ハンズオンの資料です。
座学パートの多いSQLAlchemy Primerよりもハンズオンに重点を置いて、日本語で書き直しました。

Publicado en: Ingeniería

PlaySQLAlchemy: SQLAlchemy入門

  1. 1. SQLAlchemy 入門 for Kobe Python Meetup #13 2017/09/15 Kobe Japan
  2. 2. Yasushi Masuda PhD ( @whosaysni ) Tech team, Core IT grp. IT Dept. MonotaRO Co., LTD. Pythonista since 2001 (2.0~) • elaphe (barcode library) • oikami.py (老神.py) • PyCon JP founder 翻訳もろもろ
  3. 3. アジェンダ よくある誤解 SQLAlchemyを3行で
 
 エンジンの基礎 (+hands-on) SQL式を使う (+hands-on) ORMの基礎 (+hands-on)
  4. 4. 参考文献 オンラインドキュメント:
 http://docs.sqlalchemy.org/en/ rel_1_1/
 
 (古いけど)和訳:
 http://omake.accense.com/ static/doc-ja/sqlalchemy/
  5. 5. 準備 sakila DB SQLite版 https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/ Sakila • MySQL エンジニア 作のサンプルDB • レンタルビデオ屋
 (...通じます?) のモ デル • BSD ライセンス スキーマのドキュメントは下記 https://dev.mysql.com/doc/sakila/en/sakila-structure-tables.html (上記のサイトのSQLiteデータベースバイナリは壊れているので、以下から取得してください) https://github.com/wallymathieu/sakila-sample-database-ports/blob/master/sqlite-sakila-db/sqlite-sakila.sq
  6. 6. よくある誤解 [WRONG!] ORMライブラリですよね?
 むしろ「DB操作フレームワーク」と考えましょう [WRONG!] でもORMベースだよね?
 SAのキモはコネクション管理とSQL式のフレームワークです。ORMがその上に乗ってます
 
 [WRONG!] テーブル定義めんどそう。あと生SQL書いたりできないでしょ?
 テーブル定義をしなくても使えます。生SQLの実行も問題ありません。
 
 [WRONG!] SA 使うと値をエスケープしてくれるから安全ですね
 値をエスケープしているのはDBAPIです。
 
 [WRONG!] SAは高度すぎて初心者に向かない
 SQLAlchemy は難しくないです。むしろ必要なのはSQLとDBの知識です。 [AGREE] ドキュメントよくわからん
 そうですね
  7. 7. DB プログラム SQL の生成 クエリの
 実行 データ
 型変換 パラメタ付き クエリ DBドライバ セットアップ 接続管理 (cache, pool) 結果データ へのアクセス 値の
 エスケープ スキーマ名の 管理 データ型変換 SQLの方言 クエリ構築 クエリの実行 クエリ結果 スキーマ構造の
 定義 O/R マッピング DB操作の抽象化・高水準化 DB操作 セッション DB操作にまつわる関心事
  8. 8. SQLAlchemyを 3行で
  9. 9. エンジン: DB接続を管理して、DB間のSQLの 違いや操作方法の違いを吸収する SQL式: カラムやテーブルをPythonのオブジェ クトで表現して、SQL文をPythonの式の組み 合わせで書ける ORM: テーブルをPythonクラスに対応付け て、インスタンスの操作でレコードをいじれる
  10. 10. SQLAlchemy のドキュメント
  11. 11. SQLAlchemy のドキュメント SQLAlchemy Core エンジン スキーマ定義
 SQL 式 SQLAlchemy ORM O/Rマッパー 宣言的O/Rマッピング セッション Dialect DBバックエンドごとの定義
  12. 12. エンジン Engine DB接続を管理する仕組み SQL式 SQL Expression
 SQLをPythonの式で表現する仕組み マッパー mapper
 DBレコードをPythonオブジェクトに対応付ける仕組み ダイアレクト(方言) Dialect DB バックエンドごとの違いを表現する仕組み
  13. 13. エンジン
  14. 14. 使ってみましょう • エンジンを生成してみましょう • SQLite版のSakila databaseに接続して、簡単なクエリを 実行してみましょう
  15. 15. 準備 sakila DB SQLite版 http://bit.ly/2fdeeft https://github.com/jOOQ/jOOQ/jOOQ-examples/Sakila/sqlite-sakila-db/sqlite-sakila.sq スキーマのドキュメントは下記 https://dev.mysql.com/doc/sakila/en/sakila-structure-tables.html (上記のサイトのSQLiteデータベースバイナリは壊れているので、以下から取得してください) https://github.com/wallymathieu/sakila-sample-database-ports/blob/master/sqlite-sakila-db/sqlite-sakila.sq
  16. 16. エンジンを生成してみよう from sqlalchemy import create_engine
 e = create_engine('sqlite:///sqlite-sakila.sq') # str(e) してみましょう
 # dir(e) してみましょう
 # help(e) してみましょう

  17. 17. SQLite以外のDB # インメモリ create_engine('sqlite://') # MySQL create_engine( 'mysql://scott:tiger@dbserv/dbname') # SQL Server create_engine( 'mssql://bill:gates@dbserv/dbname')
  18. 18. ちなみにDB-APIでは # sqlite3
 >>> from sqlite3 import connect
 >>> con = connect('sqlite-sakila.sq')
 # mysql
 >>> from MySQLdb import connect
 >>> con = connect(db='sakila', user='...')

  19. 19. クエリを実行してみよう >>> e = create_engine('sqlite:///sqlite-sakila.sq') >>> q = 'select title from film limit 5' >>> res = e.execute(q) >>> res <sqlalchemy....ResultProxy object at ...> >>> # dir(res) してみましょう # list(res) してみましょう
  20. 20. ちなみにDB-APIでは # DBAPIごとに挙動がまちまち # sqlite3
 >>> con.execute('select * from film')
 <sqlite3.Cursor object at ...> # mysql
 >>> # con.execute がない。カーソルが必要
 >>> cur = con.cursor()
 >>> cur.execute('select * from film')
 1000L
  21. 21. ResultProxyにアクセスしよう >>> q = 'select title from film limit 5' >>> res = e.execute(q)
 >>> for row in res: ... print(row.title) # カラム名のアトリビュート ...
 ACADEMY DINOSAUR ACE GOLDFINGER ADAPTATION HOLES AFFAIR PREJUDICE AFRICAN EGG
  22. 22. ResultProxyにアクセスしよう >>> q = 'select title from film limit 5' >>> res = e.execute(q)
 >>> for row in res: ... print(row[0]) # n 番目のカラム ...
 ACADEMY DINOSAUR ACE GOLDFINGER ADAPTATION HOLES AFFAIR PREJUDICE AFRICAN EGG
  23. 23. ResultProxyにアクセスしよう >>> q = 'select title from film limit 5' >>> res = e.execute(q)
 >>> for row in res: ... print(row['title']) # 辞書アクセス ...
 ACADEMY DINOSAUR ACE GOLDFINGER ADAPTATION HOLES AFFAIR PREJUDICE AFRICAN EGG
  24. 24. ResultProxyにアクセスしよう >>> q = 'select title from film limit 5' >>> res = e.execute(q)
 >>> list(res) # 1レコード1タプルのリストになる [(1, 'ACADEMY DINOSAUR', 'A Epic Drama of a Feminist And a Mad Scientist ...]
 
 # res.fetchall(), res.fetchone(), res.fetchmany(),
 # res.keys() を実行してみましょう
 # dir(res) してみましょう
  25. 25. update/insertを試そう # 操作の前に、sqlite-sakila.sq をバックアップ
 してください!
 
 >>> res = e.execute('insert into film (film_id, title, language_id, last_update) values (9990, "HOGE", 1, CURRENT_TIME)')
 >>> res.rowcount # 更新した行を返す
 1
 
 # "update set language_id=99 where
 # language_id=1" も試してみましょう
  26. 26. パラメタクエリを使おう # 非推奨(だけどよくある)書き方 param = 1
 e.execute('select title from film where film_id=%s' %(param)).fetchall()
 [('ACADEMY DINOSAUR',)]

  27. 27. パラメタクエリを使おう # 文字列挿入だとインジェクションできてしまう
 >>> param = '0 union select password as title from staff'
 >> e.execute('select title from film where film_id=%s' % (param)).fetchall() [('8cb2237d0679ca88db6464eac60da96345513964',), ('ACADEMY DINOSAUR',)]
 ∵ SQLiteは最終的に以下のクエリを実行する
 select title from film where film_id=0
 union
 select password as title from staff
  28. 28. パラメタクエリを使おう e.execute('select title from film where film_id=?', '0 union select password as title from staff').fetchall() [] # 何もヒットしない ∵ SQLiteは最終的に以下のクエリを実行する
 select title from film where film_id='0 union select ... staff # パラメタクエリの値のエスケープは、
 # SQLAlchemyでなくSQLiteの機能
  29. 29. トランザクションを使おう >>> conn = e.connect()
 >>> trans = conn.begin()
 >>> trans
 <sqlalchemy...RootTransaction object at ...> # dir(trans) してみましょう
 # begin() して update, insert したあとで trans.rollback() してみましょう
  30. 30. トランザクションを使おう >>> conn = e.connect()
 >>> trans = conn.begin()
 >>> conn.execute('select * from film where film_id>9000').fetchall()
 [(9990, 'HOGE HOGE', ...)]
 >>> conn.execute('delete from film where film_id>9000').rowcount # レコードを削除。2行消えた
 2
 >>> conn.execute('select * from film where film_id>9000').fetchall() # 消えたので見当たらない
 []
 >>> trans.rollback() # rollback でなかったことにする
 >>> # ロールバックしたのでもとに戻っている
 >>> list(conn.execute('select * from film where film_id>9000'))
 [(9990, 'HOGE HOGE', ..)]
  31. 31. トランザクションを使おう # with を使うと、成功なら commit, 失敗なら rollback をシンプルに書ける >>> with e.begin() as conn:
 ... conn.execute('update ...')
 ... conn.execute
  32. 32. ハンズオン: エンジン基礎 sqlite-sakila.sq に
 エンジンで接続 "DINOSAUR SECRETARY"
 という作品の actor を検索しましょう
  33. 33. ここまでのまとめ • create_engine() でエンジンを作れる • engine.execute(query, *parameters) でクエリを実行 • 実行結果はResultProxyオブジェクト
 アトリビュート、配列、辞書でアクセスできる
 result.fetchall() や list(result) で一括取得もできる • engine.connect().begin() でトランザクションを作れる
 begin() でトランザクション接続を開ける
  34. 34. SQL 式
  35. 35. エンジン: DB接続を管理して、DB間のSQLの 違いや操作方法の違いを吸収する SQL式: カラムやテーブルをPythonのオブジェ クトで表現して、SQL文をPythonの式の組み 合わせで書ける ORM: テーブルをPythonクラスに対応付け て、インスタンスの操作でレコードをいじれる
  36. 36. 俺の考えたさいきょうの... • SELECT foo, bar, baz ... が
 select(foo, bar, baz ...)
 みたいに書ければ便利じゃない? • WHERE foo='blue' AND bar=42 が
 where(foo='blue', bar=42) みたいに書けないかな? • t = table('mytable', foo, bar, baz) みたいに書けば、 t.foo='blue', t.bar=42 みたいに表現できるよね。 • 作った式をクエリに変換して実行できると嬉しい
  37. 37. SQLAlchemyではこうなる • select([col1, col2, ...], ...) • column('foo')=='blue' & column('bar')==42 • t = table('mytable', column('foo'), column('bar'), ...)
 select([t]).where(t.c.foo=='blue', t.c.bar==42) • query = select([t]).where(t.c.bar==42)
 engine.execute(query).fetchall()
  38. 38. 書いてみよう • SELECT 文を書いてみましょう • SQLite版のSakila databaseに接続して、簡単なクエリを 実行してみましょう
  39. 39. こんなモデルで film (作品) ------------------------- film_id PK title タイトル description 説明 release_year 年 language_id 言語 original_language_id rental_rate 貸出料金 length 視聴時間 replacement_cost 原価 rating 対象年齢 last_update 最終更新
  40. 40. こんなクエリを書いてみましょう • 料金 (rental_rate) 3ドル以下で借りられる • 作品 (film) のタイトル(title) と料金を知りたい
  41. 41. SQLで表すと... film (作品) ------------------------- film_id PK title タイトル description 説明 release_year 年 language_id 言語 original_language_id rental_rate 貸出料金 length 視聴時間 replacement_cost 原価 rating 対象年齢 last_update 最終更新 SELECT title, rental_rate FROM film WHERE rental_rate < 3.0

  42. 42. text() を使ってみましょう text() は SQL 式の一部を生SQLで書きたい時に使える >>> from sqlalchemy.sql import select, text >>> q = select([text('title')]) >>> q <sqlalchemy.....Select at ...; Select object> >>> str(q) 'SELECT title'
  43. 43. from を指定して実行してみましょう >>> from sqlalchemy.sql import select, text >>> q = select([text('title')], ... from_obj=text('film')) >>> str(q) 'SELECT title nFROM film' >>> from sqlalchemy create_engine >>> e = create_engine('sqlite://sqlite-sakila.sq') >>> e.execute(q).fetchall() [('ACADEMY DINOSAUR',), ('ACE GOLDFINGER',), ('ADAPTATION HOLES',)...]
  44. 44. テーブルやカラムを表す
 オブジェクトを使いましょう >>> from sqlalchemy.sql import column, table >>> q = select([column('title')], ... from_obj=table('film')) >>> str(q) 'SELECT title nFROM film'
  45. 45. カラムとテーブルを結びつけましょう >>> film = table('film', ... column('film_id'), ... column('title'),
 ... column('rental_rate')) >>> q = select([film.c.title, film.c.rental_rate]) >>> str(q) 'SELECT film.title, film.rental_rate nFROM film'
  46. 46. where rental_rate < 3.0 を表現しましょう >>> column('rental_rate') < 3.0 <sqlalchemy...BinaryExpression object at ...> >>> cond = column('rental_rate') < 3.0 >>> str(cond) 'rental_rate < :rental_rate_1' >>> str(film.c.rental_rate < 3.0) 'film.rental_rate < :rental_rate_1' >>> q = select([film.c.title], ... whereclause=(film.c.rental_rate<3.0))
 ...
 >>> print(q) SELECT film.title FROM film WHERE film.rental_rate < :rental_rate_1
  47. 47. generativeインタフェースを使いましょう >>> q = film.select(film.c.rental_rate<3.0) >>> print(q) SELECT film.title, film.rental_rate FROM film WHERE film.rental_rate < :rental_rate_1 >>> q = film.select().where(film.c.rental_rate<3.0)
 >>> print(q) # どうなりましたか? >>> q = film.select(film.c.rental_rate<3.0) 
 ... .order_by(film.c.title)
 ...
 >>> print(q) # どうなりましたか?
  48. 48. テーブル結合を表現しましょう >>> film_category = table('film_category', ... column('film_id'), column('category_id')) ... >>> on = (film.c.film_id==film_category.c.film_id)
 >>> j = film.join(film_category, onclause=on)
 >>> str(j) 'film JOIN film_category ON film.film_id = film_category.film_id' >>> query = select([film.c.title], from_obj=j) ... .where(film_category.c.category_id==1) >>> print(query) # どうなりましたか?
  49. 49. ここまでのまとめ • table() や column() を使ってテーブルやカラムを表せる • カラム定義つきで tbl=table(...) を作ると、tbl.c.colname でカラムオブ ジェクトにアクセスできる • colname と何かを比較すると条件式オブジェクトになる • select() にカラム(やテーブル)の列を指定するとSELECT文を表すオブ ジェクトになる • select() や テーブルのメソッドを呼び出して generative に式を作れる • engine.execute(sql式) でクエリを実行できる
  50. 50. ハンズオン:こんなクエリを書いてみましょう • ジャンル名 (category.name) が Animation で • 料金 (rental_rate) 3ドル以下で借りられる • 作品 (film) のタイトル(title) と料金を知りたい
  51. 51. こんなモデルで film (作品) ------------------------- film_id PK title タイトル description 説明 release_year 年 language_id 言語 original_language_id rental_rate 貸出料金 length 視聴時間 replacement_cost 原価 rating 対象年齢 last_update 最終更新 category (ジャンル) ------------------------- category_id PK name ジャンル名 last_update 最終更新 film_category 
 ------------------------- film_id 作品ID category_id カテゴリID last_update 最終更新
  52. 52. 実現例
 (他にも書き方があります。試してみて!) >>> category = table('category', ... column('category_id'), ... column('name')) >>> f, fc, cat = film, film_category, category >>> j = f.join(fc, ... onclause=(f.c.film_id==fc.c.film_id)) ... .join(cat, ... onclause=(cat.c.category_id== ... fc.c.category_id)) ... >>> query = select([f.c.title, f.c.rental_rate], from_obj=j) ... .where(category.c.name=='Animation')) ... >>> e..execute(query).fetchall() [('ALTER VICTORY', 0.99), ('ANACONDA CONFESSIONS', 0.99), ...]
  53. 53. スキーマ定義
  54. 54. エンジンチョットデキル スキーマ定義チョットデキル ORMチョットデキル 現在の習熟状況 今ココ SQL式チョットデキル
  55. 55. エンジンチョットデキル スキーマ定義チョットデキル ORMチョットデキル SQL式チョットデキル SA expertへの道 上級編 エンジンの 仕組み 上級編 上級編 上級編 SQL式の 仕組み mapper/sessionの 仕組み SQL式の 仕組み
  56. 56. エンジンチョットデキル スキーマ定義チョットデキル ORMチョットデキル SQL式チョットデキル SA expertへの道 上級編 エンジンの 仕組み 上級編 上級編 上級編 SQL式の 仕組み mapper/sessionの 仕組み SQL式の 仕組み 結構使える 誰得レベル 充分使えるちょっと便利! ←挫折する人のパターン
  57. 57. スキーマ定義
  58. 58. スキーマ定義って何 • table() や column() との違い:
 テーブルやスキーマの属性を設定できる
 カラムのデフォルト値とか、インデクスとか、制約とか
 定義に基づいてCREATE TABLEできる • クエリを書くだけならいらないですよね?
 基本のSQL式だけより、書きやすくなります • じゃ、知らなくてもいい?
 本気のソフトウェア開発で使う時は必要です。
 ORM使いたい人は覚えましょう
  59. 59. 使ってみましょう • テーブル定義を書いてみましょう • テーブル定義からSQL式を作ってみましょう • CREATE TABLEを生成してみましょう
  60. 60. location (商品棚) --------------------------------- location_id INTEGER 棚番号 store_id INTEGER 店舗ID last_update DATETIME 最終更新 こんなテーブルを作りましょう
  61. 61. from sqlalchemy import Column, MetaData, Table from sqlalchemy import TIMESTAMP, INTEGER location = Table( 'location', MetaData(), Column('location_id', INTEGER, primary_key=True), Column('store_id', INTEGER, nullable=False), Column('last_update', TIMESTAMP, nullable=False, server_default='CURRENT_TIMESTAMP', server_onupdate='CURRENT_TIMESTAMP'), ) テーブルを定義する location (商品棚) --------------------------------- location_id INTEGER 棚番号 store_id INTEGER 店舗ID last_update DATETIME 最終更新
  62. 62. # 基本は table() で作ったものと同じ >>> str(location.select())
 'SELECT location.location_id, location.store_id, location.last_update nFROM location'
 >>> location Table('location', MetaData(bind=None), Column...) 
 >>> location.c.location_id Column('location_id', INTEGER(), table=<location>, primary_key=True, nullable=False) >>> str(location.c.location_id==1)
 'location.location_id = :location_id_1' スキーマ定義を操作してみる
  63. 63. なぜまぎらわしい同じインタフェースなのか Visitable (base type) ClauseElement Selectable FromClause [ table() ] ColumnElement ColumnClause [ column() ] Column Table SchemaItem
  64. 64. なぜまぎらわしい同じインタフェースなのか Visitable (base type) ClauseElement Selectable FromClause [ table() ] ColumnElement ColumnClause [ column() ] Column Table SchemaItem 同じAPIを
 継承している CREATE/DROP
 まわりを実装 SQL式の基本機能 ◯◯節を出力 する機能 カラム・テーブル
 関連の機能
  65. 65. >>> location.exists(bind=e) # テーブル存在チェック False >>> location.create(bind=e) # CREATE TABLE 実行
 >>> location.exists(bind=e) # できてる True >>> location.drop(bind=e) # DROP TABLE 実行 >>> location.exists(bind=e) False 【悲報】SQLAlchemy本体にはALTERのSQL式がありません CREATE/DROP TABLEしてみる
  66. 66. >>> from sqlalchemy import Index >>> f = Table('film', MetaData(), Column...) >>> Index('k_title', f.c.title) >>> >>> from sqlalchemy import ForeignKeyContraint >>> fc = Table('film_category', MetaData(), ... Column('film_id', ... ForeignKey(f.c.film_id)), ...) >>> str(f.join(fc)) # onclause がなくても JOIN 可
 'film_category JOIN film ON film.film_id = film_category.film_id' 
 >>> Index('k_title', f.c.title) Index('k_title', Column('title', VARCHAR(length=255), table=<film>)) >>> f.indexes {Index('k_title', Column('title', VARCHAR(length=255), table=<film>))} インデクスと制約
  67. 67. location (商品棚) --------------------------------- location_id INTEGER 棚番号 store_id INTEGER 店舗ID last_update DATETIME 最終更新 ハンズオン: テーブル定義を書きましょう • store_id にインデクスをはってみましょう • store テーブルを定義して、location.store_id に ForeignKey を追加し、テーブルを結合してみましょう
  68. 68. ここまでのまとめ • Table() や Column() でテーブルを定義すると、DDL を 実行できる
 カラムの制約やインデクスも貼れる • Table() や Column() は、 table(), column() と同じよう に扱える
  69. 69. さて、やっと
 ORM basics
  70. 70. ORMって何だろう • オブジェクトとリレーショナル(DB) のマップ (対応付け)
 DBの内容をデータオブジェクトとして扱えるよう頑張る存在 • ActiveRecord パターンでの実現が華やか • 1テーブルを1クラス、1レコードを1オブジェクトに対応付ける仕 組がよくある • 外部キーを元に別テーブルのレコード(別クラスのインスタンス) を参照できたりする • カラムデータをプロパティやアトリビュートでアクセスできる
 ことがある
  71. 71. SQLAlchemyのORMで覚えること • ORMで頑張っているのはマッパー (mapper) とセッショ ン (session) • マッピングは古典的なのと近代的なのがある
 裏側で使っている仕組みは同じ • ORMでオブジェクトを操作するときはセッションを使う
  72. 72. マッピング Tableオブジェクト Column foo Column bar Column baz ... エンティティクラス 魔改造されたエンティティ 仕掛けの付いた foo 仕掛けの付いた bar 仕掛けの付いた baz ... method qux method quux method qux method quux ... その他有象無象 ... その他有象無象 得体の知れない
 内部オブジェクト とある
 mapper の魔法
  73. 73. classic mapping
 古いやつ昔からあるやつ
 新しいやつを支えている
 古の魔法 = よくわからんから避けられる
 
 declarative mapping
 新しいやつ 書きやすい 流行の宣言的API
 古の魔法x現代の魔法=よくわからないけど便利
 SAでORMといえばだいたいこっちの話 mapping の種類
  74. 74. >>> from sqlalchemy.orm import mapper >>> actor_tbl = Table(...) >>> class Actor(object): pass >>> mapper(Actor, actor_tbl) >>> dir(Actor) ['__class__', ... '_sa_class_manager', 'actor_id', 'first_name', 'last_name'] >>> Actor.actor_id <...InstrumentedAttribute object at ...> classic mapping またこんど
  75. 75. 使ってみましょう • 宣言的ORMの方法でテーブル定義を書いてみましょう • セッションを作ってORMを操作しましょう
  76. 76. >>> from sqlalchemy.ext.declarative import declarative_base >>> from sqlalchmy import DateTime, Integer, String >>> Base = declarative_base() 宣言的マッピングにはモデルベースクラスが必要
  77. 77. # ベースクラスを拡張するとORMで扱えるモデルクラスになる 
 class Actor(Base): __tablename__ = 'actor' actor_id = Column(Integer,primary_key=True) first_name = Column(String) last_name = Column(String) last_update = Column(DateTime) def full_name(self): return '{} {}'.format( self.first_name, self.last_name) モデルクラスを定義しましょう
  78. 78. # 見かけは普通のクラス >>> Actor <class '__main__.Actor'> # 定義したフィールドと得体の知れない属性が追加される >>> dir(Actor) ['__class__', ... '_decl_class_registry', '_sa_class_manager', 'actor_id', 'first_name', 'last_name', 'last_update', 'metadata'] # actor_id が魔法のアトリビュートになっている >>> Actor.actor_id <sqlalchemy...InstrumentedAttribute object at ...> モデルクラスを見てみましょう
  79. 79. >>> actor = Actor() >>> actor >>> vars(actor_obj) {'_sa_instance_state': <sqlalchemy...InstanceState object at ...>} >>> actor_obj.first_name = 'foo' >>> actor_obj.last_name = 'bar' >>> actor_obj.full_name() 'foo bar' >>> # 興味がある人は、dir(_sa_instance_state) # を手がかりに研究してみましょう モデルインスタンスを生成しよう
  80. 80. セッションを理解しよう • セッション=トランザクションのようなもの
 エンジンのDB接続一つが対応している
 通常、セッションの持続中は、他のプログラムは
 セッションが捕まえている接続にアクセスできない • オブジェクトの読み出し:SELECT
 新たなオブジェクトをセッションに追加:INSERT
 オブジェクトのアトリビュートを更新:UPDATE • 通常は、セッションを閉じたり明にcommit() しないと
 DBを更新しない(「1000オブジェクト追加したのに何も起きない これバグだろ」→セッション閉じてなかった は恥ずかしいFAQ)
  81. 81. session Engine Program query A select A record Aobject A start
 tracking A... (updates) flag A as "dirty" reflect new/dirty changes flush start
 tracking B ... add object B update A insert B commit begin
  82. 82. セッションを使ってみましょう # セッションを作るクラスを sessionmaker で作る # 使うエンジンが一定ならこのとき指定する >>> from sqlalchemy.orm import sessionmaker >>> SessionClass = sessionmaker(bind=e) >>> session = SessionClass() >>> session <sqlalchemy.orm.session.Session object at ...> # dir(session) してみましょう
  83. 83. クエリを生成してみましょう >>> query = session.query(Actor) >>> query <sqlalchemy.orm.query.Query object at ...> # str(query) してみましょう。どうなりますか? # dir(query) してみましょう。
  84. 84. セッション中でオブジェクトを取り出そう # first(), get(pk), スライスなどの操作で
 # インスタンス (の列) を返す >>> my_actor = query.first() >>> my_actor <...Actor Object at ...> >>> my_actor.first_name 'PENELOPE' >>> my_actor.full_name() # メソッドも呼べる 'PENELOPE GUINESS'
  85. 85. セッション中でオブジェクトを取り出そう >>> q = session.query(Actor) >>> q.first() <...Actor object at ...> >>> q.all() # WARNING: SELECTs all records <...Actor object at ...>, <...Actor object at ...>, ... >>> q[:3] <...Actor object at ...>, <...Actor object at ...>, ... >>> q.count() 200
  86. 86. フィルタでクエリを絞込みしよう >>> q = session.query(Actor) >>> q1 = q.filter(Actor.first_name=='PENELOPE') >>> str(q1) 'SELECT ... nFROM actornWHERE actor.first_name = ?' >>> q2 = q1.filter_by(last_name='GUINESS') >>> str(q2) 'SELECT ... nFROM actornWHERE actor.first_name = ? AND actor.last_name= ?' >>> [result.full_name() for result in q2.all()] ['PENELOPE GUINESS']
  87. 87. オブジェクトを追加・更新しよう # 新規オブジェクトを session に add() すると # INSERT 対象になる >>> session = Session() >>> actor_a = Actor(first_name='KEN',
 ... last_name='WATANABE') >>> session.add(actor_a) >>> session.commit() # セッションから取り出したオブジェクトを
 # 変更すると UPDATE 対象になる >>> actor_b = session.query(Actor).get(1) >>> actor_b.last_name='GEORGE' >>> session.commit()
  88. 88. ここまでのまとめ • SAのORMは、宣言的 (declarative) にモデルを定義する ことで使える • ORM 下のオブジェクトを操作するにはセッションを使う • DBレコードからオブジェクトを生成して取り出すには、 セッションでクエリを作って、 all() や first() で取り出す • SQLの実行はセッションにコントロールされている
  89. 89. ハンズオン: ORM • category テーブルをORMで定義 してみましょう • セッションを作って、Category のモデルのクエリを実行しましょ う • Nature というカテゴリを追加し ましょう。 • NatureをGeographyに変更しま しょう • Geographyを削除しましょう
  90. 90. 以上です。 おつかれさまでした!
  91. 91. おまけ:今日お話していないこと • エンジン: コネクション管理・コネクションプールの話
 SQLiteでマルチスキーマDBぽく使う話 • スキーマ定義: マイグレーションの話
 inspection/reflection でDBからスキーマ定義を自動構築する話 • ORM: いにしえのORM APIの話
 外部キーを設定してたどる話 • MonotaROでの開発でSAを導入しようとしている話 • Django ORM他のORMとSAとの比較の話 また、機会があれば。

×