sql >> Database >  >> NoSQL >> MongoDB

Object in een array vullen

Wat je hier eigenlijk hebt gemist, is het "pad" naar het veld dat je wilt populate() is eigenlijk 'portfolio.formatType' en niet alleen 'portfolio' zoals je hebt getypt. Door die fout en de structuur heb je misschien een paar algemene misvattingen.

Correctie invullen

De basiscorrectie heeft alleen het juiste pad nodig, en je hebt het model niet nodig argument aangezien dit al in het schema geïmpliceerd is:

User.findById(req.params.id).populate('portfolio.formatType');

Het is echter over het algemeen geen goed idee om zowel "ingebedde" gegevens als "verwezen" gegevens binnen arrays te "mixen", en u zou eigenlijk alles moeten insluiten of gewoon naar alles verwijzen. Het is in het algemeen ook een beetje een "anti-patroon" om een ​​reeks verwijzingen in het document te bewaren als het uw bedoeling is om te verwijzen, aangezien uw reden zou moeten zijn om het document niet te laten groeien boven de 16 MB BSON-limiet. En waar die limiet nooit zou worden bereikt door uw gegevens, is het over het algemeen beter om "volledig in te bedden". Dat is echt een bredere discussie, maar iets waar je je bewust van moet zijn.

Het volgende algemene punt hier is populate() zelf is enigszins "oude hoed", en echt niet het "magische" wat de meeste nieuwe gebruikers zien als zijnde. Voor alle duidelijkheid populate() is GEEN DOE MEE , en het enige wat het doet is het uitvoeren van een nieuwe query naar de server om de "gerelateerde" items te retourneren, en vervolgens die inhoud samenvoegen met de documenten die zijn geretourneerd door de vorige query.

$lookup Alternatief

Als je op zoek bent naar "joins", dan wilde je waarschijnlijk "embedding" zoals eerder vermeld. Dit is echt de "MongoDB-manier" om met "relaties" om te gaan, maar alle "gerelateerde" gegevens bij elkaar te houden in één document. De andere manier van "join" waarbij gegevens zich in afzonderlijke verzamelingen bevinden, is via de $lookup operator in moderne releases.

Dit wordt een beetje ingewikkelder vanwege uw "gemengde" inhoudsmatrixvorm, maar kan over het algemeen worden weergegeven als:

// Aggregation pipeline don't "autocast" from schema
const { Types: { ObjectId } } = require("mongoose");

User.aggregate([
  { "$match": { _id: ObjectId(req.params.id)  } },
  { "$lookup": {
    "from": FormatType.collection.name,
    "localField": "portfolio.formatType",
    "foreignField": "_id",
    "as": "formats"
  }},
  { "$project": {
    "name": 1,
    "portfolio": {
      "$map": {
        "input": "$portfolio",
        "in": {
          "name": "$$this.name",
          "formatType": {
            "$arrayElemAt": [
              "$formats",
              { "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
            ]
          }
        }
      }
    }
  }}
]);

Of met de meer expressieve vorm van $lookup sinds MongoDB 3.6:

User.aggregate([
  { "$match": { _id: ObjectId(req.params.id)  } },
  { "$lookup": {
    "from": FormatType.collection.name,
    "let": { "portfolio": "$portfolio" },
    "as": "portfolio",
    "pipeline": [
      { "$match": {
        "$expr": {
          "$in": [ "$_id", "$$portfolio.formatType" ]
        }
      }},
      { "$project": {
        "_id": {
          "$arrayElemAt": [
            "$$portfolio._id",
            { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
          ]
        },
        "name": {
          "$arrayElemAt": [
            "$$portfolio.name",
            { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
          ]
        },
        "formatType": "$$ROOT",
      }}
    ]
  }}
]);

De twee benaderingen werken iets anders, maar beide werken in wezen met het concept van het retourneren van de overeenkomende "gerelateerde" vermeldingen en vervolgens "opnieuw in kaart brengen" op de bestaande array-inhoud om samen te voegen met de "name" eigenschappen "ingebed" in de array. Dat is eigenlijk de belangrijkste complicatie die anders een vrij eenvoudige methode van ophalen is.

Het is vrijwel hetzelfde proces als wat populate() eigenlijk doet op de "client" maar uitgevoerd op de "server". Dus de vergelijkingen gebruiken de $indexOfArray operator om te zoeken waar de overeenkomende ObjectId waarden zijn en retourneer vervolgens een eigenschap uit de array op die overeenkomende "index" via de $arrayElemAt bediening.

Het enige verschil is dat we in de MongoDB 3.6-compatibele versie die "vervanging" doen binnen de "buitenlandse" inhoud "voor" de samengevoegde resultaten worden teruggestuurd naar de ouder. In eerdere releases retourneren we de hele overeenkomende buitenlandse array en "trouwen" met de twee om een ​​enkelvoudige "samengevoegde" array te vormen met behulp van $map .

Hoewel deze er in eerste instantie "complexer" uit kunnen zien, is het grote voordeel dat deze een "enkel verzoek" vormen. naar de server met een "single response" en het niet uitgeven en ontvangen van "meerdere" verzoeken als populate() doet. Dit bespaart in feite veel overhead in het netwerkverkeer en verhoogt de responstijd aanzienlijk.

Bovendien zijn dit "echte joins", dus er is veel meer dat u kunt doen wat niet kan worden bereikt met "meerdere zoekopdrachten". U kunt bijvoorbeeld resultaten "sorteren" op de "join" en alleen de topresultaten retourneren, waarbij u populate() gebruikt moet "alle ouders" binnenhalen voordat het zelfs maar kan zoeken welke "kinderen" in resultaat moeten terugkeren. Hetzelfde geldt ook voor "filtering"-voorwaarden op de onderliggende "join".

Er is meer informatie hierover op Query na invullen in Mongoose over de algemene beperkingen en wat u feitelijk zelfs praktisch kunt doen om het genereren van dergelijke "complexe" aggregatie-pipeline-statements waar nodig te "automatiseren".

Demonstratie

Een ander veelvoorkomend probleem bij het doen van deze "joins" en het begrijpen van het schema waarnaar verwezen wordt in het algemeen, is dat mensen vaak de concepten verkeerd hebben over waar en wanneer de referenties moeten worden opgeslagen en hoe het allemaal werkt. Daarom dienen de volgende lijsten als demonstratie van zowel het opslaan als het ophalen van dergelijke gegevens.

In een native Promises-implementatie voor oudere NodeJS-releases:

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

const uri = 'mongodb://localhost/usertest';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const formatTypeSchema = new Schema({
  name: String
});

const portfolioSchema = new Schema({
  name: String,
  formatType: { type: Schema.Types.ObjectId, ref: 'FormatType' }
});

const userSchema = new Schema({
  name: String,
  portfolio: [portfolioSchema]
});

const FormatType = mongoose.model('FormatType', formatTypeSchema);
const User = mongoose.model('User', userSchema);

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

(function() {

  mongoose.connect(uri).then(conn => {

    let db = conn.connections[0].db;

    return db.command({ buildInfo: 1 }).then(({ version }) => {
      version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);

      return Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()))
        .then(() => FormatType.insertMany(
          [ 'A', 'B', 'C' ].map(name => ({ name }))
        )
        .then(([A, B, C]) => User.insertMany(
          [
            {
              name: 'User 1',
              portfolio: [
                { name: 'Port A', formatType: A },
                { name: 'Port B', formatType: B }
              ]
            },
            {
              name: 'User 2',
              portfolio: [
                { name: 'Port C', formatType: C }
              ]
            }
          ]
        ))
        .then(() => User.find())
        .then(users => log({ users }))
        .then(() => User.findOne({ name: 'User 1' })
          .populate('portfolio.formatType')
        )
        .then(user1 => log({ user1 }))
        .then(() => User.aggregate([
          { "$match": { "name": "User 2" } },
          { "$lookup": {
            "from": FormatType.collection.name,
            "localField": "portfolio.formatType",
            "foreignField": "_id",
            "as": "formats"
          }},
          { "$project": {
            "name": 1,
            "portfolio": {
              "$map": {
                "input": "$portfolio",
                "in": {
                  "name": "$$this.name",
                  "formatType": {
                    "$arrayElemAt": [
                      "$formats",
                      { "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
                    ]
                  }
                }
              }
            }
          }}
        ]))
        .then(user2 => log({ user2 }))
        .then(() =>
          ( version >= 3.6 ) ?
            User.aggregate([
              { "$lookup": {
                "from": FormatType.collection.name,
                "let": { "portfolio": "$portfolio" },
                "as": "portfolio",
                "pipeline": [
                  { "$match": {
                    "$expr": {
                      "$in": [ "$_id", "$$portfolio.formatType" ]
                    }
                  }},
                  { "$project": {
                    "_id": {
                      "$arrayElemAt": [
                        "$$portfolio._id",
                        { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                      ]
                    },
                    "name": {
                      "$arrayElemAt": [
                        "$$portfolio.name",
                        { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                      ]
                    },
                    "formatType": "$$ROOT",
                  }}
                ]
              }}
            ]).then(users => log({ users })) : ''
        );
  })
  .catch(e => console.error(e))
  .then(() => mongoose.disconnect());

})()

En met async/await syntaxis voor nieuwere NodeJS-releases, inclusief de huidige LTS v.8.x-serie:

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

const uri = 'mongodb://localhost/usertest';

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const formatTypeSchema = new Schema({
  name: String
});

const portfolioSchema = new Schema({
  name: String,
  formatType: { type: Schema.Types.ObjectId, ref: 'FormatType' }
});

const userSchema = new Schema({
  name: String,
  portfolio: [portfolioSchema]
});

const FormatType = mongoose.model('FormatType', formatTypeSchema);
const User = mongoose.model('User', userSchema);

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

(async function() {

  try {

    const conn = await mongoose.connect(uri);
    let db = conn.connections[0].db;

    let { version } = await db.command({ buildInfo: 1 });
    version = parseFloat(version.match(new RegExp(/(?:(?!-).)*/))[0]);
    log(version);

    // Clean data
    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    // Insert some things
    let [ A, B, C ] = await FormatType.insertMany(
      [ 'A', 'B', 'C' ].map(name => ({ name }))
    );

    await User.insertMany(
      [
        {
          name: 'User 1',
          portfolio: [
            { name: 'Port A', formatType: A },
            { name: 'Port B', formatType: B }
          ]
        },
        {
          name: 'User 2',
          portfolio: [
            { name: 'Port C', formatType: C }
          ]
        }
      ]
    );


    // Show plain users
    let users = await User.find();
    log({ users });

    // Get user with populate

    let user1 = await User.findOne({ name: 'User 1' })
      .populate('portfolio.formatType');

    log({ user1 });

    // Get user with $lookup
    let user2 = await User.aggregate([
      { "$match": { "name": "User 2" } },
      { "$lookup": {
        "from": FormatType.collection.name,
        "localField": "portfolio.formatType",
        "foreignField": "_id",
        "as": "formats"
      }},
      { "$project": {
        "name": 1,
        "portfolio": {
          "$map": {
            "input": "$portfolio",
            "in": {
              "name": "$$this.name",
              "formatType": {
                "$arrayElemAt": [
                  "$formats",
                  { "$indexOfArray": [ "$formats._id", "$$this.formatType" ] }
                ]
              }
            }
          }
        }
      }}
    ]);

    log({ user2 });

    // Expressive $lookup
    if ( version >= 3.6 ) {
      let users = await User.aggregate([
        { "$lookup": {
          "from": FormatType.collection.name,
          "let": { "portfolio": "$portfolio" },
          "as": "portfolio",
          "pipeline": [
            { "$match": {
              "$expr": {
                "$in": [ "$_id", "$$portfolio.formatType" ]
              }
            }},
            { "$project": {
              "_id": {
                "$arrayElemAt": [
                  "$$portfolio._id",
                  { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                ]
              },
              "name": {
                "$arrayElemAt": [
                  "$$portfolio.name",
                  { "$indexOfArray": [ "$$portfolio.formatType", "$_id" ] }
                ]
              },
              "formatType": "$$ROOT",
            }}
          ]
        }}
      ]);
      log({ users })
    }

    mongoose.disconnect();    
  } catch(e) {
    console.log(e)
  } finally {
    process.exit()
  }

})()

De laatste lijst wordt bij elke fase becommentarieerd om de onderdelen uit te leggen, en je kunt in ieder geval door vergelijking zien hoe beide vormen van syntaxis zich tot elkaar verhouden.

Merk op dat de "expressieve" $lookup voorbeeld wordt alleen uitgevoerd waar de MongoDB-server waarmee is verbonden de syntaxis daadwerkelijk ondersteunt.

En de "output" voor degenen die niet de moeite nemen om de code zelf uit te voeren:

Mongoose: formattypes.remove({}, {})
Mongoose: users.remove({}, {})
Mongoose: formattypes.insertMany([ { _id: 5b1601d8be9bf225554783f5, name: 'A', __v: 0 }, { _id: 5b1601d8be9bf225554783f6, name: 'B', __v: 0 }, { _id: 5b1601d8be9bf225554783f7, name: 'C', __v: 0 } ], {})
Mongoose: users.insertMany([ { _id: 5b1601d8be9bf225554783f8, name: 'User 1', portfolio: [ { _id: 5b1601d8be9bf225554783fa, name: 'Port A', formatType: 5b1601d8be9bf225554783f5 }, { _id: 5b1601d8be9bf225554783f9, name: 'Port B', formatType: 5b1601d8be9bf225554783f6 } ], __v: 0 }, { _id: 5b1601d8be9bf225554783fb, name: 'User 2', portfolio: [ { _id: 5b1601d8be9bf225554783fc, name: 'Port C', formatType: 5b1601d8be9bf225554783f7 } ], __v: 0 } ], {})
Mongoose: users.find({}, { fields: {} })
{
  "users": [
    {
      "_id": "5b1601d8be9bf225554783f8",
      "name": "User 1",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fa",
          "name": "Port A",
          "formatType": "5b1601d8be9bf225554783f5"
        },
        {
          "_id": "5b1601d8be9bf225554783f9",
          "name": "Port B",
          "formatType": "5b1601d8be9bf225554783f6"
        }
      ],
      "__v": 0
    },
    {
      "_id": "5b1601d8be9bf225554783fb",
      "name": "User 2",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fc",
          "name": "Port C",
          "formatType": "5b1601d8be9bf225554783f7"
        }
      ],
      "__v": 0
    }
  ]
}
Mongoose: users.findOne({ name: 'User 1' }, { fields: {} })
Mongoose: formattypes.find({ _id: { '$in': [ ObjectId("5b1601d8be9bf225554783f5"), ObjectId("5b1601d8be9bf225554783f6") ] } }, { fields: {} })
{
  "user1": {
    "_id": "5b1601d8be9bf225554783f8",
    "name": "User 1",
    "portfolio": [
      {
        "_id": "5b1601d8be9bf225554783fa",
        "name": "Port A",
        "formatType": {
          "_id": "5b1601d8be9bf225554783f5",
          "name": "A",
          "__v": 0
        }
      },
      {
        "_id": "5b1601d8be9bf225554783f9",
        "name": "Port B",
        "formatType": {
          "_id": "5b1601d8be9bf225554783f6",
          "name": "B",
          "__v": 0
        }
      }
    ],
    "__v": 0
  }
}
Mongoose: users.aggregate([ { '$match': { name: 'User 2' } }, { '$lookup': { from: 'formattypes', localField: 'portfolio.formatType', foreignField: '_id', as: 'formats' } }, { '$project': { name: 1, portfolio: { '$map': { input: '$portfolio', in: { name: '$$this.name', formatType: { '$arrayElemAt': [ '$formats', { '$indexOfArray': [ '$formats._id', '$$this.formatType' ] } ] } } } } } } ], {})
{
  "user2": [
    {
      "_id": "5b1601d8be9bf225554783fb",
      "name": "User 2",
      "portfolio": [
        {
          "name": "Port C",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f7",
            "name": "C",
            "__v": 0
          }
        }
      ]
    }
  ]
}
Mongoose: users.aggregate([ { '$lookup': { from: 'formattypes', let: { portfolio: '$portfolio' }, as: 'portfolio', pipeline: [ { '$match': { '$expr': { '$in': [ '$_id', '$$portfolio.formatType' ] } } }, { '$project': { _id: { '$arrayElemAt': [ '$$portfolio._id', { '$indexOfArray': [ '$$portfolio.formatType', '$_id' ] } ] }, name: { '$arrayElemAt': [ '$$portfolio.name', { '$indexOfArray': [ '$$portfolio.formatType', '$_id' ] } ] }, formatType: '$$ROOT' } } ] } } ], {})
{
  "users": [
    {
      "_id": "5b1601d8be9bf225554783f8",
      "name": "User 1",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fa",
          "name": "Port A",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f5",
            "name": "A",
            "__v": 0
          }
        },
        {
          "_id": "5b1601d8be9bf225554783f9",
          "name": "Port B",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f6",
            "name": "B",
            "__v": 0
          }
        }
      ],
      "__v": 0
    },
    {
      "_id": "5b1601d8be9bf225554783fb",
      "name": "User 2",
      "portfolio": [
        {
          "_id": "5b1601d8be9bf225554783fc",
          "name": "Port C",
          "formatType": {
            "_id": "5b1601d8be9bf225554783f7",
            "name": "C",
            "__v": 0
          }
        }
      ],
      "__v": 0
    }
  ]
}



  1. $ push-equivalent voor kaart in mongo

  2. Dubbele rijen aan de linkerkant krijgen, doe mee aan Birt-rapporten

  3. Hoe kan ik de positieve en negatieve prijs berekenen met mongodb of robomongo?

  4. Mongodb Update Veel