sql >> Databasteknik >  >> NoSQL >> MongoDB

MongoDB-projektion av kapslade arrayer

Uppdatering 2017

En sådan välställd fråga förtjänar ett modernt svar. Den typ av array-filtrering som efterfrågas kan faktiskt göras i moderna MongoDB-utgåvor efter 3.2 via helt enkelt $match och $project pipeline-steg, ungefär som den ursprungliga vanliga frågeoperationen avser.

db.accounts.aggregate([
  { "$match": {
    "email" : "[email protected]",
    "groups": {
      "$elemMatch": { 
        "name": "group1",
        "contacts.localId": { "$in": [ "c1","c3", null ] }
      }
    }
  }},
  { "$addFields": {
    "groups": {
      "$filter": {
        "input": {
          "$map": {
            "input": "$groups",
            "as": "g",
            "in": {
              "name": "$$g.name",
              "contacts": {
                "$filter": {
                  "input": "$$g.contacts",
                  "as": "c",
                  "cond": {
                    "$or": [
                      { "$eq": [ "$$c.localId", "c1" ] },
                      { "$eq": [ "$$c.localId", "c3" ] }
                    ]
                  } 
                }
              }
            }
          }
        },
        "as": "g",
        "cond": {
          "$and": [
            { "$eq": [ "$$g.name", "group1" ] },
            { "$gt": [ { "$size": "$$g.contacts" }, 0 ] }
          ]
        }
      }
    }
  }}
])

Detta använder sig av $filter och $map operatörer att endast returnera elementen från arrayerna som skulle uppfylla villkoren, och är mycket bättre för prestanda än att använda $unwind . Eftersom pipeline-stegen effektivt speglar strukturen för "query" och "project" från en .find() drift, prestandan här är i princip i nivå med sådan och drift.

Observera att där avsikten är att faktiskt arbeta "över dokument" för att sammanföra detaljer från "flera" dokument snarare än "ett", då skulle detta vanligtvis kräva någon typ av $unwind operation för att göra det, vilket gör det möjligt för arrayobjekten att vara tillgängliga för "gruppering".

Detta är i grunden tillvägagångssättet:

db.accounts.aggregate([
    // Match the documents by query
    { "$match": {
        "email" : "[email protected]",
        "groups.name": "group1",
        "groups.contacts.localId": { "$in": [ "c1","c3", null ] },
    }},

    // De-normalize nested array
    { "$unwind": "$groups" },
    { "$unwind": "$groups.contacts" },

    // Filter the actual array elements as desired
    { "$match": {
        "groups.name": "group1",
        "groups.contacts.localId": { "$in": [ "c1","c3", null ] },
    }},

    // Group the intermediate result.
    { "$group": {
        "_id": { "email": "$email", "name": "$groups.name" },
        "contacts": { "$push": "$groups.contacts" }
    }},

    // Group the final result
    { "$group": {
        "_id": "$_id.email",
        "groups": { "$push": {
            "name": "$_id.name",
            "contacts": "$contacts" 
        }}
    }}
])

Detta är "matrisfiltrering" på mer än en enda matchning som de grundläggande projektionsmöjligheterna för .find() kan inte göra.

Du har "kapslade" arrayer och därför måste du bearbeta $unwind dubbelt. Tillsammans med de andra operationerna.



  1. Hur man returnerar bara värdet i MongoDB

  2. Mongo $i operatörsprestanda

  3. MongoDB $toString

  4. Mongoose - hitta underdokument efter kriterier