Mongoose 在聚合中使用子查询对数据求和

Adi*_*rta 1 mongoose mongodb node.js mongodb-query aggregation-framework

我是猫鼬的新手。我有这样的模型。

var ForumSchema = new mongoose.Schema({
User_id             : {type : Schema.Types.ObjectId, ref : 'User'},
Title               : {type : String},
Content             : {type : String},
Tags                : [{type : String}],
isPublic            : {type : Boolean, default : false},
Vote                : [{
    User_id         : {type : Schema.Types.ObjectId, ref : 'User'},
    Kind            : {type : Boolean}
}],
Rate                : [{
    User_id         : {type : Schema.Types.ObjectId, ref : 'User'},
    Count           : {type : Number}
}],
Comment             : [{
    User_id         : {type : Schema.Types.ObjectId, ref : 'User'},
    Content         : String,
    Created_at      : {type : Date, required : true, default : Date.now}
}],
Created_at          : {type : Date, required : true, default : Date.now},
Updated_at          : {type : Date}
Run Code Online (Sandbox Code Playgroud)

});

我想获得Vote该值总和为真的论坛数据。像这个json。

{
[
    _id         : <Some object id>,
    User_id     : <Some object id from User Model>,
    Title       : <Title>,
    Content     : <Content>,
    Tags        : [Some array],
    isPublic    : true,
    UpVote      : 23,
    ....
    ....
    ....
]
Run Code Online (Sandbox Code Playgroud)

}

在 mysql 中,我可以通过使用子查询来做到这一点。我怎样才能在猫鼬中做到这一点?

chr*_*dam 5

With MongoDB server 3.4 and above, you can run an aggregate pipeline that uses the $addFields operator which has a $filter on the Vote array to filter those element that have a Kind property value matching true and when you get the filtered array, use it as an input expression for the $size operator which then calculates the count of the items in the filtered array.

Consider the following operation to get the desired result:

Forum.aggregate([
    {
        "$addFields": {
            "UpVote": {
                "$size": {
                    "$filter": {
                        "input": "$Vote",
                        "as": "el",
                        "cond": "$$el.Kind"
                    }
                }
            }
        }
    }
]).exec((err, results) => {
    if (err) throw err;
    console.log(results);
})
Run Code Online (Sandbox Code Playgroud)

Explanations

In the above, the inner expression

{
    "$filter": {
        "input": "$Vote",
        "as": "el",
        "cond": "$$el.Kind"
    }
}
Run Code Online (Sandbox Code Playgroud)

selects a subset of the array to return based on the specified condition. As a result, it returns an array with only those elements that match the condition.

The input property refers to an expression that resolves to an array. In the above, the input is the Votes array.

The other field as represents the variable name for the element in the input array. The as expression accesses each element in the input array by this variable.

The cond field holds an expression that determines whether to include the element in the resulting array. The expression accesses the element by the variable name specified in as.

So in the above if the element in the array being evaluated has the Kind subproperty equal to true, denoted by the expression "$$el.Kind", then the condition is matched and the element is included in the subset to be returned.

As a simple example, take for instance this high-level expression:

{
    "$filter": {
        "input": [
            { "Kind": true, "User_id": "58afed97bc343887a9ac9206" },
            { "Kind": false, "User_id": "58ad50a429b2961777f91c97" },
            { "Kind": true, "User_id": "58b3f0f598501abacd8ff391" }
        ],
        "as": "el",
        "cond": {
            "$eq": ["$$el.Kind", true]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

returns the array

[
    { "Kind": true, "User_id": "58afed97bc343887a9ac9206" },
    { "Kind": true, "User_id": "58b3f0f598501abacd8ff391" }
]
Run Code Online (Sandbox Code Playgroud)

The conditional part

"cond": {
    "$eq": ["$$el.Kind", true]
}
Run Code Online (Sandbox Code Playgroud)

can be simplified to just

"cond": "$$el.Kind"
Run Code Online (Sandbox Code Playgroud)

as "$$el.Kind" expression already evaluates to a boolean.

The $size operator trivially calculates the number of elements in an array, thus the expression, for example

{
    "$size":    {
        "$filter": {
            "input": [
                { "Kind": true, "User_id": "58afed97bc343887a9ac9206" },
                { "Kind": false, "User_id": "58ad50a429b2961777f91c97" },
                { "Kind": true, "User_id": "58b3f0f598501abacd8ff391" }
            ],
            "as": "el",
            "cond": {
                "$eq": ["$$el.Kind", true]
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

is expressed as

{
    "$size": [
        { "Kind": true, "User_id": "58afed97bc343887a9ac9206" },
        { "Kind": true, "User_id": "58b3f0f598501abacd8ff391" }
    ]
}
Run Code Online (Sandbox Code Playgroud)

and return a result with 2 as count.

For the contrary DownVote count, the same logic applies:

Forum.aggregate([
    {
        "$addFields": {
            "UpVote": {
                "$size": {
                    "$filter": {
                        "input": "$Vote",
                        "as": "el",
                        "cond": "$$el.Kind"
                    }
                }
            },
            "DownVote": {
                "$size": {
                    "$filter": {
                        "input": "$Vote",
                        "as": "el",
                        "cond": { "$not": ["$$el.Kind"] }
                    }
                }
            }
        }
    }
]).exec((err, results) => {
    if (err) throw err;
    console.log(results);
})
Run Code Online (Sandbox Code Playgroud)

For the earlier MongoDB version 3.2, you will need to project each and every other element in the document:

Forum.aggregate([
    {
        "$project": {
            "User_id"     : 1,
            "Title"       : 1,
            "Content"     : 1,
            "Tags"        : 1,
            "isPublic"    : 1,
            "UpVote"      : {
                "$size": {
                    "$filter": {
                        "input": "$Vote",
                        "as": "el",
                        "cond": "$$el.Kind"
                    }
                }
            },
            ....
            ....
            ....
        }
    }
]).exec((err, results) => {
    if (err) throw err;
    console.log(results);
})
Run Code Online (Sandbox Code Playgroud)

对于不支持$filter运算符的版本,请使用$setDifference运算符代替:

Forum.aggregate([
    {
        "$project": {
            "User_id"     : 1,
            "Title"       : 1,
            "Content"     : 1,
            "Tags"        : 1,
            "isPublic"    : 1,
            "UpVote"      : {
                "$size": {
                    "$setDifference": [
                        { "$map": {
                            "input": "$Vote",
                            "as": "el",
                            "in": { "$cond": ["$$el.Kind", "$$el", false] }               
                        }},
                        [false]
                    ]
                }
            },
            ....
            ....
            ....
        }
    }
]).exec((err, results) => {
    if (err) throw err;
    console.log(results);
})
Run Code Online (Sandbox Code Playgroud)