前回、datastax社がオープンソースにしているcassandra-driverを使って軽くCassandraに触れてみた。cassandra-driverからデータの検索やらデータ追加などの処理をするには、ドライバに特別なメソッドなどが備えられているわけでなく、executeステートメントを使ってほぼCQL文まんまのもので命令を出していった。CassandraのデータをPythonからいじるのに必要なのはまずCQLを理解すること、というのがわかった。
というわけで今回はCQLの基礎的なところをさらっていく。
まずは具体的なデータ作成の前の準備ということでキースペースの作成、キースペースのセットまで。Pythonでcassandra-driverを使ってやることを想定しておく。
キースペースは下にテーブルが入るものなので、それほど複雑でないものなら一つのプロジェクトにつき一つのキースペースを割り当てればいいんじゃないだろうか。
session.exexute("CREATE KEYSPACE fooSpace")
session.exexute("USE fooSpace")
次にテーブルを作る。テーブル作成時にはどんなセットを持つデータを格納するかを宣言しておく。昔はこのキースペース直下のデータのまとまりの単位にTABLEでなく、COLUMNFAMILY(カラムファミリー)という言葉を使っていたようだが、これを書いている時点で公式のCQLドキュメントではTABLEという言葉になっている。
session.execute(
"""CREATE TABLE men (id int PRIMARY KEY,
fname text,
lname text,
age int,
sports set<text>)
"""
)
menというテーブルを用意した。とりあえず一人の男のデータとして適当に割り当てたid、姓、名、年齢、あとセットとしてsportsというデータを定義している。Cassandraのデータとして文字列や数値を複数並べて入れられる三種類のコレクションも使えるようになっている。コレクションはPythonで言うところのキーバリューが対応した辞書型と同様のmap、リスト型と同様のlist、ユニークな要素で常にソートされるsetの三種類がある。今回はsportsに経験したスポーツの名前を入れるとして、値の重複は必要ないのでsetを選んだ。
テーブル定義の次はテーブルにデータを突っ込む。この辺は適当に適当な名前で適当な人を仮定してやっていく。基本は"INSERT INTO tablename (key1, key2...) VALUES (value1, value2...)"
session.execute(
"""
INSERT INTO men (fname, lname, id, age, sports)
VALUES (%s, %s, %s, %s, %s)
""",
("John", "O'Reilly", 1600, 30, {"soccer", "golf", "tennis", "karate"})
)
session.execute(
"""
INSERT INTO men (fname, lname, id, age, sports)
VALUES (%s, %s, %s, %s, %s)
""",
("John", "Smith", 1601, 25, {"soccer", "golf"})
)
session.execute(
"""
INSERT INTO men (fname, lname, id, age, sports)
VALUES (%s, %s, %s, %s, %s)
""",
("John", "Suzuki", 1602, 30, {"soccer"})
)
session.execute(
"""
INSERT INTO men (fname, lname, id, age, sports)
VALUES (%s, %s, %s, %s, %s)
""",
("John", "Kawasaki", 1603, 29, {"golf"})
)
session.execute(
"""
INSERT INTO men (fname, lname, id, age, sports)
VALUES (%s, %s, %s, %s, %s)
""",
("John", "Honda", 1604, 30, {"tennis"})
)
データ検索。"SELECT fname, lname FROM men"
SELECTの後ろに欲しい値のキーをカンマ","区切りで。全部欲しければ"*"ワイルドカード。あとはどのテーブルからデータを引っ張ってくるかをFROMのあとに。
men = session.execute("SELECT * FROM men")
for man in men:
print man.id, man.fname, man.lname
WHEREを使う。けどそのまえにプライマリキー以外を条件にかけたい場合、そのインデックスを作っておくこと。
http://cassandra.apache.org/doc/cql3/CQL.html#createIndexStmtsession.execute("CREATE INDEX on men (fname)")
session.execute("CREATE INDEX on men (fname)")
session.execute("CREATE INDEX on men (lname)")
men = session.execute("SELECT * FROM men WHERE fname='John'")
for man in men:
print man.id, man.fname, man.lname
プレースホルダを使う。
session.execute("SELECT * FROM men WHERE lname=%s", ("O'Reilly",))
条件を増やす。
session.execute("SELECT * FROM men WHERE fname='John' AND age>30 ALLOW FILTERING")
下記は不正なCQLでエラーとなる。WHERE節を使うならその中に一つは"="がなければならないのがCQLとのこと。
session.execute("SELECT * FROM men WHERE age>30")
じゃあ"fname='*'"のようにやって、ワイルドカードで全行にひっかかるようにしてやればと思うけど、そういうワイルドカードはない。じゃあ全行にダミーでブール値Trueを入れるカラムを作って"dummy=True"とか条件に追加すればってstackoverflowで言ってるのがあるけど、それは悪手だと批判もされている。でもそれをやる良い手がCassandraにはないだろとも。Cassandraはここら辺がちょっとだけ難しいか。
CQL SELECT Query on non key column*********************************
ここからはCassandra2.1以降の話。これを書いている時点ではベータ版で2.1.0がリリースされているので、そのベータ版Cassandraを使う。
上で三種類のコレクションを紹介した。これにインデックスを作ってクエリで条件をかけられるのが2.1以降の機能だ(
参考)。
session.execute("CREATE INDEX ON men (sports)")
rows = session.execute("SELECT * FROM men WHERE sports CONTAINS 'tennis'")
このコレクションのなにが素晴らしいかっていうと、中間テーブルを省けるようになったりすること。多対多が中間テーブルなしに作れるようになる。
たとえばブログ記事がいくつかのタグを持っていたとして、一つの記事を読んで同じタグの記事を探したい場合、そのタグで中間テーブルにクエリをかけて、クエリに引っかかったデータから同じタグの記事への参照を洗い出す。コレクションを持っていれば、中間テーブルにでなく記事をカラムとして持つデータのテーブルに直接クエリをかけられる。一つテーブルを省けるというのはうれしいところ。