2015-08-31 20 views
5

passend Ich habe eine Sammlung, die Protokoll der Aktivitäten auf Objekte ist wie folgt:MongoDB alle Array-Elemente in allen Objekten zählen nach Kriterien

{ 
    "_id" : ObjectId("55e3fd1d7cb5ac9a458b4567"), 
    "object_id" : "1", 
    "activity" : [ 
     { 
      "action" : "test_action", 
      "time" : ISODate("2015-08-31T00:00:00.000Z") 
     }, 
     { 
      "action" : "test_action", 
      "time" : ISODate("2015-08-31T00:00:22.000Z") 
     } 
    ] 
} 

{ 
    "_id" : ObjectId("55e3fd127cb5ac77478b4567"), 
    "object_id" : "2", 
    "activity" : [ 
     { 
      "action" : "test_action", 
      "time" : ISODate("2015-08-31T00:00:00.000Z") 
     } 
    ] 
} 

{ 
    "_id" : ObjectId("55e3fd0f7cb5ac9f458b4567"), 
    "object_id" : "1", 
    "activity" : [ 
     { 
      "action" : "test_action", 
      "time" : ISODate("2015-08-30T00:00:00.000Z") 
     } 
    ] 
} 

Wenn ich das tue followoing query:

db.objects.find({ 
    "createddate": {$gte : ISODate("2015-08-30T00:00:00.000Z")}, 
    "activity.action" : "test_action"} 
    }).count() 

kehrt Anzahl der Dokumente, die "test_action" enthalten (3 in diesem Satz), aber ich muss alle test_actions zählen (4 in diesem Set). Wie mache ich das?

Antwort

8

Die "performant" Weg, dies zu tun, ist die $unwind Altogther überspringen und einfach $group zu zählen. Im Wesentlichen erhalten „Filter“ -Arrays die $size der Ergebnisse zu $sum:

db.objects.aggregate([ 
    { "$match": { 
     "createddate": { 
      "$gte": ISODate("2015-08-30T00:00:00.000Z") 
     }, 
     "activity.action": "test_action" 
    }}, 
    { "$group": { 
     "_id": null, 
     "count": { 
      "$sum": { 
       "$size": { 
        "$setDifference": [ 
         { "$map": { 
          "input": "$activity", 
          "as": "el", 
          "in": { 
           "$cond": [ 
            { "$eq": [ "$$el.action", "test_action" ] }, 
            "$$el", 
            false 
           ] 
          }    
         }}, 
         [false] 
        ] 
       } 
      } 
     } 
    }} 
]) 

Zukünftige Versionen von MongoDB $filter haben, was so viel einfacher macht:

db.objects.aggregate([ 
    { "$match": { 
     "createddate": { 
      "$gte": ISODate("2015-08-30T00:00:00.000Z") 
     }, 
     "activity.action": "test_action" 
    }}, 
    { "$group": { 
     "_id": null, 
     "count": { 
      "$sum": { 
       "$size": { 
        "$filter": { 
         "input": "$activity", 
         "as": "el", 
         "cond": { 
          "$eq": [ "$$el.action", "test_action" ] 
         } 
        } 
       } 
      } 
     } 
    }} 
]) 

Mit $unwind bewirkt, dass die Dokumente de -normalisiert und erstellt effektiv eine Kopie pro Array-Eintrag. Wo immer möglich, sollten Sie dies aufgrund der oft extremen Kosten vermeiden. Das Filtern und Zählen von Array-Einträgen pro Dokument ist im Vergleich viel schneller. Wie ist eine einfache $match und $group Pipeline im Vergleich zu vielen Stufen.

+1

Vielen Dank. Die Vermeidung von "$ unwind" ist bei großen Datensätzen ein Muss. Abfrage funktioniert wie ein Zauber. Mein Wissen ist jetzt ziemlich grundlegend und ich weiß nicht wirklich WIE es noch funktioniert :) Aber das herauszufinden wird meine Hausaufgabe für heute sein) – aokozlov

5

können Sie dies durch Aggregation mit:

db.objects.aggregate([ 
    {$match: {"createddate": {$gte : ISODate("2015-08-30T00:00:00.000Z")}, {"activity.action" : "test_action"}}}, 
    {$unwind: "$activity"}, 
    {$match: {"activity.action" : "test_action"}}}, 
    {$group: {_id: null, count: {$sum: 1}}} 
]) 

Dies wird zu einem Ergebnis führen wie:

{ 
    count: 4 
} 
+0

Vielen Dank, es funktioniert, aber es scheint, dass es keine Indizes verwendet und arbeitet extrem langsam auf 600k Dokumente-Dataset. Ich habe Indizes für '_id',' createddate' und 'activity.action'. Was für Indizes soll ich noch erstellen? – aokozlov

+0

Aggregation verwendet einen Index für die $ match-Stufe (falls zu Beginn angegeben), aber wie Blakes Seven bereits gesagt hat, verursacht die Abwicklungsstufe viel Overhead. – ZeMoon

+0

Ich habe ein Edit hinzugefügt, das sollte es ein wenig schneller laufen lassen – ZeMoon

Verwandte Themen