# 联表(population)
> 原文:[Population](http://mongoosejs.com/docs/populate.html)
## Population
在MongoDB没有join联表操作但有时我们还想引用其他集合中文档。这是population的出现的缘由。
Population是在文档中自动更换其他集合的文档指定路径的过程。我们可以填充一个单一的文档,多个文档,普通对象,多个简单的对象,或从查询返回的所有对象。让我们看看一些例子。
```
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
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);
```
到目前为止,我们已经创建了两个[模型](http://mongoosejs.com/docs/models.html)。我们的Person 模型有stories字段设置为ObjectIds数组。ref 选项告诉Mongoose哪个模型中中使用 population,在我们的案例 Story 模型。所有的\_ids我们存储的必须是 Story 模型的\_ids。我们还声明 Story 的\_creator属性作为数字型,和 \_id类型相同用在personSchema中。这是非常重要的匹配 \_id类型到 ref 类型。
> 注:ObjectId,Number,String,和Buffer都是作为refs有效的类型。
### Saving refs
引用其他文件保存的工作方式相同,通常保存属性,只是分配的\_id值:
```
var aaron = new Person({ _id: 0, name: 'Aaron', age: 100 });
aaron.save(function (err) {
if (err) return handleError(err);
var story1 = new Story({
title: "Once upon a timex.",
_creator: aaron._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
});
```
### Population
到目前为止,我们还没有做任何不同的事情。我们只是创造了一个 Person 和一个 Story 。现在让我们使用查询生成器看一下填充story的\_creator。
```
Story
.findOne({ title: 'Once upon a timex.' })
.populate('_creator')
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name);
// prints "The creator is Aaron"
});
```
阵列的refs工作方式相同。在查询时调用populate方法和文档数组将在返回在原来的\_ids的地方。
> 注:mongoose > = 3.6 在使用population暴露原始的`_ids`通过[document#populated()](http://mongoosejs.com/docs/api.html#document_Document-populated)方法。
### 设置Populated字段
在Mongoose > = 4,你也可以手动填充字段。
```
Story.findOne({ title: 'Once upon a timex.' }, function(error, story) {
if (error) {
return handleError(error);
}
story._creator = aaron;
console.log(story._creator.name); // prints "Aaron"
});
```
注意,这只工作在单个refs。你现在不能手动填充数组refs。
### 字段选择
如果我们只想要一些特定的字段返回填充的文档呢?这可以通过将常用的[字段名语法](http://mongoosejs.com/docs/api.html#query_Query-select)作为填充方法的第二个参数来完成:
```
Story
.findOne({ title: /timex/i })
.populate('_creator', 'name') // only return the Persons name
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name);
// prints "The creator is Aaron"
console.log('The creators age is %s', story._creator.age);
// prints "The creators age is null'
})
`
```
### Populating multiple paths
如果我们想在同一时间填充多个路径呢?
```
Story
.find(...)
.populate('fans _creator') // space delimited path names
.exec()
```
> 在 mongoose > = 3.6,我们可以把一个空格分隔的路径名来填充字符串。3.6之前,您必须执行`populate()`方法多次。
```
Story
.find(...)
.populate('fans')
.populate('_creator')
.exec()
```
### 查询条件和其他选项
如果我们想根据他们的年龄来填充我们的球迷数组,选择只是他们的名字,并返回最多,其中5个?
```
Story
.find(...)
.populate({
path: 'fans',
match: { age: { $gte: 21 }},
select: 'name -_id',
options: { limit: 5 }
})
.exec()
```
### Refs to children
我们可能会发现,如果我们使用aaron对象,我们无法得到一个列表的stories。这是因为没有story的对象都“推”到`aaron.stories`。
这里有两个观点。首先,很高兴aaron知道哪些stories是他的。
```
aaron.stories.push(story1);
aaron.save(callback);
```
这使我们能够执行一个查找和填充组合:
```
Person
.findOne({ name: 'Aaron' })
.populate('stories') // only works if we pushed refs to children
.exec(function (err, person) {
if (err) return handleError(err);
console.log(person);
})
```
> 这是值得商榷的,我们真的要两套指针作为他们可能不同步。相反,我们可以跳过,直接填充并`find()`我们感兴趣的故事。
```
Story
.find({ _creator: aaron._id })
.exec(function (err, stories) {
if (err) return handleError(err);
console.log('The stories are an array: ', stories);
})
```
### 更新refs
现在我们有一个故事,我们意识到`_creator`是错误的。我们可以通过更新refs通过Mongoose的铸件内部任何其他属性一样:
```
var guille = new Person({ name: 'Guillermo' });
guille.save(function (err) {
if (err) return handleError(err);
story._creator = guille;
console.log(story._creator.name);
// prints "Guillermo" in mongoose >= 3.6
// see https://github.com/Automattic/mongoose/wiki/3.6-release-notes
story.save(function (err) {
if (err) return handleError(err);
Story
.findOne({ title: /timex/i })
.populate({ path: '_creator', select: 'name' })
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name)
// prints "The creator is Guillermo"
})
})
})
```
> 返回的文档查询population成为功能齐全,可保存文件,除非指定了精益选项。不与子文档混淆。请注意,当调用它的删除方法,因为你会从数据库中删除它,不仅仅是数组。
### Populating 一个存在的文档
如果我们有一个现有的mongoose文档并且想要填充的一些它的路径。mongoose > = 3.6支持[document#populate()](http://mongoosejs.com/docs/api.html#document_Document-populate)方法。
### Populating 多个存在的文档
如果我们有一个或多个mongoose文档,甚至是简单的对象(像mapReduce输出),我们可以让他们使用[Model.populate()](http://mongoosejs.com/docs/api.html#model_Model.populate)方法 mongoose > = 3.6。这是为什么使用`document#populate()` 和`query#populate()`来populate文档。
### Populating在多个层面
说你有一个用户模式,跟踪用户的朋友。
```
var userSchema = new Schema({
name: String,
friends: [{ type: ObjectId, ref: 'User' }]
});
```
Populate可以让你得到一个用户的朋友的列表,但如果你也想要一个用户的朋友的朋友呢?指定populate选项告诉mongoose来populate所有用户的朋友的朋友的数组:
```
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});
```
### Populating整个数据库
让我们说,你有一个代表事件的模式,和一个代表对话的模式。每个事件都有一个相应的会话线程。
```
var eventSchema = new Schema({
name: String,
// The id of the corresponding conversation
// Notice there's no ref here!
conversation: ObjectId
});
var conversationSchema = new Schema({
numMessages: Number
});
```
同时,假设事件和对话都存储在单独的MongoDB实例。
```
var db1 = mongoose.createConnection('localhost:27000/db1');
var db2 = mongoose.createConnection('localhost:27001/db2');
var Event = db1.model('Event', eventSchema);
var Conversation = db2.model('Conversation', conversationSchema);
```
在这种情况下,您将无法正常`populate()`。conversation字段永远是空的,因为`populate()`不知道使用哪种模式。然而,您可以[显式指定模型](http://mongoosejs.com/docs/api.html#model_Model.populate)。
```
Event.
find().
populate({ path: 'conversation', model: Conversation }).
exec(function(error, docs) { /* ... */ });
```
这是一个被称为“跨数据库的populate“,因为它使你populate在MongoDB数据库和通过MongoDB实例。
### 动态参考
Mongoose也可以同是populate从多个集合。让我们说,你有一个用户模式,有一个“连接”的数组-用户可以连接到其他用户或组织。
```
var userSchema = new Schema({
name: String,
connections: [{
kind: String,
item: { type: ObjectId, refPath: 'connections.kind' }
}]
});
var organizationSchema = new Schema({ name: String, kind: String });
var User = mongoose.model('User', userSchema);
var Organization = mongoose.model('Organization', organizationSchema);
```
以上的refpath属性意味着mongoose会看`connections.kind`路径确定模型用于`populate()`。换句话说,这`refpath`属性使您能够ref属性的动态。
```
// Say we have one organization:
// `{ _id: ObjectId('000000000000000000000001'), name: "Guns N' Roses", kind: 'Band' }`
// And two users:
// {
// _id: ObjectId('000000000000000000000002')
// name: 'Axl Rose',
// connections: [
// { kind: 'User', item: ObjectId('000000000000000000000003') },
// { kind: 'Organization', item: ObjectId('000000000000000000000001') }
// ]
// },
// {
// _id: ObjectId('000000000000000000000003')
// name: 'Slash',
// connections: []
// }
User.
findOne({ name: 'Axl Rose' }).
populate('connections.item').
exec(function(error, doc) {
// doc.connections[0].item is a User doc
// doc.connections[1].item is an Organization doc
});
```
### Populate虚函数
新的4.5.0中
到目前为止,你只有稀少的基于\_id字段。然而,这有时不是正确的选择。特别是,数组,无束缚成长是MongoDB的反模式。用mongoose鼬虚函数,您可以定义文档之间更复杂的关系。
```
var PersonSchema = new Schema({
name: String,
band: String
});
var BandSchema = new Schema({
name: String
});
BandSchema.virtual('members', {
ref: 'Person', // The model to use
localField: 'name', // Find people where `localField`
foreignField: 'band' // is equal to `foreignField`
});
var Person = mongoose.model('Person', personSchema);
var Band = mongoose.model('Band', bandSchema);
/**
* Suppose you have 2 bands: "Guns N' Roses" and "Motley Crue"
* And 4 people: "Axl Rose" and "Slash" with "Guns N' Roses", and
* "Vince Neil" and "Nikki Sixx" with "Motley Crue"
*/
Band.find({}).populate('members').exec(function(error, bands) {
/* `bands.members` is now an array of instances of `Person` */
});
`
```
### 下一步
现在我们已经掌握了population,让我来看一下[连接](http://mongoosejs.com/docs/connections.html)。