在单个查询中匹配来自两个查询的键

时间:2015-06-28 01:40:05

标签: mongodb mongodb-query aggregation-framework

我在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}

&#34;计数&#34;意思是代码&#34; AQ0ZSQ&#34;出现在两个条件下

我能想到的唯一解决方案是使用两个查询。例如,使用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的限制。

那么在一个查询中可以这样做吗?或者我应该更改文档结构?

1 个答案:

答案 0 :(得分:1)

您在这里要求的是要求使用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进行查询,以获取“所有”“交叉”条件的所有可能文档。这就是$or表达式允许在此考虑匹配的文档必须满足任一集合。你需要他们所有人在这里找出“十字路口”。

在第二个$project管道阶段,对每个集合执行条件的布尔测试。请注意,此处$and的使用以及聚合框架的其他boolean operators与查询使用表单的使用略有不同。

在聚合框架形式(使用普通查询运算符的$match之外)中,这些运算符采用一组参数,通常表示“两个”值进行比较,而不是分配给“右”的操作字段名称。

由于这些条件是逻辑或“布尔”,我们希望将结果返回为“数字”而不是true/false值。这就是$cond在这里做的事情。因此,如果检查的文档的条件为真,则会发出1的分数,否则为0

最后,在此$project表达式中,您的所有条件都包含$add以形成“得分”结果。因此,如果没有任何条件(在$ match之后不可能)不为真,则分数将为0,如果“one”为真,则为1,或者“both”为真,则为2。

请注意,此处要求的具体条件永远不会超过单个文档的1,因为此处不存在重叠范围或“两个”“siteId”值。

现在,重要的部分是$group的“代码”值和$sum得分值,以获得每个“代码”的总数。

这使得管道的最后$match过滤阶段只保留那些文件的“得分”值等于您要求的条件数。在这种情况下2

但是有一个失败的原因是,在任何条件的匹配中存在多个“代码”值(因为有),那么这里的“得分”将是不正确的。

因此,在介绍了在聚合中使用逻辑运算符的原则之后,您可以通过在逻辑上“标记”每个结果来确定它应用于哪个“set”条件来修复该错误。那么你基本上可以考虑在这种情况下“两个”集中出现的“代码”:

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时应该最常接触的工具,因此您最好能够理解所有可能的操作。