Get part of array value - mongodb

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.

Related

mongodb: how to compare String from document to the String constructed by expression in aggregation pipeline

I have a trouble with creating a view in MongoDB. I'm just starting with Mongo, so the question could look pretty silly.
I have the following document:
{
"_id" : NumberInt(12213054),
...
"_dt" : {
"_doc" : "time",
"time" : "01:30",
"date" : "04/02/18",
"tz" : "CET",
"tzoffset" : NumberInt(3600),
"uts" : NumberInt(1517704200)
},
...
}
I'm trying to create a view for documents that have a date '04/02/18'.
The type of "_dt.date" field is String. And I'm trying to compare it using $eq operator.
I have the following query to select the data:
{$match: {
"_dt.date": {
$eq: {
$concat: [
{$dateToString: {
format: "%d/%m/",
date: new Date("2018-02-04")
}},
{$substr: [
{$year : new Date("2018-02-04")},
2,
2
]}
]
}
}
}
}
But after I execute the query the result is empty.
The date string constructed by the subquery seems to be correct:
db.matches.aggregate({ $project: { date:
{
$concat: [
{$dateToString: {
format: "%d/%m/",
date: new Date("2018-02-04")
}},
{$substr: [
{$year : new Date("2018-02-08")},
2,
2
]}
]
}
}
}
)
Output is:
{
"_id" : NumberInt(12213054),
"date" : "04/02/18"
}
Does anyone have any idea why the first query doesn't work?
$match by design compares a field against a constant value with regular query operators.
For comparing two fields in mongodb you've to use $match with $expr with aggregation query operators in 3.6.
Compare query operators vs aggregation comparison operators.
{"$match":{
"$expr":{
"$eq":[
"$_dt.date",
{"$concat":[
{"$dateToString":{"format":"%d/%m/","date":new Date("2018-02-04")}},
{"$substr":[{"$year":new Date("2018-02-04")},2,2]}
]}
}
}}
Or
You can simply pass the date string created on client side and compare directly
{"$match":{"_dt.date":"04/02/18"}}

How to project single array field

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}
])

MongoDB: How to match on the elements of an array?

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"
]
}
}
])

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.

MongoDB : limit query to a field and array projection

I have a collection that contains following information
{
"_id" : 1,
"info" : { "createdby" : "xyz" },
"states" : [ 11, 10, 9, 3, 2, 1 ]}
}
I project only states by using query
db.jobs.find({},{states:1})
Then I get only states (and whole array of state values) ! or I can select only one state in that array by
db.jobs.find({},{states : {$slice : 1} })
And then I get only one state value, but along with all other fields in the document as well.
Is there a way to select only "states" field, and at the same time slice only one element of the array. Of course, I can exclude fields but I would like to have a solution in which I can specify both conditions.
You can do this in two ways:
1> Using mongo projection like
<field>: <1 or true> Specify the inclusion of a field
and
<field>: <0 or false> Specify the suppression of the field
so your query as
db.jobs.find({},{states : {$slice : 1} ,"info":0,"_id":0})
2> Other way using mongo aggregation as
db.jobs.aggregate({
"$unwind": "$states"
}, {
"$match": {
"states": 11
}
}, // match states (optional)
{
"$group": {
"_id": "$_id",
"states": {
"$first": "$states"
}
}
}, {
"$project": {
"_id": 0,
"states": 1
}
})