在单个查询中匹配来自两个查询的键
Match on key from two queries in a single query
我在mongodb中有时间序列数据如下:
{
"_id" : ObjectId("558912b845cea070a982d894"),
"code" : "ZL0KOP",
"time" : NumberLong("1420128024000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d895"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128025000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d896"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128003000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d897"),
"code" : "ZL0KOP",
"time" : NumberLong("1420041724000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d89e"),
"code" : "YBUHCW",
"time" : NumberLong("1420041732000"),
"direction" : "10",
"siteId" : "0002"
}
{
"_id" : ObjectId("558912b845cea070a982d8a1"),
"code" : "U48AIW",
"time" : NumberLong("1420041729000"),
"direction" : "10",
"siteId" : "0002"
}
{
"_id" : ObjectId("558912b845cea070a982d8a0"),
"code" : "OJ3A06",
"time" : NumberLong("1420300927000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d89d"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420300885000"),
"direction" : "10",
"siteId" : "0003"
}
{
"_id" : ObjectId("558912b845cea070a982d8a2"),
"code" : "ZLV05H",
"time" : NumberLong("1420300922000"),
"direction" : "10",
"siteId" : "0001"
}
{
"_id" : ObjectId("558912b845cea070a982d8a3"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420300928000"),
"direction" : "10",
"siteId" : "0000"
}
需要过滤掉符合两个或两个以上条件的代码。
例如:
condition1: 1420128000000 < time < 1420128030000,siteId == 0000
condition2: 1420300880000 < time < 1420300890000,siteId == 0003
第一个条件的结果:
{
"_id" : ObjectId("558912b845cea070a982d894"),
"code" : "ZL0KOP",
"time" : NumberLong("1420128024000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d895"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128025000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d896"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128003000"),
"direction" : "10",
"siteId" : "0000"
}
第二个条件的结果:
{
"_id" : ObjectId("558912b845cea070a982d89d"),
"code" : "AQ0ZSQ", "time" : NumberLong("1420300885000"),
"direction" : "10",
"siteId" : "0003"
}
唯一符合以上所有条件的代码应该是:
{"code" : "AQ0ZSQ", "count":2}
"count"表示,代码"AQ0ZSQ"出现在两种情况下
我能想到的唯一解决方案是使用两个查询。例如,使用 python
result1 = list(db.codes.objects({'time': {'$gt': 1420128000000,'$lt': 1420128030000}, 'siteId': "0000"}).only("code"))
result2 = list(db.codes.objects({'time': {'$gt': 1420300880000,'$lt': 1420300890000}},{'siteId':'0003'}).only("code"))
然后在两个结果中都找到了共享代码。
问题是集合中有数百万个文档,两个查询很容易超过 16mb 的限制。
那么是否可以在一次查询中做到这一点?还是我应该更改文档结构?
您在此处要求的内容需要使用 aggregation framework 才能计算出服务器上的结果之间存在交集。
逻辑的第一部分是您需要 $or
查询这两个条件,然后会对这些结果进行一些额外的投影和过滤:
db.collection.aggregate([
// Fetch all possible documents for consideration
{ "$match": {
"$or": [
{
"time": { "$gt": 1420128000000, "$lt": 1420128030000 },
"siteId": "0000"
},
{
"time": { "$gt": 1420300880000, "$lt": 1420300890000 },
"siteId": "0003"
}
]
}},
// Locigically compare the conditions agaist results and add a score
{ "$project": {
"code": "$code",
"score": { "$add": [
{ "$cond": [
{ "$and":[
{ "$gt": [ "$time", 1420128000000 ] },
{ "$lt": [ "$time", 1420128030000 ] },
{ "$eq": [ "$siteId", "0000" ] }
]},
1,
0
]},
{ "$cond": [
{ "$and":[
{ "$gt": [ "$time", 1420300880000 ] },
{ "$lt": [ "$time", 1420300890000 ] },
{ "$eq": [ "$siteId", "0003" ] }
]},
1,
0
]}
]}
}},
// Now Group the results by "code"
{ "$group": {
"_id": "$code",
"score": { "$sum": "$score" }
}},
// Now filter to keep only results with score 2
{ "$match": { "score": 2 } }
])
所以分解一下,看看它是如何工作的。
首先,您需要使用 $match
进行查询,以获取符合 "intersection" 条件的 "all" 的所有可能文档。考虑到匹配的文档必须满足任一集合,这就是 $or
表达式在这里允许的。您需要所有这些才能在这里计算出 "intersection"。
聚合框架的第二个$project
pipeline stage a boolean test of your conditions is performed with each set. Notice the usage of $and
here as well as other boolean operators与查询使用形式略有不同。
在聚合框架形式中(在使用普通查询运算符的 $match
之外),这些运算符采用参数数组,通常表示用于比较的 "two" 值,而不是分配给的操作字段名的"right"。
由于这些条件是合乎逻辑的或 "boolean" 我们希望 return 结果为 "numeric" 而不是 true/false
值。这就是 $cond
在这里所做的。因此,如果检查的文档的条件为真,则发出 1
的分数,否则当为假时,它为 0
。
最后,在这个 $project
表达式中,您的两个条件都用 $add
包裹起来,形成 "score" 结果。因此,如果 none 的条件(在 $match 之后不可能)不为真,则分数将为 0,如果 "one" 为真则为 1,或者 "both" 为真则为 2。
请注意,对于单个文档,此处要求的特定条件永远不会超过 1
,因为没有文档可以具有重叠范围或存在的 "two" "siteId" 值这里。
现在重要的部分是 $group
by the "code" value and $sum
分值,得到总分 "code"。
这使管道的最后 $match
过滤阶段只保留那些 "score" 值等于您要求的条件数的文档。在这种情况下 2
.
然而,这里有一个失败之处在于,如果在任一条件的匹配中有多个 "code" 的值(确实如此),那么这里的 "score" 将是不正确的。
所以在介绍了在聚合中使用逻辑运算符的原则之后,您可以通过本质上 "tagging" 每个结果逻辑上 "set" 它适用于哪个条件来修复该故障。那么你基本上可以考虑在这种情况下"both"集中出现了哪些"code":
db.collection.aggregate([
{ "$match": {
"$or": [
{
"time": { "$gt": 1420128000000, "$lt": 1420128030000 },
"siteId": "0000"
},
{
"time": { "$gt": 1420300880000, "$lt": 1420300890000 },
"siteId": "0003"
}
]
}},
// If it's the first logical condition it's "A" otherwise it can
// only be the other, therefore "B". Extend for more sets as needed.
{ "$group": {
"_id": {
"code": "$code",
"type": { "$cond": [
{ "$and":[
{ "$gt": [ "$time", 1420128000000 ] },
{ "$lt": [ "$time", 1420128030000 ] },
{ "$eq": [ "$siteId", "0000" ] }
]},
"A",
"B"
]}
}
}},
// Simply add up the results for each "type"
{ "$group": {
"_id": "$_id.code",
"score": { "$sum": 1 }
}}
// Now filter to keep only results with score 2
{ "$match": { "score": 2 } }
])
如果这是您第一次使用聚合框架,可能有点难以理解。请花时间查看此处链接定义的运算符,并查看一般情况下的 Aggregation Pipeline Operators。
除了简单的数据选择,这是您在使用 MongoDB 时应该最常接触到的工具,因此您最好了解所有可能的操作。
我在mongodb中有时间序列数据如下:
{
"_id" : ObjectId("558912b845cea070a982d894"),
"code" : "ZL0KOP",
"time" : NumberLong("1420128024000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d895"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128025000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d896"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128003000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d897"),
"code" : "ZL0KOP",
"time" : NumberLong("1420041724000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d89e"),
"code" : "YBUHCW",
"time" : NumberLong("1420041732000"),
"direction" : "10",
"siteId" : "0002"
}
{
"_id" : ObjectId("558912b845cea070a982d8a1"),
"code" : "U48AIW",
"time" : NumberLong("1420041729000"),
"direction" : "10",
"siteId" : "0002"
}
{
"_id" : ObjectId("558912b845cea070a982d8a0"),
"code" : "OJ3A06",
"time" : NumberLong("1420300927000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d89d"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420300885000"),
"direction" : "10",
"siteId" : "0003"
}
{
"_id" : ObjectId("558912b845cea070a982d8a2"),
"code" : "ZLV05H",
"time" : NumberLong("1420300922000"),
"direction" : "10",
"siteId" : "0001"
}
{
"_id" : ObjectId("558912b845cea070a982d8a3"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420300928000"),
"direction" : "10",
"siteId" : "0000"
}
需要过滤掉符合两个或两个以上条件的代码。 例如:
condition1: 1420128000000 < time < 1420128030000,siteId == 0000
condition2: 1420300880000 < time < 1420300890000,siteId == 0003
第一个条件的结果:
{
"_id" : ObjectId("558912b845cea070a982d894"),
"code" : "ZL0KOP",
"time" : NumberLong("1420128024000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d895"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128025000"),
"direction" : "10",
"siteId" : "0000"
}
{
"_id" : ObjectId("558912b845cea070a982d896"),
"code" : "AQ0ZSQ",
"time" : NumberLong("1420128003000"),
"direction" : "10",
"siteId" : "0000"
}
第二个条件的结果:
{
"_id" : ObjectId("558912b845cea070a982d89d"),
"code" : "AQ0ZSQ", "time" : NumberLong("1420300885000"),
"direction" : "10",
"siteId" : "0003"
}
唯一符合以上所有条件的代码应该是:
{"code" : "AQ0ZSQ", "count":2}
"count"表示,代码"AQ0ZSQ"出现在两种情况下
我能想到的唯一解决方案是使用两个查询。例如,使用 python
result1 = list(db.codes.objects({'time': {'$gt': 1420128000000,'$lt': 1420128030000}, 'siteId': "0000"}).only("code"))
result2 = list(db.codes.objects({'time': {'$gt': 1420300880000,'$lt': 1420300890000}},{'siteId':'0003'}).only("code"))
然后在两个结果中都找到了共享代码。
问题是集合中有数百万个文档,两个查询很容易超过 16mb 的限制。
那么是否可以在一次查询中做到这一点?还是我应该更改文档结构?
您在此处要求的内容需要使用 aggregation framework 才能计算出服务器上的结果之间存在交集。
逻辑的第一部分是您需要 $or
查询这两个条件,然后会对这些结果进行一些额外的投影和过滤:
db.collection.aggregate([
// Fetch all possible documents for consideration
{ "$match": {
"$or": [
{
"time": { "$gt": 1420128000000, "$lt": 1420128030000 },
"siteId": "0000"
},
{
"time": { "$gt": 1420300880000, "$lt": 1420300890000 },
"siteId": "0003"
}
]
}},
// Locigically compare the conditions agaist results and add a score
{ "$project": {
"code": "$code",
"score": { "$add": [
{ "$cond": [
{ "$and":[
{ "$gt": [ "$time", 1420128000000 ] },
{ "$lt": [ "$time", 1420128030000 ] },
{ "$eq": [ "$siteId", "0000" ] }
]},
1,
0
]},
{ "$cond": [
{ "$and":[
{ "$gt": [ "$time", 1420300880000 ] },
{ "$lt": [ "$time", 1420300890000 ] },
{ "$eq": [ "$siteId", "0003" ] }
]},
1,
0
]}
]}
}},
// Now Group the results by "code"
{ "$group": {
"_id": "$code",
"score": { "$sum": "$score" }
}},
// Now filter to keep only results with score 2
{ "$match": { "score": 2 } }
])
所以分解一下,看看它是如何工作的。
首先,您需要使用 $match
进行查询,以获取符合 "intersection" 条件的 "all" 的所有可能文档。考虑到匹配的文档必须满足任一集合,这就是 $or
表达式在这里允许的。您需要所有这些才能在这里计算出 "intersection"。
聚合框架的第二个$project
pipeline stage a boolean test of your conditions is performed with each set. Notice the usage of $and
here as well as other boolean operators与查询使用形式略有不同。
在聚合框架形式中(在使用普通查询运算符的 $match
之外),这些运算符采用参数数组,通常表示用于比较的 "two" 值,而不是分配给的操作字段名的"right"。
由于这些条件是合乎逻辑的或 "boolean" 我们希望 return 结果为 "numeric" 而不是 true/false
值。这就是 $cond
在这里所做的。因此,如果检查的文档的条件为真,则发出 1
的分数,否则当为假时,它为 0
。
最后,在这个 $project
表达式中,您的两个条件都用 $add
包裹起来,形成 "score" 结果。因此,如果 none 的条件(在 $match 之后不可能)不为真,则分数将为 0,如果 "one" 为真则为 1,或者 "both" 为真则为 2。
请注意,对于单个文档,此处要求的特定条件永远不会超过 1
,因为没有文档可以具有重叠范围或存在的 "two" "siteId" 值这里。
现在重要的部分是 $group
by the "code" value and $sum
分值,得到总分 "code"。
这使管道的最后 $match
过滤阶段只保留那些 "score" 值等于您要求的条件数的文档。在这种情况下 2
.
然而,这里有一个失败之处在于,如果在任一条件的匹配中有多个 "code" 的值(确实如此),那么这里的 "score" 将是不正确的。
所以在介绍了在聚合中使用逻辑运算符的原则之后,您可以通过本质上 "tagging" 每个结果逻辑上 "set" 它适用于哪个条件来修复该故障。那么你基本上可以考虑在这种情况下"both"集中出现了哪些"code":
db.collection.aggregate([
{ "$match": {
"$or": [
{
"time": { "$gt": 1420128000000, "$lt": 1420128030000 },
"siteId": "0000"
},
{
"time": { "$gt": 1420300880000, "$lt": 1420300890000 },
"siteId": "0003"
}
]
}},
// If it's the first logical condition it's "A" otherwise it can
// only be the other, therefore "B". Extend for more sets as needed.
{ "$group": {
"_id": {
"code": "$code",
"type": { "$cond": [
{ "$and":[
{ "$gt": [ "$time", 1420128000000 ] },
{ "$lt": [ "$time", 1420128030000 ] },
{ "$eq": [ "$siteId", "0000" ] }
]},
"A",
"B"
]}
}
}},
// Simply add up the results for each "type"
{ "$group": {
"_id": "$_id.code",
"score": { "$sum": 1 }
}}
// Now filter to keep only results with score 2
{ "$match": { "score": 2 } }
])
如果这是您第一次使用聚合框架,可能有点难以理解。请花时间查看此处链接定义的运算符,并查看一般情况下的 Aggregation Pipeline Operators。
除了简单的数据选择,这是您在使用 MongoDB 时应该最常接触到的工具,因此您最好了解所有可能的操作。