MongoDB UpdateMany with $in and upsert - java

Mongo collection named persons1 contains the following data:
db.persons1.find().pretty();
{ "_id" : "Sims", "count" : 32 }
{ "_id" : "Autumn", "count" : 35 }
{ "_id" : "Becker", "count" : 35 }
{ "_id" : "Cecile", "count" : 40 }
{ "_id" : "Poole", "count" : 32 }
{ "_id" : "Nanette", "count" : 31 }
Now through Java I have written the code to increment the count for the users which are present in the list
MongoClient mongoclient = new MongoClient("localhost", 27017);
MongoDatabase db = mongoclient.getDatabase("testdb1");
MongoCollection<Document> collection = db.getCollection("persons1");
List li = new ArrayList();
li.add("Sims");
li.add("Autumn");
collection.updateMany(
in("_id",li),
new Document("$inc", new Document("count", 1)),
new UpdateOptions().upsert(true));
After I run the above java program my output was as below.
db.persons1.find().pretty();
{ "_id" : "Sims", "count" : 33 }
{ "_id" : "Autumn", "count" : 36 }
{ "_id" : "Becker", "count" : 35 }
{ "_id" : "Cecile", "count" : 40 }
{ "_id" : "Poole", "count" : 32 }
{ "_id" : "Nanette", "count" : 31 }
My question: Is it possible to Insert and start the count from 1, for the entry present in the Array list and not present in the persons1 Collection?
Problem Description:
Before Program database contains details as follows:
{ "_id" : "Sims", "count" : 33 }
{ "_id" : "Autumn", "count" : 36 }
{ "_id" : "Becker", "count" : 35 }
{ "_id" : "Cecile", "count" : 40 }
{ "_id" : "Poole", "count" : 32 }
{ "_id" : "Nanette", "count" : 31 }
Sample Java code:
MongoClient mongoclient = new MongoClient("localhost", 27017);
MongoDatabase db = mongoclient.getDatabase("testdb1");
MongoCollection<Document> collection = db.getCollection("persons1");
List li = new ArrayList();
// Entry already Present so required to increment by 1
li.add("Sims");
// Entry already Present so required to increment by 1
li.add("Autumn");
// Entry is NOT Present, hence insert into persons data base with "_id" as User1 and count as 1
li.add("User1");
// Entry is NOT Present, hence insert into persons data base with "_id" as User1 and count as 1
li.add("User2");
// Code to be written
What should be the code to get the out put from the database as shown below:
{ "_id" : "Sims", "count" : 34 } // Entry already Present, incremented by 1
{ "_id" : "Autumn", "count" : 37 } // Entry already Present, incremented by 1
{ "_id" : "Becker", "count" : 35 }
{ "_id" : "Cecile", "count" : 40 }
{ "_id" : "Poole", "count" : 32 }
{ "_id" : "Nanette", "count" : 31 }
{ "_id" : "User1", "count" : 1 } // Entry Not Present, start by 1
{ "_id" : "User2", "count" : 1 } // Entry Not Present, start by 1

The "catch" here is that $in arguments to _id will not be interpreted as a valid "filler" for the _id field within an "multi" flagged update, which is what you are doing. All the _id values will be populated by default ObjectId values instead on "upsert".
The way around this is to use "Bulk" operations, and with the Java 3.x driver you use the BulkWrite class and a construction like this:
MongoCollection<Document> collection = db.getCollection("persons1");
List li = new ArrayList();
li.add("Sims");
li.add("User2");
List<WriteModel<Document>> updates = new ArrayList<WriteModel<Document>>();
ListIterator listIterator = li.listIterator();
while ( listIterator.hasNext() ) {
updates.add(
new UpdateOneModel<Document>(
new Document("_id",listIterator.next()),
new Document("$inc",new Document("count",1)),
new UpdateOptions().upsert(true)
)
);
}
BulkWriteResult bulkWriteResult = collection.bulkWrite(updates);
That manipulates your basic List into UpdateOneModel objects with a list that is suitable for bulkWrite, and all "individual" updates are sent in the one request with the one response, even though they are "technically" mulitple update statements.
This is the only way that is valid to set multiple _id keys or matches via $in in general with update operations.

Related

MongoDB Search nested Objects without knowing Key

I have a list of objects that are given somewhat arbitrary Object keys as a result of using the async Java driver + BSON.
My issue is given the fact that jobStatuses are an arbitrary list of Dictionary items where I don't know the key, I have no idea how to access its sub-values. In the end, I'm trying to build a query that returns if ANY of jobStatus.*._id are true given a list of potential Object ID's.
So I'd be giving a list of ID's and want to return true if ANY of the items in jobStatuses have any of the given ID's. Any ideas?
Let's try this :
db.yourCollectionName.aggregate([
{
$project: {
_id: 0,
jobStatutses: { $arrayElemAt: [{ $objectToArray: "$jobStatutses" }, 0] }
}
}, {
$match: { 'jobStatutses.v._id': { $in: [ObjectId("5d6d8c3a5a0d22d3c84dd6dc"), ObjectId("5d6d8c3a5a0d22d3c84dd6ed")] } }
}
])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e06319c400289966eea6a07"),
"jobStatutses" : {
"5d6d8c3a5a0d22d3c84dd6dc" : {
"_id" : ObjectId("5d6d8c3a5a0d22d3c84dd6dc"),
"accepted" : "123",
"completed" : 0
}
},
"something" : 1
}
/* 2 */
{
"_id" : ObjectId("5e0631ad400289966eea6dd1"),
"jobStatutses" : {
"5d6d8c3a5a0d22d3c84dd6ed" : {
"_id" : ObjectId("5d6d8c3a5a0d22d3c84dd6ed"),
"accepted" : "456",
"completed" : 0
}
},
"something" : 2
}
/* 3 */
{
"_id" : ObjectId("5e0631cd400289966eea7542"),
"jobStatutses" : {
"5e06319c400289966eea6a07" : {
"_id" : ObjectId("5e06319c400289966eea6a07"),
"accepted" : "789",
"completed" : 0
}
},
"something" : 3
}
Output :
/* 1 */
{
"jobStatutses" : {
"k" : "5d6d8c3a5a0d22d3c84dd6dc",
"v" : {
"_id" : ObjectId("5d6d8c3a5a0d22d3c84dd6dc"),
"accepted" : "123",
"completed" : 0
}
}
}
/* 2 */
{
"jobStatutses" : {
"k" : "5d6d8c3a5a0d22d3c84dd6ed",
"v" : {
"_id" : ObjectId("5d6d8c3a5a0d22d3c84dd6ed"),
"accepted" : "456",
"completed" : 0
}
}
}
All you need is to check if at least one doc gets returned from DB for a given list or not, So we don't need to worry about document structure then just do result.length in your code to say at least one doc got matched for the input list.

Mongo Java - list all documents which has data as phone number or credit card number or zip code when field name is not known to us

Could you please suggest a option in Mongo Java to find all documents which has data as phone number or credit card number or zip code, when field name is not known to us.
I tried this but this is not working at all.
AggregateIterable<Document> output = collection.aggregate(
Arrays.asList(
new Document(
"$project", new Document(
"x", new Document(
"$objectToArray", "$$CURRENT"
)
)
),
new Document("$unwind", "$x"),
new Document(
"$match", new Document(
"x.k", new Document(
"$in", new BasicDBObject("$regex", "^(ISBN(-10)?:? )?([-0-9xX ]{13}|[0-9X]{10})$")
)
)
)
)
);
For example Input
{
"_id" : ObjectId("5a0bf2604510c319181df436"),
"name" : "Oneindb2",
"number" : 11,
"phonenumber" : 111111
}
{
"_id" : ObjectId("5a0bf2604510c319181df437"),
"dname" : "creditCardtwoindb2",
"zipc" : "5670228"
}
{
"_id" : ObjectId("5a0bf2604510c319181df439"),
"name" : "creditCardfourindb2",
"CHECKnumber" : 56,
"checkISO" : "second_iso"
}
{
"_id" : ObjectId("5a0bf2604510c319181df43a"),
"name" : "creditCardfiveindb2",
"ddnumber" : 56,
"ISOS" : "second_iso"
}
{
"_id" : ObjectId("5a0bf2604510c319181df438"),
"cname" : " 57656yuio89789"
"personnumber" : 33,
"NISO" : "three_iso"
}
*Expected output is*
{
"_id" : ObjectId("5a0bf2604510c319181df436"),
"name" : "Oneindb2",
"number" : 11,
"pnumber" : 111111
}
{
"_id" : ObjectId("5a0bf2604510c319181df437"),
"dname" : "creditCardtwoindb2",
"zipc" : "5670228"
}
{
"_id" : ObjectId("5a0bf2604510c319181df438"),
"cname" : " 57656yuio89789"
"number" : NumberInt(33),
"ISO11" : "three_iso"
}
because these documents fields (at least one field) has either phone number or credit card number or zip code
Note: field names are not same in all the documents it may vary.

Aggregation inconsistently prints different value

I have the following code snippet that should retrieve the total count of the account using spring-data-mongodb
TypedAggregation<Account> agg = Aggregation.newAggregation(Account.class,
group("user.id"),
group().count().as("total"));
AggregationResults<AccountTotal> result = mongos.aggregate(agg, AccountTotal.class);
AccountTotal account = result.getMappedResults().get(0);
account.getTotal(); // should print 90 but prints 1
Here is the equivalent mongo script returning from the agg field that I use in the mongo shell prints 90
{ "$group" : { "_id" : "$user.id"}} ,
{ "$group" : { "_id" : null , "total" : { "$sum" : 1}}}
> db.accounts.aggregate(
[
{ "$group" : { "_id" : "$user.id"}} ,
{ "$group" : { "_id" : null , "total" : { "$sum" : 1}}}
])
What am I missing actually that I get 1 in the Java platform.
EDIT:
After changing the previous one with the following one I get the expected count:
Aggregation agg = Aggregation.newAggregation(
group("user.id"),
group().count().as("total"));
AggregationResults<AccountTotal> result =
mongos.aggregate(agg, this.getCollectionName(), AccountTotal.class);
Btw, thanks #chridam.
The reason you are getting 1 is because of the current aggregation pipeline which returns all 90 documents grouped by user.id in an array and each may have a total of 1 (I guess). This line result.getMappedResults().get(0) will get the first element in the aggregation results and that element has a total of 1. The total you are trying to get is the total of all the grouped documents i.e. the length of the aggregation result cursor array.
I believe you want to group all the documents by the $user.id field, get the count on each grouped result and then do another $group operation to get the sum of all the group counts:
> db.accounts.aggregate(
[
{ "$group" : { "_id" : "$user.id", "count" : { "$sum" : 1 } } },
{ "$group" : { "_id" : null, "total" : { "$sum" : "$count" } } }
])
which will give you the desired results. The Spring aggregation equivalent
TypedAggregation<Account> agg = Aggregation.newAggregation(Account.class,
group("user.id").count().as("count"),
group().sum("count").as("total"));
AggregationResults<AccountTotal> result = mongos.aggregate(agg, AccountTotal.class);
List<AccountTotal> accountCount = result.getMappedResults();
(accountCount.get(0).total == 90); // should be true

Multiple update in mongodb using java

I've this document:
{
"_id" : ObjectId("54140782b6d2ca6018585093"),
"user_id" : ObjectId("53f4ae1ae750619418a20467"),
"date" : ISODate("2014-09-13T08:59:46.709Z"),
"type" : 0,
"tot" : 2,
"additional_info" : {
"item_id" : ObjectId("540986159ef9ebafd3dcb5d0"),
"shop_id" : ObjectId("53f4cc5a6e09f788a103d0a4"),
"ap_id" : ObjectId("53f4cc5a6e09f788a103d0a5")
},
"transactions" : [
{
"_id" : ObjectId("54140782b6d2ca6018585091"),
"date_creation" : ISODate("2014-09-13T08:59:46.711Z"),
"type" : -1
},
{
"_id" : ObjectId("54140782b6d2ca6018585092"),
"date_creation" : ISODate("2014-09-13T08:59:46.788Z"),
"type" : 1
}
]
}
and I need to add 2 more field to the first transaction opbject:
- date_execution: date
- result: this bson document
{ "server_used" : "xxx.xxx.xxx.xxx:27017" , "ok" : 1 , "n" : 1 , "updated_executed" : true} (m_OR.getDocument() in the following code example)
to obtaing that document
{
"_id" : ObjectId("54140811b6d25137753c1a1a"),
"user_id" : ObjectId("53f4ae1ae750619418a20467"),
"date" : ISODate("2014-09-13T09:02:09.098Z"),
"type" : 0,
"tot" : 2,
"additional_info" : {
"item_id" : ObjectId("540986159ef9ebafd3dcb5d0"),
"shop_id" : ObjectId("53f4cc5a6e09f788a103d0a4"),
"ap_id" : ObjectId("53f4cc5a6e09f788a103d0a5")
},
"transactions" : [
{
"_id" : ObjectId("54140811b6d25137753c1a18"),
"date_creation" : ISODate("2014-09-13T09:02:09.100Z"),
"type" : -1,
"result" : {
"server_used" : "xxx.xxx.xxx.xxx:27017",
"ok" : 1,
"n" : 1,
"updated_executed" : true
},
"date_execution" : ISODate("2014-09-13T09:02:15.370Z")
},
{
"_id" : ObjectId("54140811b6d25137753c1a19"),
"date_creation" : ISODate("2014-09-13T09:02:09.179Z"),
"type" : 1
}
]
}
The only way I was able to do that is the do 2 separates updates (update is a my wrapper funciont that execute the real updates in mongodb and it works fine):
// where
BasicDBObject query = new BasicDBObject();
query.append("transactions._id", m_Task.ID());
// new value for result - 1st upd
BasicDBObject value = new BasicDBObject();
value.put("$set",new BasicDBObject("transactions.$.date_execution",new Date()));
update(this._systemDB, "activities", query, value);
// new value for date_execution - 2nd upd
value = new BasicDBObject();
value.put("$set",new BasicDBObject("transactions.$.result",m_OR.getDocument()));
update(this._systemDB, "activities", query, value);
If I try to do this:
BasicDBObject value = new BasicDBObject();
value.put("$set",new BasicDBObject("transactions.$.date_execution",new Date()));
value.put("$set",new BasicDBObject("transactions.$.result",m_OR.getDocument()));
or = update(this._systemDB, "activities", query, value);
just the 2nd set will be applied.
Is there any way do avoid the double execution and apply the update with just one call?
Basic rule of "hash/map" objects is that you can only have one key. It's the "highlander" rule ( "There can be only one" ) applied in general reason. So just apply differently:
BasicDBObject value = new BasicDBObject();
value.put("$set",
new BasicDBObject("transactions.$.date_execution",new Date())
.add( new BasicDBObject("transactions.$.result",m_OR.getDocument() )
);
So basically "both" field arguments are part of the "$set" statement as in the serialized form:
{
"$set": {
"transactions.$.date_execution": new Date(),
"transactions.$.result": m_Or.getDocument()
}
}
Which is basically what you want in the end.
Your suggestion was right, just had to fix a little the syntax this way:
BasicDBObject value = new BasicDBObject();
value.put("$set",
new BasicDBObject("transactions.$.date_execution",new Date())
.append("transactions.$.result",m_OR.getDocument())
);
This worked perfectly ;)
Thanks!
Samuel

How to find the smallest number in a mongo query?

I have the following objects in my mongo db:
{
"type" : "timetype"
"time" : "18"
}
{
"type" : "timetype"
"time" : "5"
}
{
"type" : "timetype"
"time" : "43"
}
{
"type" : "timetype"
"time" : "23"
}
And my java code looks like:
BasicDBObject query = new BasicDBObject();
query.put("type", "timetype");
//I don't know what to put in the (...)
DBCursor cursor = Collection.find(query).sort(...).limit(1);
I would like to find the entry with the smallest time in my mongo database that meets the query parameter but I don't know what to put in sort to make that happen.
Something along the lines of
Collection.find(query).sort(new BasicDBObject( "time" , 1 )).limit(1);
should work.

Categories

Resources