MongoDB 多表查询

mongo 这种 NoSQL 要进行多表查询一种方法是在 client 端进行多次查询操作另一种是利用 aggregation pipeline 这种聚合流操作,前一种方法非常不推荐使用,特别涉及到分页时这几乎是个噩梦,后面一条命令组合直接在服务端运作非常推荐。说一下第二种方法的要点。
数据涉及到多表的逻辑操作,mongo 提供了 lookup, 使用方法直接在官网上查间即可,现有三个表 Slot,SlotOrder, 和 Order,SlotOrder 保存了另外两张的关联关系,当然并不是所 Slot 都会关联到 Order。
结构如下

Slot{
	_id,
	xxx,
}

SlotOrder{
	_id,
	SlotID,
	OrderID,
	xxx,
}

Order{
	_id,
	Begin,
	End,
	xxx,
}

现在要根据 Order 中的 Begin 和 End 是否在某一个时间范围来判断 Slot 的使用状态,如 nowEnd 则表示 Slot 处于 idle 状态,而如果没有 Order 关联 Slot 则也处于 idle 状态,可通过两次 lookup 还找到三表的数据,需要注意的是由于 Slot 与 Order 是一对多的关系,两次 lookup 之间还进行一些处理,直接的查询语句如下:

db.Slot.aggregate([
    {$lookup:
    {
      from: "SlotOrder",
      localField: "_id",
      foreignField: "SlotId",
      as: "SO"
    }
   },
   {$unwind:"$SO"},
   {$lookup:
    {
      from: "Order",
      localField: "SO.OrderId",
      foreignField: "_id",
      as: "Order"
    }
   },
   {$unwind:"$Order"},
   {$project:{_id:1,Order:1,OrderStat:{$cond:[{$lt:["$Order.Begin",now]},
                    scheduled,
                    $cond:[{$eq:["$Order.End",0]},
                        busy,
                        $cond:[{$lte:["$Order.End",now]},busy,idle]]}}},
   {$match:{"OrderStat":{$eq:statYouNeed}}},
   {$group:
       {
       "_id":"$_id",
       "Order":{"$AddToSet":"$Order"},
       "OrderStat":{"$AddToSet":"$OrderStat"},
       }
   },
])

这里产生的 SO(SlotOrder[]) 是一个数组,要获得每个 Order.ID 可以用 $unwind 把数据进行拆分,拆分时其它 Field 都会相应复制原来的数据,如

{1,[a,b]}
变成
{1,a}
{1,b}

同样的在进行第二次 lookup 之后要对每一个 Order 数据进行分析也要对 lookup 出来的数组进行拆分。
得到三个表中所需要的数据之后,就可以进行数据的分析和统计了,这里使用 $project 来处理原数据产生需要的数据模型,多次使用了 $cond 这个条件语句,有一点函数式编程的味道,想想
$cond:[logic,$cond,$cond]
最后使用 $match 将处理好的数据再进行过滤,
为了让之前 $unwind 后的数据合并起来,可使用 $group() 使用_id 来作为主键合并。在这里 OrderStat 就是 Slot 状态相关的数组展现。
相应的 go 代码实现点这里
如果分页的话可结合 $skip, $limit 进行操作,相关操作可参考之前我写的这篇文章

—– update —–

测试了不使用 lookup(即第一种方法)和使用 lookup(即第二种) 方法的效率,发现 lookup 方法耗时要比手动合并数据要长,而且随着数据量的增加差距更加明显,下图是测试记录,这里是测试代码

2016-10-27