Querying firestore with orderBy whereEqual and startAfter - java

I would like to make arecyclerView with pagination in my project. However, I got stuck on the process.
My firestore data is built as follows (some of the documents has visibility=false as a value so I dont want them to appear):
Now, I had like to get the first 5 documents ordered by dateCreated and then to continue pagination by the last value that I previously received.
I tried using the following:
db
.collection( "Quotes" )
.whereEqualTo( "visibility", true )
.orderBy("dateCreated", Query.Direction.DESCENDING )
.startAfter("dateCreated", dateCreatedOrig
.get(dateCreatedOrig.size()-1))
.limit(5)
.get()
.addOnCompleteListener( task -> {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
...
}
}
dAdapter = new Ddapter(...);
ddapter.notifyDataSetChanged();
rv_Quotes.setAdapter( ddapter );
}
} );
Where dateCreatedOrig is List that contains the timestamp of the first query so I get the last time stamp to continue the pagination from there.
However, I get the following error:
java.lang.IllegalArgumentException: Too many arguments provided to startAfter(). The number of arguments must be less than or equal to the number of orderBy() clauses.
Any ideas?

The problem here is that you are passing 2 arguments to startAfter. If you see the documentation for startAfter, you will see that it:
Creates and returns a new Query that starts after the provided fields relative to the order of the query. The order of the field values must match the order of the order by clauses of the query
Meaning that you define the field name that will be used in the orderBy clause, not the startAfter, and both should be used together. So in you case, all that needs to be done it removing the first argument of startAt and it will work, so it could look like this:
db.collection( "Quotes" )
.whereEqualTo( "visibility", true )
.orderBy("dateCreated", Query.Direction.DESCENDING )
.startAfter(dateCreatedOrig.get(dateCreatedOrig.size()-1))
.limit(5)
.get()
.addOnCompleteListener(...);

Related

Change where clause for batch delete

I want to like to create a batch delete something like:
DELETE t WHERE t.my_attribute = ?
First try was:
private void deleteRecord( ) {
//loop
final MyRecord myRecord = new MyRecord();
myRecord.setMyAttribute(1234);
getDslContext().batchDelete(myRecord) .execute();
}
But here the SQL contains always the pk instead of my attribute.
Second try was to create a delete statement with a bind value, but here i found no solution how i can create a where clause with ?
//loop
getDslContext().delete( MY_RECORD ).where( ???)
.bind( 12234 );
Can anybody help me further?
The DELETE statement itself
Just add your comparison predicate as you would in SQL:
getDslContext()
.delete(T)
.where(T.MY_ATTRIBUTE.eq(12234))
.execute();
This is assuming you are using the code generator, so you can static import your com.example.generated.Tables.T table reference.
Batching that
You have two options of batching such statements in jOOQ:
1. Using the explicit batch API
As explained here, create a query with a dummy bind value as I've shown above, but don't execute it directly, use the Batch API instead:
// Assuming these are your input attributes
List<Integer> attributes = ...
Query query = getDslContext().delete(T).where(T.MY_ATTRIBUTE.eq(0));
getDSLContext()
.batch(query)
.bind(attributes
.stream().map(a -> new Object[] { a }).toArray(Object[][]::new)
).execute();
2. Collect individual executions in a batched connection
You can always use the convenient batched collection in jOOQ to transparently collect executed SQL and delay it into a batch:
getDslContext().batched(c -> {
for (Integer attribute : attributes)
c.dsl().getDslContext()
.delete(T)
.where(T.MY_ATTRIBUTE.eq(attribute)
.execute(); // Doesn't execute the query yet
}); // Now the entire batch is executed
In the latter case, the SQL string might be re-generated for every single execution, so the former is probably better for simple batches.
Bulk execution
However, why batch when you can run a single query? Just do this, perhaps?
getDslContext()
.delete(T)
.where(T.MY_ATTRIBUTE.in(attributes))
.execute();

Firestore order by and limit not working in kotlin

I have the following firestore setup:
-Root
- Queue
- item1
- time : 20
- item2
- time : 1
- 2000 more items, with a random time value
What i want is to show 40 items, with smallest time first so i do the following in kotlin:
val ref = firestore.collection("Queue")
orderBy?.let{
ref.orderBy(it)
}
limit?.let{
ref.limit(it)
}
return ref.get().get().toObjects(Queue::class.java)
It actually completly ignore my order by and limit statements. and is returning all items in the Queue collection, what am i doing wrong.
The documentation here:
https://firebase.google.com/docs/reference/android/com/google/firebase/firestore/Query
says that the orderBy and limit methods return a new query object, so maybe you should try
val ref = firestore.collection("Queue").orderBy("time").limit(40)
As per the update to your question, you could create a function that returns the query you want based on whether or not the orderBy and limit query modifiers are present. You would have to make that query object a var in order to make it mutable.

How to write QueryBuilder Query to clean set or list value in Cassandra

Given a table:
CREATE TABLE User (
id text,
emails set<text>,
PRIMARY KEY ((id))
)
How do I write the equivalent of the query below using QueryBuilder?
UPDATE User SET emails = {} where id='xxx'
By checking QueryBuilder class I found how to add/remove specific elements, and how to set a non-list, but not how to clear the list without specifying every element.
Setting to null apparently have the exact same result, so:
QueryBuilder.update("User")
.where(QueryBuilder.eq("id","xxx"))
.with(QueryBuilder.set("emails",null))
Did the trick.

Spring Batch Paging with sortKeys and parameter values

I have a Spring Batch project running in Spring Boot that is working perfectly fine. For my reader I'm using JdbcPagingItemReader with a MySqlPagingQueryProvider.
#Bean
public ItemReader<Person> reader(DataSource dataSource) {
MySqlPagingQueryProvider provider = new MySqlPagingQueryProvider()
provider.setSelectClause(ScoringConstants.SCORING_SELECT_STATEMENT)
provider.setFromClause(ScoringConstants.SCORING_FROM_CLAUSE)
provider.setSortKeys("p.id": Order.ASCENDING)
JdbcPagingItemReader<Person> reader = new JdbcPagingItemReader<Person>()
reader.setRowMapper(new PersonRowMapper())
reader.setDataSource(dataSource)
reader.setQueryProvider(provider)
//Setting these caused the exception
reader.setParameterValues(
startDate: new Date() - 31,
endDate: new Date()
)
reader.afterPropertiesSet()
return reader
}
However, when I modified my query with some named parameters to replace previously hard coded date values and set these parameter values on the reader as shown above, I get the following exception on the second page read (the first page works fine because the _id parameter hasn't been made use of by the paging query provider):
org.springframework.dao.InvalidDataAccessApiUsageException: No value supplied for the SQL parameter '_id': No value registered for key '_id'
at org.springframework.jdbc.core.namedparam.NamedParameterUtils.buildValueArray(NamedParameterUtils.java:336)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.getPreparedStatementCreator(NamedParameterJdbcTemplate.java:374)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.query(NamedParameterJdbcTemplate.java:192)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.query(NamedParameterJdbcTemplate.java:199)
at org.springframework.batch.item.database.JdbcPagingItemReader.doReadPage(JdbcPagingItemReader.java:218)
at org.springframework.batch.item.database.AbstractPagingItemReader.doRead(AbstractPagingItemReader.java:108)
Here is an example of the SQL, which has no WHERE clause by default. One does get created automatically when the second page is read:
select *, (select id from family f where date_created between :startDate and :endDate and f.creator_id = p.id) from person p
On the second page, the sql is modified to the following, however it seems that the named parameter for _id didn't get supplied:
select *, (select id from family f where date_created between :startDate and :endDate and f.creator_id = p.id) from person p WHERE id > :_id
I'm wondering if I simply can't use the MySqlPagingQueryProvider sort keys together with additional named parameters set in JdbcPagingItemReader. If not, what is the best alternative to solving this problem? I need to be able to supply parameters to the query and also page it (vs. using the cursor). Thank you!
I solved this problem with some intense debugging. It turns out that MySqlPagingQueryProvider utilizes a method getSortKeysWithoutAliases() when it builds up the SQL query to run for the first page and for subsequent pages. It therefore appends and (p.id > :_id) instead of and (p.id > :_p.id). Later on, when the second page sort values are created and stored in JdbcPagingItemReader's startAfterValues field it will use the original "p.id" String specified and eventually put into the named parameter map the pair ("_p.id",10). However, when the reader tries to fill in _id in the query, it doesn't exist because the reader used the non-alias removed key.
Long story short, I had to remove the alias reference when defining my sort keys.
provider.setSortKeys("p.id": Order.ASCENDING)
had to change to in order for everything to work nicely together
provider.setSortKeys("id": Order.ASCENDING)
I had the same issue and got another possible solution.
My table T has a primary key field INTERNAL_ID.
The query in JdbcPagingItemReader was like this:
SELECT INTERNAL_ID, ... FROM T WHERE ... ORDER BY INTERNAL_ID ASC
So, the key is: in some conditions, the query didn't return results, and then, raised the error above No value supplied for...
The solution is:
Check in a Spring Batch decider element if there are rows.
If it is, continue with chunk: reader-processor-writer.
It it's not, go to another step.
Please, note that they are two different scenarios:
At the beginning, there are rows. You get them by paging and finally, there are no more rows. This has no problem and decider trick is not required.
At the beginning, there are no rows. Then, this error raised, and the decider solved it.
Hope this helps.

Get the _id of the last upserted document in java

I need to find the _id of the last upserted document in java. I am using 2.0.5. I do not see an 'upserted' field in the output returned by getLastError if the element was updated. I do see it if the element was inserted. I need to get the _id regardless of whether the element was updated or inserted. Is it possible to get the _id of the document in some way other than issuing another find command? I am trying to reduce the unnecessary query.
You can use DBCollection.findAndModify(). You can set it's parameters such that it will perform the upsert and return a DBObject containing the _id of the object you just created/modified.
Check out the docs for a lot more info:
http://www.mongodb.org/display/DOCS/findAndModify+Command
ADDENDUM: I just revisited my answer and realize that there's a better, easier way. The value of _id is always set client-side, before the document is sent over the wire to the server. The driver does this for you, if you haven't assigned a value to the _id already. Since the client sets the _id, you know what it is even before the insert operation start. consider this example.
> var id = ObjectId()
> id
ObjectId("5511062d729ddce46b99ea3f")
> db.test.insert( { _id: id, text:"a trivial experiment"})
WriteResult({ "nInserted" : 1 })
> db.test.find( { _id: id} )
{
"_id" : ObjectId("5511062d729ddce46b99ea3f"),
"text" : "a trivial experiment"
}
Following Code Will Give You last Modified record:
db.products.find().sort({"$natural":1}).limit(1);
Following Codes Will Give You last Inserted record:
db.products.find().sort({"_id":-1}).limit(1);
db.products.find().sort({"$natural":-1}).limit(1);

Categories