sql >> Databasteknik >  >> NoSQL >> MongoDB

Returnera dokument med Max underdokument

Först och främst, låt oss visa din data på ett sätt som människor kan använda dem och producera ett önskat resultat:

{ 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' } ],
}

Observera att "när" datum är faktiskt olika så det kommer att finnas en $max värde och de är inte bara samma. Nu kan vi gå igenom ärendena

Fall 1 – Hämta "singular" $max värde

Grundfallet här är att använda $arrayElemAt och $indexOfArray operatörer för att returnera den matchande $max värde:

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

Returnerar:

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

I princip "$max":"$change.when" returnerar värdet som är "maximum" inifrån den matrisen av värden. Du hittar sedan det matchande "indexet" för den matrisen med värden via $indexOfArray som returnerar den första matchande index hittat. Den "index"-positionen (från faktiskt bara en array av "när" värden transponerade i samma ordning ) används sedan med $arrayElemAt för att extrahera "hela objektet" från "ändring" array vid den angivna indexpositionen.

Fall 2 - Returnera "multipel" $max poster

Ungefär samma sak med $max , förutom den här gången $filter för att returnera flera "möjliga" värden som matchar det $max värde:

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

Returnerar:

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

$max är naturligtvis detsamma men den här gången används singularvärdet som returneras av den operatorn i en $eq jämförelse inom $filter . Detta inspekterar varje arrayelement och tittar på strömmen "när" värde ( "$$this.when" ). Där "lika" sedan returneras elementet.

I grund och botten samma som den första metoden men med undantaget att $filter tillåter "flera" element som ska returneras. Därför allt med samma $max värde.

Fall 3 - Försortera arrayinnehållet.

Nu kanske du noterar att i exemplet data jag inkluderade (anpassad från ditt eget men med ett faktiskt "max"-datum) är "max"-värdet faktiskt det sista värde i arrayen. Detta kan helt enkelt hända som ett resultat av att $push ( som standard ) "lägger till" till slutet av det befintliga arrayinnehållet. Så "nyare" poster tenderar att vara i slutet av arrayen.

Detta är naturligtvis standard beteende, men det finns goda skäl till varför du "kan" vill ändra på det. Kort sagt det bästa sätt att få den "senaste" array-inmatning är att faktiskt returnera det första elementet från arrayen.

Allt du faktiskt behöver göra är att säkerställa den "senaste" läggs faktiskt till först snarare än senast . Det finns två tillvägagångssätt:

  1. Använd $position för att "förhandsbehandla" arrayobjekt: Detta är en enkel modifierare för att $push med 0 position för att alltid lägga till framsidan :

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

    Detta skulle ändra dokumentet till:

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

Observera att detta skulle kräva att du faktiskt går och "vänder" alla dina arrayelement i förväg så att det "nyaste" redan var längst fram så att ordningen bibehölls. Tack och lov är detta något täckt i den andra metoden...

  1. Använd $sort för att ändra dokumenten i ordning på varje $push : Och det här är den andra modifieraren som faktiskt "omsorterar" atomiskt på varje ny artikeltillägg. Normal användning är i princip densamma med alla nya objekt till $each som ovan, eller till och med bara en "tom" array för att tillämpa $sort endast till befintliga data:

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

    Resultat i:

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

    Det kan ta en minut att förstå varför du skulle $push för att $sort en array som denna, men den allmänna avsikten är när ändringar kan göras i en array som "ändrar" en egenskap som ett Datum värde sorteras på och du skulle använda ett sådant uttalande för att spegla dessa förändringar. Eller lägg till nya objekt med $sort och låt det lösa sig.

Så varför "butik" arrayen beställd så här? Som nämnts tidigare vill du ha den första objektet som det "senaste" , och sedan frågan att returnera som helt enkelt blir:

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

Returnerar:

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

$slice kan sedan användas bara för att extrahera array-objekt med kända index. Tekniskt sett kan du bara använda -1 där för att returnera den sista objekt i arrayen i alla fall, men omordningen där den senaste är först tillåter andra saker som att bekräfta att den senaste ändringen gjordes av en viss användare och/eller andra villkor som ett datumintervall. dvs:

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

Noterar här att något som "change.-1.when" är ett olagligt uttalande, vilket i grunden är anledningen till att vi ordnar om arrayen så att du kan använda den lagliga 0 för först istället för -1 för sista .

Slutsats

Så det finns flera olika saker du kan göra, antingen genom att använda aggregeringsmetoden för att filtrera arrayinnehållet eller via vanliga frågeformulär efter att ha gjort en viss modifiering av hur data faktiskt lagras. Vilken du ska använda beror på dina egna omständigheter, men det bör noteras att något av standardformuläret för fråge kommer att köras avsevärt snabbare än någon manipulation via aggregeringsramverket eller andra beräknade operatörer.




  1. MongoDB db.collection.count()

  2. AttributeError:'tuple'-objektet har inget attribut 'insert'

  3. Uppdaterar 2 mongoose-scheman i ett api-anrop

  4. Stripe:Måste ange källa eller kund