# 联表（population）

> 原文：[Population](http://mongoosejs.com/docs/populate.html)\
> 翻译：小虾米（QQ:509129）

## Population

在MongoDB没有join联表操作但有时我们还想引用其他集合中文档。这是population的出现的缘由。

Population是在文档中自动更换其他集合的文档指定路径的过程。我们可以填充一个单一的文档，多个文档，普通对象，多个简单的对象，或从查询返回的所有对象。让我们看看一些例子。

```javascript
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值：

```javascript
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。

```javascript
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，你也可以手动填充字段。

```javascript
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)作为填充方法的第二个参数来完成：

```javascript
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

如果我们想在同一时间填充多个路径呢？

```javascript
Story
.find(...)
.populate('fans _creator') // space delimited path names
.exec()
```

> 在 mongoose > = 3.6，我们可以把一个空格分隔的路径名来填充字符串。3.6之前，您必须执行`populate()`方法多次。

```javascript
Story
.find(...)
.populate('fans')
.populate('_creator')
.exec()
```

### 查询条件和其他选项

如果我们想根据他们的年龄来填充我们的球迷数组，选择只是他们的名字，并返回最多，其中5个？

```javascript
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是他的。

```javascript
aaron.stories.push(story1);
aaron.save(callback);
```

这使我们能够执行一个查找和填充组合：

```javascript
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()`我们感兴趣的故事。

```javascript
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的铸件内部任何其他属性一样：

```javascript
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在多个层面

说你有一个用户模式，跟踪用户的朋友。

```javascript
var userSchema = new Schema({
  name: String,
  friends: [{ type: ObjectId, ref: 'User' }]
});
```

Populate可以让你得到一个用户的朋友的列表，但如果你也想要一个用户的朋友的朋友呢？指定populate选项告诉mongoose来populate所有用户的朋友的朋友的数组：

```javascript
User.
  findOne({ name: 'Val' }).
  populate({
    path: 'friends',
    // Get friends of friends - populate the 'friends' array for every friend
    populate: { path: 'friends' }
  });
```

### Populating整个数据库

让我们说，你有一个代表事件的模式，和一个代表对话的模式。每个事件都有一个相应的会话线程。

```javascript
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实例。

```javascript
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)。

```javascript
Event.
  find().
  populate({ path: 'conversation', model: Conversation }).
  exec(function(error, docs) { /* ... */ });
```

这是一个被称为“跨数据库的populate“，因为它使你populate在MongoDB数据库和通过MongoDB实例。

### 动态参考

Mongoose也可以同是populate从多个集合。让我们说，你有一个用户模式，有一个“连接”的数组-用户可以连接到其他用户或组织。

```javascript
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属性的动态。

```javascript
// 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鼬虚函数，您可以定义文档之间更复杂的关系。

```javascript
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)。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mongoose.shujuwajue.com/guide/population.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
