一种很常见的场景,比如电商首页中,需要同时展示最近比较火热的店铺,以及直接展示店铺里对应的商品。或者用户下单之后购物车里可以看到所选的商品以及对应的店铺。如果不知道如何用mongodb自带的查询语句快速查询的话,我们能有的实现方案,可能是先查询店铺,然后通过for循环再查询店铺里的商品,而for循环是会反复操作数据库,对性能有极大的损耗,并且速度也非常的慢,所以这里我们来学习一下如何用mongodb来多表查询。
一下以疾病分类与对应的产品表为例代码如下:
- db.disease_type.aggregate([
- {
- $lookup: {
- from: 'product',
- localField: '_id',
- foreignField: 'disease_type_id',
- as: 'products',
- },
- }])
aggregate为集合函数
$lookup 操作符将多个文档合并为一个数组
from 则为要被关联查询的表,比如的product则为产品表
localField 为当前表的id,也就是disease_type表的id
foreignField 为被关联表的要与当前表对应的id,也就是product的disease_type_id自带与disease_type表的_id字段进行关联
as 则是将这个被关联的表存放到那个字段中。
整个数据结构会自动拼接好,最终结果如下图,
如果每个产品都对应一个分类,则用unwind改变数据结构代码如下:
- db.disease_type.aggregate([
- {
- $lookup: {
- from: 'product',
- localField: '_id',
- foreignField: 'disease_type_id',
- as: 'products',
- }
-
- },{$unwind:"$products"},{
- $project: {
- _id: 1,
- date: 1,
- products: {
- _id: 1,
- name: 1,
- price: 1,
- }
- }
- }])
注意这里的products名称必须与上面的as要一致,否则也会找不到结果。
而$project则是过滤字段,只展示指定的内容。如果想展示所有则去掉此属性即可。
结果如下:
$project可以配置要展示的哪些字段,但有时候字段的名称可能要修改调整一下。比如_id改为id或者可自己配置各种结构
- db.disease_type.aggregate([
- {
- $lookup: {
- from: 'product',
- localField: '_id',
- foreignField: 'disease_type_id',
- as: 'products',
- }
-
- },{
- $project: {
- _id: 1,
- date: 1,
- products: {
- $map: {
- input: '$products',
- as: 'product',
- in: {
- _id: '$$product._id',
- parentId:"$products._id",
- name: '$$product.name',
- price: '$$product.price',
- }
- }
- }
- }
- }])
$map 重新遍历循环
input 里的$products则表示商品表数据
as 里的product则表示被遍历的每个数据(类似for(item in products))。而只要被as处理之后,就可以通过$$product的方式调用这个变量了。
in 则表示商品列表内部结构的配置,比如还需配置parentId,这里的name,price等key字段都可以自己重命名,要绑定的字段则可通过$$product来绑定。
如下图
以上功能都是查询的所有数据,但我们实际运用中还需要分页或者通过关键字筛选。可继续在数组里添加其他条件,代码如下
- db.disease_type.aggregate([{
- $lookup: {
- from: 'disease',
- localField: '_id',
- foreignField: 'disease_type_id',
- as: 'products',
- },
- },
- {
- $unwind: '$products'
- },
- {
- $match: {
- 'products.name': {
- $regex: name,
- $options: 'i'
- },
- },
- },
- {
- $skip: start,
- },
- {
- $limit: limit,
- }
- ])
$match 则是对某些字段添加过滤条件,而里面的$regex则表示用正则去匹配
$skip与$limit 则表示从那段开始过滤后面多少条数据
- let nodeSchema = new mongoose.Schema(config);
- client = mongoose.model('disease_type', nodeSchema, 'disease_type');
- client.aggregate([{
- $lookup: {
- from: 'disease',
- localField: '_id',
- foreignField: 'disease_type_id',
- as: 'products',
- },
- },
- {
- $unwind: '$products'
- },
- {
- $match: {
- 'products.name': {
- $regex: name,
- $options: 'i'
- },
- },
- },
- {
- $skip: start,
- },
- {
- $limit: limit,
- }
- ])
- .exec((err, result) => {
- if (err) {
- console.error(err);
- response.error(ctx, err)
- return;
- }
- console.log(result);
- response.success(ctx, result)
-
-
- });
exec函数则会将查询到的结果数据返回给result
注意:mongoose插件6.*.* 版本中bug还存在不少,比如任意不存在的条件也能查到数据,findOne,find都能查到
7.*.* 之后 以上的写法支持异步了,需改为如下:
- let data = await client.aggregate([{
- $lookup: {
- from: 'disease',
- localField: '_id',
- foreignField: 'disease_type_id',
- as: 'products',
- },
- },
- {
- $unwind: '$products'
- },
- {
- $match: {
- 'products.name': {
- $regex: name,
- $options: 'i'
- },
- },
- },
- {
- $skip: start,
- },
- {
- $limit: limit,
- }
- ])
- .exec();