I have a collection of content objects. Each document of this collection contains an array with tags:
{
_id: ....,
title: 'Title 1',
details: { ..., tags: ['politic', 'usa'] }
},
{
_id: ....,
title: 'Title 2',
details: { ..., tags: ['art', 'modern'] }
}
The user should be able to filter for tags. For individuals and several.
Is there any way to query for that?
Example:
User search for content with one of the following tags:
['politic', 'french'] => Title1
['music', 'autumn'] => no result
['usa', 'art'] => Title1 & Title2
['modern'] => Title2
What I tried:
const aggregate = [{ $match: { "details.tags": 'music' } }];
mongodb.collection("content").aggregate(aggregate).toArray();
This works fine for searching by one tag.
If I change 'music' to an array like ['music', 'usa'] I don't get any result.
#EDIT1
I added an Index to the collection:
db.content.createIndex( { "details.tags": 1 });
Unfortunately, the aggregation query still returns an empty result. That's why I tried also a find:
db.content.find({"details.tags": ['music', 'usa']})
But also without success.
In order to find multiple values in an array, you should use the $in-operator:
db.collection.find({
"details.tags": {
$in: [
"usa",
"art"
]
}
})
See this example on mongoplayground: https://mongoplayground.net/p/vzeHNdLhq0j
Related
What I was hoping to do was store an array of objects using RedisJSON very simply and then query that array.
I have something similar to this:
const data = [
{
_id: '63e7d1d85ad7e2f69df8ed6e',
artist: {
genre: 'rock',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6f',
artist: {
genre: 'metal',
},
},
{
_id: '63e7d1d85ad7e2f69df8ed6g',
artist: {
genre: 'rock',
},
},
]
then I can easily store and retrieve this:
await redisClient.json.set(cacheKey, '$', data)
await redisClient.json.get(cacheKey)
works great. but now I want to also query this data, I've tried creating an index as below:
await redisClient.ft.create(
`idx:gigs`,
{
'$.[0].artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS',
}
)
and when I try and search this index what I expect is it to return the 2 documents with the correct search filter, but instead it always returns the entire array:
const searchResult = await redisClient.ft.search(`idx:gigs`, '#genre:(rock)')
produces:
{
total: 1,
documents: [
{ id: 'cacheKey', value: [Array] }
]
}
I can't quite work out at which level I'm getting this wrong, but any help would be greatly appreciated.
Is it possible to store an array of objects and then search the nested objects for nested values with RedisJSON?
The Search capability in Redis stack treats each key containing a JSON document as a separate search index entry. I think what you are doing is perhaps storing your whole array of documents in a single Redis key, which means any matches will return the document at that key which contains all of your data.
I would suggest that you store each object in your data array as its own key in Redis. Make sure that these will be indexed by using the GIGS prefix in the key name, for example GIGS:63e7d1d85ad7e2f69df8ed6e and GIGS:63e7d1d85ad7e2f69df8ed6f.
You'd want to change your index definition to account for each document being an object too so it would look something like this:
await redisClient.ft.create(
`idx:gigs`,
{
'$.artist.genre': {
type: SchemaFieldTypes.TEXT,
AS: 'genre',
},
},
{
ON: 'JSON',
PREFIX: 'GIGS:',
}
)
Note I also updated your PREFIX to be GIGS: not GIGS - this isn't strictly necessary, but does stop your index from accidentally looking at other keys in Redis whose name begins GIGS<whatever other characters>.
I have documents like this in my MongoDB Listings collection.
listingID: 'abcd',
listingData: {
category: 'resedetial'
},
listingID: 'xyz',
listingData: {
category: 'resedetial'
},
listingID: 'efgh',
listingData: {
category: 'office'
}
I am trying to get total count of all listings and count according to category.
I can get total count of listings with aggregation query. But I am not sure how to get output like this resedentialCount: 2, officeCount: 1 , ListingsCount: 3
This is my aggregation query
{
$match: {
listingID,
},
},
{
$group: {
_id: 1,
ListingsCount: { $sum: 1 },
},
}
Try this:
let listingAggregationCursor = db.collection.aggregate([
{$group: {_id:"$listingData.category",ListingsCount:{$sum:1} }}
])
let listingAggregation=await listingAggregationCursor.toArray();
(I got this query from https://www.statology.org/mongodb-group-by-count)
This will give you an array of objects with each listing category as well as how many times they occur.
For getting the total listingsCount, sum up all of the count fields from the array of objects. You can do that like this:
let listingsCount=0;
for(listingCategory of listingAggregation) {
listingsCount+=listingCategory.count;
}
You should have the data you need at this point. Now it's just a matter of extracting and formatting it as you see fit.
Hope this helps!
I have a collection in my MongoDB:
{ userId: 1234, name: 'Mike' }
{ userId: 1235, name: 'John' }
...
I want to get a result of the form
dict[userId] = document
in other words, I want a result that is a dictionary where the userId is the key and the rest of the document is the value.
How can I do that?
You can use $arrayToObject to do that, you just need to format it to array of k, v before.
It is not clear if you want one dictionary for all documents, or each document in a dictionary format. I guess you want the first option, but I'm showing both:
One dictionary with all data*, requires a $group (which also format the data):
db.collection.aggregate([
{
$group: {
_id: null,
data: {$push: {k: {$toString: "$userId"}, v: "$$ROOT"}}
}
},
{
$project: {data: {$arrayToObject: "$data"}}
},
{
$replaceRoot: {newRoot: "$data"}
}
])
See how it works on the playground example - one dict
*Notice that in this option, all the data is inserted to one document, and document as a limit size.
Dictionary format: If you want to get all documents as different results, but with a dictionary format, just replace the first step of the aggregation with this:
{
$project: {
data: [{k: {$toString: "$userId"}, v: "$$ROOT"}],
_id: 0
}
},
See how it works on the playground example - dict per document
I need to retrieve let's say the documents at position 1,5 and 8 in a MongoDB database using Mongoose.
Is it possible at all to get a document by its position in a collection? If so, could you show how to do that?
I need something like this:
var someDocs = MyModel.find({<collectionIndex>: [1, 5, 8]}, function(err, docs) {
//do something with the three documents
})
I tried to do the following to see what indexes are used in collection but I get the 'getIndexes is not a function' error:
var indexes = MyModel.getIndexes();
Appreciate any help.
If by position 5 for example, you mean the literal 5th element in your collection, that isn't the best way to go. A Mongo collection is usually in the order in which you inserted the elements, but it may not always be. See the answer here and check the docs on natural order: https://stackoverflow.com/a/33018164/7531267.
In your case, you might have a unique id on each record that you can query by.
Assuming the [1, 5, 8] you mentioned are the ids, something like this should do it:
var someDocs = MyModel.find({ $or: [{ id: 1 }, { id: 5 }, { id: 8 }]}}, function(err, cb) {
//do something with the three documents
})
You can also read about $in to replace $or and clean up the query a bit.
Assume you have this document in users collections:
{
_id: ObjectId('...'),
name: 'wrq',
friends: ['A', 'B', 'C']
}
Code below to search first and thrid friend of user 'wrq':
db.users.aggregate(
[
{
$match: {
name: 'wrq'
}
},
{
$project:{
friend1: {
$arrayElemAt: ["$friends", 0]
},
friend3: {
$arrayElemAt: ["$friends", 2]
}
}
}
]
)
say I have this array property ('articles') on a Mongoose schema:
articles: [
{
kind: 'bear',
hashtag: 'foo'
},
{
kind: 'llama',
hashtag: 'baz',
},
{
kind: 'sheep',
hashtag: 'bar',
}
]
how can I use
$addToSet https://docs.mongodb.org/manual/reference/operator/update/addToSet/
to add to this array by checking the value of hashtag to see if it's unique?
For example, if I want to add the following object to the above array, I want Mongo to 'reject' it as a duplicate:
{
kind: 'tortoise',
hashtag: 'foo'
}
because hashtag=foo has already been taken.
The problem is that I only know how to use $addToSet with simple arrays of integers...
for example, if articles looked like this:
articles: [ 1 , 5 , 4, 2]
I would use $addToSet like this:
var data = {
"$addToSet": {
"articles": 9
}
}
model.update(data);
but how can I accomplish the same thing with an array of objects where the unique field is a string, in this case 'hashtag'? The docs don't make this clear and it seems like I have searched everywhere..
thanks
You need to use the $ne operator.
var data = { 'kind': 'tortoise', 'hashtag': 'foo' };
Model.update(
{ 'articles.hashtag': { '$ne': 'foo' } },
{ '$addToSet': { 'articles': data } }
)
This will update the document only if there is no sub document in the "article" array with the value of hashtag equals to "foo".
As #BlakesSeven mentioned in the comment
The $addToSet becomes irrelevant once you are testing for the presence of one of the values, so this may as well be a $push for code clarity. But the principle is correct since $addToSet works on the whole object and not just part of it.
Model.update({
{ 'articles.hashtag': { '$ne': 'foo' } },
{ '$push': {'articles': data } }
)
// add the comment's id to the commentsList :
// share.comments.commentsList.addToSet(callback._id);
share.update(
{ '$push': {'comments.commentsList': mongoose.Types.ObjectId(callback._id) } }
, function(){
console.log('added comment id to the commentsList array of obectIds')
})