How to use the for loop over multiple criteria in mongodb from spring - spring

I am trying to use the for loop over the criteria in the spring Dao to return a list of documents for each scenario matched in the list:
mongoDb contains multiple documents for the same studentId with different times.
I have a Array list with studentId's and times that I want to query on mongodb
The input looks like this
List <StudentAssociation> ={{studentId:st1234,assocstarttime=2019-08-16,assocendtime:2019-08-17},{studentId:st3456,assocstarttime=2019-07-09, assocendtime=2019-08-15}}
The query I wrote for a single student working without the for loop, but I want to send the list as the input
Working query
public List<Student> getListOfStudentRecords(String studentId, LocalDateTime assocstarttime, LocalDatetime assocendtime) {
Query query = new Query();
Criteria studentIdCriteria = Criteria
.where("studentId)
.is(studentId);
Criteria dateTimeCriteria = Criteria
.where(datetime)
.gte(assocstarttime.toString())
.lte(assocendtime.toString());
query.addCriteria(studentIdCriteria);
query.addCriteria(dateTimeCriteria);
return mongoTemplate.find(query, Student.class);
}
What I am trying is input the list, the following query
public List getListofStudentRecordsUsingList(List studentAssoc) {
Query query = new Query();
//for loop
for(StudentAssociation sa : studentAssoc) {
Criteria studentIdCriteria = Criteria
.where("studentId)
.is(studentId);
Criteria dateTimeCriteria = Criteria
.where(datetime)
.gte(sa.getAssocStarttime())
.lte(sa.getAssocEndtime());
query.addCriteria(studentIdCriteria);
query.addCriteria(dateTimeCriteria);
}
query.with(new Sort(Sort.Direction.ASC, datetime));
return mongoTemplate.find(query, Student.class);
}
Expected output is for each student in the list return list of documents and then merge the result for each student into a single list and sort based on time field to create a timeline
The error I am getting withe second query is
//this is solved when I moved the Query query = new Query(); inside the for Loop
InvalidMongoDbApiUsageException: Due to limitations of the com.mongodb.BasicDocument, you can't add a second 'studentId' criteria. Query already contains '{ "lStudentId" : "st1234" }' //this is resolved when I move the query inside the for loop

Your Query object definition is outside of loop, so the same object is used for your whole loop. At second pass, query variable already contains a 'studentId' criteria, as explained in your error.
Try to set query inside loop, or clear your object at start of loop.
//for loop
for(StudentAssociation sa : studentAssoc) {
Query query = new Query();
...

Query in a loop is such an expensive thing, i would recommend you to use and & in for reduce the number of queries.
Mongodb provides operator like :
The $and operator evaluates one or more expressions and returns true if all of the expressions are true or if evoked with no argument expressions. Otherwise, $and returns false.
The $in operator selects the documents where the value of a field equals any value in the specified array.
Query findStudent = new Query(Criteria.where("studentId").in('List of id')
.and('datetime').gte('you should filter min time')
.lte('max time'))
.with(new Sort(Sort.Direction.ASC, datetime));

Related

How to get list of a field value as the result in mongodb with Spring Data

I want to get a list of a field's value from documents.
How can I write the following in spring
db.collection.find({"name" : "tom"}).map(function(u) return u.age)
If I use
Aggregation.match(Criteria.where("name).in("tom))
Aggregation.group("age")
I get list Objects with {_id, age} but I want List<String> ages
First things first, You don't need to map to get just the age field from the collection, you can just use projection.
Projection returns _id by default, so, you might want to specify it to be not returned if that's so.
i.e db.collection.find({}, {'age': 1, '_id': 0})
In Spring Data, you can perform a similar operation with include() and exclude().
i.e
Query query = new Query();
query.fields().include("age").exclude("id");
List<User> usersWithJustAgeInformation = mongoTemplate.find(query, User.class);
but this will also return an object of type User from which you can very easily filter the List <String> ages with stream, map and collect.
Eg.
Query query = new Query();
query.fields().include("age").exclude("id");
List<String> ages = mongoTemplate.find(query, User.class).stream().map(User::getAge).collect(Collectors.toList());
Please read https://www.baeldung.com/spring-data-mongodb-projections-aggregations for more information and examples of projection and aggregation.

Elasticsearch query not returning expected results for multiple should filters

I am performing an Elasticsearch query using the high-level-rest-api for Java and expect to see records that are either active or do not have a reference id. I'm querying by name for the records and if I hit the index directly with /_search?q=, I see the results I want.
Is my logic correct (pseudo-code):
postFilters.MUST {
Should {
MustNotExist {referenceId}
Must {status = Active}
}
Should {
MustNotExist {referenceId}
Must {type = Person}
}
}
What I get are records that are active with a reference id. But, I want to include records that also do not have a referenceId, hence why I have MustNotExist {referenceId}.
For simplicity, the second Should clause can be dropped (for testing) as the first one is not working as expected by itself.
In my case, I had to use a match query instead of a term query because the value I was querying for was not a primitive or a String. For example, the part where Must, type = Person, Person was an enum, and so looking for "Person" was not quite right, whereas match allowed it to "match".

If a linq query returns empty does it return null?

I have the following linq query:
vm.logs = (from l in db.ActivityLogs
orderby l.Time
select l).Take(2);
If the db table is empty will this return null?
If not how can I detect if a query did return any information?
It will return an IEnumerable<ActivityLog> with no elements.
To check if there are any elements, use the Any() method:
if(!logs.Any())
{
Console.WriteLine("No elements found.");
}
Also note that as you've written it, vm.logs will be lazily evaluated, that is, it won't be fetched from the database until it is used. If you first do a .Any() and then later access the contents of the query, there will be two queries executed in the database. To avoid that, materialize (force the query to execute) by adding a ToList() to the query:
vm.logs = (from l in db.ActivityLogs
orderby l.Time
select l).Take(2).ToList();

Finding items from a list in an array stored in a DB field

I have a legacy database that has data elements stored as a comma delimited list in a single database field. (I didn't design that, I'm just stuck with it.)
I have a list of strings that I would like to match to any of the individual values in the "array" in the DB field and am not sure how to do this in Linq.
My list:
List<string> items= new List<string>();
items.Add("Item1");
items.Add("Item2");
The DB field "Products" would contain data something like:
"Item1,Item3,Item4"
"Item3,Item5,Item6"
"Item2,Item7,Item6"
"Item1,Item2"
"Item1"
My first pass at the Linq query was:
var results = (from o in Order
.Where(p=> items.Contains(p.Products)
But I know that won't work. because it will only return the records that contain only "Item1" or "Item2". So with the example data above it would return 0 records. I need to have it return two records.
Any suggestions?
There is a simple clever trick for searching comma-separated lists. First, add an extra , to the beginning and end of the target value (the product list), and the search value. Then search for that exact string. So for example, you would search ,Item1,Item3,Item4, for ,Item1,. The purpose of this is to prevent false positives, i.e., Item12,Item3 finding a match for Item1, while allowing items at the beginning/end of the list to be properly found.
Then, you can use the LINQ .Any method to check that any item in your list is a match to the product list, like the following:
var results = (from o in Order
.Where(o => items.Any(i => (","+o.Products+",").Contains(","+i+",")))
One way would be to parse the list in the Products field:
var results = (from o in Order
.Where(o => items.Any(i => o.Products.Split(',').Contains(i))
But that would parse the string multiple times for each record. You could try pulling back ALL of the records, parsing each record once, then doing the comparison:
var results = from o in Order
let prods = o.Products.Split(',')
where items.Any(i => prods.Contains(i))
select o;

Create a linq subquery returns error "Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator"

I have created a linq query that returns my required data, I now have a new requirement and need to add an extra field into the returned results. My entity contains an ID field that I am trying to map against another table without to much luck.
This is what I have so far.
Dictionary<int, string> itemDescriptions = new Dictionary<int, string>();
foreach (var item in ItemDetails)
{
itemDescriptions.Add(item.ItemID, item.ItemDescription);
}
DB.TestDatabase db = new DB.TestDatabase(Common.GetOSConnectionString());
List<Transaction> transactionDetails = (from t db.Transactions
where t.CardID == CardID.ToString()
select new Transaction
{
ItemTypeID= t.ItemTypeID,
TransactionAmount = t.TransactionAmount,
ItemDescription = itemDescriptions.Select(r=>r.Key==itemTypeID).ToString()
}).ToList();
What I am trying to do is key the value from the dictonary where the key = itemTypeID
I am getting this error.
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
What do I need to modify?
This is a duplicate of this question. The problem you're having is because you're trying to match an in-memory collection (itemDescriptions) with a DB table. Because of the way LINQ2SQL works it's trying to do this in the DB which is not possible.
There are essentially three options (unless I'm missing something)
1) refactor your query so you pass a simple primitive object to the query that can be passed accross to the DB (only good if itemDescriptions is a small set)
2) In your query use:
from t db.Transactions.ToList()
...
3) Get back the objects you need as you're doing, then populate ItemDescription in a second step.
Bear in mind that the second option will force LINQ to evaluate the query and return all transactions to your code that will then be operated on in memory. If the transaction table is large this will not be quick!

Resources