Get total of field of Mongodb collection in Java - java

I can find the answer (think?) for plain Mongodb querying, but nothing for Java for this particular use case...
I have a collection that is typed to the following object:
public class TestObj {
ObjectId id;
long value = 0;
// etc
}
I would like the sum of the value field across all objects held in the collection. How can I do this?
My current hangup is no matter wat I do, the aggregate method from the collection returns the type TestObj, and not a more generic object with the answer I seek. I have tried project, but didn't seem to have any effect.
Current code:
public long getTotalValue(){
Object returned = this.getCollection().aggregate(
List.of(
Aggregates.group(null, Accumulators.sum("$value", 1))
)
);
//TODO:: get sum
return 0L;
}

If you want to count how many collections have the value field you can use count in aggregation.
But if you want the sum of the values that the value field has in your collections you can calculate it like this with mongoDb aggregation
Arrays.asList(new Document("$group",
new Document("_id",
new BsonNull())
.append("value",
new Document("$sum", "$value"))))
value is the name of your field in the collection.
Full example:
Document returned = this.getCollection().aggregate(
List.of(new Document(
"$group",
new Document(
"_id",
new BsonNull()
)
.append(
"value",
new Document("$sum", "$numExpired")
)
)),
Document.class
).first();
return returned.getLong("value");

Related

Join Custom lists in java

I have two custom lists as follows.
List<OfficeName> = [{id: 1, offname: "Office1"}{id: 2, offname: "Office2"}]
List<OfficeLocation> = [{id: 1, offlocation: "location1"}{id: 2, offlocation: "locatio2"}]
I want result as follows:
list<OfficeDetails> = [{id: 1, offname: "Office1",offlocation: "location1" },
{id: 2, offname: "Office2", offlocation: "location2"}]
The first two lists needs to be joined on basis of "id" to give a new list which is equivalent to the join operation in sql tables.
My model classes are
public class OfficeName {
int id;
String offname;
//getter and setter
}
.................
public class OfficeLocation{
int id;
String offlocation;
//getter and setter
}
.........
Currently I am Iterating and manually adding as follows to a LinkedHashSet .
{
List<OfficeName> officeName = new ArrayList<OfficeName>();
onr.findById(id).forEach(officeName::add); // adding values from auto wired Repository
List<OfficeLocation> officeLocation = new ArrayList<OfficeLocation>();
olr.findById(id).forEach(officeLocation::add); // adding values from auto wired Repository
LinkedHashSet<LinkedHashSet<String>> lhs = new LinkedHashSet<LinkedHashSet<String> >();
OfficeName officeName1 = new OfficeName();
OfficeLocation officeLocation1 = new OfficeLocation();
Iterator<OfficeName> onIterator = officeName.iterator();
Iterator<OfficeLocation> olIterator = officeLocation.iterator();
while (onIterator.hasNext()) {
officeName1 =onIterator.next();
int idon =officeName1.getId();
while(olIterator.hasNext()){
officeLocation1 = olIterator.next();
int idol = officeLocation1.getId();
if(idon==idol)
{
lhs.add(new LinkedHashSet<String>(Arrays.asList( String.valueOf(officeName1.getId()),officeName1.getOffname(),officeLocation1.getOfflocation())));
olIterator.remove();
break;
}
};
}
I am not sure whether this is correct way to achieve the same as I am new to java. In C#, this could able to achieve through data tables. Please suggest whether there is any faster way?
Assuming both input lists:
Are distinct, with no duplicate id values in either, and…
Are complete, with a single object in both lists for each possible id value
… then we can get the work done with little code.
I use NavigableSet or SortedSet implementations to hold our input lists, the names and the locations. Though I have not verified, I assume being sorted will yield better performance when searching for a match across input collections.
To get the sorting done, we define a Comparator for each input collection: Comparator.comparingInt( OfficeName :: id ) & Comparator.comparingInt( OfficeLocation :: id ) where the double-colons make a method reference. To each NavigableSet we add the contents of our inputs, an unmodifiable list made with the convenient literals syntax of List.of.
To get the actual work done of joining these two input collections, we make a stream of either input collection. Then we produce a new object of our third joined class using inputs from each element of the stream plus its counterpart found via a stream of the other input collection. These newly produced objects of the third joined class are then collected into a list.
NavigableSet < OfficeName > officeNames = new TreeSet <>( Comparator.comparingInt( OfficeName :: id ) );
officeNames.addAll( List.of( new OfficeName( 1 , "Office1" ) , new OfficeName( 2 , "Office2" ) ) );
NavigableSet < OfficeLocation > officeLocations = new TreeSet <>( Comparator.comparingInt( OfficeLocation :: id ) );
officeLocations.addAll( List.of( new OfficeLocation( 1 , "location1" ) , new OfficeLocation( 2 , "locatio2" ) ) );
List < Office > offices = officeNames.stream().map( officeName -> new Office( officeName.id() , officeName.name() , officeLocations.stream().filter( officeLocation -> officeLocation.id() == officeName.id() ).findAny().get().location() ) ).toList();
Results:
officeNames = [OfficeName[id=1, name=Office1], OfficeName[id=2, name=Office2]]
officeLocations = [OfficeLocation[id=1, location=location1], OfficeLocation[id=2, location=locatio2]]
offices = [Office[id=1, name=Office1, location=location1], Office[id=2, name=Office2, location=locatio2]]
Our three classes, the two inputs and the third joined one, are all written as records here for their convenient brevity. This Java 16+ feature is a brief way to declare a class whose main purpose is to communicate data transparently and immutably. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString. Note that a record can be defined locally as well as nested or separate.
public record OfficeName( int id , String name ) { }
public record OfficeLocation( int id , String location ) { }
public record Office( int id , String name , String location ) { }
Given the conditions outlined above, we could optimize by hand-writing loops to manage the matching of objects across the input collections, rather than using streams. But I would not be concerned about the performance impact unless you had huge amounts of data that had proven to be a bottleneck. Otherwise, using streams makes for less code and more fun.
One of the lists (e.g. locations) should be converted into a map (HashMap) by a key on which the joining should be made, in this case id field.
Then, assuming that OfficeDetails class has an all-args constructor, the resulting list may be retrieved by streaming the other list offices and mapping its contents into new OfficeDetails, filling the remaining location argument by looking up the map.
List<OfficeName> offices = Arrays.asList(
new OfficeName(1, "Office1"), new OfficeName(2, "Office2"), new OfficeName(3, "Office3")
);
List<OfficeLocation> locations = Arrays.asList(
new OfficeLocation(1, "Location 1"), new OfficeLocation(2, "Location 2"), new OfficeLocation(4, "Location 4")
);
Map<Integer, OfficeLocation> mapLoc = locations
.stream()
.collect(Collectors.toMap(
OfficeLocation::getId,
loc -> loc,
(loc1, loc2) -> loc1 // to resolve possible duplicates
));
List<OfficeDetails> details = offices
.stream()
.filter(off -> mapLoc.containsKey(off.getId())) // inner join
.map(off -> new OfficeDetails(
off.getId(), off.getOffname(),
mapLoc.get(off.getId()).getOfflocation() // look up the map
))
.collect(Collectors.toList());
details.forEach(System.out::println);
Output (assuming toString is implemented in OfficeDetails):
{id: 1, offname: "Office1", offlocation: "Location 1"}
{id: 2, offname: "Office2", offlocation: "Location 2"}
If offices list is not filtered by condition mapLoc.containsKey, an implementation of LEFT JOIN is possible (when null locations are stored in the resulting OfficeDetails).
To implement RIGHT JOIN (with null office names and all available locations), a lookup map should be created for offices, and main iteration has to be run for locations list.
To implement FULL JOIN (where either name or location parts of OfficeDetails can be null), two maps need to be created and then joined:
Map<Integer, OfficeName> mapOff = offices
.stream()
.collect(Collectors.toMap(
OfficeName::getId,
off -> off,
(off1, off2) -> off1, // to resolve possible duplicates
LinkedHashMap::new
));
List<OfficeDetails> fullDetails = Stream.concat(mapOff.keySet().stream(), mapLoc.keySet().stream())
.distinct()
.map(id -> new OfficeDetails(
id,
Optional.ofNullable(mapOff.get(id)).map(OfficeName::getOffname).orElseGet(()->null),
Optional.ofNullable(mapLoc.get(id)).map(OfficeLocation::getOfflocation).orElseGet(()->null)
))
.collect(Collectors.toList());
fullDetails.forEach(System.out::println);
Output:
{id: 1, offname: "Office1", offlocation: "Location 1"}
{id: 2, offname: "Office2", offlocation: "Location 2"}
{id: 3, offname: "Office3", offlocation: null}
{id: 4, offname: null, offlocation: "Location 4"}

How to access a variable from java objects in a java collection?

I am using Mongo Java Driver, and I am trying to use filters on the collection.find() function. For example, when I have a key which is a java object, the class of which contains certain fields:
Document document = (Document) collection.find(and(
eq("m_seniority", key.getM_seniority()),
eq("m_currency",key.getM_currency()),
eq("m_redCode",key.getM_redCode()),
eq("m_companyId",key.getM_companyId())
)).first();
I use the above command. But when I want to do that in bulk, I am being passed a collection of keys, ( Collection keys ), I can't access particular variables of the java objects inside as below:
List<Document> docs = (List<Document>) collection.find(and(
eq("m_seniority", keys.getM_seniority()),
eq("m_currency",keys.getM_currency()),
eq("m_redCode",keys.getM_redCode()),
eq("m_companyId",keys.getM_companyId())
)).into(new ArrayList<Document>());
Because getters are not of the collection, but just the objects, i can't use getters on the collection. How do I do this?
To create an or query on all of the Collection keys:
List<Bson> keyFilters = new ArrayList<>();
// for each key create an 'and' filter on seniority, currency, redcode and companyid
for (Key key : keys) {
keyFilters.add(
Filters.and(eq("m_seniority", key.getM_seniority()),
Filters.eq("m_currency",key.getM_currency()),
Filters.eq("m_redCode",key.getM_redCode()),
Filters.eq("m_companyId",key.getM_companyId())
)
);
}
List<Document> docs = (List<Document>) collection.find(
// 'or' all of the individual key filters
Filters.or(keyFilters)
).into(new ArrayList<Document>());

Get the single value from mongodb Collection using java code

I want to get only the single value from Mongodb Collection.
Currently i am getting the Document which is FindIterable.
CustomObject obj = db
.getCollection("Collection",CustomObject.class)
.find(and(eq("field1", new BigDecimal(10409)),eq("field2", new BigDecimal(1))));
But , i dont want any result in to the Object or List.like in Oracle we use Query to get single Object :
SELECT NAME FROM EMPLOYEE_TABLE WHERE ID=10 AND DEPT_ID=23;
This query gives us the Single name of the employee on the basis of filter conditions, and we get the output in String type Object.
Same i want to get from the mongodb , i don't want to use any bean to populate the data. i want only single String object as result.
You may use the find method on Collection, passing a query and fields to retrieve:
BasicDBObject fields = new BasicDBObject();
fields.put("name", true);
BasicDBObject query = new BasicDBObject();
query.put("id", 13);
query.put("dept_id", 23);
DBCursor find = mongoTemplate.getCollection("example").find(query, select);
List<DBObject> list = find.toArray();
List<String> names = list.stream().map(o -> String.valueOf(o.get("name"))).collect(Collectors.toList());
If you are seeking for a function that returns just one document, then you can use findOne.

Query in DynamoDB using Java API

I need to implement a Query operation on DynamoDB. Right now I'm doing it by giving the HashKey and then filtering out the results according to my conditions on non-key attributes.
This is what I'm doing :
MusicData hashKey = new MusicData();
hashKey.setID(singer);
DynamoDBQueryExpression<MusicData> queryExpression = new DynamoDBQueryExpression<MusicData>().withHashKeyValues(hashKey);
List<MusicData> queryResult = mapper.query(MusicData.class, queryExpression);
for (MusicData musicData : queryResult) {
if( my condtions ) {
do something;
}
}
What I'm trying to do is to be able to do something like this :
MusicData hashKey = new MusicData();
hashKey.setID(singer);
hashKey.setAlbum(sampleAlbum);
hashKey.setSinger(duration);
DynamoDBQueryExpression<MusicData> queryExpression = new DynamoDBQueryExpression<MusicData>().withHashKeyValues(hashKey);
List<MusicData> queryResult = mapper.query(MusicData.class, queryExpression);
for (MusicData musicData : queryResult) {
if( my condtions ) {
do something;
}
}
And get results already filtered out. Is there a way to do this in DynamoDB?
Yes, you can ask DynamoDB to perform filtering of queries before it returns results. However, you will still incur the 'cost' of reading these items even though they are not returned to your client. This is still a good practice as it will eliminate unnecessary transfer of items over the network.
To do this you will call additional methods on your DynamoDBQueryExpression object, specifically withFilterExpression and addExpressionAttributeNamesEntry / addExpressionAttributeValuesEntry to complete the expression.
Without the specific example of what type of conditions you want to apply it is hard to give an example, but depending on how simple your condition you want to apply is you could contain it in just the withFilterExpression method.
DynamoDBQueryExpression<MusicData> queryExpression = new DynamoDBQueryExpression<MusicData>().withHashKeyValues(hashKey).withFilterExpression("foo > 10");

Mongo spring query where two fields are equal

I want to execute a query in java where path and _id are two fields of the mongo document.
I want to get results list where these two fields are equal in the document.
I have tried using the following query.But could not retrieve the results properly.Received empty list which is not the case.
List<Metadata> MetadataList= ops.find(new Query(Criteria.where("path").is("_id")), Metadata.class);
How to get results where two field values are equal in mongo.
What you are looking for is the $where operator in MongoDB. Standard query operations do not compare the values of one field against another. In order to do this, you need to employ the JavaScript evaluation server side which can actually compare the two field values:
BasicQuery query = new BasicQuery(
new BasicDBObject("$where", "return this._id == this.path")
);
<Metadata> MetadataList = ops.find(query, Metadata.class);
Or you can do the same thing with native operators through the $redact pipeline stage available to the aggregation framework.
Pretty sure there is no $redact support in spring mongo as yet, but you can wrap the aggregation operation with a class to do so:
public class CustomAggregationOperation implements AggregationOperation {
private DBObject operation;
public CustomAggregattionOperation (DBObject operation) {
this.operation = operation;
}
#Override
public DBObject toDBObject(AggregationOperationContext context) {
return context.getMappedObject(operation);
}
}
And use it like this:
Aggregation aggregation = newAggregation(
new CustomAggregationOperation(
new BasicDBObject(
"$redact",
new BasicDBObject("$cond",
new BasicDBObject()
.append("if", new BasicDBObject(
"$eq", Arrays.asList("$_id", "$path")
))
.append("then", "$$KEEP")
.append("else", "$$PRUNE")
)
)
)
);
AggregationResults<Metadata> results = ops.aggregate(
(TypedAggregation<Metadata>) aggregation, Metadata.class);
So basic MongoDB query operations do not compare field values against each other. To do this you need to follow one of the methods here.
You can use BasicDBObject to add condition.
Try something
BasicDBObject query = new BasicDBObject("path", new BasicDBObject("$eq", "_id");
collection.find(query);
Please refer the below link for more information
http://mongodb.github.io/mongo-java-driver/2.13/getting-started/quick-tour/

Categories

Resources