简体   繁体   中英

MongoDB/Mongoose aggregation or $or not working as expected?

Have some checkbox buttons in html that I would like to filter the results via the API on.click, over AJAX. There are 3 kinds of results: upVoted, noVote, downVoted. Subject to which of the checkboxes are checked, a combination of upVoted, noVote, and downVoted docs would appear to the user.

The ajax calls the URL of the API, and the data reaches the database call correctly after running through the following loop in the node.js API, which loops through the array of the strings ["upVoted", "noVote", "downVoted"] (or any combination thereof) and assigns the boolean value of true to the respective variable:

var upVoted = false;
var noVote = false;
var downVoted = false;
storyFiltering.forEach(storyFiltering);
function storyFiltering(element, index, array) {
    if (element == 'upVoted') {upVoted = true}
    else if (element == 'noVote') {noVote = true}
    else if (element == 'downVoted') {downVoted = true}
}
console.log('upVoted: ' + upVoted + ', noVote: ' + noVote + ', downVoted: ' + downVoted);

These variables then match the Booleans of the voting in each of the documents.

Here's the database structure (with voting only one Boolean is ever true at once, the rest are false):

to: ["user1"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 4

to: ["user2"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 2

to: ["user1", "user2"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 1

to: ["user1", "user2"],
voting: {
  upVoted: false,
  noVote: true,
  downVoted: false
},
rank: 5

to: ["user1"],
voting: {
  upVoted: false,
  noVote: false,
  downVoted: true
},
rank: 3

The result of the find (or aggregate) would be a callback of data filtered to match the toggled voting booleans..and then sorting them by descending 'rank'.

Toggling for all of the upVoted and noVote documents for user1 would return:

to: ["user1", "user2"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 1

to: ["user1"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 4

to: ["user1", "user2"],
voting: {
  upVoted: false,
  noVote: true,
  downVoted: false
},
rank: 5

..whereas toggling for only the upVote documents for user1 would return:

to: ["user1", "user2"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 1

to: ["user1"],
voting: {
  upVoted: true,
  noVote: false,
  downVoted: false
},
rank: 4

..and in the same pattern, toggling for all of the upVoted + noVote + downVoted documents for User 1 would return all of the documents that include User 1, sorted by rank (1, 3, 4, 5)..while, constrastly, toggling for all of the downVoted documents for User 2 would return zero documents.

Here is your basic problem with your approach as I see it. What you are trying to do is have interaction with a user interface that "selects" certain items that you want to appear in your query results. That is all fine, but clearly you seem to be looking for "one query to rule them all" and this is where you come unstuck.

Taking your first example, your query for all "upvote" and "novote" documents comes out like this. Also stating away from aggregate right now, but the same applies as a query to a $match pipeline:

db.collection.find({
    "to": { "$in": ["user1"] },
    "$or": [
        { "voting.upVoted": true },
        { "voting.noVote": true }
    ]
})

That returns your expected results as the $or considers that "either" of those conditions could evaluate to true in order to satisfy the match. As explained before, all queries in MongoDB are implicitly an "and" query, so there is no need for the additional operator here.

In your next example, you really only need one of your criteria to be true, so we could remove the $or but I'll leave it here for the explanation later.

db.collection.find({
    "to": { "$in": ["user1"] },
    "$or": [
        { "voting.upVoted": true }
    ]
})

Again that matches your expected result as this returns both where the "user1" is present "and" that the "upVoted" value is true .

But now onto your interface interaction and how to handle that. Let us say that you have input coming from your interface that looks something like this:

{
   "to": "user1",
   "voting": {
      "upVoted": true,
      "downVoted": false,
      "noVote": true
   }
}

That confirms your basic selections in your user interface that were made for the query. In order to "process" this into your desired query then consider the following JavaScript code, to translate into whatever language you want to implement in. I'm just going to assume that structure as shown to be in a variable known as request :

var query = {
    "to": { "$in": [] },
    "$or": []
};

for ( k in request ) {
    if ( k == "to" )
        query.to["$in"].push( request.to );

    if ( k == "voting" ) {
        for ( v in request[k] ) {
            if ( request[k][v] ) {      // if that value is `true`
                var obj = {};
                obj[ k + "." + v] = true;
                query["$or"].push( obj );
            }
        }
    }
}

Running the code over that data the resulting query object now looks like:

{
    "to" : {
            "$in" : [
                    "user1"
            ]
    },
    "$or" : [
            {
                    "voting.upVoted" : true
            },
            {
                    "voting.noVote" : true
            }
    ]
}

Which is the same as the query we issued first, and changing your selections in request to look the way it would in the second example:

{
   "to": "user1",
   "voting": {
      "upVoted": true,
      "downVoted": false,
      "noVote": false
   }
}

The the resulting query object is:

{
    "to" : {
            "$in" : [
                    "user1"
            ]
    },
    "$or" : [
            {
                    "voting.upVoted" : true
            }
    ]
}

All that is left to do is issue the query which uses the object as it's argument:

db.collection.find( query )

So that is how you interact with data from your interface. You build dynamically, rather than try to throw all of the values you got in the response as parameters in the query.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM