How to avoid duplicating a function call in this JPA/JPQL query? - java

I have the following JPA query that truncates dates to a full hour and counts them:
SELECT a.alertconfiguration.id.id,
date_trunc('hour', a.date) AS fromDate,
count(*) AS count
FROM alert a
GROUP BY a.alertconfiguration.id.id,
a.alertlevel,
date_trunc('hour', a.date)
I'm running this in a Spring Boot application using Hibernate. It works fine. But I don't want to duplicate the function call to date_trunc.
I have tried referring to fromDate in the GROUP BY clause but then I get an exception org.postgresql.util.PSQLException: ERROR: column "fromdate" does not exist
http://hibernate.atlassian.net/browse/HHH-9301 also states it is not possible to refer to aliases in the group by clause.
How could I rewrite my query without the duplicate function call?

Can you give a try using 2 instead of date_trunc('hour', a.date) in group by clause, as fromdate is 2nd column

Hibernate does not work with alias in group by or any aggregate functions. And as you will see in your sql query generated, the alias is different than that you have assigned.

Related

Retrieve generated keys from multiple queries in single Spring JDBC Update

I am using a single Spring JDBC update to make an update to two tables in my Postgres database. My SQL query is as follows:
UPDATE accounts SET last_transaction_amount = :transaction_amount WHERE acct_num = :acct_num; INSERT INTO transactions (transaction_amout) VALUES (:transaction_amount);
Using NamedParameterJdbcTemplate#update, I have no issue executing this query and achieving the expected results.
The transactions table generates a sequential transaction identifier, and I want to return this to my application.
I've tried passing a GeneratedKeyHolder in the update call. This is returning the error "A result was returned when none was expected". Docs link.
I've tried passing a GeneratedKeyHolder and array of column names (new String[] {"transaction_id"}). This is returning the error that the column doesn't exist. Note this method call does work to return the transaction id when I only pass the INSERT query without the preceding UPDATE query. Docs link.
How can I retrieve the generated key? Thank you!
You seem to be looking for the RETURNING clause. Assuming that the serial number is called transaction_id:
INSERT INTO transactions (transaction_amout)
VALUES (:transaction_amount)
RETURNING transaction_id;

Executing group by DB query in JUNIT

I have database query similar to below one, it is working fine when i execute this in mySql but it failing in Junit with error "expression not in aggregate or GROUP BY columns:". My JUnit uses in memory HSQL DB. I have gone through the Strange behavior of HSQLDB and group by and understand we need to give group by for all fields when aggregate method is used in the query.
But i have a requirement where i need to get the all the value based on grouping with only one column(which is not primary key), can you please suggest how can i achieve this in JUnit.
Query which I'm executing :
Select *, count(sampleField) from TestTable where sampleField2 != null group by sampleField
You can use min(column_name) or max(column_name) for the other columns.
For example, if you have columns named firstname and lastname
Select min(firstname), min(lastname), count(sampleField) from TestTable where sampleField2 is not null group by sampleField
Edited: use is not null instead of != null for correct results.

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.

JPA Criteria API using ISNULL in a order by statement

I use MySQL 5.5 with Hibernate 3.6 and JPA 2.0. I have a User table with a firstName which could also be null or an empty string. I want to have those empty firstName results last in my search results. For that I wrote the following SQL query which works just fine:
SELECT * FROM User ORDER BY ISNULL(firstName), firstName = "", firstName ASC LIMIT 100
Now want to translate this to JPA using the criteria API and I am not quite so sure about the order by. Here is what I have:
criteriaQuery = criteriaQuery.orderBy(cb.asc(cb.isNull(users.get(User_.firstName))), cb.asc(cb.equal(users.get(User_.firstName), "")), cb.asc(users.get(User_.firstName)));
However, the code snippet above does not work, because the CriteriaBuilder.isNull() method is traslated to IS NULL and not to the ISNULL() function of MySQL. I get the following exception:
org.hibernate.hql.ast.QuerySyntaxException: unexpected AST node: is null
Any ideas on how to check for null in the Order by statement with JPA 2.0
That is not possible. In JPA you can ORDER BY fields only that you select (that are in the SELECT part of your query). The problem is that there is no IS_NULL function, that can be used in the SELECT part.
I got the exact same problem as you do, finally I solve it using this way, maybe you can try:
CriteriaQuery<> query;
query.orderBy(cb.desc(cb.selectCase().
when(cb.isNull("field name"),0).otherwise(1)),
cb.asc("field name");

Hibernate unknown column

I am trying to get the day month and year of s.eventTime by using:
DAY(s.eventTime) as theDay, MONTH(s.eventTime) as theMonth, YEAR(s.eventTime) as theYear
Then later I use GROUP BY theYear, theMonth, theDay
This gives me the error: org.hibernate.exception.SQLGrammarException: Unknown column 'theYear' in 'group statement'
I have used this method before in SQL, and was wondering if it is invalid in HQL? If so, does anyone have a suggestion?
Don't use aliases on columns. Refer to them by their name. Like on this example from
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html#queryhql-grouping
select cat
from Cat cat
join cat.kittens kitten
group by cat.id, cat.name, cat.other, cat.properties
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc
It seems that you can't cast day(), month(), or year() with as so instead I made my selects like:
DAY(s.eventTime), MONTH(s.eventTime), YEAR(s.eventTime)
Then grouped the same way with:
GROUP BY DAY(s.eventTime), MONTH(s.eventTime), YEAR(s.eventTime)
Expressions such as second(...), minute(...), hour(...), day(...), month(...), and year(...) can only be used in the where clause.
If you need to execute a query that cannot be produced via HQL you can always make a view in your database and map the view as an entity with Hibernate. It works the same way as a table.

Categories