sql >> Database >  >> NoSQL >> MongoDB

beperk en sorteer elke groep op in mongoDB met behulp van aggregatie

De beste optie hier is om afzonderlijke zoekopdrachten uit te voeren voor elk "Land" (idealiter parallel) en de gecombineerde resultaten te retourneren. De zoekopdrachten zijn vrij eenvoudig en retourneren gewoon de top 2 waarden na het toepassen van een sortering op de beoordelingswaarde en zullen vrij snel worden uitgevoerd, zelfs als u meerdere zoekopdrachten moet uitvoeren om het volledige resultaat te verkrijgen.

Het aggregatieraamwerk is hier niet geschikt voor, nu en ook niet in de nabije toekomst. Het probleem is dat er niet zo'n operator is die het resultaat van een groepering op enigerlei wijze "begrenst". Dus om dit te doen, moet je in principe $push alle inhoud in een array en extraheer daaruit de "top n"-waarden.

De huidige operaties die daarvoor nodig zijn, zijn behoorlijk afschuwelijk, en het kernprobleem is dat de resultaten waarschijnlijk de BSON-limiet van 16 MB per document op de meeste echte gegevensbronnen overschrijden.

Er is ook een n complexiteit vanwege hoe je het nu zou moeten doen. Maar gewoon om te demonstreren met 2 items:

db.collection.aggregate([
    // Sort content by country and rating
    { "$sort": { "Country": 1, "rating": -1 } },

    // Group by country and push all items, keeping first result
    { "$group": {
        "_id": "$Country",
        "results": {
            "$push": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        },
        "first": { 
            "$first": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        }
    }},

    // Unwind the array
    { "$unwind": "results" },

    // Remove the seen result from the array
    { "$redact": {
        "$cond": {
            "if": { "$eq": [ "$results.id", "$first.id" ] },
            "then": "$$PRUNE",
            "else": "$$KEEP"
        }
    }},

    // Group to return the second result which is now first on stack
    { "$group": {
        "_id": "$_id",
        "first": { "$first": "$first" },
        "second": { 
            "$first": {
                "name": "$results.name", 
                "rating": "$results.rating",
                "id": "$results.id"
            }
        }
    }},

    // Optionally put these in an array format
    { "$project": {
        "results": { 
            "$map": {
                "input": ["A","B"],
                "as": "el",
                "in": {
                    "$cond": {
                        "if": { "$eq": [ "$$el", "A" ] },
                        "then": "$first",
                        "else": "$second"
                    }
                }
            }
        }
    }}
])

Dat geeft het resultaat, maar het is geen geweldige aanpak en wordt een stuk complexer met iteraties voor hogere limieten of zelfs waar groepen mogelijk minder dan n hebben resultaten om in sommige gevallen terug te keren.

De huidige ontwikkelingsreeks ( 3.1.x ) heeft op het moment van schrijven een $slice operator die dit een beetje eenvoudiger maakt, maar nog steeds dezelfde "grootte" valkuil heeft:

db.collection.aggregate([
    // Sort content by country and rating
    { "$sort": { "Country": 1, "rating": -1 } },

    // Group by country and push all items, keeping first result
    { "$group": {
        "_id": "$Country",
        "results": {
            "$push": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        }
    }},
    { "$project": {
        "results": { "$slice": [ "$results", 2 ] }
    }}
])

Maar eigenlijk totdat het aggregatieraamwerk een manier heeft om het aantal items dat door $push wordt geproduceerd te "beperken" of een vergelijkbare "limiet"-operator voor groeperen, dan is het aggregatieraamwerk niet echt de optimale oplossing voor dit soort problemen.

Eenvoudige zoekopdrachten zoals deze:

db.collection.find({ "Country": "USA" }).sort({ "rating": -1 }).limit(1)

Uitvoeren voor elk afzonderlijk land en idealiter in parallelle verwerking door een gebeurtenislus van een thread met een gecombineerd resultaat, produceert op dit moment de meest optimale aanpak. Ze halen alleen op wat nodig is, wat het grote probleem is dat het aggregatieraamwerk nog niet aankan bij zo'n groepering.

Zoek dus naar ondersteuning om deze "gecombineerde zoekopdrachtresultaten" op de meest optimale manier voor de door u gekozen taal te doen, omdat het veel minder complex en veel beter presteert dan dit in het aggregatieraamwerk te gooien.




  1. MongoDB $dateFromString

  2. MongoDB voor elk()

  3. is dit de optimale minimale setup voor mongodb om sharding/scaling toe te staan?

  4. Kan ik globale TTL in redis instellen?