sql >> Databáze >  >> NoSQL >> MongoDB

Dotaz na vnořené pole MongoDB Intersection Query

Existuje několik způsobů, jak toho dosáhnout pomocí agregačního rámce

Například jednoduchá sada dat:

{
    "_id" : ObjectId("538181738d6bd23253654690"),
    "movies": [
        { "_id": 1, "rating": 5 },
        { "_id": 2, "rating": 6 },
        { "_id": 3, "rating": 7 }
    ]
},
{
    "_id" : ObjectId("538181738d6bd23253654691"),
    "movies": [
        { "_id": 1, "rating": 5 },
        { "_id": 4, "rating": 6 },
        { "_id": 2, "rating": 7 }
    ]
},
{
    "_id" : ObjectId("538181738d6bd23253654692"),
    "movies": [
        { "_id": 2, "rating": 5 },
        { "_id": 5, "rating": 6 },
        { "_id": 6, "rating": 7 }
    ]
}

Pomocí prvního „uživatele“ jako příkladu nyní chcete zjistit, zda některý z dalších dvou uživatelů má alespoň dva stejné filmy.

Pro MongoDB 2.6 a vyšší můžete jednoduše použít $setIntersection operátor spolu s $size operátor:

db.users.aggregate([

    // Match the possible documents to reduce the working set
    { "$match": {
        "_id": { "$ne": ObjectId("538181738d6bd23253654690") },
        "movies._id": { "$in": [ 1, 2, 3 ] },
        "$and": [
            { "movies": { "$not": { "$size": 1 } } }
        ]
    }},

    // Project a copy of the document if you want to keep more than `_id`
    { "$project": {
        "_id": {
            "_id": "$_id",
            "movies": "$movies"
        },
        "movies": 1,
    }},

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

    // Build the array back with just `_id` values
    { "$group": {
        "_id": "$_id",
        "movies": { "$push": "$movies._id" }
    }},

    // Find the "set intersection" of the two arrays
    { "$project": {
        "movies": {
            "$size": {
                "$setIntersection": [
                   [ 1, 2, 3 ],
                   "$movies"
                ]
            }
        }
    }},

    // Filter the results to those that actually match
    { "$match": { "movies": { "$gte": 2 } } }

])

To je stále možné v dřívějších verzích MongoDB, které tyto operátory nemají, stačí provést několik dalších kroků:

db.users.aggregate([

    // Match the possible documents to reduce the working set
    { "$match": {
        "_id": { "$ne": ObjectId("538181738d6bd23253654690") },
        "movies._id": { "$in": [ 1, 2, 3 ] },
        "$and": [
            { "movies": { "$not": { "$size": 1 } } }
        ]
    }},

    // Project a copy of the document along with the "set" to match
    { "$project": {
        "_id": {
            "_id": "$_id",
            "movies": "$movies"
        },
        "movies": 1,
        "set": { "$cond": [ 1, [ 1, 2, 3 ], 0 ] }
    }},

    // Unwind both those arrays
    { "$unwind": "$movies" },
    { "$unwind": "$set" },

    // Group back the count where both `_id` values are equal
    { "$group": {
        "_id": "$_id",
        "movies": {
           "$sum": {
               "$cond":[
                   { "$eq": [ "$movies._id", "$set" ] },
                   1,
                   0
               ]
           }
        } 
    }},

    // Filter the results to those that actually match
    { "$match": { "movies": { "$gte": 2 } } }
])

Podrobně

To může být trochu k zamyšlení, takže se můžeme podívat na každou fázi a rozdělit je, abychom viděli, co dělají.

$match :Nechcete pracovat s každým dokumentem ve sbírce, takže toto je příležitost k odstranění položek, které se možná neshodují, i když je ještě potřeba udělat více práce s nalezením přesného jedničky. Takže samozřejmé věci jsou vyloučit stejného „uživatele“ a poté porovnat pouze dokumenty, které mají alespoň jeden stejný film, jaký byl nalezen pro tohoto „uživatele“.

Další věc, která dává smysl, je zvážit to, když chcete najít shodu s n záznamy pak pouze dokumenty, které mají pole "filmy" větší než n-1 může skutečně obsahovat shody. Použití $and tady to vypadá legračně a není to konkrétně vyžadováno, ale pokud požadované shody byly 4 pak by skutečná část příkazu vypadala takto:

        "$and": [
            { "movies": { "$not": { "$size": 1 } } },
            { "movies": { "$not": { "$size": 2 } } },
            { "movies": { "$not": { "$size": 3 } } }
        ]

Takže v podstatě "vyloučíte" pole, která nejsou možná dostatečně dlouhá, aby měla n zápasy. Upozorňujeme, že tento $size operátor ve formuláři dotazu se liší od $size pro agregační rámec. Neexistuje žádný způsob, jak to například použít s operátorem nerovnosti, jako je $gt jeho účelem je konkrétně odpovídat požadované "velikosti". Proto tento formulář dotazu specifikuje všechny možné velikosti, které jsou menší než.

$project :V tomto prohlášení je několik účelů, z nichž některé se liší v závislosti na verzi MongoDB, kterou máte. Za prvé, a volitelně, kopie dokumentu je uchovávána pod _id hodnotu, aby tato pole nebyla změněna zbývajícími kroky. Další částí je ponechání pole „filmy“ v horní části dokumentu jako kopie pro další fázi.

Co se také děje ve verzi prezentované pro verze před 2.6, je zde další pole představující _id hodnoty pro "filmy", které se mají shodovat. Použití $cond operátor zde je jen způsob, jak vytvořit "doslovnou" reprezentaci pole. Zajímavé je, že MongoDB 2.6 zavádí operátor známý jako $literal udělat přesně to bez legračního způsobu, jakým používáme $cond přímo tady.

$unwind :Chcete-li udělat cokoli dalšího, je třeba rozvinout pole filmů, protože v obou případech je to jediný způsob, jak izolovat existující _id hodnoty pro položky, které je třeba porovnat s "množinou". Takže pro verzi starší než 2.6 musíte „rozvinout“ obě přítomná pole.

$group :Pro MongoDB 2.6 a vyšší se pouze seskupujete zpět do pole, které obsahuje pouze _id hodnoty filmů s odstraněným „hodnocením“.

Před 2.6, protože všechny hodnoty jsou prezentovány "vedle sebe" (a se spoustou duplikací), provádíte porovnání dvou hodnot, abyste zjistili, zda jsou stejné. Kde je to true , to říká $cond příkaz operátora, který vrátí hodnotu 1 nebo 0 kde je podmínka false . Toto je přímo předáváno zpět prostřednictvím $sum k sečtení počtu odpovídajících prvků v poli na požadovanou "množinu".

$project :Toto je jiná část pro MongoDB 2.6 a vyšší, protože jste odsunuli pole "filmů" _id hodnoty, které pak používáte $setIntersection přímo porovnávat tato pole. Výsledkem je pole obsahující prvky, které jsou stejné, a toto je pak zabaleno do $size operátor, aby bylo možné určit, kolik prvků bylo vráceno v dané odpovídající sadě.

$match :Je poslední fáze, která zde byla implementována a která provádí jasný krok spárování pouze těch dokumentů, jejichž počet protínajících se prvků byl větší nebo roven požadovanému počtu.

Konečná

V podstatě to tak děláte. Před verzí 2.6 je to trochu neohrabanější a bude vyžadovat trochu více paměti kvůli rozšíření, které se provádí duplikováním každého člena pole, který je nalezen všemi možnými hodnotami sady, ale stále je to platný způsob, jak toho dosáhnout.

Vše, co musíte udělat, je použít toto s větším n odpovídající hodnoty, aby splňovaly vaše podmínky, a samozřejmě se ujistěte, že vaše původní shoda uživatele má požadované n možnosti. Jinak to vygenerujte na n-1 z délky „uživatelského“ pole „filmů“.




  1. Zpracování polí s výskytem mongoose v GraphQL

  2. Dobrý způsob řešení MongoError:fond instancí serveru byl zničen

  3. MongoDb:Výhoda použití ObjectID oproti řetězci obsahujícímu Id?

  4. Ukládání numpy pole v mongodb