Mongo Aggregate根据是否在另一个表中找到记录而排除一个表中的记录

时间:2017-06-02 23:34:58

标签: mongodb aggregation-framework

公寓表

{
    "_id": "AAA",
    "name": "Apartment AAA"
},
{
    "_id": "BBB",
    "name": "Apartment BBB"
},
{
    "_id": "CCC",
    "name": "Apartment CCC"
},
{
    "_id": "DDD",
    "name": "Apartment DDD"
},
{
    "_id": "EEE",
    "name": "Apartment EEE"
}

预订表

{
    "_id": 1,
    "apartmentID": "AAA",
    "checkin": 1490000000,
    "checkout": 1499000000
}
{
    "_id": 2,
    "apartmentID": "BBB",
    "checkin": 1500000000,
    "checkout": 1590000000
}
{
    "_id": 3,
    "apartmentID": "CCC",
    "checkin": 1490000000,
    "checkout": 1499000000
}
{
    "_id": 4,
    "apartmentID": "DDD",
    "checkin": 1500000000,
    "checkout": 1590000000
}

我需要找到所有“name”的“apartment”,其中“1510000000”与“1520000000”之间没有预订

在这种情况下,结果应为:

{
    "name": "Apartment AAA"
},
{
    "name": "Apartment CCC"
},
{
    "name": "Apartment EEE"
}

请注意,EEE公寓没有任何预订。

通常我会将所有属性放入一个数组中,然后将所有预订放入一个数组中,然后在javascript中运行一些循环来查找可用的公寓。

问题:有没有办法在一个mongodb聚合管道中使用这个并且只返回可用公寓的“名称”?

我的旧查询:

db.apartment.aggregate([
{
    $match: {}
},
{ 
    $project: { 
        "name": 1
    }
}


db.booking.aggregate([
{
    $match: {
        checkin: {$lte: 1520000000},
        checkout: {$gte: 1510000000}
    }
},
{ 
    $project: { 
        "apartmentID": 1
    }
}

2 个答案:

答案 0 :(得分:1)

从版本3.2开始,您可以使用$lookup运算符来外连接两个集合:

db.apartment.aggregate([
{
    $lookup: {
        from: "booking",
        localField: "_id",
        foreignField: "apartmentID",
        as: "booking"
    }
},
{
    $unwind: { path: "$booking", preserveNullAndEmptyArrays: true }
},
{
    $match: {
        $or: [
          { "booking": {$exists: false }},
          { "booking.checkin": {$gte: 1520000000} },
          { "booking.checkout": {$lte: 1510000000} }
        ]
    }
},
{
    $group: { _id: "$name" }
},
{
    $project: { _id: 0, name: "$_id" }
}
])

输出:

{
    "name" : "Apartment AAA"
},
{
    "name" : "Apartment CCC"
},
{
    "name" : "Apartment EEE"
}

详细说明:

第一阶段创建公寓的外部连接及其预订。此阶段产生的结果如下:

{
    "_id": "AAA",
    "name": "Apartment AAA",
    "booking": [ 
        {
          "_id": 1,
          "apartmentID": "AAA",
          "checkin": 1490000000,
          "checkout": 1499000000
        },
        {
          "_id": 5,
          "apartmentID": "AAA",
          "checkin": 1500000000,
          "checkout": 1590000000
        },
    ]
}
...
{
    "_id": "EEE",
    "name": "Apartment EEE"
}

请注意,如果公寓AAA有几个预订,所有预订文件将被添加到booking公寓。另请注意,没有预订的公寓将不会有booking阵列。接下来,我们展开联合预订数组以摆脱阵列并通过单次预订(如果有)生成扁平的公寓对象:

{
    "_id": "AAA",
    "name": "Apartment AAA",
    "booking": {
         "_id": 1,
         "apartmentID": "AAA",
         "checkin": 1490000000,
         "checkout": 1499000000
     }
},
{
    "_id": "AAA",
    "name": "Apartment AAA",
    "booking": {
         "_id": 5,
         "apartmentID": "AAA",
         "checkin": 1500000000,
         "checkout": 1590000000
     }
}
...
{
    "_id": "EEE",
    "name": "Apartment EEE"
}

接下来,我们按公寓名称对过滤结果进行过滤和分组(因为一个公寓可以有几个不属于给定范围的预订)。最后一个阶段 - 投射。

答案 1 :(得分:1)

您可以使用$not + $elemMatch查找是否有任何公寓预订被占用。

$elemMatch查找是否有符合查询条件的占用公寓,然后$not返回公寓文件时没有占用预订。

这样的东西
   db.apartment.aggregate({
    $lookup: {
        from: "booking",
        localField: "_id",
        foreignField: "apartmentID",
        as: "bookings"
    }
   }, {
    $match: {
        "bookings": {
            $not: {
                $elemMatch: {
                    checkin: {
                        $lte: 1520000000
                    },
                    checkout: {
                        $gte: 1510000000
                    }
                }
            }
        }
    }
   }, {
    $project: {
        _id: 0,
        name: 1
    }
   })