MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

Съвпадение на ObjectId към String за $graphLookup

В момента използвате версия за разработка на MongoDB, която има активирани някои функции, които се очаква да бъдат пуснати с MongoDB 4.0 като официална версия. Имайте предвид, че някои функции може да подлежат на промяна преди окончателното издание, така че производственият код трябва да е наясно с това, преди да се ангажирате с него.

Защо $convert не успява тук

Вероятно най-добрият начин да обясните това е да разгледате променената си извадка, но замените с 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" } ]
}

Това трябва да даде обща симулация на това, с което се опитвате да работите.

Това, което се опитахте да конвертирате _id стойност в "низ" чрез $project преди да въведете $graphLookup сцена. Причината за неуспех е докато сте правили първоначален $project "в" този конвейер, проблемът е, че източникът за $graphLookup в "from" опцията все още е непроменената колекция и следователно не получавате правилните подробности за последващите итерации за „търсене“.

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" : [ ]
}

„Поправяне“ на проблема

Това обаче е основният проблем, а не грешка на $convert или самият псевдоним. За да накараме това действително да работи, можем вместо това да създадем „изглед“, който се представя като колекция с цел въвеждане.

Ще направя това обратното и ще преобразувам "низовете" в ObjectId чрез $toObjectId :

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 заема много по-малко място за съхранение, отколкото представянето на низове в шестнадесетични знаци.

Използвайки методите на 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 ще добави тези "кастинг" функции, които наистина могат да бъдат много полезни, тяхното действително намерение всъщност не е за случаи като този. Те всъщност са много по-полезни, както е показано в „преобразуването“ в нова колекция с помощта на конвейер за агрегация, отколкото повечето други възможни употреби.

Докато ние „можем“ създайте "изглед", който трансформира типовете данни, за да активира неща като $lookup и $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"
      }
    ]
  }
}



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Mongodb обобщава (брои) в множество полета едновременно

  2. Какви механизми за сигурност има Meteor?

  3. Заявка за IDE за MongoDB?

  4. Фатална грешка:Клас 'MongoDB\Driver\Manager' не е намерен

  5. Преобразувайте MongoDB BsonDocument в валиден JSON в C#