我在PHP / MYsql中创建了一个平台,现在我正在迁移到mongo
我对mysql的旧查询:
select sum(game_won) as game_won,count(id) as total,position
from games_player_stats
where position < 6 and position > 0 and user_id = :pa_id
group by position
order by total desc
新的json格式如下所示:
{
"region" : "EUW",
"players" : [
{
"position" : 2,
"summoner_id" : 123456,
"game_won": 1
},
{
"position" : 1,
"summoner_id" : 123459,
"game_won": 0
},
{
"position" : 3,
"summoner_id" : 123458,
"game_won": 1
},
{
"position" : 4,
"summoner_id" : 123457,
"game_won": 0
}
]
}
有了这样的多个文件,我需要找到多少次summoner_id 123456有位置2或任何其他位置1-6以及他在那个位置赢了多少次
索引需要在region和summoner_id上查询
结果看起来像
{
"positions" :
[
{ "position" : 1,
"total" : 123,
"won" : 65
},
{ "position" : 2,
"total" : 37,
"won" : 10
}
]
}
我需要使用Map / Reduce吗?
答案 0 :(得分:2)
最好的结果是通过MongoDB的聚合框架获得的。它与mapReduce的不同之处在于,所有操作都是使用&#34;本机编码的运算符&#34;而不是mapReduce使用的JavaScript评估。
这意味着&#34;更快&#34;,并且显着如此。更不用说还有某些部分你正在寻找的结果实际上有利于&#34;多个组&#34;管道本身可用的概念&#34;操作,否则使用mapReduce会相当丑陋。
根据MongoDB&#34;服务器&#34;最佳方法会有所不同。你有的版本。
理想情况下,使用 MongoDB 3.2 ,您可以使用$filter
来预先过滤&#34;使用$unwind
处理前的数组内容:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
// Filter the array content for matched conditions
{ "$project": {
"players": {
"$filter": {
"input": "$players",
"as": "player"
"cond": {
"$and": [
{ "$eq": [ "$$player.summoner_id", 123456 ] },
{ "$gte": [ "$$player.position", 1 ] },
{ "$lte": [ "$$player.position", 6 ] }
]
}
}
}
}},
// Unwind the array contents to de-normalize
{ "$unwind": "$players" },
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
db.collection.aggregate(pipeline);
对于 MongoDB 2.6.x 版本,仍然是&#34;预过滤&#34;但使用$map
和$setDifference
:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
// Filter the array content for matched conditions
{ "$project": {
"players": {
"$setDifference": [
{ "$map": {
"input": "$players",
"as": "player",
"in": {
"$cond": {
"if": {
"$and": [
{ "$eq": [ "$$player.summoner_id", 123456 ] },
{ "$gte": [ "$$player.position", 1 ] },
{ "$lte": [ "$$player.position", 6 ] }
]
},
"then": "$$player",
"else": false
}
}
}},
[false]
]
}
}},
// Unwind the array contents to de-normalize
{ "$unwind": "$players" },
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
对于具有MongoDB 2.2聚合框架的早期版本,&#34; post filter&#34;与$match
&#34;&#34; $unwind
:
var pipeline = [
// Match documents with array members matching conditions
{ "$match": {
"players": {
"$elemMatch": {
"summoner_id": 123456,
"position": { "$gte": 1, "$lte": 6 }
}
}
}},
{ "$unwind": "$players" },
// Post filter the denormalized content
{ "$match": {
"players.summoner_id": 123456,
"players.position": { "$gte": 1, "$lte": 6 }
}},
// Group on the inner "position"
{ "$group": {
"_id": "$players.position",
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
}},
// Optionally Sort by position since $group is not ordered
{ "$sort": { "total": -1 } },
// Optionally $group to a single document response with an array
{ "$group": {
"_id": null,
"positions": {
"$push": {
"position": "$_id",
"total": "$total",
"won": "$won"
}
}
}}
];
匹配文档:这主要是使用$elemMatch
完成的,因为您正在寻找&#34;多个&#34;数组元素中的条件。使用&#34;单身&#34;条件在数组元素上使用"dot notation":
"players.summoner_id": 12345
但对于任何超过&#34;一个&#34;条件你需要使用$elemMatch
,否则所有的陈述都是&#34;这是否匹配数组中的某些东西?&#34; ,并且不包括&#34;所有&#34;在元素内。因此,即使仅$gte
和$lte
组合实际上也是&#34;两个&#34;条件,因此需要$elemMatch
:
"players": {
"$elemMatch": {
"position": { "$gte": 1, "$lte": 6 }
}
}
此处还注意到&#34; 1到6包含&#34; 表示&#34;大于或等于&#34; ,反之亦然&#34;小于&#34; 条件。
-
&#34;预过滤&#34; :请注意,最终的目标是&#34; group&#34;由数组中的元素组成,"position"
。这意味着最终您需要$unwind
内容来执行此操作。
然而,$unwind
管道操作的成本非常高,考虑到它会分开&#34;数组并创建一个新文档来处理每个数组成员。因为你只想要&#34;一些&#34;实际上符合条件的成员,他们希望&#34;删除&#34;来自阵列的任何未匹配的内容&#34;之前&#34;你取消了这个内容的标准化。
使用$filter
运算符,MongoDB 3.2有一个很好的方法。它完全按照&#34;过滤&#34;的命名执行。数组的内容仅限于与特定条件集匹配的元素。
在汇总管道阶段,我们使用它的逻辑变体&#34; $gte
和$lte
等运算符。这些值将根据条件匹配的位置返回true/false
值。同样在数组中,实际上可以使用&#34;点符号&#34;使用成员字段来引用它们。到"as"
中指向当前已处理成员的别名参数。
$and
这里也是另一个&#34;逻辑运算符&#34;它做了相同的true/false
响应。所以这意味着&#34;所有&#34;必须满足其中的参数才能返回true
。对于$filter
本身,true/false
中评估的"cond"
确定是否返回数组元素。
对于没有$filter
运算符的MongoDB 2.6,同样用$map
和$setDifference
的组合表示。只需将$map
看一下每个元素即可在"in"
中应用表达式。在这种情况下,我们使用$cond
作为&#34;三元&#34;运营商评估&#39; if / then / else`表格。
所以这里"if"
返回true
,"then"
中的表达式将作为当前数组成员返回。在false
的位置,else
中的表达式返回,在这种情况下,我们返回false
的值(PHP False
)。
由于所有成员实际上都是由$map
的结果返回的,因此我们通过应用$filter
运算符来模拟$setDifference
。这与数组成员进行了比较,并有效地&#34;删除&#34;从结果中返回元素false
的所有成员。因此,对于您所拥有的不同阵列成员,所得到的&#34; set&#34; (作为&#34;&#34;&#34;唯一&#34;元素)只包含那些条件为true
且返回非假值的元素。
<强>&#34;邮政&#34;过滤:MongoDB 2.6下面的服务器版本必须采用的替代方法是&#34; post&#34;过滤数组内容。由于这些版本中没有运算符允许在$unwind
之前对数组内容执行此类操作,因此此处的简单过程是将另一个$match
应用于内容&#34;&#34;之后已处理$unwind
:
{ "$match": {
"players.summoner_id": 123456,
"players.position": { "$gte": 1, "$lte": 6 }
}}
在这里你使用&#34;点符号&#34;因为现在每个数组元素实际上都是它自己的文档,除了查看指定路径上的条件之外,没有别的东西可以比较。
这是不理想,因为当您处理$unwind
时,实际上不符合条件的所有元素仍然存在。这最终意味着需要处理的更多文件&#34;并且具有以下双重成本:
必须为每个成员创建一个新文档,尽管它与条件不匹配
现在你必须在每个&#34;文件中应用条件&#34;由于$unwind
这对性能有潜在的巨大影响,因此现代MongoDB版本引入了对数组进行操作的方法,而无需借助$unwind
来处理。你仍然需要它用于剩余的处理,因为你是&#34;分组&#34;在数组中包含的属性上。但是,当然希望首先摆脱不匹配的元素&#34;
正在进行分组:现在,元素已经过滤和反规范化,只剩下实际的$group
条件,这将导致"position"
在每个元素内。这是向"_id"
提供分组键并使用适当的数据累积的简单问题。
在这种情况下,您有两个结构,即:
"total": { "$sum": 1 },
"won": { "$sum": "$players.won" }
基本的{ "$sum": 1 }
只是&#34;计算&#34;每个组匹配的元素和{ "$sum": "$players.won" }
实际上使用"won"
值来累计总数。这是$sum
累加器的标准用法。
当然你的输出显示了一个&#34;数组&#34;中的内容,所以以下几个阶段实际上是&#34;可选&#34;因为真正的工作实际上是&#34;分组&#34;已经完成了。因此,您实际上只能使用第一个$group
之前提供的表单中的结果,剩下的只是将所有内容放入单个文档响应中,而不是“#34;每个位置的文档”。价值&#34;,这将是此时的回报。
第一个音符是从$group
输出的不是。因此,如果您想要特定的结果顺序(即按位置升序),则必须在$sort
阶段之后$group
。这将对管道的结果文档进行排序,直到应用它为止。
在你的情况下,你实际上是要求"total"
进行排序,所以你当然会将其应用于-1
含义&#34;降序&#34;在这种情况下。但无论如何,你仍然不应该假设$group
的输出是以任何方式排序的。
&#34;第二&#34; $group
这里基本上是装饰性的,因为这就是制作一个单一文件的原因。响应。在分组键中使用null
(PHP NULL)基本上表示&#34;对所有内容进行分组&#34;并将生成一份文件作为回应。这里的$push
累加器实际上是&#34;数组&#34;来自此前的管道中的文件。
这就是积累数据的一般过程:
匹配条件所需的文档,因为如果它们甚至不包含与您最终想要的条件匹配的数组元素,那么稍后将条件应用于每个文档将是一种浪费。< / p>
过滤数组内容并取消规范化。理想情况下,作为&#34;预过滤器&#34;在可能的情况。这将文档转换为一个表格,用于分组,从原始数组形式。
使用适当的运算符累计内容,$sum
或$avg
或$push
或根据需要提供的任何其他运算符。根据结构和条件,您可以随时使用&#34;不止一个&#34; $group
管道阶段。
PHP表示法中的初始示例:
pipeline = array(
array(
'$match' => array(
'players' => array(
'$elemMatch' => array(
'summoner_id' => 123456,
'position' => array( '$gte' => 0, '$lte' => 6 )
)
)
)
),
array(
'$project' => array(
'$filter' => array(
'input' => '$players',
'as' => 'player',
'cond' => (
'$and' => array(
array( '$eq' => array( '$$player.summoner_id' => 123456 ) ),
array( '$gte' => array( '$$player.position' => 1 ) ),
array( '$lte' => array( '$$player.position' => 6 ) )
)
)
)
)
),
array( '$unwind' => '$players' ),
array(
'$group' => array(
'_id' => '$players.position',
'total' => array( '$sum' => 1 ),
'won' => array( '$sum' => '$players.won' )
)
),
array( '$sort' => array( 'total' => -1 ) ),
array(
'$group' => array(
'_id' => NULL,
'positions' => array(
'$push' => array(
'position' => '$_id',
'total' => '$total',
'won' => '$won'
)
)
)
)
)
$result = $collection->aggregate($pipeline);
在PHP中使用与JSON进行比较的数据结构时,通过以下方式检查结构通常很有用:
echo json_encode($pipeline, JSON_PRETTY_PRINT)
然后您可以看到您在PHP表示法中所做的与您所遵循的JSON示例相同。这是一个有用的提示,这样你就不会出错。如果它看起来不同那么你就不会做同样的事情。的事情。