0

“帐户”集合中有文档。某些字段可能未声明。看这个。

{ _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。我想,我应该使用$projectbefore $group,如果当前字段为空,我如何保留最后一个文档的值?

4

2 回答 2

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 阶段和第 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
    }
}

在第四阶段,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
}

最后阶段只是聚合每个产品的这些文档。

于 2016-06-18T13:18:04.303 回答
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()
})
于 2016-06-18T12:36:11.553 回答