Spring Data - MongoDB comparing two fields in the same document after aggregation - mongodb

I am new to MongoDB and am attempting a query using Spring Boot Data Mongo Templates. Below is the sample data that I’m using for this application:
{
"book" : {
"isbn" : "ABCD1234",
"publisher" : "Penguin",
"dateCheckedOutLast" : "2019-12-22",
"library" : "Pickwah"
},
"isLost" : false,
},
{
"book" : {
"isbn" : "ABCD1234",
"publisher" : "Penguin",
"dateCheckedOutLast" : "2018-12-22",
"library" : "BlueRidge"
},
"isLost" : false,
},
{
"book" : {
"isbn" : "DECF1234",
"publisher" : "Marvel",
"dateCheckedOutLast" : "2019-07-22",
"library" : "Pickwah"
},
"isLost" : false
},
{
"book" : {
"isbn" : "DECF1234",
"publisher" : "Marvel",
"dateCheckedOutLast" : "2020-01-07",
"library" : "BlueRidge"
},
"isLost" : false
}
I would like the query to return all the books in BlueRidge library such that the dateCheckedOutLast at BlueRidge library is greater than the dateCheckedOutLast at Pickwah library. The association between the books in the collection is the isbn attribute which uniquely identifies the books.
I have attempted the following code (BookData is the name of the Mongo Collection), it appears that when I try to compare the two date fields (dateCheckedOutLast) after the lookup, it fails.
Aggregation agg = newAggregation(
match(Criteria.where("book.library").is("BlueRidge")),
lookup("BookData", "book.isbn”, " book.isbn" , "anotherLib"),
unwind("anotherLib"),
match(Criteria.where("anotherLib.book.library").is("Pickwah")),
match(Criteria.where("book.dateCheckedOutLast")
.gt("anotherLib.book.dateCheckedOutLast"));
The correct query output should return 1 document (given the sample data) with "isbn = DECF1234".
Any feedback is appreciated. Thank you!

Related

Add field to every document with existing data (move fields data to new field)

I have almost no experience in SQL or noSQL.
I need to update every document so that my fields "Log*" are under the new field "Log"
I found some help from this StackOverflow, but I am still wondering how to move the data.
Thank you very much
Original document
// collection: Services
{
"_id" : ObjectId("5ccb4f99f4953d4894acbe79"),
"Name" : "WebAPI",
"LogPath" : "Product\\APIService\\",
"LogTypeList" : [
{
"Name" : "ApiComCounter",
"FileName" : "ApiComCounter.log"
},
{
"Name" : "ApiService",
"FileName" : "ApiService.log"
}
]
}
Final Document
// collection: Services
{
"_id" : ObjectId("5ccb6fa2ae8f8a5d7037a5dd"),
"Name" : "InvoicingService",
"Log" : {
"LogPath" : "Product\\APIService\\",
"LogTypeList" : [
{
"Name" : "ApiComCounter",
"FileName" : "ApiComCounter.log"
},
{
"Name" : "ApiService",
"FileName" : "ApiService.log"
}
]
}
}
This requires MongoDB 4.2 or higher:
db.<collection>.updateMany({}, [
{$set: {"Log.LogPath": "$LogPath", "Log.LogTypeList": "$LogTypeList"}},
{$unset: ["LogPath", "LogTypeList"]}
])

And Operator in Criteria not working as expected for nested documents inside aggregation Spring Data Mongo

I am trying to fetch total replies where read values for a replies is true. But I am getting count value as 3 but expected value is 2 (since only two read value is true) through Aggregation function available in Spring Data Mongo. Below is the code which I wrote:
Aggregation sumOfRepliesAgg = newAggregation(match(new Criteria().andOperator(Criteria.where("replies.repliedUserId").is(userProfileId),Criteria.where("replies.read").is(true))),
unwind("replies"), group("replies").count().as("repliesCount"),project("repliesCount"));
AggregationResults<Comments> totalRepliesCount = mongoOps.aggregate(sumOfRepliesAgg, "COMMENTS",Comments.class);
return totalRepliesCount.getMappedResults().size();
Using AND Operator inside Criteria Query and passed two criteria condition but not working as expected. Below is the sample data set:
{
"_id" : ObjectId("5c4ca7c94807e220ac5f7ec2"),
"_class" : "com.forum.api.domain.Comments",
"comment_data" : "logged by karthe99",
"totalReplies" : 2,
"replies" : [
{
"_id" : "b33a429f-b201-449b-962b-d589b7979cf0",
"content" : "dasdsa",
"createdDate" : ISODate("2019-01-26T18:33:10.674Z"),
"repliedToUser" : "#karthe99",
"repliedUserId" : "5bbc305950a1051dac1b1c96",
"read" : false
},
{
"_id" : "b886f8da-2643-4eca-9d8a-53f90777f492",
"content" : "dasda",
"createdDate" : ISODate("2019-01-26T18:33:15.461Z"),
"repliedToUser" : "#karthe50",
"repliedUserId" : "5c4bd8914807e208b8a4212b",
"read" : true
},
{
"_id" : "b56hy4rt-2343-8tgr-988a-c4f90598h492",
"content" : "dasda",
"createdDate" : ISODate("2019-01-26T18:33:15.461Z"),
"repliedToUser" : "#karthe50",
"repliedUserId" : "5c4bd8914807e208b8a4212b",
"read" : true
}
],
"last_modified_by" : "karthe99",
"last_modified_date" : ISODate("2019-01-26T18:32:41.394Z")
}
What is the mistake in the query that I wrote?

MongoDB-design for revisioned data

There are many articles and SO questions about MongoDB data-model for storing old revisions of documents.
However, I found nothing satisfying one of my requirements; I need to be able to retroactively query the database to unambiguously find all documents that matched an arbitrary criteria for a given point in time.
To clarify, I need to be able to efficiently answer the question;
"Which documents (and preferably versions) matched criteria {X:Y...} at time T".
Pseudocode:
/* Would match a version that were active from 2010 - 2016-05-01 with zipcode 12345 */
db.my_objs.find({zipcode: "12345", ~time: ISODate("2016-01-01 22:14:31.003")~})
I haven't managed to find any solution, neither on google nor myself. I have tried;
Having a simple "from"-timestamp on data, and then select "the first item before my queried timepoint, that also matches other criteria", but I have not managed to express that in Mongo.
Having a from/to on each version, and whenever I write a new version, update "to" on the previous version to match from on the new version. However, I have not found a way to do this atomically or with eventual consistency, meaning multiple updates could wreak havoc and create ambiguous timelines. (Double entries for the same timepoint)
Any ideas?
edit
an undesirable example query for #1
db.my_objs.find({
data : {
$elemMatch : {
from : {
$lte : ISODate('2015-01-01')
}
}
}
}, {
"data.$" : 1
}).forEach(function (obj) {
    if(obj.data[0].state == 'active') {
printjson(registrar)
}
})–
aggregation framework and $unwind phase which transforms array into single document so we can create sophisticated $match condition
Example Document
{
"_id" : ObjectId("577275589ea91b3799341aba"),
"title" : "Test of design",
"firstCreated" : ISODate("2016-06-28T13:02:16.156Z"),
"lastUpdated" : ISODate("2016-06-28T13:02:16.156Z"),
"firstAuthor" : "profesor79",
"lastAuthor" : "Rawler",
"versions" : [{
"versionId" : 1.0,
"dateCreated" : ISODate("2015-10-10T00:00:00.000Z"),
"datePublished" : ISODate("2015-10-12T00:00:00.000Z"),
"isActive" : false,
"documnetPayload" : {
"a" : 1.0,
"b" : 2.0,
"c" : 3.0
}
}, {
"versionId" : 2.0,
"dateCreated" : ISODate("2015-12-10T00:00:00.000Z"),
"datePublished" : ISODate("2015-12-31T00:00:00.000Z"),
"isActive" : true,
"documnetPayload" : {
"a" : 1.0,
"b" : 3.0,
"c" : 30.0
}
}, {
"versionId" : 3.0,
"dateCreated" : ISODate("2016-01-31T00:00:00.000Z"),
"datePublished" : ISODate("2016-02-21T00:00:00.000Z"),
"isActive" : true,
"documnetPayload" : {
"a" : 11.0,
"b" : 3.0,
"c" : 31.0
}
}
]
}
Aggregation framework example
db.rawler.aggregate([{
$match : {
"_id" : ObjectId("577275589ea91b3799341aba")
}
}, {
$unwind : "$versions"
}, {
$match : {
$and : [{
"versions.dateCreated" : {
$gt : ISODate("2015-10-10T00:00:00.000Z")
}
}, {
"versions.dateCreated" : {
$lte : ISODate("2016-01-30T00:00:00.000Z")
}
}
],
"versions.datePublished" : {
$gt : new Date("2015-10-13T00:00:00.000")
},
// "versions.versionId" :{$in:[1,3,4,5]},
}
}, {
$sort : {
"versions.dateCreated" : -1
}
},
])

Get specific object in array of array in MongoDB

I need get a specific object in array of array in MongoDB.
I need get only the task object = [_id = ObjectId("543429a2cb38b1d83c3ff2c2")].
My document (projects):
{
"_id" : ObjectId("543428c2cb38b1d83c3ff2bd"),
"name" : "new project",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b"),
"members" : [
ObjectId("5424ac37eb0ea85d4c921f8b")
],
"US" : [
{
"_id" : ObjectId("5434297fcb38b1d83c3ff2c0"),
"name" : "Test Story",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b"),
"tasks" : [
{
"_id" : ObjectId("54342987cb38b1d83c3ff2c1"),
"name" : "teste3",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
},
{
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
]
}
]
}
Result expected:
{
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
But i not getting it.
I still testing with no success:
db.projects.find({
"US.tasks._id" : ObjectId("543429a2cb38b1d83c3ff2c2")
}, { "US.tasks.$" : 1 })
I tryed with $elemMatch too, but return nothing.
db.projects.find({
"US" : {
"tasks" : {
$elemMatch : {
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2")
}
}
}
})
Can i get ONLY my result expected using find()? If not, what and how use?
Thanks!
You will need an aggregation for that:
db.projects.aggregate([{$unwind:"$US"},
{$unwind:"$US.tasks"},
{$match:{"US.tasks._id":ObjectId("543429a2cb38b1d83c3ff2c2")}},
{$project:{_id:0,"task":"$US.tasks"}}])
should return
{ task : {
"_id" : ObjectId("543429a2cb38b1d83c3ff2c2"),
"name" : "jklasdfa_XXX",
"author" : ObjectId("5424ac37eb0ea85d4c921f8b")
}
Explanation:
$unwind creates a new (virtual) document for each array element
$match is the query part of your find
$project is similar as to project part in find i.e. it specifies the fields you want to get in the results
You might want to add a second $match before the $unwind if you know the document you are searching (look at performance metrics).
Edit: added a second $unwind since US is an array.
Don't know what you are doing (so realy can't tell and just sugesting) but you might want to examine if your schema (and mongodb) is ideal for your task because the document looks just like denormalized relational data probably a relational database would be better for you.

Get nested fields with MongoDB shell

I've "users" collection with a "watchlists" field, which have many inner fields too, one of that is "arrangeable_values" (the second field within "watchlists").
I need to find for each user in "users" collection, each "arrangeable_values" within "watchlists".
How can I do that with mongodb shell ?
Here is an example of data model :
> db.users.findOne({'nickname': 'superj'})
{
"_id" : ObjectId("4f6c42f6018a590001000001"),
"nickname" : "superj",
"provider" : "github",
"user_hash" : null,
"watchlists" : [
{
"_id" : ObjectId("4f6c42f7018a590001000002"),
"arrangeable_values" : {
"description" : "My introduction presentation to node.js along with sample code at various stages of building a simple RESTful web service with journey, cradle, winston, optimist, and http-console.",
"tag" : "",
"html_url" : "https://github.com/indexzero/nodejs-intro"
},
"avatar_url" : "https://secure.gravatar.com/avatar/d43e8ea63b61e7669ded5b9d3c2e980f?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png",
"created_at" : ISODate("2011-02-01T10:20:29Z"),
"description" : "My introduction presentation to node.js along with sample code at various stages of building a simple RESTful web service with journey, cradle, winston, optimist, and http-console.",
"fork_" : false,
"forks" : 13,
"html_url" : "https://github.com/indexzero/nodejs-intro",
"pushed_at" : ISODate("2011-09-12T17:54:58Z"),
"searchable_values" : [
"description:my",
"description:introduction",
"description:presentation",
"html_url:indexzero",
"html_url:nodejs",
"html_url:intro"
],
"tags_array" : [ ],
"watchers" : 75
},
{
"_id" : ObjectId("4f6c42f7018a590001000003"),
"arrangeable_values" : {
"description" : "A Backbone alternative idea",
"tag" : "",
"html_url" : "https://github.com/maccman/spine.todos"
},
"avatar_url" : "https://secure.gravatar.com/avatar/baf018e2cc4616e4776d323215c7136c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png",
"created_at" : ISODate("2011-03-18T11:03:42Z"),
"description" : "A Backbone alternative idea",
"fork_" : false,
"forks" : 31,
"html_url" : "https://github.com/maccman/spine.todos",
"pushed_at" : ISODate("2011-11-20T22:59:45Z"),
"searchable_values" : [
"description:a",
"description:backbone",
"description:alternative",
"description:idea",
"html_url:https",
"html_url:github",
"html_url:com",
"html_url:maccman",
"html_url:spine",
"html_url:todos"
],
"tags_array" : [ ],
"watchers" : 139
}
]
}
For the document above, the following find() query would extract both the "nickname" of the document, and its associated "arrangeable_values" (where the document is in the users collection):
db.users.find({}, { "nickname" : 1, "watchlists.arrangeable_values" : 1 })
The result you get for your single document example would be:
{ "_id" : ObjectId("4f6c42f6018a590001000001"), "nickname" : "superj",
"watchlists" : [
{ "arrangeable_values" : { "description" : "My introduction presentation to node.js along with sample code at various stages of building a simple RESTful web service with journey, cradle, winston, optimist, and http-console.", "tag" : "", "html_url" : "https://github.com/indexzero/nodejs-intro" } },
{ "arrangeable_values" : { "description" : "A Backbone alternative idea", "tag" : "", "html_url" : "https://github.com/maccman/spine.todos" } }
] }
MongoDB queries return entire documents. You are looking for a field inside an array inside of the document and this will break the find().
The problem here is that any basic find() query, will return all matching documents. The find() does have the option to only return specific fields. But that will not work with your array of sub-objects. You could returns watchlists, but not watchlist entries that match.
As it stands you have two options:
Write some client-side code that loops through the documents and does the filtering. Remember that the shell is effectively a javascript driver, so you can write code in there.
Use the new aggregation framework. This will have a learning curve, but it can effectively extract the sub-items you're looking for.