복붙노트

[MONGODB] $ graphLookup에 대한 문자열에 ObjectId가 일치

MONGODB

$ graphLookup에 대한 문자열에 ObjectId가 일치

나는 인쇄 우는 소리에서 증명 같은 $ graphLookup을 실행하기 위해 노력하고있어 :

목적은, ($ 일치 댓글을 달았) 특정 기록을 주어가 가득 "경로"축복하는 immediateAncestors 숙박 시설의 검색하는 것입니다. 당신이 볼 수 있듯이, 그것은 일이 아닙니다.

나는 (문자열입니다) immediateAncestors 기록 목록에서 _id과 "일치"를 할 수 있었다 믿음, 문자열로 컬렉션 _id를 다루는 여기에 $ 변환을 소개했다.

그래서, 다른 데이터와 다른 테스트를 (더하며 Object가 포함되지)를 실행 않았다

db.nodos.insert({"id":5,"name":"cinco","children":[{"id":4}]})
db.nodos.insert({"id":4,"name":"quatro","ancestors":[{"id":5}],"children":[{"id":3}]})
db.nodos.insert({"id":6,"name":"seis","children":[{"id":3}]})
db.nodos.insert({"id":1,"name":"um","children":[{"id":2}]})
db.nodos.insert({"id":2,"name":"dois","ancestors":[{"id":1}],"children":[{"id":3}]})
db.nodos.insert({"id":3,"name":"três","ancestors":[{"id":2},{"id":4},{"id":6}]})
db.nodos.insert({"id":7,"name":"sete","children":[{"id":5}]})

그리고 쿼리 :

db.nodos.aggregate( [
  { $match: { "id": 3 } },
  { $graphLookup: {
      from: "nodos",
      startWith: "$ancestors.id",
      connectFromField: "ancestors.id",
      connectToField: "id",
      as: "ANCESTORS_FROM_BEGINNING"
    }
  },
  { $project: {
      "name": 1,
      "id": 1,
      "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING.id"
    }
  }
] )

내가 (직접 및 간접적으로 ID를 3 사람에 연결된 다섯 개의 레코드)을 기대하고 있었는지 ... 어떤 출력 :

{
    "_id" : ObjectId("5afe270fb4719112b613f1b4"),
    "id" : 3.0,
    "name" : "três",
    "ANCESTORS_FROM_BEGINNING" : [ 
        1.0, 
        4.0, 
        6.0, 
        5.0, 
        2.0
    ]
}

질문 : 처음에 언급 한 목적 I를 달성하는 방법이 있나요?

나는 (공식 도커에서) 몽고 3.7.9을 실행하는거야

사전에 감사합니다!

해결법

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

    1.현재 일부 기능이 공식 출시로 MongoDB를 4.0과 함께 출시 될 것으로 예상 활성화가 MongoDB를의 개발 버전을 사용하고 있습니다. 당신이 그것을 커밋하기 전에 프로덕션 코드이 알고 있어야하므로 일부 기능은 최종 릴리스 전에 변경 될 수 있음을 알려드립니다.

    현재 일부 기능이 공식 출시로 MongoDB를 4.0과 함께 출시 될 것으로 예상 활성화가 MongoDB를의 개발 버전을 사용하고 있습니다. 당신이 그것을 커밋하기 전에 프로덕션 코드이 알고 있어야하므로 일부 기능은 최종 릴리스 전에 변경 될 수 있음을 알려드립니다.

    아마 이것을 설명하는 가장 좋은 방법은 변경된 샘플을하지만 배열 미만은 특히 ObjectId _id 값과 "문자열"로 대체 보는 것입니다 :

    {
      "_id" : ObjectId("5afe5763419503c46544e272"),
       "name" : "cinco",
       "children" : [ { "_id" : "5afe5763419503c46544e273" } ]
    },
    {
      "_id" : ObjectId("5afe5763419503c46544e273"),
      "name" : "quatro",
      "ancestors" : [ { "_id" : "5afe5763419503c46544e272" } ],
      "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
    },
    { 
      "_id" : ObjectId("5afe5763419503c46544e274"),
      "name" : "seis",
      "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
    },
    { 
      "_id" : ObjectId("5afe5763419503c46544e275"),
      "name" : "um",
      "children" : [ { "_id" : "5afe5763419503c46544e276" } ]
    }
    {
      "_id" : ObjectId("5afe5763419503c46544e276"),
      "name" : "dois",
      "ancestors" : [ { "_id" : "5afe5763419503c46544e275" } ],
      "children" : [ { "_id" : "5afe5763419503c46544e277" } ]
    },
    { 
      "_id" : ObjectId("5afe5763419503c46544e277"),
      "name" : "três",
      "ancestors" : [
        { "_id" : "5afe5763419503c46544e273" },
        { "_id" : "5afe5763419503c46544e274" },
        { "_id" : "5afe5763419503c46544e276" }
      ]
    },
    { 
      "_id" : ObjectId("5afe5764419503c46544e278"),
      "name" : "sete",
      "children" : [ { "_id" : "5afe5763419503c46544e272" } ]
    }
    

    그것은 당신과 함께 작업을하려고했는지에 대한 일반적인 시뮬레이션을 제공해야합니다.

    당신이 시도하면 $ graphLookup 단계에 들어가기 전에 $ 프로젝트를 통해 "문자열"로 _id 값을 변환하는 것이 었습니다. 이 파이프 라인 "에서"초기 $ 프로젝트를 한 동안이 실패하는 이유는, 문제는 점에서 $ graphLookup의 소스 옵션이 계속 변경되지 않은 모음입니다 때문에 당신이에 대한 정확한 정보를 얻지 않는다 "에서" 다음 "검색"반복.

    db.strcoll.aggregate([
      { "$match": { "name": "três" } },
      { "$addFields": {
        "_id": { "$toString": "$_id" }
      }},
      { "$graphLookup": {
        "from": "strcoll",
        "startWith": "$ancestors._id",
        "connectFromField": "ancestors._id",
        "connectToField": "_id",
        "as": "ANCESTORS_FROM_BEGINNING"
      }},
      { "$project": {
        "name": 1,
        "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
      }}
    ])
    

    그러므로 "조회"에 일치하지 않습니다 :

    {
            "_id" : "5afe5763419503c46544e277",
            "name" : "três",
            "ANCESTORS_FROM_BEGINNING" : [ ]
    }
    

    그러나 그 핵심 문제가 아니라 $ 변환의 실패 또는 그것의 별칭 자체. 이 사실은 우리가 대신하는 입력을 위해서 컬렉션으로 선물 자체에 "보기"를 만들 수 있습니다 작동하게하기 위해.

    나는 주변이 다른 방법을하고 $ toObjectId를 통해 ObjectId가에있는 "문자열을"변환 할 수 있습니다 :

    db.createView("idview","strcoll",[
      { "$addFields": {
        "ancestors": {
          "$ifNull": [ 
            { "$map": {
              "input": "$ancestors",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        },
        "children": {
          "$ifNull": [
            { "$map": {
              "input": "$children",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        }
      }}
    ])
    

    "보기"를 사용하지만 값이 변환 된 데이터를 지속적으로 보이는 것을 의미한다. 뷰를 사용하여 다음 집계 그래서 :

    db.idview.aggregate([
      { "$match": { "name": "três" } },
      { "$graphLookup": {
        "from": "idview",
        "startWith": "$ancestors._id",
        "connectFromField": "ancestors._id",
        "connectToField": "_id",
        "as": "ANCESTORS_FROM_BEGINNING"
      }},
      { "$project": {
        "name": 1,
        "ANCESTORS_FROM_BEGINNING": "$ANCESTORS_FROM_BEGINNING._id"
      }}
    ])
    

    예상 출력을 반환합니다 :

    {
        "_id" : ObjectId("5afe5763419503c46544e277"),
        "name" : "três",
        "ANCESTORS_FROM_BEGINNING" : [
            ObjectId("5afe5763419503c46544e275"),
            ObjectId("5afe5763419503c46544e273"),
            ObjectId("5afe5763419503c46544e274"),
            ObjectId("5afe5763419503c46544e276"),
            ObjectId("5afe5763419503c46544e272")
        ]
    }
    

    이 모든 말한다면, 여기 진짜 문제는 그러나 그것이 "문자열"로 기록되었습니다, 당신은 ObjectId가 값 "와 같은 외모"일부 데이터가 있는지이며 ObjectId가 유효한 사실이다. 예상대로 작동하는 모든 것에 대한 기본적인 문제는 두 가지 "종류"동일하지 않습니다와 동등 불일치에서이 결과를 시도하고 있습니다 "조인"로한다는 것입니다.

    실제 수정은 항상 대신 데이터를 통해 이동하고 "문자열"또한 실제로 ObjectId가 값이되도록 그것을 해결하기 위해 인로되어있는 한 여전히 동일합니다 그래서. 다음은 그 때를 참조 의미하는 _id 키를 일치, 그리고 ObjectId가 16 진수 문자에 그것의 문자열 표현보다 가게에 훨씬 적은 공간을 차지하기 때문에 당신은 저장 공간의 상당한 금액을 절약 할 수 있습니다.

    MongoDB를 4.0 방법을 사용하여, 당신은 실제로 단지 우리가 "보기"이전에 만든 것으로 거의 같은 문제에, 새 컬렉션을 쓰기 위해 "$ toObjectId을"사용 "수"

    db.strcoll.aggregate([
      { "$addFields": {
        "ancestors": {
          "$ifNull": [ 
            { "$map": {
              "input": "$ancestors",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        },
        "children": {
          "$ifNull": [
            { "$map": {
              "input": "$children",
              "in": { "_id": { "$toObjectId": "$$this._id" } }
            }},
            "$$REMOVE"
          ]
        }
      }}
      { "$out": "fixedcol" }
    ])
    

    아니면 "필요"같은 콜렉션을 유지 물론, 다음 "루프 업데이트"전통은 항상 요구되고있다 것과 동일하게 유지 :

    var updates = [];
    
    db.strcoll.find().forEach(doc => {
      var update = { '$set': {} };
    
      if ( doc.hasOwnProperty('children') )
        update.$set.children = doc.children.map(e => ({ _id: new ObjectId(e._id) }));
      if ( doc.hasOwnProperty('ancestors') )
        update.$set.ancestors = doc.ancestors.map(e => ({ _id: new ObjectId(e._id) }));
    
      updates.push({
        "updateOne": {
          "filter": { "_id": doc._id },
          update
        }
      });
    
      if ( updates.length > 1000 ) {
        db.strcoll.bulkWrite(updates);
        updates = [];
      }
    
    })
    
    if ( updates.length > 0 ) {
      db.strcoll.bulkWrite(updates);
      updates = [];
    }
    

    때문에 실제로는 하나의 이동에 배열 전체를 덮어에 "망치"의 비트가 실제로 어떤이다. 아니 큰 프로덕션 환경에 대한 생각,하지만이 연습의 목적을 위해 데모로 충분.

    그래서 MongoDB를 4.0 동안 참으로 아주 유용 할 수있다 이러한 '캐스팅'기능을 추가 할 것입니다, 실제 목적은 이와 같은 경우에 정말 아니다. 대부분의 다른 가능한 사용보다 통합 파이프 라인을 사용하여 새 컬렉션에 "변환"에서 입증 된 바와 같이 그들은 실제로는 훨씬 더 유용합니다.

    우리는 실제 수집 데이터 다르다, 이것은 정말 데이터로 실제 문제에 불과 "반창고를"입니다 작업에 $ 조회 및 $ graphLookup 등을 사용할 수 있도록 데이터 유형을 변환하는 "보기를"만들 "수"동안 종류는 정말 차이가해야하고, 실제로 영구적으로 변환해야합니다.

    은 "보기"를 사용하면 실제로 효율적으로 건설 요구에 대한 집계 파이프 라인이 진짜 오버 헤드를 생성 "컬렉션"(실제로는 "보기")에 액세스 할 때마다 실행 있다는 것을 의미한다.

    오버 헤드를 방지하는 것이 보통 그러므로 이러한 데이터 저장 실수를 정정 설계 목표 것은 애플리케이션의 실제 성능을 얻기보다는 그냥 "브 루트 포스"단지 속도가 느린 것을 아래로 의지 작업에 필수적입니다.

    각 배열 요소에 '일치'업데이트를 적용 훨씬 더 안전 "변환"스크립트. 여기 코드는 NodeJS의 v10.x과 최신 버전 MongoDB의 노드 드라이버 3.1.X가 필요합니다 :

    const { MongoClient, ObjectID: ObjectId } = require('mongodb');
    const EJSON = require('mongodb-extended-json');
    
    const uri = 'mongodb://localhost/';
    
    const log = data => console.log(EJSON.stringify(data, undefined, 2));
    
    (async function() {
    
      try {
    
        const client = await MongoClient.connect(uri);
        let db = client.db('test');
        let coll = db.collection('strcoll');
    
        let fields = ["ancestors", "children"];
    
        let cursor = coll.find({
          $or: fields.map(f => ({ [`${f}._id`]: { "$type": "string" } }))
        }).project(fields.reduce((o,f) => ({ ...o, [f]: 1 }),{}));
    
        let batch = [];
    
        for await ( let { _id, ...doc } of cursor ) {
    
          let $set = {};
          let arrayFilters = [];
    
          for ( const f of fields ) {
            if ( doc.hasOwnProperty(f) ) {
              $set = { ...$set,
                ...doc[f].reduce((o,{ _id },i) =>
                  ({ ...o, [`${f}.$[${f.substr(0,1)}${i}]._id`]: ObjectId(_id) }),
                  {})
              };
    
              arrayFilters = [ ...arrayFilters,
                ...doc[f].map(({ _id },i) =>
                  ({ [`${f.substr(0,1)}${i}._id`]: _id }))
              ];
            }
          }
    
          if (arrayFilters.length > 0)
            batch = [ ...batch,
              { updateOne: { filter: { _id }, update: { $set }, arrayFilters } }
            ];
    
          if ( batch.length > 1000 ) {
            let result = await coll.bulkWrite(batch);
            batch = [];
          }
    
        }
    
        if ( batch.length > 0 ) {
          log({ batch });
          let result = await coll.bulkWrite(batch);
          log({ result });
        }
    
        await client.close();
    
      } catch(e) {
        console.error(e)
      } finally {
        process.exit()
      }
    
    })()
    

    생산 및 실행하는 일곱 문서에 대한 다음과 같은 작업을 일괄 :

    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e272"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e273"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e273"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e273"
          }
        },
        "update": {
          "$set": {
            "ancestors.$[a0]._id": {
              "$oid": "5afe5763419503c46544e272"
            },
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e277"
            }
          }
        },
        "arrayFilters": [
          {
            "a0._id": "5afe5763419503c46544e272"
          },
          {
            "c0._id": "5afe5763419503c46544e277"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e274"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e277"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e277"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e275"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e276"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e276"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e276"
          }
        },
        "update": {
          "$set": {
            "ancestors.$[a0]._id": {
              "$oid": "5afe5763419503c46544e275"
            },
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e277"
            }
          }
        },
        "arrayFilters": [
          {
            "a0._id": "5afe5763419503c46544e275"
          },
          {
            "c0._id": "5afe5763419503c46544e277"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5763419503c46544e277"
          }
        },
        "update": {
          "$set": {
            "ancestors.$[a0]._id": {
              "$oid": "5afe5763419503c46544e273"
            },
            "ancestors.$[a1]._id": {
              "$oid": "5afe5763419503c46544e274"
            },
            "ancestors.$[a2]._id": {
              "$oid": "5afe5763419503c46544e276"
            }
          }
        },
        "arrayFilters": [
          {
            "a0._id": "5afe5763419503c46544e273"
          },
          {
            "a1._id": "5afe5763419503c46544e274"
          },
          {
            "a2._id": "5afe5763419503c46544e276"
          }
        ]
      }
    },
    {
      "updateOne": {
        "filter": {
          "_id": {
            "$oid": "5afe5764419503c46544e278"
          }
        },
        "update": {
          "$set": {
            "children.$[c0]._id": {
              "$oid": "5afe5763419503c46544e272"
            }
          }
        },
        "arrayFilters": [
          {
            "c0._id": "5afe5763419503c46544e272"
          }
        ]
      }
    }
    
  2. from https://stackoverflow.com/questions/50402500/matching-objectid-to-string-for-graphlookup by cc-by-sa and MIT license