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

MongoDB vám pomůže s doporučeními

Pro konečný výsledek zde musíte udělat několik věcí, ale první fáze jsou relativně jednoduché. Vezměte objekt uživatele, který zadáte:

var user = {
    user_id : 1,
    Friends : [3,5,6],
    Artists : [
        {artist_id: 10 , weight : 345},
        {artist_id: 17 , weight : 378}
    ]
};

Nyní za předpokladu, že již máte tato data načtena, pak jde o nalezení stejných struktur pro každého „přítele“ a odfiltrování obsahu pole „Umělci“ do jediného odlišného seznamu. Pravděpodobně zde bude uvažována také každá „váha“ jako celek.

Toto je jednoduchá agregační operace, která nejprve odfiltruje umělce, kteří jsou již v seznamu pro daného uživatele:

var artists = user.Artists.map(function(artist) { return artist.artist_id });

User.aggregate(
    [ 
        // Find possible friends without all the same artists
        { "$match": {
            "user_id": { "$in": user.Friends },
            "Artists.artist_id": { "$nin": artists }
        }},
        // Pre-filter the artists already in the user list
        { "$project": 
            "Artists": {
                "$setDifference": [
                    { "$map": {
                        "input": "$Artists",
                        "as": "$el",
                        "in": {
                            "$cond": [
                                "$anyElementTrue": {
                                    "$map": {
                                        "input": artists,
                                        "as": "artist",
                                        "in": { "$eq": [ "$$artist", "$el.artist_id" ] }
                                    }
                                },
                                false,
                                "$$el"
                            ]
                        } 
                    }}
                    [false]
                ]
            } 
        }},
        // Unwind the reduced array
        { "$unwind": "$Artists" },
        // Group back by each artist and sum weights
        { "$group": {
            "_id": "$Artists.artist_id",
            "weight": { "$sum": "$Artists.weight" }
        }},
        // Sort the results by weight
        { "$sort": { "weight": -1 } }
    ],
    function(err,results) {
        // more to come here
    }
);

„Předfiltr“ je zde jediná opravdu záludná část. Stačí $unwind pole a $match znovu, abyste odfiltrovali položky, které nechcete. I když se chceme $unwind výsledky později, aby je bylo možné zkombinovat, je efektivnější je z pole „nejprve“ odstranit, takže je méně co rozšiřovat.

Zde je tedy $map Operátor umožňuje kontrolu každého prvku uživatelského pole "Umělci" a také pro porovnání s filtrovaným seznamem "uživatelů" umělců, aby pouze vrátil požadované detaily. $setDifference se používá ke skutečnému "filtrování" jakýchkoli výsledků, které nebyly vráceny jako obsah pole, ale spíše jako false .

Poté už zbývá jen $unwind k denormalizaci obsahu v poli a $group dát dohromady celkový počet na umělce. Pro zábavu používáme $sort ukázat, že seznam je vrácen v požadovaném pořadí, ale v pozdější fázi to nebude nutné.

To je alespoň část cesty, protože výsledný seznam by měl obsahovat pouze ostatní umělce, kteří ještě nejsou ve vlastním seznamu uživatele, a seřazený podle součtu „váhy“ od všech umělců, kteří by se případně mohli objevit u více přátel.

Další část bude potřebovat data ze sbírky "umělci", aby bylo možné vzít v úvahu počet posluchačů. Zatímco mongoose má .populate() metodu, tohle tu opravdu nechcete, protože hledáte počty "odlišných uživatelů". To znamená další implementaci agregace za účelem získání těchto odlišných počtů pro každého interpreta.

V návaznosti na seznam výsledků předchozí operace agregace byste použili $_id hodnoty jako toto:

// First get just an array of artist id's
var artists = results.map(function(artist) {
    return artist._id;
});

Artist.aggregate(
    [
        // Match artists
        { "$match": {
            "artistID": { "$in": artists }
        }},
        // Project with weight for distinct users
        { "$project": {
            "_id": "$artistID",
            "weight": {
                "$multiply": [
                    { "$size": {
                        "$setUnion": [
                            { "$map": {
                                "input": "$user_tag",
                                "as": "tag",
                                "in": "$$tag.user_id"
                            }},
                            []
                        ]
                    }},
                    10
                ]
            }
        }}
    ],
    function(err,results) {
        // more later
    }
);

Zde se trik provádí souhrnně pomocí $map provést podobnou transformaci hodnot, která je přiváděna do $setUnion vytvořit z nich jedinečný seznam. Poté $size Pomocí operátoru zjistíte, jak velký je tento seznam. Dodatečná matematika má dát tomuto číslu nějaký význam, když je aplikován na již zaznamenané váhy z předchozích výsledků.

Samozřejmě to všechno musíte nějak dát dohromady, protože právě teď existují pouze dvě odlišné sady výsledků. Základním procesem je "Hash Table", kde se jako klíč používají jedinečné hodnoty "artist" id a hodnoty "weight" jsou kombinovány.

Můžete to udělat mnoha způsoby, ale protože existuje touha "uspořádat" kombinované výsledky, pak bych preferoval něco "MongoDBish", protože se řídí základními metodami, na které byste již měli být zvyklí.

Šikovný způsob, jak to implementovat, je použití nedb , který poskytuje úložiště „v paměti“, které používá většinu stejných metod, jaké se používají ke čtení a zápisu do kolekcí MongoDB.

To se také dobře škáluje, pokud potřebujete použít skutečnou kolekci pro velké výsledky, protože všechny principy zůstávají stejné.

  1. První operace agregace vloží nová data do úložiště

  2. Druhá agregace „aktualizuje“ data a zvyšuje pole „váha“

Jako úplný výpis funkcí a s nějakou další pomocí async knihovna by to vypadalo takto:

function GetUserRecommendations(userId,callback) {

    var async = require('async')
        DataStore = require('nedb');

    User.findOne({ "user_id": user_id},function(err,user) {
        if (err) callback(err);

        var artists = user.Artists.map(function(artist) {
            return artist.artist_id;
        });

        async.waterfall(
            [
                function(callback) {
                    var pipeline =  [ 
                        // Find possible friends without all the same artists
                        { "$match": {
                            "user_id": { "$in": user.Friends },
                            "Artists.artist_id": { "$nin": artists }
                        }},
                        // Pre-filter the artists already in the user list
                        { "$project": 
                            "Artists": {
                                "$setDifference": [
                                    { "$map": {
                                        "input": "$Artists",
                                        "as": "$el",
                                        "in": {
                                            "$cond": [
                                                "$anyElementTrue": {
                                                    "$map": {
                                                        "input": artists,
                                                        "as": "artist",
                                                        "in": { "$eq": [ "$$artist", "$el.artist_id" ] }
                                                    }
                                                },
                                                false,
                                                "$$el"
                                            ]
                                        } 
                                    }}
                                    [false]
                                ]
                            } 
                        }},
                        // Unwind the reduced array
                        { "$unwind": "$Artists" },
                        // Group back by each artist and sum weights
                        { "$group": {
                            "_id": "$Artists.artist_id",
                            "weight": { "$sum": "$Artists.weight" }
                        }},
                        // Sort the results by weight
                        { "$sort": { "weight": -1 } }
                    ];

                    User.aggregate(pipeline, function(err,results) {
                        if (err) callback(err);

                        async.each(
                            results,
                            function(result,callback) {
                                result.artist_id = result._id;
                                delete result._id;
                                DataStore.insert(result,callback);
                            },
                            function(err)
                                callback(err,results);
                            }
                        );

                    });
                },
                function(results,callback) {

                    var artists = results.map(function(artist) {
                        return artist.artist_id;  // note that we renamed this
                    });

                    var pipeline = [
                        // Match artists
                        { "$match": {
                            "artistID": { "$in": artists }
                        }},
                        // Project with weight for distinct users
                        { "$project": {
                            "_id": "$artistID",
                            "weight": {
                                "$multiply": [
                                    { "$size": {
                                        "$setUnion": [
                                            { "$map": {
                                                "input": "$user_tag",
                                                "as": "tag",
                                                "in": "$$tag.user_id"
                                            }},
                                            []
                                        ]
                                    }},
                                    10
                                ]
                            }
                        }}
                    ];

                    Artist.aggregate(pipeline,function(err,results) {
                        if (err) callback(err);
                        async.each(
                            results,
                            function(result,callback) {
                                result.artist_id = result._id;
                                delete result._id;
                                DataStore.update(
                                    { "artist_id": result.artist_id },
                                    { "$inc": { "weight": result.weight } },
                                    callback
                                );
                            },
                            function(err) {
                                callback(err);
                            }
                        );
                    });
                }
            ],
            function(err) {
                if (err) callback(err);     // callback with any errors
                // else fetch the combined results and sort to callback
                DataStore.find({}).sort({ "weight": -1 }).exec(callback);
            }
        );

    });

}

Takže po shodě s původním zdrojovým uživatelským objektem jsou hodnoty předány do první agregační funkce, která se spouští v sérii a používá async.waterfall předat výsledek.

Než k tomu dojde, ale výsledky agregace jsou přidány do DataStore s běžným .insert() příkazy, přičemž dbejte na přejmenování _id pole jako nedb nemá rád nic jiného než své vlastní _id hodnoty. Každý výsledek je vložen s artist_id a weight vlastnosti z výsledku agregace.

Tento seznam je poté předán druhé agregační operaci, která vrátí každého zadaného „umělce“ s vypočítanou „váhou“ na základě odlišné velikosti uživatele. Existují "aktualizované" se stejným .update() v DataStore pro každého umělce a zvýšením pole "váha".

Vše jde dobře, poslední operace je .find() tyto výsledky a .sort() jejich kombinovanou "váhou" a jednoduše vrátíte výsledek na předaný zpětným voláním funkce.

Takže byste to použili takto:

GetUserRecommendations(1,function(err,results) {
   // results is the sorted list
});

A vrátí všechny umělce, kteří nejsou momentálně v seznamu daného uživatele, ale v jejich seznamech přátel a seřazení podle kombinované váhy počtu přátel, kteří poslouchají, plus skóre z počtu různých uživatelů tohoto umělce.

Takto nakládáte s daty ze dvou různých kolekcí, které je třeba spojit do jednoho výsledku s různými agregovanými detaily. Je to mnohonásobné dotazy a pracovní prostor, ale také součást filozofie MongoDB, že takové operace se lépe provádějí tímto způsobem, než je házet do databáze za účelem „spojení“ výsledků.




  1. Jak se MongoDb instaluje pomocí Meteoru?

  2. Vraťte nejnovější záznam z vnořeného dokumentu v Mongodb

  3. mongodb - načtení podmnožiny pole

  4. Dlouhý akumulátor místo Double ve funkci MongoDB group().