非リレーショナルデータベースが結合演算を扱えない、、、という誤解を解く。
明日の勉強会の準備をしていたら、今回の本の範囲外ですが、ちゃんとまとめておかないと、というところを思い出したので書いておきます。だいぶ前に書いた気がしないでもないけど。
CouchDBではMapReduceを工夫することで関係モデルの結合演算を扱えます。なぜかKVSと一緒くたにされて、JOINができないから、だのなんだのいわれますが、SQLの関係演算の本質(? ... いや知らないけど)を見極めていれば、CouchDBのMapReduceであらかたの演算ができます。
という話。だったら非リレーショナルデータベースっていうなよ、という話は抜きです。だってリレーショナルデータベースっていうとめんどくさいことになるんだもの。
まずは普通に外部結合
ブログエントリとブログのコメントのリレーション、だとエンタープライズ脳の人が納得しないようなので、取引伝票と取引明細でいきます。
まずは、取引伝票
{ "_id": "order1" "title": 取引1", "description" : "..." "type": "Order" }
で、明細。
{ "_id" : "....", "name" : "りんご", "unit_price": 180, "num" : 5, "type" : "OrderDetail", "order_id" : "order1" }
{ "_id" : "....", "name" : "みかん", "unit_price": 140, "num" : 7, "type" : "OrderDetail", "order_id" : "order1" }
{ "_id" : "....", "name" : "ぶどう", "unit_price": 880, "num" : 2, "type" : "OrderDetail", "order_id" : "order1" }
こんな感じ。いかにもリレーショナル。で、本物の伝票を作るときには、次のようにMapReduce(のMap)します。
function(doc){ if( doc.type == "Order") { emit([doc._id, 0], doc); } if( doc.type == "OrderDetail") { emit([doc.order_id, 1, doc.name], doc); } }
こうすると次のような表ができあがるのです。
order_id | 種類 | 商品名 | データ |
---|---|---|---|
"order1" | 0 | undefined | Orderドキュメント |
"order1" | 1 | ぶどう | OrderDetailドキュメント |
"order1" | 1 | みかん | OrderDetailドキュメント |
"order1" | 1 | りんご | OrderDetailドキュメント |
"order2" | 0 | undefined | Orderドキュメント |
"order2" | 1 | XXXX | OrderDetailドキュメント |
"order2" | 1 | XXXX | OrderDetailドキュメント |
"order2" | 1 | XXXX | OrderDetailドキュメント |
"order3" | 0 | undefined | Orderドキュメント |
... | ... | ... | ... |
order_id <-> 商品名 の間はKeyです。
したがって、Order._id = OrderDetail.order_id で LEFT OUTER JOINを実行するには、startkey=["order1",0]&endkey=["order1", 1, "\u9999"] と指定します。
つまり、配列をkeyに試用して、先頭要素からN個目までをJOINキーにすることができる(この場合N=1)んです。CouchDBではemit(k,v)で登録したkの値はB+-Treeに格納されます。ほら、主キーと外部キーにRDBでインデックス張るのと変わらない。
Reduceで計算をする
ところで、Orderの金額の合計を出すには
SUM({OrderDetail}.num * OrderDetail.unit_price)
しなければなりません。これは簡単ですね。reduceに次のものを定義しておけばOK。
function(ks, vs, rr){ if( rr ){ return sum(vs); }else{ var s = 0; for(var i in vs){ var doc = vs[i]; if( doc.type == "OrderDetail") { s += doc.unit_price * doc.num; } } return s; } }
お得意さんには10%引きで
Orderドキュメントにお得意さんマークがついていたら10%OFFにしましょうか。というのは少し難しいです。これをCouchDBのMapReduceに落とし込む、ができればあなたもRelax Goldです。
答えはまた後で。ヒントはReduceが交換律(訂正)結合律を満たす演算になるように演算子と式をモデル化する、a * (b + c + d + ...) != (a * b) + c + d + ... なのでどうしたらいいでしょうか、というのがヒントでしょうか。
でも。
JOINができるってそんなに重要なことだとは思わないんですよ....だから当分は非リレーショナルってことで。