MongoDB - Lookup with pipeline get error: $nin needs an array - mongodb

Hello everyone I am having some trouble basically I am getting
$nin needs an array
even after passing an array but using a variable when attempting to run the following query.
I want to get documents from a collection numberData that contains a property value which is not assigned to an array in another collection named assignedData.
Example of numbersData docs are given below:
/* 1 */
{
"_id" : ObjectId("61f2b5923327b3f6f4dad27e"),
"value" : 1.0
}
/* 2 */
{
"_id" : ObjectId("61f2b5923327b3f6f4dad27f"),
"value" : 2.0
}
/* 3 */
{
"_id" : ObjectId("61f2b5923327b3f6f4dad280"),
"value" : 3.0
}
/* 4 */
{
"_id" : ObjectId("61f2b5923327b3f6f4dad281"),
"value" : 4.0
}
and the contents of assignedData are given below:
/* 1 */
{
"_id" : ObjectId("61f2b5e43327b3f6f4dad282"),
"name" : "john",
"assigned" : [
1.0,
4.0
]
}
The query I used:
db.assignedData.aggregate([
{
//i only want to get single doc from assigned data
//so using this match
$match: {
_id: ObjectId("61f2b5e43327b3f6f4dad282")
},
},
{
$lookup: {
from: 'numbersData',
let: { aArr: "$assigned" },
pipeline: [
{
$match: {
//here i am using $$maArr
//which is refering to assigned array
value:
{
$nin: "$$aArr"
}
}
}
],
as: 'matchData'
}
}
])
After running this query I got the error:
$nin needs an array
I don't know why please suggest me solution if you can.

From Join Conditions and Subqueries on a Joined Collection section (refer to the table),
let
A $match stage requires the use of an $expr operator to access the variables. The $expr operator allows the use of aggregation expressions inside of the $match syntax.
Instead of using $nin operator, change as below:
{
$match: {
$expr: {
$not: {
$in: [
"$value",
"$$aArr"
]
}
}
}
}
Sample Mongo Playground

Related

How to return Mongodb Aggregate pipeline docs to ONE document?

I know this has got to be simple, but for the life of me I can't seem to generate the correct final stage in my pipeline to get this working. Here are the documents output from a stage that I have in a mongo query:
{ "_id" : ObjectId("61435ceb233ce0118c1d93ec") }
{ "_id" : ObjectId("61435cf29598d31c17f0d839") }
{ "_id" : ObjectId("611e5cf953396d78985d222f") }
{ "_id" : ObjectId("61435cf773b8b06c848af83e") }
{ "_id" : ObjectId("61435cfd7ac204efa857e7ce") }
{ "_id" : ObjectId("611e5cf953396d78985d2237") }
I would like to get these documents into ONE single document with an array as such:
{
"_id" : [
ObjectId("61435ceb233ce0118c1d93ec"),
ObjectId("61435cf29598d31c17f0d839"),
ObjectId("611e5cf953396d78985d222f"),
ObjectId("61435cf773b8b06c848af83e"),
ObjectId("61435cfd7ac204efa857e7ce"),
ObjectId("611e5cf953396d78985d2237")
]
}
My last stage in the pipeline is simply:
{
$group:{_id:"$uniqueIds"}
}
I've tried everything from $push to $mergeObjects, but no matter what I do, it keeps returning the original 6 documents in some shape or form instead of ONE document. Any advice would be greatly appreciated! Thanks in advance.
Test code here
Query
group by null, sees all collection as 1 group
db.collection.aggregate([
{
"$group": {
"_id": null,
"ids": {
"$push": "$_id"
}
}
},
{
"$unset": "_id"
}
])

How to write mongodb query to query only a certain field from an array of documents?

I've just started to learn mongodb and I'm stuck on a problem. I have the following collection schema:
{
"address":string,
"users" : [{"id":integer, "timestamp":integer}] //users is an array of documents
}
I want to write a query that will fetch me all the user IDs for a particular address given that they are within a certain time frame. I.e. conditions:
1) address : given_address
AND
2) given_timestamp <= timestamp <= given_timestamp + X
Follow up, How do I query ONLY the user IDs instead of all documents, I tried setting
$project:{address:0,users.id:1 , users.timestamp:0 }
but that threw me an error.
You should be using aggregation-pipeline for that :
db.collection.aggregate([
/** Filter docs where address: "abcd" */
{ $match: { address: "abcd" } },
/** Re-create 'users' array field with elements which match given condition,
* it will empty array if nothing matched */
{
$addFields: {
users: {
$filter: {
input: "$users",
cond: {
$and: [
{ $gte: ["$$this.timestamp", 12] },
{ $lte: ["$$this.timestamp", 1500] }
]
}
}
}
}
}
]);
Test : MongoDB-Playground
$projection is redundant.
Try this:
db.user.find({"$and" : [{"address" : "xxx"}, {"users.timestamp" : {"$gte" : 12}}, {"users.timestamp" : {"$lte" : 200}}]}, {"users.id" : 1})
Also by default, fields won't be returned, so you don't need to set {"address" : 0, "users.timestamp" : 0}

Compare Properties from Array to Single Property in Document

I have a mongoDB orders collection, the documents of which look as follows:
[{
"_id" : ObjectId("59537df80ab10c0001ba8767"),
"shipments" : {
"products" : [
{
"orderDetails" : {
"id" : ObjectId("59537df80ab10c0001ba8767")
}
},
{
"orderDetails" : {
"id" : ObjectId("59537df80ab10c0001ba8767")
}
}
]
},
}
{
"_id" : ObjectId("5953831367ae0c0001bc87e1"),
"shipments" : {
"products" : [
{
"orderDetails" : {
"id" : ObjectId("5953831367ae0c0001bc87e1")
}
}
]
},
}]
Now, from this collection, I want to filter out the elements in which, any of the values at shipments.products.orderDetails.id path is same as value at _id path.
I tried:
db.orders.aggregate([{
"$addFields": {
"same": {
"$eq": ["$shipments.products.orderDetails.id", "$_id"]
}
}
}])
to add a field same as a flag to decide whether the values are equal, but the value of same comes as false for all documents.
EDIT
What I want to do is compare the _id field the the documents with all shipments.products.orderDetails.id values in the array.
If even 1 of the shipments.products.orderDetails.ids match the value of the _id field, I want that document to be present in the final result.
PS I am using MongoDB 3.4, and have to use the aggregation pipeline.
Your current attempt fails because the notation returns an "array" in comparison with a "single value".
So instead either use $in where available, which can compare to see if one value is "in" an array:
db.orders.aggregate([
{ "$addFields": {
"same": {
"$in": [ "$_id", "$shipments.products.orderDetails.id" ]
}
}}
])
Or notate both as arrays using $setIsSubset
db.orders.aggregate([
{ "$addFields": {
"same": {
"$setIsSubset": [ "$shipments.products.orderDetails.id", ["$_id"] ]
}
}}
])
Where in that case it's doing a comparison to see if the "sets" have an "intersection" that makes _id the "subset" of the array of values.
Either case will return true when "any" of the id properties within the array entries at the specified path are a match for the _id property of the document.

Does Mongodb have something like SQL "As" feature?

For example, if I want to simplify the result set for a nested doc', get the field score.date as score_date in order to get a flat result
Yes, this is possible through the aggregation framework, in particular you would want to use the $project operator. This reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document. Using your example, suppose your collection has documents with this schema:
{
"_id" : ObjectId("557330473a79b31f0c805db3"),
"player": "A",
"score": {
"date": ISODate("2015-05-30T15:14:48.000Z"),
"value": 2
}
}
Then you would apply the $project operator in the aggregation pipeline as:
db.collection.aggregate([
{
"$project": {
"player": 1,
"score_date": "$score.date",
"score_value": "$score.value"
}
}
]);
Result:
/* 0 */
{
"result" : [
{
"_id" : ObjectId("557330473a79b31f0c805db3"),
"player" : "A",
"score_date" : ISODate("2015-05-30T15:14:48.000Z"),
"score_value" : 2
}
],
"ok" : 1
}

Complex Logic quires on simple mongo document

I am battling with forming complex logic queries on very basic data types in mongo. Essentially I can have millions of user attributes so my basic mongo document is:
{
name: "Gender"
value: "Male"
userId : "ABC123"
}
{
name: "M-Spike"
value: 0.123
userId : "ABC123"
}
What I would like to do is search for things like findAll userId where {name : "Gender, value: "Male"} AND { name : "m-spike", value : { $gt : 0.1} }
I have tried using the aggregation framework but the complexity of the queries is limited, basically I was ORing all the criteria and counting the results by sampleId (which replicated a rudimentary AND)
I can see a way to do it for N being the number of attributes you want to query about (N being 2 in your example). Try something like this:
db.collection.aggregate(
[ { $match: {$or: [
{"name":"M-Spike","value":{$gt:.1}},
{"name":"Gender","value":"Male"}
]
}
},
{ $group: { _id:"$userId",total:{$sum:1}}
},
{ $project: { _id:1,
matchedAttr : { $eq: ["$total",2] }
}
}
]
)
You will get back:
{
"result" : [
{
"_id" : "XYZ123",
"matchedAttr" : false
},
{
"_id" : "ABC123",
"matchedAttr" : true
}
],
"ok" : 1
}
Now, if you had 2 conditions you matched via "$or" then you get back true for _id that matched both of them. So for five conditions, your $match: $or array will have five conditional pairs, and the last $project transformation will be $eq: ["$total",5]
Built in to this solution is the assumption that you cannot have duplicate entries (i.e. _id cannot have "M-Spike":.5 and also "M-Spike":.2. If you can, then this won't work.