sql >> Databáze >  >> NoSQL >> MongoDB

Agregace Akumulovat vnitřní objekty

Pro rychlou poznámku je třeba změnit "value" pole uvnitř "hodnoty" být číselný, protože v současnosti je to řetězec. Ale k odpovědi:

Pokud máte přístup k $reduce z MongoDB 3.4, pak můžete ve skutečnosti udělat něco takového:

db.collection.aggregate([
  { "$addFields": {
     "cities": {
       "$reduce": {
         "input": "$cities",
         "initialValue": [],
         "in": {
           "$cond": {
             "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
             "then": {
               "$concatArrays": [
                 { "$filter": {
                   "input": "$$value",
                   "as": "v",
                   "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                 }},
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": {
                     "$add": [
                       { "$arrayElemAt": [
                         "$$value.visited",
                         { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                       ]},
                       1
                     ]
                   }
                 }]
               ]
             },
             "else": {
               "$concatArrays": [
                 "$$value",
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": 1
                 }]
               ]
             }
           }
         }
       }
     },
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "_id": "$$this._id",
           "name": "$$this.name",
           "defaultValue": "$$this.defaultValue",
           "lastValue": "$$this.lastValue",
           "value": { "$avg": "$$this.values.value" }
         }
       }
     }
  }}
])

Pokud máte MongoDB 3.6, můžete to trochu vyčistit pomocí $mergeObjects :

db.collection.aggregate([
  { "$addFields": {
     "cities": {
       "$reduce": {
         "input": "$cities",
         "initialValue": [],
         "in": {
           "$cond": {
             "if": { "$ne": [{ "$indexOfArray": ["$$value._id", "$$this._id"] }, -1] },
             "then": {
               "$concatArrays": [
                 { "$filter": {
                   "input": "$$value",
                   "as": "v",
                   "cond": { "$ne": [ "$$this._id", "$$v._id" ] }
                 }},
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": {
                     "$add": [
                       { "$arrayElemAt": [
                         "$$value.visited",
                         { "$indexOfArray": [ "$$value._id", "$$this._id" ] }
                       ]},
                       1
                     ]
                   }
                 }]
               ]
             },
             "else": {
               "$concatArrays": [
                 "$$value",
                 [{
                   "_id": "$$this._id",
                   "name": "$$this.name",
                   "visited": 1
                 }]
               ]
             }
           }
         }
       }
     },
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "$mergeObjects": [
             "$$this",
             { "values": { "$avg": "$$this.values.value" } }
           ]
         }
       }
     }
  }}
])

Ale je to víceméně to samé, kromě toho, že ponecháme další data

Když se vrátíte o něco zpět, pak můžete vždy $unwind "města" hromadit:

db.collection.aggregate([
  { "$unwind": "$cities" },
  { "$group": {
     "_id": { 
       "_id": "$_id",
       "cities": {
         "_id": "$cities._id",
         "name": "$cities.name"
       }
     },
     "_class": { "$first": "$class" },
     "name": { "$first": "$name" },
     "startTimestamp": { "$first": "$startTimestamp" },
     "endTimestamp" : { "$first": "$endTimestamp" },
     "source" : { "$first": "$source" },
     "variables": { "$first": "$variables" },
     "visited": { "$sum": 1 }
  }},
  { "$group": {
     "_id": "$_id._id",
     "_class": { "$first": "$class" },
     "name": { "$first": "$name" },
     "startTimestamp": { "$first": "$startTimestamp" },
     "endTimestamp" : { "$first": "$endTimestamp" },
     "source" : { "$first": "$source" },
     "cities": {
       "$push": {
         "_id": "$_id.cities._id",
         "name": "$_id.cities.name",
         "visited": "$visited"
       }
     },
     "variables": { "$first": "$variables" },
  }},
  { "$addFields": {
     "variables": {
       "$map": {
         "input": {
           "$filter": {
             "input": "$variables",
             "cond": { "$eq": ["$$this.name", "Budget"] } 
           }
         },
         "in": {
           "_id": "$$this._id",
           "name": "$$this.name",
           "defaultValue": "$$this.defaultValue",
           "lastValue": "$$this.lastValue",
           "value": { "$avg": "$$this.values.value" }
         }
       }
     }
  }}
])

Všechny vrátí (téměř) to samé:

{
        "_id" : ObjectId("5afc2f06e1da131c9802071e"),
        "_class" : "Traveler",
        "name" : "John Due",
        "startTimestamp" : 1526476550933,
        "endTimestamp" : 1526476554823,
        "source" : "istanbul",
        "cities" : [
                {
                        "_id" : "ef8f6b26328f-0663202f94faeaeb-1122",
                        "name" : "Cairo",
                        "visited" : 1
                },
                {
                        "_id" : "ef8f6b26328f-0663202f94faeaeb-3981",
                        "name" : "Moscow",
                        "visited" : 2
                }
        ],
        "variables" : [
                {
                        "_id" : "c8103687c1c8-97d749e349d785c8-9154",
                        "name" : "Budget",
                        "defaultValue" : "",
                        "lastValue" : "",
                        "value" : 3000
                }
        ]
}

První dva formuláře jsou samozřejmě nejoptimálnější, protože prostě vždy pracují „v rámci“ stejného dokumentu.

Operátoři jako $reduce povolit výrazy "akumulace" na polích, takže je zde můžeme použít k udržení "redukovaného" pole, které testujeme na jedinečné "_id" hodnotu pomocí $indexOfArray abyste zjistili, zda již existuje nahromaděná položka, která odpovídá. Výsledek -1 znamená, že tam není.

Abychom vytvořili "redukované pole", vezmeme "initialValue" z [] jako prázdné pole a poté do něj přidat pomocí $concatArrays . O celém tomto procesu rozhoduje "ternární" $cond operátor, který bere v úvahu "if" podmínkou a "pak" buď „připojí“ výstup $filter na aktuální $$value pro vyloučení aktuálního indexu _id vstup, samozřejmě s dalším "polem" představujícím singulární objekt.

Pro tento „objekt“ opět používáme $indexOfArray skutečně získat odpovídající index, protože víme, že položka "je tam", a použít jej k extrahování aktuálního "navštíveného" hodnotu z tohoto záznamu přes $arrayElemAt a $add za účelem zvýšení.

V "else" v případě jednoduše přidáme "pole" jako "objekt", který má pouze výchozí "visited" hodnotu 1 . Použití obou těchto případů efektivně shromažďuje jedinečné hodnoty v poli pro výstup.

Ve druhé verzi jsme pouze $unwind pole a použijte postupné $group fáze, aby bylo možné nejprve „počítat“ s jedinečnými vnitřními položkami a poté „znovu zkonstruovat pole“ do podobné podoby.

Pomocí $unwind vypadá mnohem jednodušeji, ale protože to, co ve skutečnosti dělá, je vzít kopii dokumentu pro každou položku pole, pak to ve skutečnosti zvyšuje značnou režii na zpracování. V moderních verzích obecně existují operátory polí, což znamená, že je nemusíte používat, pokud není vaším záměrem „hromadit se napříč dokumenty“. Pokud tedy skutečně potřebujete $group na hodnotě klíče z "uvnitř" pole, pak je to místo, kde jej skutečně potřebujete použít.

Pokud jde o "proměnné" pak můžeme jednoduše použít $filter znovu zde, abyste získali odpovídající "Rozpočet" vstup. Děláme to jako vstup do $map operátor, který umožňuje "přetvoření" obsahu pole. Chceme to hlavně proto, abyste si mohli vzít obsah "hodnot" (jakmile to celé uděláte numericky) a použijte $avg operátor, který dodává onen „zápis cesty k poli“ přímo do hodnot pole, protože ve skutečnosti může vrátit výsledek z takového vstupu.

To obecně umožňuje prohlídku v podstatě VŠECH hlavních „operátorů pole“ pro agregační kanál (kromě „setových“ operátorů), vše v rámci jedné fáze potrubí.

Nikdy také nezapomeňte, že téměř vždy chcete $match s běžnými operátory dotazů jako „úplně první fázi“ jakéhokoli agregačního kanálu, abyste si vybrali pouze dokumenty, které potřebujete. Ideálně pomocí indexu.

Alternativy

Náhradníci pracují s dokumenty v klientském kódu. Obecně by se to nedoporučovalo, protože všechny výše uvedené metody ukazují, že ve skutečnosti „redukují“ obsah vrácený ze serveru, jak je obecně smyslem „agregací serverů“.

Vzhledem k povaze „založené na dokumentu“ „může“ být možné, že větší sady výsledků mohou pomocí $unwind trvat podstatně déle a zpracování klienta by mohlo být možností, ale považoval bych to za mnohem pravděpodobnější

Níže je uveden seznam, který ukazuje použití transformace na proud kurzoru, protože výsledky jsou vráceny stejným způsobem. Existují tři demonstrované verze transformace, které ukazují "přesně" stejnou logiku jako výše, implementace s lodash metody pro akumulaci a "přirozenou" akumulaci na mapě implementace:

const { MongoClient } = require('mongodb');
const { chain } = require('lodash');

const uri = 'mongodb://localhost:27017';
const opts = { useNewUrlParser: true };

const log = data => console.log(JSON.stringify(data, undefined, 2));

const transform = ({ cities, variables, ...d }) => ({
  ...d,
  cities: cities.reduce((o,{ _id, name }) =>
    (o.map(i => i._id).indexOf(_id) != -1)
      ? [
          ...o.filter(i => i._id != _id),
          { _id, name, visited: o.find(e => e._id === _id).visited + 1 }
        ]
      : [ ...o, { _id, name, visited: 1 } ]
  , []).sort((a,b) => b.visited - a.visited),
  variables: variables.filter(v => v.name === "Budget")
    .map(({ values, additionalData, ...v }) => ({
      ...v,
      values: (values != undefined)
        ? values.reduce((o,e) => o + e.value, 0) / values.length
        : 0
    }))
});

const alternate = ({ cities, variables, ...d }) => ({
  ...d,
  cities: chain(cities)
    .groupBy("_id")
    .toPairs()
    .map(([k,v]) =>
      ({
        ...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
        visited: v.length
      })
    )
    .sort((a,b) => b.visited - a.visited)
    .value(),
  variables: variables.filter(v => v.name === "Budget")
    .map(({ values, additionalData, ...v }) => ({
      ...v,
      values: (values != undefined)
        ? values.reduce((o,e) => o + e.value, 0) / values.length
        : 0
    }))

});

const natural = ({ cities, variables, ...d }) => ({
  ...d,
  cities: [
    ...cities
      .reduce((o,{ _id, name }) => o.set(_id,
        [ ...(o.has(_id) ? o.get(_id) : []), { _id, name } ]), new Map())
      .entries()
  ]
  .map(([k,v]) =>
    ({
      ...v.reduce((o,{ _id, name }) => ({ ...o, _id, name }),{}),
      visited: v.length
    })
  )
  .sort((a,b) => b.visited - a.visited),
  variables: variables.filter(v => v.name === "Budget")
    .map(({ values, additionalData, ...v }) => ({
      ...v,
      values: (values != undefined)
        ? values.reduce((o,e) => o + e.value, 0) / values.length
        : 0
    }))

});

(async function() {

  try {

    const client = await MongoClient.connect(uri, opts);

    let db = client.db('test');
    let coll = db.collection('junk');

    let cursor = coll.find().map(natural);

    while (await cursor.hasNext()) {
      let doc = await cursor.next();
      log(doc);
    }

    client.close();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }

})()



  1. Mnoho až mnoho vztahů s MongoDB ve velkém měřítku

  2. Je možné přejmenovat pole _id po agregaci skupiny mongo?

  3. Jak Hadoop funguje – Pochopte fungování Hadoopu

  4. MongoDB:Aktualizace dokumentů pomocí dat ze stejného dokumentu