starting from this document schema
{
"_id" : ObjectId("5a5cfde58c8a4a35a89726b4"),
"Names" : [
{ "Code" : "en", "Name" : "ITALY" },
{ "Code" : "it", "Name" : "ITALIA" }
],
"TenantID" : ObjectId("5a5cfde58c8a4a35a89726b2"),
...extra irrelevant fields
}
I need to get an object like this
{
"ID" : ObjectId("5a5cfde58c8a4a35a89726b4"),
"Name" : "ITALY"
}
Filtering by array's Code field (in the sample by 'en').
I wrote an aggregate query, this
db.Countries.aggregate([{$project: {_id:0, 'ID': '$_id', 'Names': {$filter: {input: '$Names', as: 'item', cond: {$eq: ['$$item.Code', 'en']}}}}},{$skip: 10},{$limit: 5}]);
that correctly return only documents with 'en' Names values, returning only the sub array matching elements.
Now I cannot find the way to return only the Name field value into a new 'Name' field.
Which is the best and most performing method to do this ?
Actually I'm trying on Mongo shell, but later I need to replicate this behavior with .NET driver.
Using MongoDB 3.6
Thanks
You can try below aggregation query.
$match to only consider documents where there is atleast one element in array matching input criteria.
$filter array on the input criteria and $arrayElemAt to project the single matched element.
$let to output name from matched object.
db.Countries.aggregate([
{"$match":{"Names.Code":"en"}},
{"$project":{
"_id":0,
"ID": "$_id",
"Name":{
"$let":{
"vars":{
"obj":{
"$arrayElemAt":[
{"$filter":{
"input":"$Names",
"as":"name",
"cond":{"$eq":["$$name.Code","en"]}
}},
0]
}
},
"in":"$$obj.Name"
}
}
}},
{"$skip": 10},
{"$limit": 5}
])
Related
Just trying to wrap my head around how to reference a collection based on a substring match.
Say I have one collection with chat information (including cell_number: 10 digits). I have another collection in which every document includes a field for area code (3 digits) and some other information about that area code, like so:
Area codes collection excerpt:
{
"_id" : ObjectId("62cf3d56580efcedf7c3b845"),
"code" : "206",
"region" : "WA",
"abbr" : "WA"
}
{
"_id" : ObjectId("62cf3d56580efcedf7c3b846"),
"code" : "220",
"region" : "OH",
"abbr" : "OH"
}
{
"_id" : ObjectId("62cf3d56580efcedf7c3b847"),
"code" : "226",
"region" : "Ontario",
"abbr" : "ON"
}
What I want to do is be able to grab out just the document from the area codes collection which has a "code" field value matching the first 3 characters of a cell number. A simple example is below. Note that in this example I have hardcoded the cell number for simplicity, but in reality it would be coming from the chat_conversations "parent" collection:
db.chat_conversations.aggregate([
{$match: {wait_start: {$gte: new Date("2020-07-01"), $lt: new Date("2022-07-12")}}},
{$lookup: {
from: "area_codes",
let: {
areaCode: "$code",
cellNumber: "2065551234"
},
pipeline: [
{$match: {
$expr: {
$eq: ["$$areaCode", {$substr: ["$$cellNumber", 0, 3]}]
}
}}
],
as: "area_code"
}},
]).pretty()
Unfortunately nothing I try here seems to work and I just get back an empty array from the area codes collection.
What am I missing here?
you should do a let on chat_converstion
or you should use let with vars and in expressions
this query should work for you
db.chat_conversations.aggregate([
{$match: {wait_start: {$gte: new Date("2020-07-01"), $lt: new Date("2022-07-12")}}},
{
$lookup: {
from: "area_codes",
let:{
"cell_Number":{$substr:["$cellNumber",0,3]}
},
pipeline:[
{
$match:{
$expr:{
$eq: [ "$code", "$$cell_Number" ]
}
}
}],
as: "data"
}
},
])
I have two collections as follows:
db.qnames.find()
{ "_id" : ObjectId("5a4da53f97a9ca769a15d49e"), "domain" : "mail.google.com", "tldOne" : "google.com", "clients" : 10, "date" : "2016-12-30" }
{ "_id" : ObjectId("5a4da55497a9ca769a15d49f"), "domain" : "mail.google.com", "tldOne" : "google.com", "clients" : 9, "date" : "2017-01-30” }
and
db.dropped.find()
{ "_id" : ObjectId("5a4da4ac97a9ca769a15d49c"), "domain" : "google.com", "dropDate" : "2017-01-01", "regStatus" : 1 }
I would like to join the two collections and choose the documents for which 'dropDate' field (from dropped collection) is larger than the 'date' filed (from qnames field). So I used the following query:
db.dropped.aggregate( [{$lookup:{ from:"qnames", localField:"domain",foreignField:"tldOne",as:"droppedTraffic"}},
{$match: {"droppedTraffic":{$ne:[]} }},
{$unwind: "$droppedTraffic" } ,
{$match: {dropDate:{$gt:"$droppedTraffic.date"}}} ])
but this query does not filter the records where dropDate < date. Anyone can give me a clue of why it happens?
The reason why you are not getting the record is
Date is used as a String in your collections, to make use of the comparison operators to get the desired result modify your collection documents using new ISODate("your existing date in the collection")
Please note even after modifying both the collections you need to modify your aggregate query, since in the final $match query two values from the same document is been compared.
Sample query to get the desired documents
db.dropped.aggregate([
{$lookup: {
from:"qnames",
localField:"domain",
foreignField:"tldOne",
as:"droppedTraffic"}
},
{$project: {
_id:1, domain:1,
regStatus:1,
droppedTraffic: {
$filter: {
input: "$droppedTraffic",
as:"droppedTraffic",
cond:{ $gt: ["$$droppedTraffic.date", "$dropDate"]}
}
}
}}
])
In this approach given above we have used $filter which avoids the $unwind operation
You should use $redact to compare two fields of the same document. Following example should work:
db.dropped.aggregate( [
{$lookup:{ from:"qnames", localField:"domain",foreignField:"tldOne",as:"droppedTraffic"}},
{$match: {"droppedTraffic":{$ne:[]} }},
{$unwind: "$droppedTraffic" },
{
"$redact": {
"$cond": [
{ "$lte": [ "$dropDate", "$droppedTraffic.date" ] },
"$$KEEP",
"$$PRUNE"
]
}
}
])
I have a MongoDB collection with documents in the following format:
{
"_id" : ObjectId("4e8ae86d08101908e1000001"),
"name" : ["Name"],
"zipcode" : ["2223"]
}
{
"_id" : ObjectId("4e8ae86d08101908e1000002"),
"name" : ["Another ", "Name"],
"zipcode" : ["2224"]
}
I can currently get documents that match a specific array size:
db.accommodations.find({ name : { $size : 2 }})
This correctly returns the documents with 2 elements in the name array. However, I can't do a $gt command to return all documents where the name field has an array size of greater than 2:
db.accommodations.find({ name : { $size: { $gt : 1 } }})
How can I select all documents with a name array of a size greater than one (preferably without having to modify the current data structure)?
There's a more efficient way to do this in MongoDB 2.2+ now that you can use numeric array indexes (0 based) in query object keys.
// Find all docs that have at least two name array elements.
db.accommodations.find({'name.1': {$exists: true}})
You can support this query with an index that uses a partial filter expression (requires 3.2+):
// index for at least two name array elements
db.accommodations.createIndex(
{'name.1': 1},
{partialFilterExpression: {'name.1': {$exists: true}}}
);
Update:
For mongodb versions 2.2+ more efficient way to do this described by #JohnnyHK in another answer.
Using $where
db.accommodations.find( { $where: "this.name.length > 1" } );
But...
Javascript executes more slowly than the native operators listed on
this page, but is very flexible. See the server-side processing page
for more information.
Create extra field NamesArrayLength, update it with names array length and then use in queries:
db.accommodations.find({"NamesArrayLength": {$gt: 1} });
It will be better solution, and will work much faster (you can create index on it).
I believe this is the fastest query that answers your question, because it doesn't use an interpreted $where clause:
{$nor: [
{name: {$exists: false}},
{name: {$size: 0}},
{name: {$size: 1}}
]}
It means "all documents except those without a name (either non existant or empty array) or with just one name."
Test:
> db.test.save({})
> db.test.save({name: []})
> db.test.save({name: ['George']})
> db.test.save({name: ['George', 'Raymond']})
> db.test.save({name: ['George', 'Raymond', 'Richard']})
> db.test.save({name: ['George', 'Raymond', 'Richard', 'Martin']})
> db.test.find({$nor: [{name: {$exists: false}}, {name: {$size: 0}}, {name: {$size: 1}}]})
{ "_id" : ObjectId("511907e3fb13145a3d2e225b"), "name" : [ "George", "Raymond" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225c"), "name" : [ "George", "Raymond", "Richard" ] }
{ "_id" : ObjectId("511907e3fb13145a3d2e225d"), "name" : [ "George", "Raymond", "Richard", "Martin" ] }
>
You can use aggregate, too:
db.accommodations.aggregate(
[
{$project: {_id:1, name:1, zipcode:1,
size_of_name: {$size: "$name"}
}
},
{$match: {"size_of_name": {$gt: 1}}}
])
// you add "size_of_name" to transit document and use it to filter the size of the name
You can use $expr ( 3.6 mongo version operator ) to use aggregation functions in regular query.
Compare query operators vs aggregation comparison operators.
db.accommodations.find({$expr:{$gt:[{$size:"$name"}, 1]}})
Try to do something like this:
db.getCollection('collectionName').find({'ArrayName.1': {$exists: true}})
1 is number, if you want to fetch record greater than 50 then do ArrayName.50
Thanks.
MongoDB 3.6 include $expr
https://docs.mongodb.com/manual/reference/operator/query/expr/
You can use $expr in order to evaluate an expression inside a $match, or find.
{ $match: {
$expr: {$gt: [{$size: "$yourArrayField"}, 0]}
}
}
or find
collection.find({$expr: {$gte: [{$size: "$yourArrayField"}, 0]}});
None of the above worked for me. This one did so I'm sharing it:
db.collection.find( {arrayName : {$exists:true}, $where:'this.arrayName.length>1'} )
db.accommodations.find({"name":{"$exists":true, "$ne":[], "$not":{"$size":1}}})
Although the above answers all work, What you originally tried to do was the correct way, however you just have the syntax backwards (switch "$size" and "$gt")..
Correct:
db.collection.find({items: {$gt: {$size: 1}}})
I found this solution, to find items with an array field greater than certain length
db.allusers.aggregate([
{$match:{username:{$exists:true}}},
{$project: { count: { $size:"$locations.lat" }}},
{$match:{count:{$gt:20}}}
])
The first $match aggregate uses an argument thats true for all the documents. If blank, i would get
"errmsg" : "exception: The argument to $size must be an Array, but was of type: EOO"
You can MongoDB aggregation to do the task:
db.collection.aggregate([
{
$addFields: {
arrayLength: {$size: '$array'}
},
},
{
$match: {
arrayLength: {$gt: 1}
},
},
])
This will work in Compass also. This is the fastest of all i have tried without indexing.
{$expr: {
$gt: [
{
$size: { "$ifNull": [ "$name", [] ] }
},
1
]
}}
you can use $expr to cover this
// $expr: Allows the use of aggregation expressions within the query language.
// syntax: {$expr: {<expression>}}
db.getCollection("person_service").find(
{
"$expr" : {
// services is Array, find services length gt 3
"$gt" : [
{
"$size" : "$services"
},
3.0
]
}
}
)
I have an object in the database that looks like:
{
'name': 'foo',
'table':
[
{
'date' : ISODate("2000-01-13T23:00:00Z")
},
{
'date' : ISODate("2000-01-14T23:00:00Z")
},
{
'date' : ISODate("2000-01-15T23:00:00Z")
},
{
'date' : ISODate("2000-01-16T23:00:00Z")
},
{
'date' : ISODate("2000-01-17T23:00:00Z")
}
]
}
I wish to query for the following result:
{
'name': 'foo',
'table':
[
{
'date' : ISODate("2000-01-15T23:00:00Z")
},
{
'date' : ISODate("2000-01-16T23:00:00Z")
}
]
}
So I'm looking for a way to extract the children that is between two different dates.
So far I have tried the following:
db.stock.find({'table.date' : {$gte : '2000-01-15', $lt : '2000-01-17'}});
db.stock.find({'table.date' : {$gte : new Date('2000-01-15'), $lt : new Date('2000-01-17')}});
db.stock.find({'table.date' : {$gte : '2000-01-15T23:00:00Z', $lt : '2000-01-17T23:00:00Z'}});
db.stock.find({'table.date' : {$gte : ISODate('2000-01-15T23:00:00Z'), $lt : ISODate('2000-01-17T23:00:00Z')}});
db.stock.find({'table.date' : {$gte : new Date('2000-01-15T23:00:00Z'), $lt : new Date('2000-01-17T23:00:00Z')}});
Is this possible to do? If so, how can it be resolved?
It is not possible for .find() to "filter" the returned elements of an array by itself. You can use the positional $ operator with a match condition to "project" one matching element only ( see the documentation ).
The reason here is that the query here is not matching the array elements, but rather the document that "contains" the matching array elements.
But to "filter" elements to just the ones you want, you need to use aggregate:
db.stock.aggregate([
// Matching first is a good idea to filter the documents that contain
// the matching elements
{ "$match": {
"table.date": {
"$gte": new Date("2000-01-15"),
"$lt": new Date("2000-01-17")
}
}},
// Unwind the array, this "de-normalizes"
{ "$unwind": "$table" },
// Now use the match to actually "filter" results in the array
{ "$match": {
"table.date": {
"$gte": new Date("2000-01-15"),
"$lt": new Date("2000-01-17")
}
}},
// Group the results back by a "key", probably the document
{ "$group": {
"_id": "$_id",
"table": { "$push": "$table" }
}}
])
Also worth noting from your examples that you need to use dates as actual date types and not "strings". This is also true in other language implementations, where the native date type will be sent as a BSON date to the MongoDB server where it can be compared internally.
For more information on returning the original document form with this type of query, see here.
If I have this mongo document.
{
"_id" : ObjectId("52fd40e781ddcb34819ac21e"),
"name" : "fred",
"fruits_i_like" : [
"pear",
"apple",
"banana"
]
}
{
"_id" : ObjectId("52fd40fa81ddcb34819ac21f"),
"name" : "barney",
"fruits_i_like" : [
"pear",
"apple",
"peach"
]
}
can I sort the subkeys so that I get
{
"_id" : ObjectId("52fd40e781ddcb34819ac21e"),
"name" : "fred",
"fruits_i_like" : [
"apple",
"banana",
"pear"
]
}
{
"_id" : ObjectId("52fd40fa81ddcb34819ac21f"),
"name" : "barney",
"fruits_i_like" : [
"apple",
"peach",
"pear"
]
}
i.e - I don't really care about the ordering of the documents, but each time a document is printed in the find cursor, the list of fruits in the subkey should be sorted. I can do this with passing a custom javascript function to forEach() on the find() - but I wondered if there was a more built in way with the build in mongo features.
As you note operations such as forEach are iterators and you would have to do your sort actions on every item you retrieve. While this might be fine for some cases, there are a lot of reasons why this is not what you want. Particularly if you want to do any meaningful filtering of the content in inner arrays.
You can do a lot with array manipulation in the aggregation pipeline, so to return your results with the array items sorted:
db.collection.aggregate([
// Unwind the array elements into each document
{$unwind: "$fruits_i_like"},
// Sort on the array key
{$sort: { fruits_i_like: 1 }},
// Push the elements back into an array
{$group: {
_id: {_id: "$_id", name: "$name"},
fruits_i_like: {$push: "$fruits_i_like"}
}},
// Restore the original document form
{$project: { _id:0, _id: "$_id", name: "$_id.name", fruits_i_like: 1}}
])
So the use of $unwind denormalizes in a way so that you can do other operations with the inner elements. Also the aggregate command returns a cursor in latest drivers just like find, and will do so internally in future releases.
Of course if you want your documents to always have sorted array, look at the $sort modifier for use in update actions.