sql >> Database >  >> NoSQL >> MongoDB

Retourdocument met max. subdocument

Laten we eerst uw gegevens laten zien op een manier waarop mensen ze kunnen gebruiken en een gewenst resultaat opleveren:

{ value: 1,
  _id: ObjectId('5cb9ea0c75c61525e0176f96'),
  name: 'Test',
  category: 'Development',
  subcategory: 'Programming Languages',
  status: 'Supported',
  description: 'Test',
  change:
   [ { version: 1,
       who: 'ATL User',
       when: new Date('2019-04-19T15:30:39.912Z'),
       what: 'Item Creation' },
     { version: 2,
       who: 'ATL Other User',
       when: new Date('2019-04-19T15:31:39.912Z'),
       what: 'Name Change' } ],
}

Merk op dat de "wanneer" datums zijn in feite anders, dus er zal een $max waarde en ze zijn niet alleen hetzelfde. Nu kunnen we de cases doornemen

Geval 1 - Haal de "enkelvoud" $max op waarde

Het basisgeval hier is het gebruik van de $arrayElemAt en $indexOfArray operators om de overeenkomende $max te retourneren waarde:

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$arrayElemAt": [
        "$change",
        { "$indexOfArray": [
          "$change.when",
          { "$max": "$change.when" }
        ]}
      ]
    }
  }}
])

Retourneren:

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : {
                "version" : 2,
                "who" : "ATL Other User",
                "when" : ISODate("2019-04-19T15:31:39.912Z"),
                "what" : "Name Change"
        }
}

In feite de "$max":"$change.when" retourneert de waarde die het "maximum" is binnen die reeks waarden. U vindt dan de overeenkomende "index" van die reeks waarden via $indexOfArray die de eerste . retourneert overeenkomende index gevonden. Die "index"-positie (van eigenlijk slechts een array van "wanneer" waarden getransponeerd in dezelfde volgorde ) wordt vervolgens gebruikt met $arrayElemAt om het "hele object" te extraheren uit de "wijziging" array op de opgegeven indexpositie.

Geval 2 - Retourneer de "multiple" $max inzendingen

Vrijwel hetzelfde met $max , behalve deze keer hebben we $filter om de meervoudige "mogelijke" . terug te geven waarden die overeenkomen met die $max waarde:

db.items.aggregate([
  { "$match": {
    "subcategory": "Programming Languages", "name": { "$exists": true }
  }}, 
  { "$addFields": {
    "change": {
      "$filter": {
        "input": "$change",
        "cond": {
          "$eq": [ "$$this.when", { "$max": "$change.when" } ]
        }
      }       
    }
  }}
])

Retourneren:

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
}

Dus de $max is natuurlijk hetzelfde, maar deze keer wordt de enkelvoudige waarde die door die operator wordt geretourneerd, gebruikt in een $eq vergelijking binnen $filter . Dit inspecteert elk array-element en kijkt naar de huidige "wanneer" waarde ( "$$this.when" ). Waar "gelijk" dan wordt het element geretourneerd.

In principe hetzelfde als de eerste benadering, maar met de uitzondering dat $filter staat "meerdere" toe elementen terug te sturen. Daarom alles met de dezelfde $max waarde.

Geval 3 - Sorteer de array-inhoud vooraf.

Nu zou u kunnen opmerken dat in de voorbeeldgegevens die ik heb opgenomen (aangepast van uw eigen maar met een werkelijke "max"-datum) de "max" -waarde in feite de laatste is waarde in de array. Dit kan natuurlijk gebeuren als gevolg dat $push ( standaard ) "voegt toe" aan het einde van de bestaande array-inhoud. Dus "nieuwere" inzendingen staan ​​meestal aan het einde van de array.

Dit is natuurlijk de standaard gedrag, maar er zijn goede redenen waarom u 'mag' wil daar verandering in brengen. Kortom de beste manier om de "meest recente" . te krijgen array-invoer is om in feite het eerste element te retourneren uit de array.

Het enige dat u hoeft te doen, is ervoor zorgen dat de "meest recente" wordt feitelijk eerst toegevoegd in plaats van laatste . Er zijn twee benaderingen:

  1. Gebruik $position om array-items "vooraf te plaatsen": Dit is een eenvoudige wijziging van $push met behulp van de 0 positie om altijd toe te voegen aan de voorkant :

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [{
              "version": 3,
              "who": "ATL User",
              "when": new Date(),
              "what": "Another change"
            }],
            "$position": 0
          }
       }}
    )
    

    Dit zou het document veranderen in:

    {
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                },
                {
                        "version" : 1,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-19T15:30:39.912Z"),
                        "what" : "Item Creation"
                },
                {
                        "version" : 2,
                        "who" : "ATL Other User",
                        "when" : ISODate("2019-04-19T15:31:39.912Z"),
                        "what" : "Name Change"
                }
        ]
    }
    

Houd er rekening mee dat je hiervoor al je array-elementen van tevoren moet "omkeren", zodat de "nieuwste" al vooraan staat, zodat de volgorde behouden blijft. Gelukkig wordt dit enigszins behandeld in de tweede benadering...

  1. Gebruik $sort om de documenten in volgorde aan te passen op elke $push : En dit is de andere modifier die in feite atomair "opnieuw sorteert" bij elke toevoeging van een nieuw item. Normaal gebruik is in principe hetzelfde met alle nieuwe items voor $each zoals hierboven, of zelfs gewoon een "lege" array om de toe te passen $sort alleen naar bestaande gegevens:

    db.items.updateOne(
      { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") },
      { "$push": {
          "change": {
            "$each": [],
            "$sort": { "when": -1 } 
          }
       }}
    )
    

    Resultaten in:

    {
            "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
            "value" : 1,
            "name" : "Test",
            "category" : "Development",
            "subcategory" : "Programming Languages",
            "status" : "Supported",
            "description" : "Test",
            "change" : [
                    {
                            "version" : 3,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-20T02:40:30.024Z"),
                            "what" : "Another change"
                    },
                    {
                            "version" : 2,
                            "who" : "ATL Other User",
                            "when" : ISODate("2019-04-19T15:31:39.912Z"),
                            "what" : "Name Change"
                    },
                    {
                            "version" : 1,
                            "who" : "ATL User",
                            "when" : ISODate("2019-04-19T15:30:39.912Z"),
                            "what" : "Item Creation"
                    }
            ]
    }
    

    Het kan even duren voordat u begrijpt waarom u $push om $sort een array als deze, maar de algemene bedoeling is wanneer er wijzigingen kunnen worden aangebracht in een array die een eigenschap zoals een Datum "veranderen" waarde waarop wordt gesorteerd en u zou een dergelijke verklaring gebruiken om die wijzigingen weer te geven. Of voeg gewoon nieuwe items toe met de $sort en laat het werken.

Dus waarom "winkel" de array zo geordend? Zoals eerder vermeld, wil je de eerste item als de "meest recente" , en dan de vraag om terug te keren die eenvoudig wordt:

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true }
  },
  { "change": { "$slice": 1 } }
)

Retourneren:

{
        "_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
        "value" : 1,
        "name" : "Test",
        "category" : "Development",
        "subcategory" : "Programming Languages",
        "status" : "Supported",
        "description" : "Test",
        "change" : [
                {
                        "version" : 3,
                        "who" : "ATL User",
                        "when" : ISODate("2019-04-20T02:40:30.024Z"),
                        "what" : "Another change"
                }
        ]
}

Dus de $slice kan dan alleen worden gebruikt om array-items op basis van bekende indexen te extraheren. Technisch gezien kun je gewoon -1 . gebruiken daar om de laatste . terug te sturen item van de array sowieso, maar de herschikking waarbij de meest recente eerst is, maakt andere dingen mogelijk, zoals bevestigen dat de laatste wijziging door een bepaalde gebruiker is aangebracht, en/of andere voorwaarden zoals een beperking van een datumbereik. dat wil zeggen:

db.items.find(
  {
    "subcategory": "Programming Languages",
    "name": { "$exists": true },
    "change.0.who": "ATL User",
    "change.0.when": { "$gt": new Date("2018-04-01") }
  },
  { "change": { "$slice": 1 } }
)

Hierbij opmerkend dat zoiets als "change.-1.when" is een illegale verklaring, wat in feite de reden is waarom we de array opnieuw ordenen, zodat u de legale . kunt gebruiken 0 voor eerste in plaats van -1 voor laatste .

Conclusie

Er zijn dus verschillende dingen die u kunt doen, hetzij door de aggregatiebenadering te gebruiken om de array-inhoud te filteren, hetzij via standaardqueryformulieren nadat u enige wijziging heeft aangebracht in de manier waarop de gegevens feitelijk worden opgeslagen. Welke u moet gebruiken, hangt af van uw eigen omstandigheden, maar het moet worden opgemerkt dat elk van de standaardvragenformulieren zal aanzienlijk sneller werken dan enige manipulatie via het aggregatieraamwerk of enige berekende operator.




  1. ImportError:Geen module met de naam objectid

  2. Maak een ISO-datumobject in javascript

  3. MongoDB $tan

  4. Lui laden in MongoDB met NoRM