Match multiple values in array - mongodb

I have the following data structure:
[{"url":"http://guyanachronicle.com/","originalUrl":"http://www.guyanachronicle.com","applications":[]}
,{"url":"http://www.lightbot.com","originalUrl":"http://www.lightbot.com","applications":[{"name":"Apache","confidence":"100","version":"","icon":"Apache.svg","website":"http://apache.org","categories":["Web Servers"]},{"name":"Facebook","confidence":"100","version":"","icon":"Facebook.svg","website":"http://facebook.com","categories":["Widgets"]},{"name":"Google Analytics","confidence":"100","version":"","icon":"Google Analytics.svg","website":"http://google.com/analytics","categories":["Analytics"]},{"name":"Twitter","confidence":"100","version":"","icon":"Twitter.svg","website":"http://twitter.com","categories":["Widgets"]},{"name":"jQuery","confidence":"100","version":"1.4","icon":"jQuery.svg","website":"http://jquery.com","categories":["JavaScript Frameworks"]}]}
,{"url":"http://www.fitnessfirst.com.au","originalUrl":"http://www.fitnessfirst.com.au","applications":[{"name":"AdInfinity","confidence":"100","version":"","icon":"AdInfinity.png","website":"http://adinfinity.com.au","categories":["Advertising Networks"]},{"name":"CloudFlare","confidence":"100","version":"","icon":"CloudFlare.svg","website":"http://www.cloudflare.com","categories":["CDN"]},{"name":"Facebook","confidence":"100","version":"","icon":"Facebook.svg","website":"http://facebook.com","categories":["Widgets"]},{"name":"Google Analytics","confidence":"100","version":"UA","icon":"Google Analytics.svg","website":"http://google.com/analytics","categories":["Analytics"]},{"name":"Google Tag Manager","confidence":"100","version":"","icon":"Google Tag Manager.png","website":"http://www.google.com/tagmanager","categories":["Tag Managers"]},{"name":"Microsoft ASP.NET","confidence":"100","version":"4.0.30319","icon":"Microsoft ASP.NET.png","website":"http://www.asp.net","categories":["Web Frameworks"]},{"name":"Modernizr","confidence":"100","version":"","icon":"Modernizr.png","website":"http://www.modernizr.com","categories":["JavaScript Frameworks"]},{"name":"New Relic","confidence":"100","version":"","icon":"New Relic.png","website":"http://newrelic.com","categories":["Analytics"]},{"name":"Nginx","confidence":"100","version":"","icon":"Nginx.svg","website":"http://nginx.org/en","categories":["Web Servers"]},{"name":"OWL Carousel","confidence":"100","version":"","icon":"OWL Carousel.png","website":"https://owlcarousel2.github.io/OwlCarousel2/","categories":["Widgets"]},{"name":"Optimizely","confidence":"100","version":"","icon":"Optimizely.png","website":"http://optimizely.com","categories":["Analytics"]},{"name":"YouTube","confidence":"100","version":"","icon":"YouTube.png","website":"http://www.youtube.com","categories":["Video Players"]},{"name":"jQuery","confidence":"100","version":"1.11.1","icon":"jQuery.svg","website":"http://jquery.com","categories":["JavaScript Frameworks"]},{"name":"IIS","confidence":"100","version":"","icon":"IIS.png","website":"http://www.iis.net","categories":["Web Servers"]},{"name":"Windows Server","confidence":"100","version":"","icon":"Microsoft.svg","website":"http://microsoft.com/windowsserver","categories":["Operating Systems"]}]}
,{"url":"https://www.totaltools.com.au/","originalUrl":"http://www.totaltools.com.au","applications":[{"name":"Facebook","confidence":"100","version":"","icon":"Facebook.svg","website":"http://facebook.com","categories":["Widgets"]},{"name":"Google Analytics","confidence":"100","version":"UA","icon":"Google Analytics.svg","website":"http://google.com/analytics","categories":["Analytics"]},{"name":"Google Tag Manager","confidence":"100","version":"","icon":"Google Tag Manager.png","website":"http://www.google.com/tagmanager","categories":["Tag Managers"]},{"name":"Hotjar","confidence":"100","version":"","icon":"Hotjar.png","website":"https://www.hotjar.com","categories":["Analytics"]},{"name":"Magento","confidence":"100","version":"2","icon":"Magento.png","website":"http://www.magentocommerce.com","categories":["Ecommerce"]},{"name":"Prototype","confidence":"100","version":"","icon":"Prototype.png","website":"http://www.prototypejs.org","categories":["JavaScript Frameworks"]},{"name":"RequireJS","confidence":"100","version":"","icon":"RequireJS.png","website":"http://requirejs.org","categories":["JavaScript Frameworks"]},{"name":"Twitter Bootstrap","confidence":"100","version":"","icon":"Twitter Bootstrap.png","website":"http://getbootstrap.com","categories":["Web Frameworks"]},{"name":"Underscore.js","confidence":"100","version":"","icon":"Underscore.js.png","website":"http://underscorejs.org","categories":["JavaScript Frameworks"]},{"name":"jQuery","confidence":"100","version":"","icon":"jQuery.svg","website":"http://jquery.com","categories":["JavaScript Frameworks"]},{"name":"jQuery Mobile","confidence":"100","version":"","icon":"jQuery Mobile.svg","website":"http://jquerymobile.com","categories":["Mobile Frameworks"]},{"name":"jQuery UI","confidence":"100","version":"","icon":"jQuery UI.svg","website":"http://jqueryui.com","categories":["JavaScript Frameworks"]},{"name":"PHP","confidence":"100","version":"","icon":"PHP.svg","website":"http://php.net","categories":["Programming Languages"]}]}
,{"url":"http://www.bestandless.com.au","originalUrl":"http://www.bestandless.com.au","applications":[]}
,{"url":"http://www.flightsimstore.com","originalUrl":"http://www.flightsimstore.com","applications":[{"name":"Google Analytics","confidence":"100","version":"","icon":"Google Analytics.svg","website":"http://google.com/analytics","categories":["Analytics"]},{"name":"HeadJS","confidence":"50","version":"","icon":"HeadJS.png","website":"http://headjs.com","categories":["JavaScript Frameworks"]},{"name":"Nginx","confidence":"100","version":"1.12.0","icon":"Nginx.svg","website":"http://nginx.org/en","categories":["Web Servers"]},{"name":"PHP","confidence":"100","version":"5.3.29","icon":"PHP.svg","website":"http://php.net","categories":["Programming Languages"]},{"name":"SWFObject","confidence":"100","version":"","icon":"SWFObject.png","website":"http://github.com/swfobject/swfobject","categories":["Miscellaneous"]},{"name":"StatCounter","confidence":"100","version":"","icon":"StatCounter.png","website":"http://www.statcounter.com","categories":["Analytics"]},{"name":"SumoMe","confidence":"100","version":"","icon":"SumoMe.png","website":"http://sumome.com","categories":["Widgets","Marketing Automation"]},{"name":"osCommerce","confidence":"100","version":"","icon":"osCommerce.png","website":"http://www.oscommerce.com","categories":["Ecommerce"]},{"name":"MySQL","confidence":"100","version":"","icon":"MySQL.svg","website":"http://mysql.com","categories":["Databases"]}]}]);
That I uploaded to a collection in MongoDB called websites (it essentially is just a list of URLs and the applications that are running on them)
I use the following query to return all websites who have a certain application installed:
db.websites.find({"applications.name":"osCommerce"}).pretty();
This works and returns a list of websites using "osCommerce" however, when trying to use $and to search for websites that are using multiple applications my query doesn't work.
I'm trying to use:
db.inventory.find( { $and: [ {applications.name:{"Apache"}},
{applications.name: {"osCommerce"}} ] } ).pretty();
With no luck, what is the correct way to do this?

You want $all
db.inventory.find({
"applications.name": { "$all": [ "Apache", "osCommerce" ] }
})
Or combined with with $elemMatch if you want more conditions than just "name" inside the array:
db.inventory.find({
"applications": {
"$all": ]
{ "$elemMatch": { "name": "Apache" } },
{ "$elemMatch": { "name": "osCommerce" } }
]
}
})
Noting though that the data you present in the question has no document that matches this condition.
If you want "either" value then that is an $in query:
db.inventory.find({
"applications.name": { "$in": [ "Apache", "osCommerce" ] }
})
or in fact represented with $or if you wanted "multiple properties" just like above:
db.inventory.find({
"$or": [
{ "applications": { "$elemMatch": { "name": "Apache" } } }
{ "applications": { "$elemMatch": { "name": "osCommerce" } } }
]
})

Related

Find document with only expected values allowed in nested array field in MongoDB

I'll start with the example as it's easier to explain for me.
[
{
"_id": 100,
"narr": [
{
"field": 1
}
]
},
{
"_id": 101,
"narr": [
{
"field": 1,
},
{
"field": 2
}
]
}
]
Goal is to find document exactly with values specified by me for a field.
Example:
for lookup = [1] find document with _id=100.
for lookup = [1,2] find document with _id=101.
So far I came up with (for second example with [1,2]):
db.col.find(
{
"narr": {
"$all": [
{
"$elemMatch": {
"field": {
"$in": [1, 2]
}
}
}
]
}
}
)
But it also includes document with _id=100. How can I make it perform strict match?
Building whole arrays won't work as there are multiple fields with unknown values in each nested structure.
Without considering duplication in the field and your input, you can simply do a find on narr.field. It is like performing search on an array with values from field.
db.collection.find({
$expr: {
$eq: [
"$narr.field",
[
1,
2
]
]
}
})
Here is the Mongo playground for your reference.
If duplication may happens, try to use $setEquals.
db.collection.find({
$expr: {
"$setEquals": [
"$narr.field",
[
1,
2
]
]
}
})
Here is the Mongo playground for your reference.

mongoDB - find a specific result, with a document nested in different ways

I have a little problem with my command lines in MongoDB. Here is what one of my documents looks like :
{
"id": "6249c4eb85a7563e673b4360",
"collection": {
"id": "6244099cb9ebc40007ac2ce3"
},
"characters": [
{
"id": "6244074ab9ebc40007ac2cf9"
},
{
"id": "6244074ab9ebc40007ac2ce1"
}
],
}
Then, in the MongoDB shell, I try to retrieve some books according to their criteria, here is an example :
db.comic.find({
characters: {
$elemMatch:{
_id: {
$in: [
ObjectId("6244074ab9ebc40007ac2ce5")
]
}
}
},
collection: {
$elemMatch:{
_id: {
$in: [
ObjectId("6244099cb9ebc40007ac2cfb")
]
}
}
}
}).pretty()
If I remove the "collection" part, I find the books related to the characters. However, if I put back the "collection" part, I have no result. I think it's related to the fact that "collection" is not an array, but I don't know how to solve this problem, knowing that the goal is to have several collections in the search, hence the "$elemMatch" and the "$in".
Thanks in advance for your help, have a nice day.
You need to use collection._id instead of collection:{$elemMatch:{_id since $elemMatch is for finding an object inside an array.
So your query should look like:
db.comic.find({
characters: {
$elemMatch:{
_id: {
$in: [
ObjectId("6244074ab9ebc40007ac2ce5")
]
}
}
},
"collection._id": {
$in: [ObjectId("6244099cb9ebc40007ac2cfb")]
}
}).pretty()

$elemMatch doesn't work on nested documents in MongoDB

Stack Overflow!
I have a very strange problem with using $elemMatch in MongoDB. I added multiple documents to a collection. Some of these documents were added using import feature in MongoDB Compass (Add Data -> Import File -> JSON) and some of them were added using insertMany().
Here is an example structure of a single document:
{
"id": "1234567890",
"date": "YYYY-MM-DD",
"contents": {
"0": {
"content": {
"id": "1111111111",
"name": "Name 1"
}
},
"1": {
"content": {
"id": "2222222222",
"name": "Name 2"
}
},
"2": {
"content": {
"id": "3333333333",
"name": "Name 3"
}
}
}
}
The thing is, when I use the following find query using this filter:
{date: "<some_date_here>", "contents": {
$elemMatch: {
"content.id": <some_id_here>
}
}}
ONLY documents that were imported from MongoDB Compass are showing up. Documents that were added by Mongosh or by NodeJS driver (doesn't matter), do NOT show up.
Am I missing something obvious here? What should I do in order to make all documents in a collection (that matches filter) to show up?
Simple filters that do not include $elemMatch work well and all documents that match the filtering rules show up. Problem seems to be with $elemMatch.
I tried adding the same batch of documents using different methods but only direct importing a JSON file in MongoDB Compass make them appear using a filter mentioned above.
Thank you for your help!
$elemMatch if for matching array , and in this case you don't have array
first you should convert contents object to array and then check the query for example id with filter and use match to find all doc that have specific data and size of new filters array
db.collection.aggregate([
{
"$addFields": {
"newField": {
"$objectToArray": "$contents"
}
}
},
{
"$addFields": {
"newField": {
"$filter": {
"input": "$newField",
"as": "z",
"cond": {
$eq: [
"$$z.v.content.id",
"1111111111"
]
}
}
}
}
},
{
"$addFields": {
"newField": {
$size: "$newField"
}
}
},
{
$match: {$and:[ {newField: {
$gt: 0
}},{date:{$gt:Date}}]}
},
{$project:{
contents:1,
date:1,
id:1,
}}
])
https://mongoplayground.net/p/pue4QPp1dYR
in mongoplayground I don't add filter of date

Aggregate documents where objects in array matches multiple conditions

I have a collection with documents similar to such:
{
"_id": ObjectId("xxxxx"),
"item": [
{ "property": ["attr1", "+1"] },
{ "property": ["attr2", "-1"] }
]
}
{
"_id": ObjectId("xxxxy"),
"item": [
{ "property": ["attr1", "-1"] },
{ "property": ["attr2", "0"] }
]
}
{
"_id": ObjectId("xxxxz"),
"item": [
{ "property": ["attr1", "0"] },
{ "property": ["attr2", "+1"] }
]
}
Preferably using an aggregation pipeline, is there any way to match the document if and only if any one of the properties match more than one condition?
For example, I want a query where one object in the array matches both of these conditions:
("item.property": "attr1") AND ("item.property": /^\+/)
That is, a single property where it contains "attr1" and an element that starts with "+".
However, using my current query that looks like this:
collection.aggregate(
{ $match:
{ $and:
[
{ "item.property": "attr1" },
{ "item.property": /^\+/ }
]
}
}
This would match both the first and last document because both contain a property with "attr1" and an element that stats with "+". However, I do not want this query to match the last document, since the element that starts with "+" does not belong to the same object in the array.
Is there any way to achieve this behavior using the aggregation framework?
You can use the below query with $elemMatch to match the array's both values.
Something like
db.collection_name.aggregate({
"$match": {
"item": {
"$elemMatch": {
"property.0": "attr1",
"property.1": /^\+/
}
}
}
});
Also, you can use $all operator if you don't want to reference array index.
db.collection_name.aggregate({
"$match": {
"item": {
"$elemMatch": {
"property": {
"$all": [
"attr1",
/^\+/
]
}
}
}
}
});

Are there restrictions on MongoDB collections property names?

I've a document structure wich contains a property named shares which is an array of objects.
Now I tried to match all documents where shared contains the matching _account string with dot notation (shares._account).
It's not working but it seems it's because of the _ char in front of property _account.
So if I put the string to search for inside the name property in that object everything works fine with dot notation.
Are there any limitations on property names?
Thought an _ is allowed because the id has it also in mongodb and for me it's a kind of convention to daclare bindings.
Example:
// Collection Item example
{
"_account": { "$oid" : "526fd2a571e1e13b4100000c" },
"_id": { "$oid" : "5279456932db6adb60000003" },
"name": "shared.jpg",
"path": "/upload/24795-4ui95s.jpg",
"preview": "/img/thumbs/24795-4ui95s.jpg",
"shared": false,
"shares": [
{
"name": "526fcb177675f27140000001",
"_account": "526fcb177675f27140000001"
},
{
"name": "tim",
"_account": "526fd29871e1e13b4100000b"
}
],
"tags": [
"grüngelb",
"farbe"
],
"type": "image/jpeg"
},
I tried to get the item with following query:
// Query example
{
"$or": [
{
"$and": [
{
"type": {
"$in": ["image/jpeg"]
}
}, {
"shares._account": "526fcb177675f27140000001" // Not working
//"shares.name": "526fcb177675f27140000001" // Working
}
]
}
]
}
Apart from the fact that $and can be omitted and $or is pointless "image/jpeg" != "image/jpg":
db.foo.find({
"type": {"$in": ["image/jpeg"]},
"shares._account": "526fcb177675f27140000002"
})
Or if you really want old one:
db.foo.find({
"$or": [
{
"$and": [
{
"type": {
"$in": ["image/jpeg"]
}
}, {
"shares._account": "526fcb177675f27140000002"
}
]
}
]
}
Both will return example document.
Your current query has some unnecessarily complicated constructs:
you don't need the $or and $and clauses ("and" is the default behaviour)
you are matching a single value using $in
The query won't find match the sample document because your query doesn't match the data:
the type field you are looking for is "image/jpg" but your sample document has "image/jpeg"
the shares._account value you are looking for is "526fcb177675f27140000001" but your sample document doesn't include this.
A simplified query that should work is:
db.shares.find(
{
"type": "image/jpeg",
"shares._account": "526fcb177675f27140000002"
}
)