MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Mongoose phổ biến sau khi tổng hợp

Vì vậy, bạn thực sự đang thiếu một số khái niệm ở đây khi bạn yêu cầu "điền" vào một kết quả tổng hợp. Thông thường, đây không phải là những gì bạn thực sự làm, mà là để giải thích các điểm:

  1. Kết quả của aggregate() không giống như Model.find() hoặc hành động tương tự vì mục đích ở đây là "định hình lại kết quả". Về cơ bản, điều này có nghĩa là mô hình bạn đang sử dụng làm nguồn tổng hợp không còn được coi là mô hình đó trên đầu ra. Điều này thậm chí đúng nếu bạn vẫn duy trì cùng một cấu trúc tài liệu trên đầu ra, nhưng trong trường hợp của bạn, đầu ra rõ ràng khác với tài liệu nguồn.

    Ở bất kỳ mức độ nào, nó không còn là một bản sao của Warranty mô hình mà bạn đang tìm nguồn cung ứng, nhưng chỉ là một đối tượng đơn giản. Chúng tôi có thể giải quyết vấn đề đó khi chúng tôi tiếp xúc sau này.

  2. Có lẽ điểm chính ở đây là populate() có phần "mũ cũ" dù sao. Đây thực sự chỉ là một chức năng tiện lợi được thêm vào Mongoose trong những ngày đầu triển khai. Tất cả những gì nó thực sự làm là thực thi "một truy vấn khác" trên liên quan dữ liệu trong một bộ sưu tập riêng biệt và sau đó kết hợp các kết quả trong bộ nhớ với kết quả đầu ra của bộ sưu tập ban đầu.

    Vì nhiều lý do, điều đó không thực sự hiệu quả hoặc thậm chí không mong muốn trong hầu hết các trường hợp. Và trái với quan niệm sai lầm phổ biến, điều này KHÔNG thực sự là một "tham gia".

    Để có một "tham gia" thực, bạn thực sự sử dụng $lookup giai đoạn đường ống tổng hợp, mà MongoDB sử dụng để trả về các mục phù hợp từ một bộ sưu tập khác. Không giống như populate() điều này thực sự được thực hiện trong một yêu cầu duy nhất đến máy chủ với một phản hồi duy nhất. Điều này tránh được các chi phí chung của mạng, nói chung là nhanh hơn và vì "tham gia thực" cho phép bạn thực hiện những việc populate() không làm được.

Sử dụng $ lookup thay thế

Rất nhanh chóng phiên bản của điều bị thiếu ở đây là thay vì cố gắng populate() trong .then() sau khi kết quả được trả về, những gì bạn làm thay vào đó là thêm $lookup đến đường ống:

  { "$lookup": {
    "from": Account.collection.name,
    "localField": "_id",
    "foreignField": "_id",
    "as": "accounts"
  }},
  { "$unwind": "$accounts" },
  { "$project": {
    "_id": "$accounts",
    "total": 1,
    "lineItems": 1
  }}

Lưu ý rằng có một hạn chế ở đây trong đó đầu ra của $lookup luôn luôn một mảng. Không quan trọng nếu chỉ có một hoặc nhiều mục liên quan được lấy làm đầu ra. Giai đoạn đường ống sẽ tìm kiếm giá trị của "localField" từ tài liệu hiện tại được trình bày và sử dụng tài liệu đó để khớp với các giá trị trong "foreignField" được chỉ định. Trong trường hợp này, đó là _id từ tập hợp $group nhắm mục tiêu đến _id của bộ sưu tập nước ngoài.

Vì đầu ra luôn là một mảng như đã đề cập, cách hiệu quả nhất để làm việc với điều này cho trường hợp này là chỉ cần thêm $unwind giai đoạn trực tiếp sau $lookup . Tất cả điều này sẽ thực hiện nó trả về một tài liệu mới cho mỗi mục được trả về trong mảng đích và trong trường hợp này, bạn mong đợi nó là một. Trong trường hợp _id không phù hợp trong bộ sưu tập nước ngoài, kết quả không có kết quả phù hợp sẽ bị xóa.

Một lưu ý nhỏ, đây thực sự là một mẫu được tối ưu hóa như được mô tả trong $ lookup + $ unwind Coalescence trong tài liệu cốt lõi. Một điều đặc biệt xảy ra ở đây khi $unwind hướng dẫn thực sự được hợp nhất vào $lookup hoạt động một cách hiệu quả. Bạn có thể đọc thêm về điều đó ở đó.

Sử dụng điền

Từ nội dung trên, bạn sẽ có thể hiểu về cơ bản tại sao populate() đây là điều sai lầm để làm. Ngoài thực tế cơ bản là đầu ra không còn bao gồm Warranty các đối tượng mô hình, mô hình đó thực sự chỉ biết về các mục nước ngoài được mô tả trên _accountId thuộc tính không tồn tại trong đầu ra.

Bây giờ bạn có thể thực sự xác định một mô hình có thể được sử dụng để truyền các đối tượng đầu ra một cách rõ ràng thành một kiểu đầu ra xác định. Một minh chứng ngắn về một sẽ liên quan đến việc thêm mã vào ứng dụng của bạn cho điều này như:

// Special models

const outputSchema = new Schema({
  _id: { type: Schema.Types.ObjectId, ref: "Account" },
  total: Number,
  lineItems: [{ address: String }]
});

const Output = mongoose.model('Output', outputSchema, 'dontuseme');

Output mới này sau đó, mô hình có thể được sử dụng để "truyền" các đối tượng JavaScript đơn giản kết quả vào Mongoose Documents để các phương thức như Model.populate() thực sự có thể được gọi là:

// excerpt
result2 = result2.map(r => new Output(r));   // Cast to Output Mongoose Documents

// Call populate on the list of documents
result2 = await Output.populate(result2, { path: '_id' })
log(result2);

Kể từ khi Output có một lược đồ được xác định nhận biết về "tham chiếu" trên _id trường của nó tài liệu Model.populate() nhận thức được những gì nó cần phải làm và trả lại các mục.

Hãy cẩn thận vì điều này thực sự tạo ra một truy vấn khác. tức là:

Mongoose: warranties.aggregate([ { '$match': { payStatus: 'Invoiced Next Billing Cycle' } }, { '$group': { _id: '$_accountId', total: { '$sum': '$warrantyFee' }, lineItems: { '$push': { _id: '$_id', address: { '$trim': { input: { '$reduce': { input: { '$objectToArray': '$address' }, initialValue: '', in: { '$concat': [ '$$value', ' ', [Object] ] } } }, chars: ' ' } } } } } } ], {})
Mongoose: accounts.find({ _id: { '$in': [ ObjectId("5bf4b591a06509544b8cf75c"), ObjectId("5bf4b591a06509544b8cf75b") ] } }, { projection: {} })

Trong đó dòng đầu tiên là kết quả tổng hợp và sau đó bạn liên hệ lại với máy chủ để trả về Account có liên quan mục mô hình.

Tóm tắt

Vì vậy, đó là các tùy chọn của bạn, nhưng cần khá rõ ràng rằng cách tiếp cận hiện đại cho việc này là thay vào đó sử dụng $lookup và nhận được một "tham gia" thực sự không phải là những gì populate() đang thực sự làm.

Bao gồm một danh sách như một minh chứng đầy đủ về cách mỗi phương pháp này thực sự hoạt động trong thực tế. Một số giấy phép nghệ thuật được lấy ở đây, vì vậy các mô hình được đại diện có thể không chính xác giống như những gì bạn có, nhưng có đủ để chứng minh các khái niệm cơ bản theo cách có thể tái tạo:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/joindemo';
const opts = { useNewUrlParser: true };

// Sensible defaults
mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// Schema defs

const warrantySchema = new Schema({
  address: {
    street: String,
    city: String,
    state: String,
    zip: Number
  },
  warrantyFee: Number,
  _accountId: { type: Schema.Types.ObjectId, ref: "Account" },
  payStatus: String
});

const accountSchema = new Schema({
  name: String,
  contactName: String,
  contactEmail: String
});

// Special models


const outputSchema = new Schema({
  _id: { type: Schema.Types.ObjectId, ref: "Account" },
  total: Number,
  lineItems: [{ address: String }]
});

const Output = mongoose.model('Output', outputSchema, 'dontuseme');

const Warranty = mongoose.model('Warranty', warrantySchema);
const Account = mongoose.model('Account', accountSchema);


// log helper
const log = data => console.log(JSON.stringify(data, undefined, 2));

// main
(async function() {

  try {

    const conn = await mongoose.connect(uri, opts);

    // clean models
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.deleteMany())
    )

    // set up data
    let [first, second, third] = await Account.insertMany(
      [
        ['First Account', 'First Person', '[email protected]'],
        ['Second Account', 'Second Person', '[email protected]'],
        ['Third Account', 'Third Person', '[email protected]']
      ].map(([name, contactName, contactEmail]) =>
        ({ name, contactName, contactEmail })
      )
    );

    await Warranty.insertMany(
      [
        {
          address: {
            street: '1 Some street',
            city: 'Somewhere',
            state: 'TX',
            zip: 1234
          },
          warrantyFee: 100,
          _accountId: first,
          payStatus: 'Invoiced Next Billing Cycle'
        },
        {
          address: {
            street: '2 Other street',
            city: 'Elsewhere',
            state: 'CA',
            zip: 5678
          },
          warrantyFee: 100,
          _accountId: first,
          payStatus: 'Invoiced Next Billing Cycle'
        },
        {
          address: {
            street: '3 Other street',
            city: 'Elsewhere',
            state: 'NY',
            zip: 1928
          },
          warrantyFee: 100,
          _accountId: first,
          payStatus: 'Invoiced Already'
        },
        {
          address: {
            street: '21 Jump street',
            city: 'Anywhere',
            state: 'NY',
            zip: 5432
          },
          warrantyFee: 100,
          _accountId: second,
          payStatus: 'Invoiced Next Billing Cycle'
        }
      ]
    );

    // Aggregate $lookup
    let result1 = await Warranty.aggregate([
      { "$match": {
        "payStatus": "Invoiced Next Billing Cycle"
      }},
      { "$group": {
        "_id": "$_accountId",
        "total": { "$sum": "$warrantyFee" },
        "lineItems": {
          "$push": {
            "_id": "$_id",
            "address": {
              "$trim": {
                "input": {
                  "$reduce": {
                    "input": { "$objectToArray": "$address" },
                    "initialValue": "",
                    "in": {
                      "$concat": [ "$$value", " ", { "$toString": "$$this.v" } ] }
                  }
                },
                "chars": " "
              }
            }
          }
        }
      }},
      { "$lookup": {
        "from": Account.collection.name,
        "localField": "_id",
        "foreignField": "_id",
        "as": "accounts"
      }},
      { "$unwind": "$accounts" },
      { "$project": {
        "_id": "$accounts",
        "total": 1,
        "lineItems": 1
      }}
    ])

    log(result1);

    // Convert and populate
    let result2 = await Warranty.aggregate([
      { "$match": {
        "payStatus": "Invoiced Next Billing Cycle"
      }},
      { "$group": {
        "_id": "$_accountId",
        "total": { "$sum": "$warrantyFee" },
        "lineItems": {
          "$push": {
            "_id": "$_id",
            "address": {
              "$trim": {
                "input": {
                  "$reduce": {
                    "input": { "$objectToArray": "$address" },
                    "initialValue": "",
                    "in": {
                      "$concat": [ "$$value", " ", { "$toString": "$$this.v" } ] }
                  }
                },
                "chars": " "
              }
            }
          }
        }
      }}
    ]);

    result2 = result2.map(r => new Output(r));

    result2 = await Output.populate(result2, { path: '_id' })
    log(result2);

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()

Và đầu ra đầy đủ:

Mongoose: dontuseme.deleteMany({}, {})
Mongoose: warranties.deleteMany({}, {})
Mongoose: accounts.deleteMany({}, {})
Mongoose: accounts.insertMany([ { _id: 5bf4b591a06509544b8cf75b, name: 'First Account', contactName: 'First Person', contactEmail: '[email protected]', __v: 0 }, { _id: 5bf4b591a06509544b8cf75c, name: 'Second Account', contactName: 'Second Person', contactEmail: '[email protected]', __v: 0 }, { _id: 5bf4b591a06509544b8cf75d, name: 'Third Account', contactName: 'Third Person', contactEmail: '[email protected]', __v: 0 } ], {})
Mongoose: warranties.insertMany([ { _id: 5bf4b591a06509544b8cf75e, address: { street: '1 Some street', city: 'Somewhere', state: 'TX', zip: 1234 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75b, payStatus: 'Invoiced Next Billing Cycle', __v: 0 }, { _id: 5bf4b591a06509544b8cf75f, address: { street: '2 Other street', city: 'Elsewhere', state: 'CA', zip: 5678 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75b, payStatus: 'Invoiced Next Billing Cycle', __v: 0 }, { _id: 5bf4b591a06509544b8cf760, address: { street: '3 Other street', city: 'Elsewhere', state: 'NY', zip: 1928 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75b, payStatus: 'Invoiced Already', __v: 0 }, { _id: 5bf4b591a06509544b8cf761, address: { street: '21 Jump street', city: 'Anywhere', state: 'NY', zip: 5432 }, warrantyFee: 100, _accountId: 5bf4b591a06509544b8cf75c, payStatus: 'Invoiced Next Billing Cycle', __v: 0 } ], {})
Mongoose: warranties.aggregate([ { '$match': { payStatus: 'Invoiced Next Billing Cycle' } }, { '$group': { _id: '$_accountId', total: { '$sum': '$warrantyFee' }, lineItems: { '$push': { _id: '$_id', address: { '$trim': { input: { '$reduce': { input: { '$objectToArray': '$address' }, initialValue: '', in: { '$concat': [ '$$value', ' ', [Object] ] } } }, chars: ' ' } } } } } }, { '$lookup': { from: 'accounts', localField: '_id', foreignField: '_id', as: 'accounts' } }, { '$unwind': '$accounts' }, { '$project': { _id: '$accounts', total: 1, lineItems: 1 } } ], {})
[
  {
    "total": 100,
    "lineItems": [
      {
        "_id": "5bf4b591a06509544b8cf761",
        "address": "21 Jump street Anywhere NY 5432"
      }
    ],
    "_id": {
      "_id": "5bf4b591a06509544b8cf75c",
      "name": "Second Account",
      "contactName": "Second Person",
      "contactEmail": "[email protected]",
      "__v": 0
    }
  },
  {
    "total": 200,
    "lineItems": [
      {
        "_id": "5bf4b591a06509544b8cf75e",
        "address": "1 Some street Somewhere TX 1234"
      },
      {
        "_id": "5bf4b591a06509544b8cf75f",
        "address": "2 Other street Elsewhere CA 5678"
      }
    ],
    "_id": {
      "_id": "5bf4b591a06509544b8cf75b",
      "name": "First Account",
      "contactName": "First Person",
      "contactEmail": "[email protected]",
      "__v": 0
    }
  }
]
Mongoose: warranties.aggregate([ { '$match': { payStatus: 'Invoiced Next Billing Cycle' } }, { '$group': { _id: '$_accountId', total: { '$sum': '$warrantyFee' }, lineItems: { '$push': { _id: '$_id', address: { '$trim': { input: { '$reduce': { input: { '$objectToArray': '$address' }, initialValue: '', in: { '$concat': [ '$$value', ' ', [Object] ] } } }, chars: ' ' } } } } } } ], {})
Mongoose: accounts.find({ _id: { '$in': [ ObjectId("5bf4b591a06509544b8cf75c"), ObjectId("5bf4b591a06509544b8cf75b") ] } }, { projection: {} })
[
  {
    "_id": {
      "_id": "5bf4b591a06509544b8cf75c",
      "name": "Second Account",
      "contactName": "Second Person",
      "contactEmail": "[email protected]",
      "__v": 0
    },
    "total": 100,
    "lineItems": [
      {
        "_id": "5bf4b591a06509544b8cf761",
        "address": "21 Jump street Anywhere NY 5432"
      }
    ]
  },
  {
    "_id": {
      "_id": "5bf4b591a06509544b8cf75b",
      "name": "First Account",
      "contactName": "First Person",
      "contactEmail": "[email protected]",
      "__v": 0
    },
    "total": 200,
    "lineItems": [
      {
        "_id": "5bf4b591a06509544b8cf75e",
        "address": "1 Some street Somewhere TX 1234"
      },
      {
        "_id": "5bf4b591a06509544b8cf75f",
        "address": "2 Other street Elsewhere CA 5678"
      }
    ]
  }
]


  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Không thể bắt đầu / khởi chạy db mongo cục bộ

  2. Mongoose:Làm thế nào để điền dân số sâu 2 cấp mà không điền các trường cấp 1? trong mongodb

  3. Bộ lọc ngày Mongoose

  4. Mongo:tìm tài liệu phụ không có ký hiệu dấu chấm

  5. Không thể cập nhật đối tượng danh sách mảng bên trong bằng Trình điều khiển Java Mongodb