MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Dopasowywanie ObjectId do String dla $graphLookup

Obecnie używasz rozwojowej wersji MongoDB, która ma włączone pewne funkcje, które mają zostać wydane wraz z MongoDB 4.0 jako oficjalna wersja. Pamiętaj, że niektóre funkcje mogą ulec zmianie przed ostatecznym wydaniem, więc kod produkcyjny powinien być o tym świadomy, zanim się do tego zaangażujesz.

Dlaczego $convert tutaj się nie udaje

Prawdopodobnie najlepszym sposobem wyjaśnienia tego jest przyjrzenie się zmienionej próbce, ale zastąpienie jej przez ObjectId wartości dla _id i "strings" dla tych pod tablicami:

{
  "_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" } ]
}

To powinno dać ogólną symulację tego, z czym próbowałeś pracować.

Próbowałeś przekonwertować _id wartość do „ciągu” za pomocą $project przed wprowadzeniem $graphLookup scena. Powodem niepowodzenia jest to, że wykonałeś początkowy $project "w" tym potoku problem polega na tym, że źródło $graphLookup w "from" opcja jest nadal niezmienioną kolekcją i dlatego nie otrzymujesz poprawnych szczegółów dotyczących kolejnych iteracji wyszukiwania.

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

Nie pasuje do „wyszukiwania”, dlatego:

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

"Załatanie" problemu

Jednak to jest główny problem, a nie porażka $convert lub sam jest aliasem. Aby to faktycznie zadziałało, możemy zamiast tego utworzyć „widok”, który prezentuje się jako kolekcja ze względu na dane wejściowe.

Zrobię to na odwrót i przekonwertuję "ciągi" na ObjectId przez $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"
      ]
    }
  }}
])

Użycie „widoku” oznacza jednak, że dane są konsekwentnie widoczne z przekonwertowanymi wartościami. Tak więc następująca agregacja przy użyciu widoku:

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

Zwraca oczekiwany wynik:

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

Rozwiązywanie problemu

Biorąc to wszystko pod uwagę, prawdziwym problemem jest to, że masz pewne dane, które „wyglądają” jak ObjectId wartość i jest w rzeczywistości prawidłowy jako ObjectId , jednak został nagrany jako „string”. Podstawowym problemem, który sprawia, że ​​wszystko działa tak, jak powinno, jest to, że te dwa „typy” nie są takie same, co skutkuje niezgodnością równości przy próbach „połączeń”.

Tak więc prawdziwa poprawka jest nadal taka sama, jak zawsze, czyli zamiast tego przejrzeć dane i naprawić je tak, aby "łańcuchy" były w rzeczywistości również ObjectId wartości. Będą one następnie pasować do _id klucze, do których mają się odwoływać, a oszczędzasz znaczną ilość miejsca w pamięci, ponieważ ObjectId zajmuje dużo mniej miejsca do przechowywania niż jego reprezentacja w postaci ciągu znaków w postaci szesnastkowej.

Korzystając z metod MongoDB 4.0, „możesz” faktycznie użyj "$toObjectId" w celu napisania nowej kolekcji, dokładnie w tej samej sprawie, w której wcześniej stworzyliśmy „widok”:

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

Lub oczywiście tam, gdzie „trzeba” zachować tę samą kolekcję, tradycyjna „pętla i aktualizacja” pozostaje taka sama, jak zawsze była wymagana:

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 = [];
}

Co w rzeczywistości jest trochę „młotem”, ponieważ faktycznie nadpisuje całą tablicę za jednym razem. Nie jest to świetny pomysł na środowisko produkcyjne, ale wystarczy jako demonstracja na potrzeby tego ćwiczenia.

Wniosek

Tak więc, podczas gdy MongoDB 4.0 doda te funkcje „przesyłania”, które rzeczywiście mogą być bardzo przydatne, ich faktycznym zamiarem nie jest tak naprawdę dla przypadków takich jak ten. W rzeczywistości są one o wiele bardziej przydatne, jak pokazano w „konwersji” na nową kolekcję przy użyciu potoku agregacji, niż większość innych możliwych zastosowań.

Podczas gdy my „możemy” utwórz "widok", który przekształca typy danych, aby umożliwić takie rzeczy jak $lookup i $graphLookup do pracy tam, gdzie rzeczywiste dane są różne, to naprawdę jest tylko "opaskę" na prawdziwym problemie, ponieważ typy danych naprawdę nie powinny się różnić, a w rzeczywistości powinny być trwale przekonwertowane.

Korzystanie z „widoku” w rzeczywistości oznacza, że ​​potok agregacji na potrzeby budowy musi skutecznie działać każdy czas, w którym uzyskuje się dostęp do „kolekcji” (w rzeczywistości „widoku”), co powoduje prawdziwy narzut.

Unikanie narzutów jest zwykle celem projektowym, dlatego poprawienie takich błędów przechowywania danych jest konieczne, aby uzyskać rzeczywistą wydajność aplikacji, a nie tylko pracować z „brute force”, która tylko spowolni działanie.

O wiele bezpieczniejszy skrypt „konwersji”, który stosował „dopasowane” aktualizacje do każdego elementu tablicy. Poniższy kod wymaga NodeJS v10.x i najnowszej wersji sterownika węzła 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()
  }

})()

Tworzy i wykonuje operacje zbiorcze, takie jak te dla siedmiu dokumentów:

{
  "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. Jak utworzyć plik konfiguracyjny dla MongoDB

  2. DB z najlepszą wydajnością insertów/s?

  3. mongoDB:format $dateToString do wyświetlania czasu w odstępie 15 minut

  4. Wyciągnij wpis z tablicy przez Meteor

  5. Połącz ciąg i liczbę w SQL