MongoDB 教程索引 MongoDB 教程一: 安装和使用 (Mongodb启动命令mongod参数说明) MongoDB 教程二: 添加,…
mongodb 联表查询方法, PHP操作远程mongodb数据库
本篇是 PHP操作远程mongodb数据库, MongoDB PHP Library, php connects with remote MongoDB 的延伸:
这里只对同库联表查询做介绍,跨库联表查询可能在之后也会介绍(因为公司架构变动,之后可能会联表查询)
我用到的联表查询有两种,一种是mongoose的populate,一种是$lookup
populate
populate是使用外键关联子表
例如现在有一张订单表结构(动态外键):
var orderSchema = new mongoose.Schema({ uid: { type: String, required: true }, // 用户id amount: { type: Number, required: true }, oType: { type: Number, required: true }, // 订单类型 status: { type: Number, required: true }, // 订单的状态:1完成 2未完成 3失效 })
用户表:
var userSchema = new mongoose.Schema({ phone: String, status: String, createdAt: Date, updatedAt: Date })
现在我想根据查询order表,并返回对应用户phone字段
order.find().populate({path: 'uid', model: User, select: '_id real_name phone bankcard'}).exec(function(err, order) { // order: { // uid: { // phone: '15626202254', // status: "expand", // createdAt: Date, // updatedAt: Date // }, // amount: 5000, // oType: 2, // 订单类型 // status: 1, // 订单的状态:1完成 2未完成 3失效 // } });
这里order表的uid指向了user表的_id字段,当然也可以在新建表的时候定义外键,这里就不细说了
$lookup
lookup就是使用aggregate的$lookup属性,直接上官网例子非常好懂
orders表
{ "_id" : 1, "item" : "abc", "price" : 12, "quantity" : 2 } { "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1 } { "_id" : 3 }
inventory表
{ "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 } { "_id" : 2, "sku" : "def", description: "product 2", "instock" : 80 } { "_id" : 3, "sku" : "ijk", description: "product 3", "instock" : 60 } { "_id" : 4, "sku" : "jkl", description: "product 4", "instock" : 70 } { "_id" : 5, "sku": null, description: "Incomplete" } { "_id" : 6 }
db.orders.aggregate([ { $lookup: { from: "inventory", localField: "item", foreignField: "sku", as: "inventory_docs" } } ])
就是使用order的item字段作为inventory表的查询条件{sku: item},并赋值给inventory_docs字段,但值得注意的是两个字段的类型必须一样(3.5以上貌似可以转,没试过)
补充一:Mongoose中的关联表查询 && 聚合查询
例: 有两张表,一张是博客列表,另外一张是博客的标签表。现在我们要做两张表的插入和关联查询。
创建两张表的Schema
主表blog
var blogSchema = new mongoose.Schema({ title: {type: String}, //博客题目 abstract: {type: String}, //摘要 content: {type: String}, //文章内容 click: {type: Number},//点击量 createtime: {type: String} //消费时间 }) //创建model,第三个参数是实际表名 var blogModel = db.model("blog", blogSchema, "blog");
子表label
//标签表 var labelSchema = new mongoose.Schema({ blogid: {type: mongoose.Schema.Types.ObjectId, ref: 'blog'},//这里即为子表的外键,关联主表。 ref后的blog代表的是主表blog的Model。 label: {type: String} //标签名 }); //创建model,第三个参数是实际表名 var labelModel = db.model("label", labelSchema, "label");
插入数据
//1.主表插入数据 blogModel.create({...}, function (err, doc) { if(err) return xxx; //2.子表插入数据。 blogid为刚插入主表的主键 labelModel.create({blogid: doc._id, label: label}, function (err, doc) { if (err) return xxx; }) })
关联查询
//子表关联主表查询,populate里面为子表外键 labelModel.find({}).populate('blogid').exec(function(err,docs){ })
简单的表关联查询就是这样。
当然也可以用主表关联子表查询,那就需要修改两张表的Schema了。 外键应该定义在主表中,而不是上面例子的子表中。
聚合查询
SQL语句: select count(1),label from table group by label 。
那么在mongo中我们该如何实现呢? 直接上例子
//MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果; 注意_id,num_tutorial这两个字段都不能变(固定写法) labelModel.aggregate([{$group : {_id : "$label", num_tutorial : {$sum : 1}}}],function(err,docs){ console.log(docs) })
补充二:在mongoose中填充外键
MongoDB是典型的NoSQL数据库,因此不提供JOIN操作。 但有时我们仍然希望引用其他集合中的文档。此时便需要外键填充(population)。 mongoose是极具JavaScript特点的程序库,提供了极其简单和强大的填充功能。 mongoose不仅支持填充单个文档,也支持多文档,甚至直接填充某个对象。
本文中部分代码来自mongoose文档。
外键引用
在Schema字段的定义中,可以添加ref
属性来指向另一个Schema。 该ref
属性在此后被填充(populate
)时将被mongoose读取。 下面是存在互相引用的Person
与Story
的Schema定义。
var mongoose = require('mongoose'), Schema = mongoose.Schema var personSchema = Schema({ // _id默认为Schema.Types.ObjectId类型 _id : Number, name : String }); var storySchema = Schema({ creator : { type: Number, ref: 'Person' }, title : String, // 可以看到外键引用可以定义在嵌套的属性中。 fans : [{ type: Number, ref: 'Person' }] }); var Story = mongoose.model('Story', storySchema); var Person = mongoose.model('Person', personSchema);
外键的类型可以是ObjectId
, Number
, String
, Buffer
中任何一种,在赋值与填充时保持一致即可(见下文)。
保存与填充
Story
中保存Person
对象的_id
,此后在Query上调用.populate()
即可用Person
的文档来替换掉原来的字段。
var alice = new Person({ _id: 0, name: 'Alice'}); # 保存其ID即可 var story = new Story({ title: 'xx', creator: alice._id }); Story.findOne({title: 'yy'}) .populate('creator') .exec(function(err, story){ if(err) throw err; console.log(story.creator.name); });
填充指定的字段
有时我们只想要很少的几个字段,这可以用字段名语法来指定它们。
Story.findOne({title: 'xx'}) .populate('creator', 'name') // 只返回Person的name字段 .exec(function(err, story){ if(err) throw err; console.log(story.creator.name); });
填充多个属性
有时我们需要填充多个字段,这时可以多次调用.populate()
, 也可以在一次调用中指定多个字段:
Story.find(...) .populate('creator fans') .exec(); Story.find(...) .populate('creator') .populate('fans') .exec();
填充引用数组与填充单个引用的语法没有区别,mongoose会识别字段类型的不同。
填充选项
在.populate()
的同时,还可以指定过滤器以及限制大小。 将.populate()
的参数换为一个对象即可。
Story.find(...) .populate({ path: 'fans', match: { age: { $gte: 21 }}, select: 'name', options: { limit: 5 } }); .exec();
上述查询只会选择年龄大于21
的fans
,只返回其name
字段,且最多返回5
个。
完整的选项请访问:http://mongoosejs.com/docs/api.html#model_Model.populate
多级填充
想填充引用的引用怎么办?给.populate()
传入嵌套的参数即可。 比如填充用户的朋友的朋友(两级):
var userSchema = new Schema({ name: String, friends: [{ type: ObjectId, ref: 'User' }] }); User. findOne({ name: 'Val' }). populate({ path: 'friends', populate: { path: 'friends' } });
注意多级填充和嵌套属性填充的区别。如果是填充属性的属性(都在当前模型中保存) 则只需要用.
分隔,比如:.populate('relations.mother')
。
动态引用
上文中调用.populate()
之前有一个条件:被填充的字段已被设置过ref
选项。 mongoose会去ref
指定的集合中去查找对应ID。 如果是动态字段怎么办?可以在填充的同时指定其ref
:
var userSchema = new Schema({ _id: Number, name: String, teacher: Number }); User. findOne({ name: 'Val' }). populate({ path: 'teacher', model: 'User' // 在User集合中查找该ID })
任意对象填充
mongoose不仅可以填充Query中的对象,还可以填充任何对象。 当然这时就需要指定用哪个模型来填充,有两种方式来指定填充模型。
设置populate的model参数
与动态填充类似,填充时可以直接设置model
参数。 这时用任意一个Schema都可以操作,比如User
:
var user = { name: 'Indiana Jones', weapon: 389 }; User.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, users) { console.log(user.weapon.name); });
直接使用对应Schema
直接使用Weapon
来填充该类型的属性,则不需要设置model
字段:
var user = { name: 'Indiana Jones', weapon: 389 }; Weapon.populate(user, { path: 'weapon' }, function (err, users) { console.log(user.weapon.name); });
mongoose会默认使用当前Schema对应的MongoDB的集合。
本文:mongodb 联表查询方法, PHP操作远程mongodb数据库