node.js - query - node js mongoose insert




Abfrage nach dem Auffüllen in Mongoose (4)

Versuchen Sie zu ersetzen

.populate('tags').where('tags.tagName').in(['funny', 'politics']) 

durch

.populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } )

Ich bin neu bei Mongoose und MongoDB im Allgemeinen, so dass es mir schwer fällt, herauszufinden, ob so etwas möglich ist:

Item = new Schema({
    id: Schema.ObjectId,
    dateCreated: { type: Date, default: Date.now },
    title: { type: String, default: 'No Title' },
    description: { type: String, default: 'No Description' },
    tags: [ { type: Schema.ObjectId, ref: 'ItemTag' }]
});

ItemTag = new Schema({
    id: Schema.ObjectId,
    tagId: { type: Schema.ObjectId, ref: 'Tag' },
    tagName: { type: String }
});



var query = Models.Item.find({});

query
    .desc('dateCreated')
    .populate('tags')
    .where('tags.tagName').in(['funny', 'politics'])
    .run(function(err, docs){
       // docs is always empty
    });

Gibt es einen besseren Weg dies zu tun?

Bearbeiten

Entschuldigung für jede Verwirrung. Was ich versuche, ist, alle Elemente zu erhalten, die entweder das lustige Tag oder das Politik-Tag enthalten.

Bearbeiten

Dokument ohne Where-Klausel:

[{ 
    _id: 4fe90264e5caa33f04000012,
    dislikes: 0,
    likes: 0,
    source: '/uploads/loldog.jpg',
    comments: [],
    tags: [{
        itemId: 4fe90264e5caa33f04000012,
        tagName: 'movies',
        tagId: 4fe64219007e20e644000007,
        _id: 4fe90270e5caa33f04000015,
        dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
        rating: 0,
        dislikes: 0,
        likes: 0 
    },
    { 
        itemId: 4fe90264e5caa33f04000012,
        tagName: 'funny',
        tagId: 4fe64219007e20e644000002,
        _id: 4fe90270e5caa33f04000017,
        dateCreated: Tue, 26 Jun 2012 00:29:36 GMT,
        rating: 0,
        dislikes: 0,
        likes: 0 
    }],
    viewCount: 0,
    rating: 0,
    type: 'image',
    description: null,
    title: 'dogggg',
    dateCreated: Tue, 26 Jun 2012 00:29:24 GMT 
 }, ... ]

Mit der where-Klausel erhalte ich ein leeres Array.


Das, wonach Sie fragen, wird nicht direkt unterstützt, kann aber durch Hinzufügen eines weiteren Filterschritts nach dem Zurückgeben der Abfrage erreicht werden.

Erstens, .populate( 'tags', null, { tagName: { $in: ['funny', 'politics'] } } ) ist definitiv, was Sie tun müssen, um die Tag-Dokumente zu filtern. Nach dem Zurückgeben der Abfrage müssen Sie dann Dokumente manuell ausfiltern, für die keine tags Dokumente vorhanden sind, die den Kriterien für das Auffüllen entsprechen. etwas wie:

query....
.exec(function(err, docs){
   docs = docs.filter(function(doc){
     return doc.tags.length;
   })
   // do stuff with docs
});

Nachdem ich kürzlich das gleiche Problem hatte, habe ich folgende Lösung gefunden:

Finde zuerst alle ItemTags, in denen tagName entweder 'funny' oder 'politics' ist, und gebe ein Array von ItemTag _ids zurück.

Suchen Sie dann Elemente, die alle ItemTag _ids im Tag-Array enthalten

ItemTag
  .find({ tagName : { $in : ['funny','politics'] } })
  .lean()
  .distinct('_id')
  .exec((err, itemTagIds) => {
     if (err) { console.error(err); }
     Item.find({ tag: { $all: itemTagIds} }, (err, items) => {
        console.log(items); // Items filtered by tagName
     });
  });

Mit einer modernen MongoDB größer als 3.2 können Sie in den meisten Fällen $lookup als Alternative zu .populate() verwenden. Dies hat auch den Vorteil, den Join "auf dem Server" zu machen, im Gegensatz zu dem, was .populate() tut, was eigentlich "multiple Abfragen" ist, um einen Join zu "emulieren" .

So ist .populate() nicht wirklich ein "Join" im Sinne einer relationalen Datenbank. Der $lookup Operator auf der anderen Seite arbeitet tatsächlich auf dem Server und ist mehr oder weniger analog zu einem "LINKEN JOIN" :

Item.aggregate(
  [
    { "$lookup": {
      "from": ItemTags.collection.name,
      "localField": "tags",
      "foreignField": "_id",
      "as": "tags"
    }},
    { "$unwind": "$tags" },
    { "$match": { "tags.tagName": { "$in": [ "funny", "politics" ] } } },
    { "$group": {
      "_id": "$_id",
      "dateCreated": { "$first": "$dateCreated" },
      "title": { "$first": "$title" },
      "description": { "$first": "$description" },
      "tags": { "$push": "$tags" }
    }}
  ],
  function(err, result) {
    // "tags" is now filtered by condition and "joined"
  }
)

NB: Der .collection.name hier tatsächlich als "String" ausgewertet, .collection.name als der tatsächliche Name der MongoDB-Sammlung, die dem Modell zugewiesen ist. Da mongoose Sammlungsnamen standardmäßig "pluralisiert" und $lookup den eigentlichen MongoDB-Sammlungsnamen als Argument benötigt (da es sich um eine Serveroperation handelt), ist dies ein praktischer Trick für den Einsatz in Mangocode, im Gegensatz zur "harten Codierung" der Sammlung Name direkt.

Wir könnten zwar auch $filter für Arrays verwenden, um unerwünschte Elemente zu entfernen, dies ist jedoch das effizienteste Formular aufgrund der Aggregationspipeline-Optimierung für die spezielle Bedingung von as $lookup gefolgt von $unwind und $match .

Dies führt tatsächlich dazu, dass die drei Pipelinestufen in einer zusammengefasst werden:

   { "$lookup" : {
     "from" : "itemtags",
     "as" : "tags",
     "localField" : "tags",
     "foreignField" : "_id",
     "unwinding" : {
       "preserveNullAndEmptyArrays" : false
     },
     "matching" : {
       "tagName" : {
         "$in" : [
           "funny",
           "politics"
         ]
       }
     }
   }}

Dies ist sehr optimal, da die eigentliche Operation "die Sammlung zuerst filtert", dann die Ergebnisse zurückgibt und das Array "abwickelt". Beide Methoden werden verwendet, so dass die Ergebnisse das BSON-Limit von 16 MB nicht überschreiten, was eine Einschränkung darstellt, die der Client nicht hat.

Das einzige Problem ist, dass es in einigen Aspekten "kontraintuitiv" zu sein scheint, besonders wenn Sie die Ergebnisse in einem Array haben wollen, aber dafür ist die $group hier gedacht, da sie das ursprüngliche Dokumentenformular rekonstruiert.

Es ist auch bedauerlich, dass wir zu diesem Zeitpunkt tatsächlich $lookup in der gleichen Syntax schreiben können, die der Server verwendet. IMHO, das ist ein zu korrigierendes Versehen. Aber für den Moment funktioniert einfach die Sequenz und ist die beste Option mit der besten Leistung und Skalierbarkeit.

Arbeitsbeispiel

Im folgenden Beispiel wird eine statische Methode für das Modell verwendet. Sobald diese statische Methode implementiert ist, wird der Aufruf einfach:

  Item.lookup(
    {
      path: 'tags',
      query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
    },
    callback
  )

Es ist sehr ähnlich zu .populate() in der Struktur, aber es macht tatsächlich den Join auf dem Server statt. Der Vollständigkeit halber werden bei der Verwendung die zurückgegebenen Daten gemäß den Eltern- und untergeordneten Fällen an die Mungo-Dokumenteninstanzen zurückgegeben.

Es ist ziemlich trivial und einfach anzupassen oder einfach zu verwenden, wie es für die häufigsten Fälle ist.

NB Die Verwendung von async hier ist nur für die Kürze des ausgeführten Beispiels. Die tatsächliche Implementierung ist frei von dieser Abhängigkeit.

const async = require('async'),
      mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.connect('mongodb://localhost/looktest');

const itemTagSchema = new Schema({
  tagName: String
});

const itemSchema = new Schema({
  dateCreated: { type: Date, default: Date.now },
  title: String,
  description: String,
  tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
});

itemSchema.statics.lookup = function(opt,callback) {
  let rel =
    mongoose.model(this.schema.path(opt.path).caster.options.ref);

  let group = { "$group": { } };
  this.schema.eachPath(p =>
    group.$group[p] = (p === "_id") ? "$_id" :
      (p === opt.path) ? { "$push": `$${p}` } : { "$first": `$${p}` });

  let pipeline = [
    { "$lookup": {
      "from": rel.collection.name,
      "as": opt.path,
      "localField": opt.path,
      "foreignField": "_id"
    }},
    { "$unwind": `$${opt.path}` },
    { "$match": opt.query },
    group
  ];

  this.aggregate(pipeline,(err,result) => {
    if (err) callback(err);
    result = result.map(m => {
      m[opt.path] = m[opt.path].map(r => rel(r));
      return this(m);
    });
    callback(err,result);
  });
}

const Item = mongoose.model('Item', itemSchema);
const ItemTag = mongoose.model('ItemTag', itemTagSchema);

function log(body) {
  console.log(JSON.stringify(body, undefined, 2))
}
async.series(
  [
    // Clean data
    (callback) => async.each(mongoose.models,(model,callback) =>
      model.remove({},callback),callback),

    // Create tags and items
    (callback) =>
      async.waterfall(
        [
          (callback) =>
            ItemTag.create([{ "tagName": "movies" }, { "tagName": "funny" }],
              callback),

          (tags, callback) =>
            Item.create({ "title": "Something","description": "An item",
              "tags": tags },callback)
        ],
        callback
      ),

    // Query with our static
    (callback) =>
      Item.lookup(
        {
          path: 'tags',
          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
        },
        callback
      )
  ],
  (err,results) => {
    if (err) throw err;
    let result = results.pop();
    log(result);
    mongoose.disconnect();
  }
)




mongoose