忽略MongoDB聚合中$ last的空值

时间:2016-06-18 11:21:18

标签: mongodb aggregation-framework mongodb-aggregation

'帐户'采集。某些字段可能未声明。看看这个。

{ _id: ObjectId(1), price: 5400, product: ObjectId(2), count: 1            }
{ _id: ObjectId(1),              product: ObjectId(2), count: 0, sale: 0.2 }
{ _id: ObjectId(1),              product: ObjectId(2), count: 1            }

使用聚合框架,我想要一个产品的最后价格,最后销售和计数。 目前我使用此查询

db.collection('account').aggregate([
{
    $group: {
            _id: '$product',
            count: { $sum: '$count' },
            price: { $last: '$price' }, 
            sale: { $last: '$sale' }
        }
    }
], { allowDiskUse: true }).toArray()

saleprice字段变为null。 我想,我应该在$project之前使用$group,如果当前字段为空,如何保留最后一个文档的值?

2 个答案:

答案 0 :(得分:2)

<强>解决方案
以下管道应该为您提供所需的结果

db.getCollection('account').aggregate(
[
    {
        $project: {
            _id: '$product',
            fields: [
                { name: { $literal: 'price' }, value: '$price',        count: { $literal: 0 } },
                { name: { $literal: 'sale' },  value: '$sale',         count: { $literal: 0 } },
                { name: { $literal: 'count' }, value: { $literal: 0 }, count: '$count' }
            ]
        }
    },
    {
        $unwind: {
            path: '$fields'
        }
    },
    {
        $match: {
            'fields.value': {
                $exists: true
            }
        }
    },
    {
        $group: {
            _id: {
                product: '$_id',
                field: '$fields.name'
            },
            value: {
                $last: '$fields.value'
            },
            count: {
                $sum: '$fields.count'
            }
        }
    },
    {
        $project: {
            _id: '$_id.product',
            price: {
                $cond: { if: { $eq: [ '$_id.field', 'price' ] }, then: '$value', else: null }
            },
            sale: {
                $cond: { if: { $eq: [ '$_id.field', 'sale' ] }, then: '$value', else: null }
            },
            count: {
                $cond: { if: { $eq: [ '$_id.field', 'count' ] }, then: '$count', else: 0 }
            }
        }
    },
    {
        $group: {
            _id: '$_id',
            price: {
                $max: '$price'
            },
            sale: {
                $max: '$sale'
            },
            count: {
                $sum: '$count'
            }
        }
    }
])

<强>解释
它首先创建一个包含每个字段元素的新数组,其中包含字段名称,字段值和计数值。 请注意,字段count被视为特殊字段,因为它应该被累积而不是获取它的最后一个值。 所以在第一阶段文件看起来像这样:

/* 1 */
{
    "_id" : "2",
    "fields" : [ 
        {
            "name" : "price",
            "value" : 5400,
            "count" : 0.0
        }, 
        {
            "name" : "sale",
            "count" : 0.0
        }, 
        {
            "name" : "count",
            "value" : 0.0,
            "count" : 1
        }
    ]
}

/* 2 */
{
    "_id" : "2",
    "fields" : [ 
        {
            "name" : "price",
            "count" : 0.0
        }, 
        {
            "name" : "sale",
            "value" : 0.2,
            "count" : 0.0
        }, 
        {
            "name" : "count",
            "value" : 0.0,
            "count" : 0
        }
    ]
}

/* 3 */
{
    "_id" : "2",
    "fields" : [ 
        {
            "name" : "price",
            "count" : 0.0
        }, 
        {
            "name" : "sale",
            "count" : 0.0
        }, 
        {
            "name" : "count",
            "value" : 0.0,
            "count" : 1
        }
    ]
}

然后展开数组并对其进行过滤以消除空值,因此在第2阶段&amp; 3份文件如下:

/* 1 */
{
    "_id" : "2",
    "fields" : {
        "name" : "price",
        "value" : 5400,
        "count" : 0.0
    }
}

/* 2 */
{
    "_id" : "2",
    "fields" : {
        "name" : "count",
        "value" : 0.0,
        "count" : 1
    }
}

/* 3 */
{
    "_id" : "2",
    "fields" : {
        "name" : "sale",
        "value" : 0.2,
        "count" : 0.0
    }
}

/* 4 */
{
    "_id" : "2",
    "fields" : {
        "name" : "count",
        "value" : 0.0,
        "count" : 0
    }
}

/* 5 */
{
    "_id" : "2",
    "fields" : {
        "name" : "count",
        "value" : 0.0,
        "count" : 1
    }
}

在第四阶段,领域&#39;建立了count的最后一个值和总和。结果如下所示:

/* 1 */
{
    "_id" : {
        "product" : "2",
        "field" : "sale"
    },
    "value" : 0.2,
    "count" : 0.0
}

/* 2 */
{
    "_id" : {
        "product" : "2",
        "field" : "count"
    },
    "value" : 0.0,
    "count" : 2
}

/* 3 */
{
    "_id" : {
        "product" : "2",
        "field" : "price"
    },
    "value" : 5400,
    "count" : 0.0
}

由于这些值现在位于不同形状的单独文档中,我们需要将它们重新投射到我们最终可以分组的内容中。所以在第五阶段文件之后是这样的:

/* 1 */
{
    "_id" : "2",
    "count" : 0.0,
    "price" : null,
    "sale" : 0.2
}

/* 2 */
{
    "_id" : "2",
    "count" : 2,
    "price" : null,
    "sale" : null
}

/* 3 */
{
    "_id" : "2",
    "count" : 0.0,
    "price" : 5400,
    "sale" : null
}

最后阶段就是按照产品汇总这些文件。

答案 1 :(得分:0)

我目前的解决方案在三个查询中完成。

1. - Group by `product` and sum `count` field
   - Remove any entries, where `count` is 0 and lower
   - Hold `_ids` array as `product_ids`
   - Hold result array as `firstArray`

2. - Match any entries, where 
            - `sale` is 0 and greater
            - `_id` is in `product_ids`
   - Group by `product` and get last `sale` field
   - Hold result array as `secondArray`

3. - Match any entries, where
            - `price` is 0 and greater
            - `_id` is in `product_ids`
   - Group by `product` and get last `price` field
   - Enumerate `firstArray` and add `count` field to entries
   - Enumerate `secondArray` and add `sale` field to entries

代码

let firstArray
let secondArray
let product_ids
Promise.resolve().then(function () {
    return db.collection(ACCOUNT).aggregate([
        {
            $group: {
                _id: '$product',
                count: { $sum: '$count' }
            }
        }, {
            $match: {
                count: { $gt: 0 }
            }
        }
    ], { allowDiskUse: true }).toArray()
}).then(function (array) {
    firstArray = array
    product_ids = array.map(function (item) {
        return item._id
    })
    return db.collection(ACCOUNT).aggregate([
        {
            $match: {
                product: { $in: product_ids },
                sale: { $gte: 0 }
            }
        }, {
            $group: {
                _id: '$product',
                sale: { $last: '$sale' }
            }
        }
    ], { allowDiskUse: true }).toArray()
}).then(function (array) {
    secondArray = array

    return db.collection(ACCOUNT).aggregate([
        {
            $match: {
                product: { $in: product_ids },
                price: { $gte: 0 }
            }
        }, {
            $group: {
                _id: '$product',
                price: { $last: '$price' }
            }
        }
    ], { allowDiskUse: true }).toArray()
}).then(function (array) {
    req.data = array.map(function (item) {
        let count = 0
        let sale = 0
        firstArray.some(function (_item) {
            if (item._id.toHexString() == _item._id.toHexString()) {
                count = _item.count
                return true
            }

            return false
        })
        secondArray.some(function (_item) {
            if (item._id.toHexString() == _item._id.toHexString()) {
                sale = _item.sale
                return
            }
            return false
        })

        item.count = count
        item.sale = sale
        return item
    })
    next()
})