2012-09-06 9 views
8

In meinem mongoDB Buchsammlung zurück ich Dokumente wie folgt strukturiert haben:MongoDB Aggregation: Wie nur passende Elemente eines Arrays

/* 0 */ 
{ 
    "_id" : ObjectId("50485b89b30f1ea69110ff4c"), 

    "publisher" : { 
    "$ref" : "boohya", 
    "$id" : "foo" 
    }, 
    "displayName" : "Paris Nightlife", 
    "catalogDescription" : "Some desc goes here", 
    "languageCode" : "en", 
    "rating" : 0, 
    "status" : "LIVE", 
    "thumbnailId" : ObjectId("50485b89b30f1ea69110ff4b"), 
    "indexTokens" : ["Nightlife", "Paris"] 
} 

ich die folgende regex Abfrage führen alle Dokumente zu finden, mit einer indexToken beginnend mit " par“:

{ "indexTokens" : { "$regex" : "^Par" , "$options" : "i"}} 

Wenn ich wählen Sie das indexTokens Feld nur wie folgt zurückgegeben werden:

{ "indexTokens" : 1} 

Das resultierende DBObject ist

{ "_id" : { "$oid" : "50485b89b30f1ea69110ff4c"} , "indexTokens" : [ "Nightlife" , "Paris"]} 

Was würde Ich mag nur zu bekommen, ist das Token/Tag, das die regex abgestimmt (I don0t Sorge um das Dokument zu diesem Zeitpunkt abrufen, weder brauche ich alle Tags des passendes Dokument)

Ist dies ein Fall für das neue Aggregations-Framework unter MongoDB v2.2. ?

Wenn ja, wie kann ich eine Abfrage ändern, so dass das tatsächliche Ergebnis aussehen würde:

{ "indexTokens": [ "Paris", "Paradise Fluss", "Parma", etc ....] }

Bonus Frage (haben Sie die Codez): Wie mache ich es mit dem Java-Treiber?

Vorerst mein Java wie folgt aussieht:

DBObject query = new BasicDBObject("indexTokens", java.util.regex.Pattern.compile("^"+filter+"", Pattern.CASE_INSENSITIVE)); 
    BasicDBObject fields = new BasicDBObject("indexTokens",1); 
    DBCursor curs = getCollection() 
        .find(query, fields) 
        .sort(new BasicDBObject("indexTokens" , 1)) 
        .limit(maxSuggestionCount); 

Thx :)

EDIT:

Wie pro Ihre Antworten, die ich meine JAVA-Code wie folgt geändert:

BasicDBObject cmdBody = new BasicDBObject("aggregate", "Book"); 
    ArrayList<BasicDBObject> pipeline = new ArrayList<BasicDBObject>(); 

    BasicDBObject match = new BasicDBObject("$match", new BasicDBObject("indexTokens", java.util.regex.Pattern.compile("^"+titleFilter+"", Pattern.CASE_INSENSITIVE))); 
    BasicDBObject unwind = new BasicDBObject("$unwind", "$indexTokens"); 
    BasicDBObject match2 = new BasicDBObject("$match", new BasicDBObject("indexTokens", java.util.regex.Pattern.compile("^"+titleFilter+"", Pattern.CASE_INSENSITIVE))); 
    BasicDBObject groupFilters = new BasicDBObject("_id",null); 
    groupFilters.append("indexTokens", new BasicDBObject("$push", "$indexTokens")); 
    BasicDBObject group = new BasicDBObject("$group", groupFilters); 

    pipeline.add(match); 
    pipeline.add(unwind); 
    pipeline.add(match2); 
    pipeline.add(group); 

    cmdBody.put("pipeline", pipeline); 



    CommandResult res = getCollection().getDB().command(cmdBody); 
    System.out.println(res); 

Welche Ausgänge

{ "result" : [ { "_id" : null , "indexTokens" : [ "Paris"]}] , "ok" : 1.0} 

Das ist genial!

Vielen Dank!

Antwort

10

Sie könnten dies mit dem 2.2 Aggregation Framework tun. Etwas wie das;

Oder dies könnte noch näher an was Sie suchen, wenn Sie das Array ohne das Dokument wirklich wollen;

db.books.runCommand("aggregate", { 
    pipeline: [ 
     { // find docs that contain Par* 
      $match: { "indexTokens" : { "$regex" : "^Par" , "$options" : "i"}}, 
     }, 
     { // create a doc with a single array elemm for each indexToken entry 
      $unwind: "$indexTokens" 
     }, 
     { // now throw out any unwind's that DON'T contain Par* 
      $match: { "indexTokens": { "$regex": "^Par", "$options": "i" } }, 
     }, 
     { // now produce the list of index tokens 
      $group: { 
       _id: null, 
       indexTokens: { $push: "$indexTokens" }, 
      }, 
     }, 
    ], 
}) 
+0

Sie können dies zu Ihrer ursprünglichen Antwort als eine zweite Lösung hinzufügen. Auf diese Weise werden die Leute nicht verwirrt, warum gibt es zwei Antworten von Ihnen :) – Sammaye

+0

ok. getan, dass .. – cirrus

+0

Dank euch beiden funktioniert es wie ein Charme. Ich habe eine Antwort hinzugefügt, um zu zeigen, wie ich es in JAVA gemacht habe (ich habe nicht den neuesten Treiber, also konnte ich die aggregate() Methode für DBCollection nicht verwenden. – azpublic

2

Aufbauend auf die Antwort von Zirrus, ich empfehle, die $unwind zuerst die redundante $match zu vermeiden. Etwas wie:

Wie machen Sie das in Java? Sie können die DBCollection.aggregate(...)-Methode des MongoDB v2.9.0-Treibers verwenden. Jeder Pipeline-Betreiber, z. $unwind oder $match, entspricht einem DBObject Objekt.

+1

Eigentlich glaube ich nicht, dass $ match überflüssig ist. Das Problem mit $ unwind ist, dass es eine riesige Menge an Dokumenten im Arbeitsspeicher erstellen muss und Sie diese so früh wie möglich reduzieren wollen.Das erste $ -Match stellt sicher, dass wir NUR mit Dokumenten arbeiten, die sogar Par * in der Datenbank haben indexTokens, bevor wir sie abwickeln. Das zweite $ -Match schneidet dann das Set auf genau diejenigen ab, die wir wollen. Denken Sie daran, dass Sie Ihr $ -Match früh erreichen wollen, um das Pipeline-Volumen zu reduzieren. – cirrus

+1

Sie haben Recht. Übereinstimmende Dokumente, wickeln Sie die Arrays ab und passen Sie sie erneut an, um die Dokumente auszusortieren, die nicht mit dem regulären Ausdruck übereinstimmen. – slee