복붙노트

[MONGODB] 몽구스의 채우기 후 쿼리

MONGODB

몽구스의 채우기 후 쿼리

나는 일반적으로 몽구스와 MongoDB를 꽤 새로운 해요 내가 이런 일이 가능하면 알아내는 힘든 시간을 보내고있어 너무 :

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
    });

더 좋은 방법이있다 할 수 있습니까?

편집하다

혼란에 대한 사과. 난 할 노력하고있어 재미있는 태그 또는 정치 태그 중 하나를 포함하는 모든 항목을 얻을 수 있습니다.

편집하다

where 절없이 문서 :

[{ 
    _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 
 }, ... ]

where 절과 함께, 나는 하늘의 배열을 얻는다.

해결법

  1. ==============================

    1.3.2보다 현대 MongoDB를의 큰하면 대부분의 경우 () .populate하는 대체로 $ 조회를 사용할 수 있습니다. 이것은 또한 ()는 실제로 "에뮬레이션"에서 "여러 쿼리"조인되는 않는 .populate 무엇에 반대 "서버"에 가입 실제로 수행하는 장점이있다.

    3.2보다 현대 MongoDB를의 큰하면 대부분의 경우 () .populate하는 대체로 $ 조회를 사용할 수 있습니다. 이것은 또한 ()는 실제로 "에뮬레이션"에서 "여러 쿼리"조인되는 않는 .populate 무엇에 반대 "서버"에 가입 실제로 수행하는 장점이있다.

    .populate은 () 정말 아닙니다 그래서 관계형 데이터베이스가 어떻게하는지의 의미에서 "참여". 반면에 $ 조회 연산자는 실제로 서버에서 작업을 수행하고, 더 많거나 적은 유사한 "LEFT 조인"하는 것입니다 :

    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"
      }
    )
    

    우리는 또한 원치 않는 항목을 제거하기 위해 배열에 $ 필터를 사용할 수있는 동안, 이것은 실제로는 $ 조회 둘 모두 $ 긴장을 풀고 달러 (A $) 정합 조건에 의해 다음과 같이의 특별한 조건에 대한 집계 파이프 라인 최적화에 가장 효율적인 형태입니다.

    이것은 실제로 하나에 출시되는 세 개의 파이프 라인 단계가 발생합니다

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

    실제 조작은 "제 가입 콜렉션 필터"다음의 결과 "풀려"배열을 반환 이것은 매우 적합하다. 결과는 클라이언트가없는 것을 제약이다 16메가바이트의 BSON 한계를 아프게하지 않도록 두 가지 방법이 사용된다.

    유일한 문제는 당신이 배열의 결과를 원하는 특히, "직관"어떤 방법으로 보이지만, 그게 원본 문서 양식으로 재구성으로 $ 그룹이 여기에 대해 어떤 점이다.

    우리가 단순히이 시간에 실제로 서버가 사용하는 동일한 최종 구문 $를 조회 쓸 수없는 것이 유감입니다. IMHO,이 수정 될 수있는 감독이다. 하지만 지금은, 단순히 순서를 사용하여 작업과 최고의 성능과 확장 성을 가장 실행 가능한 옵션입니다 것입니다.

    여기에 표시된 패턴이 상당히 인해 다른 단계가 모두 $ 조회 및 채우기의 행동 (에 일반적으로 내재되어있는 "LEFT이 가입"고에 실패한 $ 조회, 그것은이 있습니까 하나에 압연 얻을 방법에 최적화되어 있지만) 부정한다 언 와인드 여기에 $의 "최적의"사용에 의해 빈 배열을 보존하지 않는다. 당신은 preserveNullAndEmptyArrays 옵션을 추가 할 수 있지만, "최적화"순서는 기본적으로 위에서 설명한이을 Negate은 일반적으로 최적화에 결합 될 것 그대로 세 단계를 떠난다.

    MongoDB를은 "하위 파이프 라인"표현을 허용 $ 조회의 "더 표현"양식 3.6 확장합니다. 어느뿐만 아니라 충족 유지의 목표 "LEFT JOIN을"하지만 여전히 최적의 쿼리가 반환 된 결과를 줄이고 훨씬 더 단순화 된 구문을 할 수 있습니다 :

    Item.aggregate([
      { "$lookup": {
        "from": ItemTags.collection.name,
        "let": { "tags": "$tags" },
        "pipeline": [
          { "$match": {
            "tags": { "$in": [ "politics", "funny" ] },
            "$expr": { "$in": [ "$_id", "$$tags" ] }
          }}
        ]
      }}
    ])
    

    EXPR 경기하기 위해 사용되는 $는 "외국"값 "로컬"값이 MongoDB를 원래 $ 조회 구문 이제 "내부적으로"무엇을 실제로 선언했다. 이 형태로 표현함으로써 우리는 "하위 파이프 라인"자신 내에서 초기 $ 일치 식을 조정할 수 있습니다.

    사실, 진정한 "통합 파이프 라인"당신은 당신이 다른 관련 컬렉션에 $ 조회의 "중첩"수준을 포함하여이 "하위 파이프 라인"표현에서 집계 파이프 라인으로 할 수있는 일에 대해 할 수 있습니다.

    이 가득에서 또한 사용은 약간의 문제는 여기 요구하시는의 범위를 벗어나지 만도 "중첩 된 인구"과 관련하여 다음 $ 조회의 새로운 사용 패턴이 훨씬 동일 할 수 있도록하고, "많은"더 강력한 용법.

    다음 모델에 고정 방법을 사용한 예이다. 정적 메소드가 호출을 구현하면 간단하게 :

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

    심지어 좀 더 현대적인 것으로 강화하는 것은이된다 :

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

    매우 유사한 구조 () .populate 할 수있게하지만, 실제로 대신 서버에 참여하고있어. 완성도를 들어, 사용은 여기에 반환 된 데이터는 모두 부모와 자식 경우에 따라서에서 몽구스 문서 인스턴스에 다시 던진다.

    그것은 아주 사소한 및 적응하거나 대부분의 경우에 대해있는 그대로 사용하기 쉽습니다.

    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();
      }
    )
    

    또는 노드 8.x의과와 비동기 / await를하고 추가 종속성 위 현대 조금 :

    const { Schema } = mongoose = require('mongoose');
    const uri = 'mongodb://localhost/looktest';
    
    mongoose.Promise = global.Promise;
    mongoose.set('debug', true);
    
    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) {
      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
      ];
    
      return this.aggregate(pipeline).exec().then(r => r.map(m => 
        this({ ...m, [opt.path]: m[opt.path].map(r => rel(r)) })
      ));
    }
    
    const Item = mongoose.model('Item', itemSchema);
    const ItemTag = mongoose.model('ItemTag', itemTagSchema);
    
    const log = body => console.log(JSON.stringify(body, undefined, 2));
    
    (async function() {
      try {
    
        const conn = await mongoose.connect(uri);
    
        // Clean data
        await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
    
        // Create tags and items
        const tags = await ItemTag.create(
          ["movies", "funny"].map(tagName =>({ tagName }))
        );
        const item = await Item.create({ 
          "title": "Something",
          "description": "An item",
          tags 
        });
    
        // Query with our static
        const result = (await Item.lookup({
          path: 'tags',
          query: { 'tags.tagName' : { '$in': [ 'funny', 'politics' ] } }
        })).pop();
        log(result);
    
        mongoose.disconnect();
    
      } catch (e) {
        console.error(e);
      } finally {
        process.exit()
      }
    })()
    

    그리고 MongoDB를 3.6에서 위쪽으로, 심지어는 $의 긴장을 풀고 $ 그룹 건물없이 :

    const { Schema, Types: { ObjectId } } = mongoose = require('mongoose');
    
    const uri = 'mongodb://localhost/looktest';
    
    mongoose.Promise = global.Promise;
    mongoose.set('debug', true);
    
    const itemTagSchema = new Schema({
      tagName: String
    });
    
    const itemSchema = new Schema({
      title: String,
      description: String,
      tags: [{ type: Schema.Types.ObjectId, ref: 'ItemTag' }]
    },{ timestamps: true });
    
    itemSchema.statics.lookup = function({ path, query }) {
      let rel =
        mongoose.model(this.schema.path(path).caster.options.ref);
    
      // MongoDB 3.6 and up $lookup with sub-pipeline
      let pipeline = [
        { "$lookup": {
          "from": rel.collection.name,
          "as": path,
          "let": { [path]: `$${path}` },
          "pipeline": [
            { "$match": {
              ...query,
              "$expr": { "$in": [ "$_id", `$$${path}` ] }
            }}
          ]
        }}
      ];
    
      return this.aggregate(pipeline).exec().then(r => r.map(m =>
        this({ ...m, [path]: m[path].map(r => rel(r)) })
      ));
    };
    
    const Item = mongoose.model('Item', itemSchema);
    const ItemTag = mongoose.model('ItemTag', itemTagSchema);
    
    const log = body => console.log(JSON.stringify(body, undefined, 2));
    
    (async function() {
    
      try {
    
        const conn = await mongoose.connect(uri);
    
        // Clean data
        await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));
    
        // Create tags and items
        const tags = await ItemTag.insertMany(
          ["movies", "funny"].map(tagName => ({ tagName }))
        );
    
        const item = await Item.create({
          "title": "Something",
          "description": "An item",
          tags
        });
    
        // Query with our static
        let result = (await Item.lookup({
          path: 'tags',
          query: { 'tagName': { '$in': [ 'funny', 'politics' ] } }
        })).pop();
        log(result);
    
    
        await mongoose.disconnect();
    
      } catch(e) {
        console.error(e)
      } finally {
        process.exit()
      }
    
    })()
    
  2. ==============================

    2.당신을 위해 무엇을 요구하는 것은 직접 지원이 아니라 쿼리 반환 후 다른 필터 단계를 추가함으로써 달성 될 수있다.

    당신을 위해 무엇을 요구하는 것은 직접 지원이 아니라 쿼리 반환 후 다른 필터 단계를 추가함으로써 달성 될 수있다.

    첫째, .populate ( '태그', 널 (null) {태그 이름 : {$에서 : [ '재미', '정치']}})는 태그 문서를 필터링해야 할 일을 확실히이다. 다음 쿼리 반환 후 수동으로 채우기 기준과 일치하는 것이 어떤 태그 문서가없는 문서를 필터링해야합니다. 뭔가 같은 :

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

    3.교체 시도

    교체 시도

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

    으로

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

    4.업데이트 : 코멘트에서 봐 주시기 바랍니다 -이 "답"을 삭제하지 않도록이 답변이 제대로 질문에 일치하지 않는,하지만 어쩌면 그것은 우연히 사용자의 다른 질문에 대한 답변 (I 때문에 upvotes의 생각)

    업데이트 : 코멘트에서 봐 주시기 바랍니다 -이 "답"을 삭제하지 않도록이 답변이 제대로 질문에 일치하지 않는,하지만 어쩌면 그것은 우연히 사용자의 다른 질문에 대한 답변 (I 때문에 upvotes의 생각)

    첫째 :이 질문은 정말 오래된 알고 있지만 정확히이 문제에 대한 검색이 SO 포스트는 구글 항목 # 1이었다. 나는 docs.filter 버전 (허용 대답)를 구현하지만 몽구스 v4.6.0 워드 프로세서에서 읽을 때 우리가 간단하게 사용할 수 있도록 :

    Item.find({}).populate({
        path: 'tags',
        match: { tagName: { $in: ['funny', 'politics'] }}
    }).exec((err, items) => {
      console.log(items.tags) 
      // contains only tags where tagName is 'funny' or 'politics'
    })
    

    이 미래의 검색 시스템 사용자가 도움이되기를 바랍니다.

  5. ==============================

    5.최근 자신을 동일한 문제를 가진 후, 나는 다음과 같은 해결책을 마련했습니다 :

    최근 자신을 동일한 문제를 가진 후, 나는 다음과 같은 해결책을 마련했습니다 :

    첫째, 모든 태그 이름은 '재미'중 하나입니다 ItemTags 또는 '정치'를 찾아 ItemTag의 _ids의 배열을 반환합니다.

    그런 다음, 태그 배열의 모든 ItemTag의 _ids를 포함하는 항목을 찾을 수

    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
         });
      });
    
  6. ==============================

    6.@aaronheckmann의 대답은 나를 위해 일하지만 반환 doc.tags.length을 대체했다; 반환 doc.tags에 = 널! 해당 필드가 null이 포함되어 있기 때문에 채우기 내부에 기록 된 조건에 일치하지 않는 경우. 최종 코드 그래서 :

    @aaronheckmann의 대답은 나를 위해 일하지만 반환 doc.tags.length을 대체했다; 반환 doc.tags에 = 널! 해당 필드가 null이 포함되어 있기 때문에 채우기 내부에 기록 된 조건에 일치하지 않는 경우. 최종 코드 그래서 :

    query....
    .exec(function(err, docs){
       docs = docs.filter(function(doc){
         return doc.tags != null;
       })
       // do stuff with docs
    });
    
  7. from https://stackoverflow.com/questions/11303294/querying-after-populate-in-mongoose by cc-by-sa and MIT license