C# MongoDB 基于积分数组的排名
C# MongoDB ranking base on points array
正在研究最新的 C# mongodb 驱动程序和 .NET 4.5.1。
我想在玩家之间进行一些定制的比赛。
假设我有以下模型。
public sealed class PlayerPoints
{
[BsonId]
public ObjectId PlayerId;
public DateTime CreateDate;
public int Points;
public int[] SeasonalPoints;
}
我希望能够在特定 SeasonalPoints
索引之间获得 player/s 的排名。
一个例子:
{PlayerId : someId1, CreateDate : <someCreateDate>, Points : 1000, SeasonalPoints : [100,100,100,100,100,100,100,100,100,100,100]}
{PlayerId : someId2, CreateDate : <someCreateDate>, Points : 1000, SeasonalPoints : [100,100,100,100,100,100,100,100,50,150,100]}
{PlayerId : someId3, CreateDate : <someCreateDate>, Points : 1100, SeasonalPoints : [200,100,100,100,100,100,100,100,0,0,300]}
请注意这里有 10 个季节。
我在搜索 returns 一个根据排名排序的球员列表的查询。排名由提供的索引之间的点数总和设置。
如果我查询第 9 季到第 10 季的排名,那么 someId3 是第一个,someId2 之后,someId1 是最后一个。
如果我查询第 7-9 季的排名,那么 someId1 是第一位,someId2 是第二位,someId3 是第三位。
我考虑过使用聚合,它会如何影响大约 1m 文档的性能,同时这个查询将被非常频繁地调用。
澄清
主要问题是如何构建将产生上述结果的查询,次要问题是查询将从服务器消耗多少性能。
谢谢。
至少,如果托管服务器的机器与数据库的机器不同,您将获得改进的服务器性能。
另一方面,这可能意味着数据库机器可以更少 "available",因为它太忙于计算聚合结果。这是应该进行基准测试的东西,因为它因应用程序而异,并且不时变化。
这取决于用户负载、数据量、主机等。
至于查询,这里是我验证实际工作的程序:
using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
namespace MongoAggregation
{
public sealed class PlayerPoints
{
public ObjectId Id { get; set; }
//Note that mongo addresses everything as UTC 0, so if you store local time zone values, make sure to use this attribute
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime CreateDate { get; set; }
public int Points { get; set; }
//note that your model did not allow a player to not participate in some season, so I took the liberty of introducing a new sub document.
//It is better to create sub documents that store metadata to make the query easier to implement
public int[] SeasonalPoints { get; set; }
}
class Program
{
static void Main(string[] args)
{
//used v 2.4.3 of C# driver and v 3.4.1 of the db engine for this example
var client = new MongoClient();
IMongoDatabase db = client.GetDatabase("agg_example");
var collectionName = "points";
db.DropCollection(collectionName);
IMongoCollection<BsonDocument> collection = db.GetCollection<BsonDocument>(collectionName);
IEnumerable<BsonDocument> data = GetDummyData().Select(d=>d.ToBsonDocument());
collection.InsertMany(data);
//some seasons to filter by - note transformation to zero based
var seasons = new[] {6, 7};
//This is the query body:
var seasonIndex = seasons.Select(i => i - 1);
//This shall remove all un-necessary seasons from aggregation pipeline
var bsonFilter = new BsonDocument { new BsonElement("Season", new BsonDocument("$in", new BsonArray(seasonIndex))) };
var groupBy = new BsonDocument// think of this as a grouping with an anonyous object declaration
{
new BsonElement("_id", "$_id"),//This denotes the key by which to group - in this case the player's id
new BsonElement("playerSum", new BsonDocument("$sum", "$SeasonalPoints")),//We aggregate the player's points after unwinding the array
new BsonElement("player", new BsonDocument("$first", "$$CURRENT")),// preserve player reference for projection stage
};
var sort = Builders<BsonDocument>.Sort.Descending(doc => doc["playerSum"]);
var unwindOptions = new AggregateUnwindOptions<BsonDocument>
{
IncludeArrayIndex = new StringFieldDefinition<BsonDocument>("Season")
};
var projection = Builders<BsonDocument>.Projection.Expression((doc => doc["player"]));
List<BsonValue> sorted = collection
.Aggregate()
.Unwind(x=>x["SeasonalPoints"], unwindOptions)
.Match(bsonFilter)
.Group(groupBy)
.Sort(sort)
.Project(projection)
.ToList();
}
private static IEnumerable<PlayerPoints> GetDummyData()
{
return new[]
{
new PlayerPoints
{
CreateDate = DateTime.Today,
SeasonalPoints = Enumerable.Repeat(100,7).ToArray()
},
new PlayerPoints
{
CreateDate = DateTime.Today,
SeasonalPoints = new []
{
100,100,100,100,100,150,100
}
},
new PlayerPoints
{
CreateDate = DateTime.Today,
SeasonalPoints = new []
{
100,100,100,100,100,0,300
}
},
};
}
}
}
您可以尝试使用 3.4
版本进行以下聚合。
聚合阶段 - $project
- $sort
- $project
.
数组聚合运算符 - $reduce
& $slice
算术运算符 - $add
示例:
If I query for rank in season 9 to 10 then someId3 is first, someId2
after and someId1 is last
下面的代码将使用 $project
阶段来保持 PlayerId
和 TotalPoints
。
`
TotalPoints
使用 $slice
和 SeasonalPoints
数组,起始位置为 9
并返回最多 2
个元素,后跟 $reduce
获取数组值并对每个文档的值求和。
$sort
阶段按 TotalPoints
值降序排序。
$project
阶段输出 PlayerId
值。
class Program {
static void Main(string[] args) {
IMongoClient client = new MongoClient();
IMongoDatabase db = client.GetDatabase("db");
IMongoCollection < PlayerPoints > collection = db.GetCollection < PlayerPoints > ("collection");
var pipeline = collection.Aggregate()
.Project(p => new {
PlayerId = p.PlayerId, TotalPoints = p.SeasonalPoints.Skip(9).Take(2).Aggregate((s1, s2) => s1 + s2)
})
.SortByDescending(s => s.TotalPoints)
.Project(e => new {
e.PlayerId
});
var result = pipeline.ToListAsync();
}
}
Mongo Shell 查询:
db.collection.aggregate([{
"$project": {
"PlayerId": "$_id",
"TotalPoints": {
"$reduce": {
"input": {
"$slice": ["$SeasonalPoints", 9, 2]
},
"initialValue": 0,
"in": {
"$add": ["$$value", "$$this"]
}
}
},
"_id": 0
}
}, {
"$sort": {
"TotalPoints": -1
}
}, {
"$project": {
"PlayerId": "$PlayerId",
"_id": 0
}
}])
正在研究最新的 C# mongodb 驱动程序和 .NET 4.5.1。
我想在玩家之间进行一些定制的比赛。 假设我有以下模型。
public sealed class PlayerPoints
{
[BsonId]
public ObjectId PlayerId;
public DateTime CreateDate;
public int Points;
public int[] SeasonalPoints;
}
我希望能够在特定 SeasonalPoints
索引之间获得 player/s 的排名。
一个例子:
{PlayerId : someId1, CreateDate : <someCreateDate>, Points : 1000, SeasonalPoints : [100,100,100,100,100,100,100,100,100,100,100]}
{PlayerId : someId2, CreateDate : <someCreateDate>, Points : 1000, SeasonalPoints : [100,100,100,100,100,100,100,100,50,150,100]}
{PlayerId : someId3, CreateDate : <someCreateDate>, Points : 1100, SeasonalPoints : [200,100,100,100,100,100,100,100,0,0,300]}
请注意这里有 10 个季节。 我在搜索 returns 一个根据排名排序的球员列表的查询。排名由提供的索引之间的点数总和设置。
如果我查询第 9 季到第 10 季的排名,那么 someId3 是第一个,someId2 之后,someId1 是最后一个。 如果我查询第 7-9 季的排名,那么 someId1 是第一位,someId2 是第二位,someId3 是第三位。
我考虑过使用聚合,它会如何影响大约 1m 文档的性能,同时这个查询将被非常频繁地调用。
澄清
主要问题是如何构建将产生上述结果的查询,次要问题是查询将从服务器消耗多少性能。
谢谢。
至少,如果托管服务器的机器与数据库的机器不同,您将获得改进的服务器性能。
另一方面,这可能意味着数据库机器可以更少 "available",因为它太忙于计算聚合结果。这是应该进行基准测试的东西,因为它因应用程序而异,并且不时变化。
这取决于用户负载、数据量、主机等。
至于查询,这里是我验证实际工作的程序:
using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
namespace MongoAggregation
{
public sealed class PlayerPoints
{
public ObjectId Id { get; set; }
//Note that mongo addresses everything as UTC 0, so if you store local time zone values, make sure to use this attribute
[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime CreateDate { get; set; }
public int Points { get; set; }
//note that your model did not allow a player to not participate in some season, so I took the liberty of introducing a new sub document.
//It is better to create sub documents that store metadata to make the query easier to implement
public int[] SeasonalPoints { get; set; }
}
class Program
{
static void Main(string[] args)
{
//used v 2.4.3 of C# driver and v 3.4.1 of the db engine for this example
var client = new MongoClient();
IMongoDatabase db = client.GetDatabase("agg_example");
var collectionName = "points";
db.DropCollection(collectionName);
IMongoCollection<BsonDocument> collection = db.GetCollection<BsonDocument>(collectionName);
IEnumerable<BsonDocument> data = GetDummyData().Select(d=>d.ToBsonDocument());
collection.InsertMany(data);
//some seasons to filter by - note transformation to zero based
var seasons = new[] {6, 7};
//This is the query body:
var seasonIndex = seasons.Select(i => i - 1);
//This shall remove all un-necessary seasons from aggregation pipeline
var bsonFilter = new BsonDocument { new BsonElement("Season", new BsonDocument("$in", new BsonArray(seasonIndex))) };
var groupBy = new BsonDocument// think of this as a grouping with an anonyous object declaration
{
new BsonElement("_id", "$_id"),//This denotes the key by which to group - in this case the player's id
new BsonElement("playerSum", new BsonDocument("$sum", "$SeasonalPoints")),//We aggregate the player's points after unwinding the array
new BsonElement("player", new BsonDocument("$first", "$$CURRENT")),// preserve player reference for projection stage
};
var sort = Builders<BsonDocument>.Sort.Descending(doc => doc["playerSum"]);
var unwindOptions = new AggregateUnwindOptions<BsonDocument>
{
IncludeArrayIndex = new StringFieldDefinition<BsonDocument>("Season")
};
var projection = Builders<BsonDocument>.Projection.Expression((doc => doc["player"]));
List<BsonValue> sorted = collection
.Aggregate()
.Unwind(x=>x["SeasonalPoints"], unwindOptions)
.Match(bsonFilter)
.Group(groupBy)
.Sort(sort)
.Project(projection)
.ToList();
}
private static IEnumerable<PlayerPoints> GetDummyData()
{
return new[]
{
new PlayerPoints
{
CreateDate = DateTime.Today,
SeasonalPoints = Enumerable.Repeat(100,7).ToArray()
},
new PlayerPoints
{
CreateDate = DateTime.Today,
SeasonalPoints = new []
{
100,100,100,100,100,150,100
}
},
new PlayerPoints
{
CreateDate = DateTime.Today,
SeasonalPoints = new []
{
100,100,100,100,100,0,300
}
},
};
}
}
}
您可以尝试使用 3.4
版本进行以下聚合。
聚合阶段 - $project
- $sort
- $project
.
数组聚合运算符 - $reduce
& $slice
算术运算符 - $add
示例:
If I query for rank in season 9 to 10 then someId3 is first, someId2 after and someId1 is last
下面的代码将使用 $project
阶段来保持 PlayerId
和 TotalPoints
。
`
TotalPoints
使用 $slice
和 SeasonalPoints
数组,起始位置为 9
并返回最多 2
个元素,后跟 $reduce
获取数组值并对每个文档的值求和。
$sort
阶段按 TotalPoints
值降序排序。
$project
阶段输出 PlayerId
值。
class Program {
static void Main(string[] args) {
IMongoClient client = new MongoClient();
IMongoDatabase db = client.GetDatabase("db");
IMongoCollection < PlayerPoints > collection = db.GetCollection < PlayerPoints > ("collection");
var pipeline = collection.Aggregate()
.Project(p => new {
PlayerId = p.PlayerId, TotalPoints = p.SeasonalPoints.Skip(9).Take(2).Aggregate((s1, s2) => s1 + s2)
})
.SortByDescending(s => s.TotalPoints)
.Project(e => new {
e.PlayerId
});
var result = pipeline.ToListAsync();
}
}
Mongo Shell 查询:
db.collection.aggregate([{
"$project": {
"PlayerId": "$_id",
"TotalPoints": {
"$reduce": {
"input": {
"$slice": ["$SeasonalPoints", 9, 2]
},
"initialValue": 0,
"in": {
"$add": ["$$value", "$$this"]
}
}
},
"_id": 0
}
}, {
"$sort": {
"TotalPoints": -1
}
}, {
"$project": {
"PlayerId": "$PlayerId",
"_id": 0
}
}])