sql >> Databasteknik >  >> NoSQL >> MongoDB

Matchvillkor och senaste datum från array

Det grundläggande konceptet här är att du behöver aggregeringsramverket för att tillämpa villkor för att "filtrera" arrayelementen till villkoren. Beroende på tillgänglig version finns det olika tekniker som kan tillämpas.

I alla fall är detta resultatet:

{
    "_id" : ObjectId("593921425ccc8150f35e7664"),
    "user1" : 1,
    "user2" : 4,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-09T10:04:50Z"),
            "body" : "hiii 1"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7663"),
    "user1" : 1,
    "user2" : 3,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-10T10:04:50Z"),
            "body" : "hiii 2"
    }
}
{
    "_id" : ObjectId("593921425ccc8150f35e7662"),
    "user1" : 1,
    "user2" : 2,
    "messages" : {
            "sender" : 1,
            "datetime" : ISODate("2017-06-08T10:04:50Z"),
            "body" : "hiii 0"
    }
}

MongoDB 3.4 och senare

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$replaceRoot": {
    "newRoot": {
      "$let": {
        "vars": {
          "messages": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "maxDate": {
            "$max": {
              "$map": {
                "input": {
                  "$filter": {
                    "input": "$messages",
                    "as": "m",
                    "cond": { "$eq": [ "$$m.sender", 1 ] }
                  }
                },
                "as": "m",
                "in": "$$m.datetime"
              }
            }
          }
        },
        "in": {
          "_id": "$_id",
          "user1": "$user1",
          "user2": "$user2",
          "messages": {
            "$arrayElemAt": [
              { "$filter": {
                "input": "$$messages",
                "as": "m",
                "cond": { "$eq": [ "$$m.datetime", "$$maxDate" ] }
              }},
              0
            ]
          }    
        }
      }
    }
  }}
])

Detta är det mest effektiva sättet att dra fördel av $replaceRoot som tillåter oss att deklarera variabler att använda i den "ersatta" strukturen med code>$let . Den största fördelen här är att detta endast kräver "två" pipelinesteg.

För att matcha arrayinnehållet använder du $filter där du använder $eq logisk operation för att testa värdet på "sender" . Om villkoret matchar, returneras endast de matchande arrayposterna.

Använder samma $filter så att endast de matchande "avsändare"-posterna beaktas vill vi då tillämpa $max över den "filtrerade" listan till värdena i "datetime" . $max ]5 värde är det "senaste" datumet enligt villkoren.

Vi vill ha detta värde så att vi senare kan jämföra de returnerade resultaten från den "filtrerade" arrayen med denna "maxDate". Vilket är vad som händer inuti "in" block av $let där de två "variabler" som deklarerats tidigare för det filtrerade innehållet och "maxDate" återigen tillämpas på $filter för att returnera det som borde vara det enda värdet som uppfyllde båda villkoren med "senaste datumet".

Eftersom du bara vill ha "ett" resultat använder vi $arrayElemAt för att använda värdet istället för arrayen.

MongoDB 3.2

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$filter": {
        "input": "$messages",
        "as": "m",
        "cond": { "$eq": [ "$$m.sender", 1 ] }
      }
    },
    "maxDate": {
      "$max": {
        "$map": {
          "input": {
            "$filter": {
              "input": "$messages",
              "as": "m",
              "cond": { "$eq": [ "$$m.sender", 1 ] }
            }
          },
          "as": "m",
          "in": "$$m.datetime"
        }
      }
    }         
  }},
  { "$project": {
    "user1": 1,
    "user2": 1,
    "messages": {
      "$arrayElemAt":[
       { "$filter": {
         "input": "$messages",
          "as": "m",
          "cond": { "$eq": [ "$$m.datetime", "$maxDate" ] }
       }},
       0
      ]
    }
  }}
])

Detta är i princip samma process som beskrivs, men utan $ replaceRoot pipeline-stadiet måste vi ansöka i två $project etapper. Anledningen till detta är att vi behöver det "beräknade värdet" från "maxDate" för att göra den sista $filter , och det är inte tillgängligt att göra i ett sammansatt uttalande, så istället delar vi upp pipelines. Detta har en liten inverkan på den totala kostnaden för operationen.

I MongoDB 2.6 till 3.0 kan vi använda det mesta av tekniken här förutom $arrayElemAt och antingen acceptera "array"-resultatet med en enda post eller lägg in en $unwind skede för att ta itu med vad som nu borde vara en enda post.

MongoDB tidigare versioner

db.chat.aggregate([
  { "$match": { "messages.sender": 1 } },
  { "$unwind": "$messages" },
  { "$match": { "messages.sender": 1 } },
  { "$sort": { "_id": 1, "messages.datetime": -1 } },
  { "$group": {
    "_id": "$_id",
    "user1": { "$first": "$user1" },
    "user2": { "$first": "$user2" },
    "messages": { "$first": "$messages" }
  }}
])

Även om det ser kort ut, är detta den överlägset mest kostsamma operationen. Här måste du använda $unwind för att tillämpa villkoren på arrayelementen. Detta är en mycket kostsam process eftersom den producerar en kopia av varje dokument för varje matrispost, och i huvudsak ersätts av moderna operatörer som undviker detta i fallet med "filtrering".

Den andra $match steg här kasserar alla element (nu "dokument") som inte matchade "avsändarvillkoret". Sedan tillämpar vi en $sort för att sätta det "senaste" datumet överst för varje dokument med _id , därav de två "sorterings"-tangenterna.

Slutligen tillämpar vi $group för att bara referera till originaldokumentet, med $first som ackumulator för att få elementet som är "överst".




  1. MongoDB-Escape prickar '.' i kartnyckel]

  2. MongoDB-data ta bort - återta diskutrymme

  3. Gör något om inget hittas med .find() mongoose

  4. Kör MongoTemplate.aggregate utan radhämtning