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

Agregacja akumulacja obiektów wewnętrznych

Krótko mówiąc, musisz zmienić swoją „wartość” pole wewnątrz "wartości" być liczbowym, ponieważ obecnie jest to łańcuch. Ale do odpowiedzi:

Jeśli masz dostęp do $reduce z MongoDB 3.4, możesz zrobić coś takiego:

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

Jeśli masz MongoDB 3.6, możesz to trochę posprzątać za 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 to mniej więcej to samo, z wyjątkiem tego, że zachowujemy additionalData

Wracając trochę wcześniej, zawsze możesz $unwind "miasta" gromadzić:

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

Wszystkie zwracają (prawie) to samo:

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

Pierwsze dwa formularze są oczywiście najbardziej optymalnym rozwiązaniem, ponieważ po prostu działają „w” tym samym dokumencie przez cały czas.

Operatory takie jak $reduce zezwalaj na wyrażenia "akumulacji" na tablicach, więc możemy go tutaj użyć, aby zachować "zredukowaną" tablicę, którą testujemy pod kątem unikalnego "_id" wartość przy użyciu $indexOfArray aby sprawdzić, czy istnieje już skumulowany element, który pasuje. Wynik -1 oznacza, że ​​go tam nie ma.

Aby skonstruować "tablicę zredukowaną" bierzemy "initialValue" z [] jako pustą tablicę, a następnie dodaj do niej za pomocą $concatArrays . Cały ten proces jest określany przez „trójargumentowy” $cond operator, który uwzględnia "if" warunek i "wtedy" albo „dołącza” do wyjścia $filter na bieżącej wartości $$ aby wykluczyć bieżący indeks _id wpis, z oczywiście kolejną "tablicą" reprezentującą pojedynczy obiekt.

Dla tego "obiektu" ponownie używamy $indexOfArray aby faktycznie uzyskać dopasowany indeks, ponieważ wiemy, że element "tam jest" i użyć go do wyodrębnienia bieżącego "odwiedzonego" wartość z tego wpisu za pośrednictwem $arrayElemAt i $add do niego w celu zwiększenia.

W "innym" przypadku po prostu dodajemy "tablicę" jako "obiekt", który ma po prostu domyślny "odwiedzony" wartość 1 . Korzystanie z obu tych przypadków skutecznie akumuluje unikalne wartości w tablicy do wyprowadzenia.

W drugiej wersji po prostu $unwind tablicę i użyj kolejnych $group etapy, aby najpierw "policzyć" unikalne wpisy wewnętrzne, a następnie "zrekonstruować tablicę" w podobnej formie.

Korzystanie z $unwind wygląda o wiele prościej, ale ponieważ tak naprawdę robi kopię dokumentu dla każdego wpisu tablicy, to w rzeczywistości powoduje znaczne obciążenie przetwarzania. We współczesnych wersjach istnieją generalnie operatory tablicowe, co oznacza, że ​​nie musisz ich używać, chyba że chcesz „kumulować w dokumentach”. Więc jeśli rzeczywiście potrzebujesz $group na wartości klucza z „wewnątrz” tablicy, to właśnie tam naprawdę musisz go użyć.

Jeśli chodzi o "zmienne" wtedy możemy po prostu użyć $filter ponownie tutaj, aby uzyskać pasujący „Budżet” wejście. Robimy to jako dane wejściowe do $map operator, który umożliwia „przekształcenie” zawartości tablicy. Chcemy tego głównie, abyś mógł wziąć zawartość "wartości" (kiedy wszystko zrobisz numerycznie) i użyj $avg operator, który jest dostarczany, że "notacja ścieżki pola" tworzy bezpośrednio wartości tablicy, ponieważ w rzeczywistości może zwrócić wynik z takiego wejścia.

To generalnie sprawia, że ​​przeglądanie prawie WSZYSTKICH głównych „operatorów tablicowych” dla potoku agregacji (z wyjątkiem operatorów „ustawionych”) w ramach jednego etapu potoku.

Nie zapominaj też, że prawie zawsze chcesz $match ze zwykłymi operatorami zapytań jako „pierwszy etap” dowolnego potoku agregacji, aby po prostu wybrać potrzebne dokumenty. Najlepiej przy użyciu indeksu.

Alternatywne

Alternatywni pracują nad dokumentami w kodzie klienta. Generalnie nie jest to zalecane, ponieważ wszystkie powyższe metody pokazują, że faktycznie "redukują" zawartość zwracaną z serwera, jak to zwykle ma miejsce w przypadku "agregacji serwera".

"Może" być możliwe ze względu na naturę "opartą na dokumencie", że większe zestawy wyników mogą zająć znacznie więcej czasu przy użyciu $unwind i przetwarzanie klienta może być opcją, ale uważam to za znacznie bardziej prawdopodobne

Poniżej znajduje się lista, która pokazuje zastosowanie transformacji do strumienia kursora, gdy wyniki są zwracane, robiąc to samo. Istnieją trzy zademonstrowane wersje transformacji, pokazujące "dokładnie" tę samą logikę, co powyżej, implementacja z lodash metody akumulacji i "naturalna" akumulacja na Mapie realizacja:

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. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Regex, aby dopasować słowa w zdaniu według jego prefiksu

  2. Używanie Mongo / BSON ObjectId z Parse Server

  3. Uzyskaj indeks elementu w zapytaniu mongodb

  4. Sterownik MongoDB c# — czy pole o nazwie Id not be Id?

  5. MongoDB $degreesToRadians