Querying against nested arrays - mongodb

I have a users collection whose schema is as follow :
var userSchema = new mongoose.Schema({
id : Number,
tags :
[
{
tag_name : String,
tag_url : String,
posts :
[
{
post_id: String
}
]
}
]
});
What I would like to do is retrieving only tag_name whose post_id value is in the posts array.
So, I tried query as follow
db.users.find({'tags.posts.post_id':'000000'}, {'tags.tag_name':1})
Unfortunatelly, I got all tag_name although post_id is not in posts array.
Can you help me to write query for this?
Edit =================================================================
Let's say I have data as follow :
tags
[
{
tag_name: 'java',
posts : [{post_id:'000000'}, {post_id:'111111'}
},
{
tag_name: 'ruby',
posts : [{post_id:'000000'}, {post_id:'111111'}
},
{
tag_name: 'php',
posts : [{post_id:'111111'}
},
]
I want to have tag element by post_id, if I search by post_id is '000000' I want to get only tag elements whose tag_name is 'java' and 'ruby' not the last tag element, is it possible?

$ in tags.$.tag_name should help:
db.users.find({'tags.posts.post_id':'000000'}, {'tags.$.tag_name':1})
EDIT:
ok, I read your update. In this case I see a solution in aggregation framework. We can try build pipeline like this:
db.col.aggregate(
{$match: {'tags.posts.post_id':'000000'}},
{$unwind: '$tags'},
{$match: {'tags.posts.post_id':'000000'}},
{$group: {_id:'$_id', tags: {$push: '$tags'}}}
)
result will be:
{
"_id" : ObjectId("5209f5e4ef21a864f6f6ed54"),
"tags" : [
{
"tag_name" : "java",
"posts" : [
{ "post_id" : "000000" },
{ "post_id" : "111111" }
]
},
{
"tag_name" : "ruby",
"posts" : [
{ "post_id" : "000000" },
{ "post_id" : "111111" }
]
}
]
}
as you might see I did $match twice. It was for performance purposes. By first matching we reduce set of documents that contain post_id:000000 from a collection. Second match filters tags

Related

What's the most economical alternative to multiple positional identifiers in MongoDB?

I have a collection named authors with the following schema:
authors: {
_id,
firstName,
lastName,
posts: [
post 1: {...},
post 2: {...},
post 3: {
_id,
title,
content,
tags: [
tag 1: {...},
tag 2: {...},
tag 3: {
_id,
name,
description,
},
],
},
],
}
As can be seen, posts is an array of objects inside the authors collection. Each object inside this array, in turn, has tags, another array of objects. And each of these tags objects has three fields: _id, name, and description.
I'm trying to write a GraphQL mutation to update this name field on matching documents in the collection.
const updatedTagInAuthor = await Author
.updateMany({ 'posts.tags._id': args.newTagInfo._id },
{
$set: {
'posts.$.tags.$.name': args.newTagInfo.name,
'posts.$.tags.$.description': args.newTagInfo.description,
},
}, opts);
The above snippet obviously fails since MongoDB doesn't allow multiple positional elements ($) in a query. So is there any economical alternative to accomplish what I'm trying to do?
I tried the ArrayFilter method as MongoDB suggests:
const updatedTagInAuthor = await Author.update(
{},
{ $set: { 'posts.$[].tags.$[tag].name': args.newTagInfo.name } },
{ arrayFilters: [{ 'tag._id': args.newTagInfo._id }], multi: true }
);
But this throws the following error:
Cannot read property 'castForQuery' of undefined
Still confused!
These are the documents I'm updating with the kind of query I have given,
{"name" : "Steve","details" : [ {
"work" : [ {
"Company" : "Byjus",
"id" : 1,
"country" : "India"
},
{
"Company" : "Vodafone",
"id" : 2,
"country" : "UK"
}]
}]},{"name" : "Virat","details" : [ {
"work" : [ {
"Company" : "Byjus",
"id" : 1,
"country" : "India"
},
{
"Company" : "Verizon",
"id" : 3,
"country" : "US"
}]
}]}
QUERY:
db.getCollection('Users').update({"details": {"$elemMatch": {"work.id": 1}}}, {'$set': {'details.$[].work.$.Company': 'Boeing', 'details.$[].work.$.country': 'US'} }, {multi: true});
It's similar to what you asked right?
Try inserting those two Documents in a collection called User and try the above query in Mongo CONSOLE directly, not in GUI. Use the Query completely not just the $set method.
Try this,
Author.update({"posts": { "$elemMatch": { "tags.id": args.newTagInfo._id } }},
{'$set': {'posts.$[].tags.$.name': args.newTagInfo.name, 'posts.$[].tags.$.description': args.newTagInfo.description} },
{multi: true});

mongoDB: Querying for documents that may have some specifics options

I'm quite new to mongodb and there is one thing I can't solve right now:
Let's pretend, you have the following document structure:
{
"_id": ObjectId("some object id"),
name: "valueName",
options: [
{idOption: "optionId", name: "optionName"},
{idOption: "optionId", name: "optionName"}
]
}
And each document can have multiples options that are already classified.
I'm trying to get all the documents in the collection that have, at least one, of the multiples options that I pass for the query.
I was trying with the operator $elemMatch something like this:
db.collectioName.find({"options.name": { $elemMatch: {"optName1","optName2"}}})
but it never show me the matches documents.
Can someone help and show me, what I'm doing wrong?
Thanks!
Given a collection which contains the following documents:
{
"_id" : ObjectId("5a023b8d027b5bd06add627a"),
"name" : "valueName",
"options" : [
{
"idOption" : "optionId",
"name" : "optName1"
},
{
"idOption" : "optionId",
"name" : "optName2"
}
]
}
{
"_id" : ObjectId("5a023b9e027b5bd06add627d"),
"name" : "valueName",
"options" : [
{
"idOption" : "optionId",
"name" : "optName3"
},
{
"idOption" : "optionId",
"name" : "optName4"
}
]
}
This query ...
db.collection.find({"options": { $elemMatch: {"name": {"$in": ["optName1"]}}}})
.. will return the first document only.
While, this query ...
db.collection.find({"options": { $elemMatch: {"name": {"$in": ["optName1", "optName3"]}}}})
...will return both documents.
The second example (I think) meeets this requirement:
I'm trying to get all the documents in the collection that have, at least one, of the multiples options that I pass for the query.

In a Mongo collection, how do you query for a specific object in an array?

I'm trying to retrieve an object from an array in mongodb. Below is my document:
{
"_id" : ObjectId("53e9b43968425b29ecc87ffd"),
"firstname" : "john",
"lastname" : "smith",
"trips" : [
{
"submitted" : 1407824585356,
"tripCategory" : "staff",
"tripID" : "1"
},
{
"tripID" : "2",
"tripCategory" : "volunteer"
},
{
"tripID" : "3",
"tripCategory" : "individual"
}
]
}
My ultimate goal is to update only when trips.submitted is absent so I thought I could query and determine what the mongo find behavior would look like
if I used the $and query operator. So I try this:
db.users.find({
$and: [
{ "trips.tripID": "1" },
{ "trips": { $elemMatch: { submitted: { $exists: true } } } }
]
},
{ "trips.$" : 1 } //projection limits to the FIRST matching element
)
and I get this back:
{
"_id" : ObjectId("53e9b43968425b29ecc87ffd"),
"trips" : [
{
"submitted" : 1407824585356,
"tripCategory" : "staff",
"tripID" : "1"
}
]
}
Great. This is what I want. However, when I run this query:
db.users.find({
$and: [
{ "trips.tripID": "2" },
{ "trips": { $elemMatch: { submitted: { $exists: true } } } }
]
},
{ "trips.$" : 1 } //projection limits to the FIRST matching element
)
I get the same result as the first! So I know there's something odd about my query that isn't correct. But I dont know what. The only thing I've changed between the queries is "trips.tripID" : "2", which in my head, should have prompted mongo to return no results. What is wrong with my query?
If you know the array is in a specific order you can refer to a specific index in the array like this:-
db.trips.find({"trips.0.submitted" : {$exists:true}})
Or you could simply element match on both values:
db.trips.find({"trips" : {$elemMatch : {"tripID" : "1",
"submitted" : {$exists:true}
}}})
Your query, by contrast, is looking for a document where both are true, not an element within the trips field that holds for both.
The output for your query is correct. Your query asks mongo to return a document which has the given tripId and the field submitted within its trips array. The document you have provided in your question satisfies both conditions for both tripIds. You are getting the first element in the array trips because of your projection.
I have assumed you will be filtering records by the person's name and then retrieving the elements inside trips based on the field-exists criteria. The output you are expecting can be obtained using the following:
db.users.aggregate(
[
{$match:
{
"firstname" : "john",
"lastname" : "smith"
}
},
{$unwind: "$trips"},
{$match:
{
"trips.tripID": "1" ,
"trips.submitted": { $exists: true }
}
}
]
)
The aggregation pipeline works as follows. The first $match operator filters one document (in this case the document for john smith) The $unwind operator in mongodb aggregation unwinds the specified array (trips in this case), in effect denormalizing the sub-records associated with the parent records. The second $match operator filters the denormalized/unwound documents further to obtain the one required as per your query.

$project new boolean field based on element exists in an array of a subdocument

I've got a collection of blogs where the posts are embedded into:
db.blogs.insert({
name: 'Smashing Magazine',
url: 'http://www.smashingmagazine.com/',
posts: [{
date: new Date('2013-05-10'),
title: 'How To Avoid Duplicate Downloads In Responsive Images',
url: 'http://mobile.smashingmagazine.com/2013/05/10/how-to-avoid-duplicate-downloads-in-responsive-images/',
tags: ['Responsive Design', 'Techniques']}]
});
I'd like to run a query that returns all the posts with an additional boolean field that represents if a specific tag exists or not for each post. This is what I've tried and failed:
db.blogs.aggregate(
{$unwind: "$posts"},
{$project: {
name: 1,
date: "$posts.date",
title: "$posts.title",
// isResponsiveDesign should be true or false based on if the post is tagged as "Responsive Design" or not
isResponsiveDesign: {$and: [{"$posts.tags": 'Responsive Design'}]}
}}
);
What is the right way of writing this query?
The only way that I could figure this out as an aggregation turned out to be quite long-winded. Modeling the query as a map-reduce operation might be easier / simpler. Anyway, here we go:
db.blogs.aggregate([
{$unwind: '$posts'},
// Grab the only fields we'll need
{$project: {
name: 1,
posts: 1
}},
// Generate a document for each tag within each post
{$unwind: '$posts.tags'},
// Add an attribute to post tags which are equivalent to our search
// term
{$project: {
name: 1,
posts: 1,
isResponsiveDesign: {
$cond: [{$eq: ['$posts.tags', 'Responsive Design']}, 1, 0]
}
}},
// Recombine so that we have one document per post. Use '$max' to
// simulate a boolean OR between two documents' binary
// 'isResponsiveDesign' fields
{$group: {
_id: '$_id',
posts: {$push: '$posts'},
isResponsiveDesign: {$max: '$isResponsiveDesign'}
}}
]);
This is the output of the aggregation, using the example data you provided. I added a dumb copy of the document you gave with the "Responsive Design" tag removed, just to test.
{
"result" : [
{
"_id" : ObjectId("51901e3a8fa65c820b9aae85"),
"posts" : [
{
"date" : ISODate("2013-05-10T00:00:00Z"),
"title" : "How To Avoid Duplicate Downloads In Responsive Images",
"url" : "http://mobile.smashingmagazine.com/2013/05/10/how-to-avoid-duplicate-downloads-in-responsive-images/",
"tags" : null
},
{
"date" : ISODate("2013-05-10T00:00:00Z"),
"title" : "How To Avoid Duplicate Downloads In Responsive Images",
"url" : "http://mobile.smashingmagazine.com/2013/05/10/how-to-avoid-duplicate-downloads-in-responsive-images/",
"tags" : "Techniques"
}
],
"isResponsiveDesign" : 0
},
{
"_id" : ObjectId("5190190f6ab03ad381d1cb0f"),
"posts" : [
{
"date" : ISODate("2013-05-10T00:00:00Z"),
"title" : "How To Avoid Duplicate Downloads In Responsive Images",
"url" : "http://mobile.smashingmagazine.com/2013/05/10/how-to-avoid-duplicate-downloads-in-responsive-images/",
"tags" : "Responsive Design"
},
{
"date" : ISODate("2013-05-10T00:00:00Z"),
"title" : "How To Avoid Duplicate Downloads In Responsive Images",
"url" : "http://mobile.smashingmagazine.com/2013/05/10/how-to-avoid-duplicate-downloads-in-responsive-images/",
"tags" : "Techniques"
}
],
"isResponsiveDesign" : 1
}
],
"ok" : 1
}

MongoDB: get documents by tags

I've got documents containing tags array. I want to provide tags based recommendations on site, so I need to get documents containing same tags + documents that don't match 1 tag + documents that don't match 2 tags and etc...
How do I do that?
example collection:
db.tags.insert({"tags":["red", "tall", "cheap"]});
db.tags.insert({"tags":["blue", "tall", "expensive"]});
db.tags.insert({"tags":["blue", "little", "cheap"]});
find all that include the tag "blue"
db.tags.find({tags: { $elemMatch: { $eq: "blue" } }})
find all tagged "blue" and only blue
db.tags.find({tags: "blue"})
find all tagged "blue" and "cheap"
db.tags.find({ tags: { $all: ["cheap", "blue"] } } )
find all not "blue"
db.tags.find({tags: { $ne: "blue" } })
find all "blue" and "cheap" but not "red" and not "tall"
not possible in my mongo db. From mongodb 1.9.1 on something like this should work, though (not tested):
db.tags.find({ $and: [ {tags: { $all: ["blue", "cheap"] } }, { tags: { $nin: ["red", "tall"] } } ] })
The rephrased question is:
Suppose if job postings have search tags attached like
Job Postings
[{_id : ObjectId(1249999493),tags : ['Location1', 'SkillSet1', 'SkillSet2', 'Someother1', 'Someother2']},
{_id : ObjectId(1249999494),tags : ['Location3', 'SkillSet1', 'SkillSet0', 'Someother4', 'Someother3']}]
Now, he wants the records having tags ['Location1','SkillSet1', 'SkillSet0']
And the selected docs having more keywords from the query should come first. Less keywords matching should come last. So, that one can get more suitable job posting for the search query.
Am I sensible or do I need to re-phrase ?
Steps:
Find matching products which contains any of the specified keys.
Unfold on keys
Do find again to filter unwanted after unfolding
Group them by adding key occurrence
Sort desc to get most relevant first
[{ "$match" : { "keys" : { "$in" : [ { "$regex" : "text" , "$options" : "i"}]}}}, { "$unwind" : "$keys"}, { "$match" : { "keys" : { "$in" : [ { "$regex" : "text" , "$options" : "i"}]}}}, { "$group" : { "_id" : { "productId" : "$productId"} , "relatedTags" : { "$sum" : 1} }}, { "$sort" : { "relatedTags" : -1}},{ "$limit" : 10}]