jjzjj

mongodb - MongoDB协助推荐

coder 2023-11-04 原文

我有一个3集合架构,如下所示:
用户收藏有关于他们的朋友和每个艺术家的收听计数(权重)的信息

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

艺术家集合模式包含有关艺术家的名称、不同用户给他们的标记的信息。
{
    artistID : 56,
    name : "Ed Sheeran",
    user_tag : [
       {user_id : 2, tag_id : 6},
       {user_id : 2, tag_id : 5},
       {user_id : 3, tag_id : 7}
    ]
}

包含各种标记信息的标记集合。
{tag_id : 3, tag_value : "HipHop"}

我想使用以下规则为用户提供艺术家推荐:
规则1:找到用户的朋友而不是用户所听的艺术家,按朋友的收听次数之和排序。
规则2:选择用户使用的任何标签,找到该标签不在用户监听列表中的所有艺术家,并按唯一监听者的数量排序。
有谁能帮我写一个查询来执行上面的操作吗?

最佳答案

您需要在这里做一些事情来获得最终结果,但第一阶段相对简单。获取您提供的用户对象:

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

现在假设您已经检索了该数据,那么这就归结为为为每个“朋友”找到相同的结构,并将“艺术家”的数组内容过滤到一个单独的列表中。想必每一个“重量”在这里也都会被考虑在内。
这是一个简单的聚合操作,它将首先筛选出给定用户列表中已存在的艺术家:
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
    }
);

“预过滤”是这里唯一真正棘手的部分。您只需$unwind数组并再次$match即可筛选出不需要的条目。即使我们想在稍后将结果合并起来,但从数组中“首先”删除结果会更有效,因此扩展的空间更小。
因此在这里,$unwind操作符允许检查用户“艺术家”数组的每个元素,也允许与筛选的“用户”艺术家列表进行比较,以返回所需的详细信息。$map实际上用于“过滤”未作为数组内容返回但作为$setDifference返回的任何结果。
在那之后,只有false来反规范化数组中的内容,而$unwind来合并每个艺术家的总数。为了好玩,我们使用$group来显示列表是按所需的顺序返回的,但在稍后阶段不需要这样做。
这至少是其中的一部分,因为最终的列表应该只是用户自己列表中没有的其他艺术家,并根据可能出现在多个好友上的任何艺术家的总和“权重”进行排序。
下一部分将需要来自“艺术家”集合的数据,以便考虑听众的数量。虽然Mongoose有一个$sort方法,但您确实不希望在这里看到这个,因为您正在寻找“独特的用户”计数。这意味着另一个聚合实现,以便为每个艺术家获取这些不同的计数。
在上一个聚合操作的结果列表中,您将使用如下.populate()值:
// 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
    }
);

这里,这个技巧与$_id一起完成,对输入到$map的值进行类似的转换,使它们成为一个唯一的列表。然后应用$setUnion运算符来找出该列表有多大。另一种数学方法是,当对先前结果中已经记录的权重应用时,给这个数字一些含义。
当然你需要以某种方式把所有这些结合起来,因为现在只有两组不同的结果。基本过程是一个“哈希表”,其中唯一的“艺术家”id值用作键,并将“权重”值组合在一起。
你可以用很多方法来实现这一点,但是由于人们希望对合并后的结果进行“排序”,所以我更喜欢“mongodbish”,因为它遵循了你已经习惯的基本方法。
实现这一点的一种简便方法是使用$size,它提供了一个“内存中”存储,它使用与mongodb集合的读写方法大致相同的类型。
如果需要将实际集合用于大型结果,这也可以很好地扩展,因为所有原则都保持不变。
第一个聚合操作将新数据插入存储区
第二个聚合“更新”数据并增加“权重”字段
作为一个完整的函数列表,在nedb库的一些其他帮助下,它将如下所示:
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);
            }
        );

    });

}

因此,在匹配初始源用户对象之后,这些值被传递到第一个聚合函数中,该聚合函数以串行方式执行,并使用async传递其结果。
在这之前,尽管聚合结果被添加到带有常规async.waterfall语句的DataStore中,但要注意将.insert()字段重命名为_id除了它自己生成的nedb值之外,其他都不喜欢。每个结果都从聚合结果中插入_idartist_id属性。
然后将该列表传递给第二个聚合操作,该操作将返回每个指定的“艺术家”,并根据不同的用户大小计算“权重”。每个艺术家的weight上都有相同的.update()语句的“updated”,并递增“weight”字段。
一切顺利,最后的操作是通过组合的“权重”将结果DataStore.find()返回给函数的传入回调。
所以你可以这样使用它:
GetUserRecommendations(1,function(err,results) {
   // results is the sorted list
});

它将返回当前不在该用户列表中但在其好友列表中的所有艺术家,并按好友收听计数加上该艺术家的不同用户数的分数的组合权重排序。
这是处理来自两个不同集合的数据的方式,您需要将它们组合成具有各种聚合细节的单个结果。它是多个查询和一个工作空间,但也是MongoDB Philosopy的一部分,这样的操作比将它们扔到数据库中“连接”结果更好。

关于mongodb - MongoDB协助推荐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32857398/

有关mongodb - MongoDB协助推荐的更多相关文章

  1. ruby-on-rails - 在一个 Rails 应用程序中使用 PostgreSQL 的 MongoDB - 2

    我可以在一个Rails应用程序中同时使用MongoDB和PostgreSQL吗?具体来说,我最终会想要使用像MongoHQ这样的东西。到目前为止,我未能在实验中进行这项工作。令我担心的是,MongoDB文档特别指出我必须禁用ActiveRecord。任何建议将不胜感激。 最佳答案 您无需禁用ActiveRecord即可使用MongoDB。查看Mongoid只需将gem加上任何模型与您现有的任何ActiveRecord模型一起添加。您应该注意到MongoHQ只是MongoDB的托管服务,可以与任何对象文档映射器(ODM)一起使用。更多

  2. ruby - 使用 mongodb/mongoid 运行时更改模型 - 2

    我必须在mongoid模型中添加几个字段,我知道MongoDB没有迁移,但如果我继续而不删除数据库,使rails完全“重新生成”数据库,它不会显示或使用新的领域!去这里最好的方法是什么?有比删除/重新打开mongodb更软的东西吗?提前致谢卢卡 最佳答案 一般来说,应该可以在运行时用新字段更新旧文档。MongoDB中不需要迁移。您可能想编写rake任务以使用新字段和默认值更新旧文档。您可以通过检查那些默认值为nil的新字段来找到这些文档。更新简单风格:如果您使用默认值定义一个新字段,只要您设置了一个新值,就应该始终使用该值:应用程序

  3. ruby-on-rails - 我如何从 Ruby 代码连接到 mongodb? - 2

    我如何从Ruby代码连接到mongodb? 最佳答案 首先,您必须安装MongoDbgem:geminstallmongo然后运行代码:require'rubygems'#notnecessaryforRuby1.9require'mongo'db=Mongo::Connection.new.db("mydb")#ORdb=Mongo::Connection.new("localhost").db("mydb")#ORdb=Mongo::Connection.new("localhost",27017).db("mydb")

  4. ruby - MongoDB:无法从 BSON 类型 EOO 转换为 Date - 2

    我正在尝试使用聚合框架(使用ruby​​)并像这样投影日期:db['requests'].aggregate([{"$project"=>{_id:0,method:'$method',user:'$user',year:{'$year'=>'$timestamp'}}}])文档是这样的:{_id:ObjectId("5177d7d7df26358289da7dfd"),timestamp:ISODate("2013-04-12T03:58:05+00:00"),method:"POST",status:"200",inputsize:"874",outputsize:"4981",u

  5. ruby - 在 Ruby 中从 MongoDB 中检索字段的子集 - 2

    我试图通过在Ruby中进行的查询从MongoDB获取字段的子集,但它似乎不起作用。它不返回任何结果这是ruby代码:coll.find("title"=>'Halo',:fields=>["title","isrc"])#thisdoesn'twork如果我删除字段散列,它会工作,返回包含所有字段的结果coll.find("title"=>'Halo')#thisworks查看mongodb控制台,第一个查询在mongodb服务器上结束,如下所示:{title:"Halo",fields:["title","isrc"]}如果我尝试从mongo客户端控制台进行查询,它会工作,我会得到结

  6. Elasticsearch和MongoDB对比 - 2

    文章目录Elasticsearch和MongoDB对比关于ElasticsearchElasticsearch应用场景关于MongoDBMongoDB优点mongodb适用场景Elasticsearch和MongoDB对比Elasticsearch和MongoDB开源许可协议参考Elasticsearch和MongoDB对比关于Elasticsearch官网:https://www.elastic.co/cn/elasticsearch/Elasticistheleadingplatformforsearch-poweredsolutions.Weaccelerateresultsthatma

  7. javascript - 类型错误 : mongodb property insertmany is not a function - 2

    db.col.insertMany([{"_id":"tt0084726","title":"StarTrekII:TheWrathofKhan","year":1982,"type":"movie"},{"_id":"tt0796366","title":"StarTrek","year":2009,"type":"movie"},{"_id":"tt0084726","title":"StarTrekII:TheWrathofKhan","year":1982,"type":"movie"}]);OS:LinuxMint17.3RosaMongoDB:dbversionv2.6.1

  8. javascript - 如何使用 javascript 中的 http.post 将图像发送到服务器并在 mongodb 中存储 base64 - 2

    我在使用mongodb在服务器端存储图像的客户端访问http请求时遇到了问题。我非常感谢帮助。我需要一个简单的示例来说明如何将图像文件作为数据添加到httppost请求(例如XMLhttprequest)中。比方说,我知道服务器方法的网址。图片来源定义在imgsrc文件名存放在name我有这个自动取款机:varhttp=newXMLHttpRequest();httpPost.onreadystatechange=function(err){if(httpPost.readyState==4&&httpPost.status==200){console.log(httpPost.res

  9. 【MongoDB】windows安装MongoDB6.0.5+可视化界面软件 - 2

    目录MongoDB简介安装MongoDB  安装MongoDBShell添加账户密码 安装MongoDBCompassMongoDB简介MongoDB是一个流行的开源文档型NoSQL数据库管理系统,使用C++语言编写。与传统的关系型数据库不同,MongoDB使用文档模型来存储数据。文档模型是一种灵活的数据模型,它允许您在单个文档中存储和查询相关数据。文档模型还支持嵌套文档和数组结构,这使得它非常适合处理复杂的数据结构。MongoDB的特点包括:非常灵活的文档模型,可以轻松存储复杂数据类型。分布式系统设计,可以通过分片技术实现横向扩展,适合大规模数据处理。支持丰富的查询语言和聚合框架,使得开发人

  10. Helm部署minio\nginx\mongodb\elasticsearch - 2

    minioappVersion:2022-06-25chartVersion:11.7.7一、独立模式auth:auth:rootPassword:"12345678rtt"#密码长度需>=8位rootUser:"root"mode:standalone#默认为单机模式persistence:storageClass:minio-data#存储类,必填size:8Giservice:type:NodePort#暴露端口port:9000nodePort:31311二、分布式模式auth:auth:rootPassword:"12345678rtt"#密码长度需>=8位rootUser:"roo

随机推荐