Count Date in JPA or JPQL - jpa

I need to count registered users in current month.
I use a primitive method
I use H2 Database.
In Model(Reader) i have a field: private String LocalDate
because i save the date filed in database as String:
#Override
public String getCurrentDate() {
LocalDate localDate = LocalDate.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formatDate = localDate.format(format);
return formatDate;
}
In ReaderRepository I have this:
Integer getCountDatesCurrentMonth();
List<String> allDates();
Where in List I get all dates from table.
In ReaderServiceImpl I have this method:
#Override
public Integer getCurrentMonthRegisteredReaders() {
List<String> dates = readerRepository.allDates();
Integer date = LocalDate.now().getMonthValue();
String s;
int count=0;
for(int i =0;i<dates.size();i++){
s = dates.get(i).charAt(5)+""+dates.get(i).charAt(6);
if(s.equals(date.toString()))
count++;
}
return count;
Where I get the count of users registered in current month
The code works perfect and shows me on the web page the readers registered in current month,
but I want do this in one line or two with JPA or JPQL and I cant do that.
I've tried with JPA, but doesn't work:
Integer countByDateMonthValue(int currentMonth);
I get error:
Caused by: java.lang.IllegalStateException: Illegal attempt to dereference path source [null.date] of basic type
Or with JPQL:
#Query("select count(date) from Reader where... ")
but I don't know further what should I do...

The challenge here is that JPQL doesn't define many useful methods to manipulate dates. So the query will either depend on your JPA implementation (probably Hibernate or EclipseLink) or on the database you are using or both.
This one should work with Hibernate
#Query("select count(date) from Reader r where month(r.date) = :month")
int countForMonth(int month);
This one should work with Oracle
#Query("select count(date) from Reader r where EXTRACT(month FROM r.date) = :month", nativeQuery = true)
int countForMonth(int month);
Or if you always want the current month as a basis
#Query("select count(date) from Reader r where EXTRACT(month FROM r.date) = EXTRACT(month FROM sysdate)", nativeQuery = true)
int countForMonth();
Your current logic an the queries above ignore the year, so rows from all years get counted, if the month matches.
If you really want only the rows from the current month counted, or if it doesn't matter because you have rows from a single row anyway, you could create the date range using a SpEL expression.
You could register custom functions that provide the beginning and end of the current month an do something like this in the query:
#Query("select count(date) from Reader r where r.date between :#{startOfMonth(month)} and :#{endOfMonth(month)}")
int countForMonth(int month);
See this blog article how to use SpEL expressions in queries and how to register custom extensions to the evaluation context.

Done.
I did this select, and works perfect. Now i don't need to send
LocalDate().now().getMonthValue as parameter in my method.
#Query(value = "select count(date) from Reader r where extract(month from r.date) = extract(month from sysdate)" +
"and extract(year from r.date) = extract(year from sysdate)",nativeQuery = true)
int findAllByMonth();
Danke Schön Jens Schauder !

Related

Group by date intervals using JPA's Criteria API

I'm trying to group entities by date intervals using JPA's Criteria API. I use this way of querying for entities as this is a part of the service that serves API requests which may ask for any field of any entity, including sorting, filtering, grouping and aggregations. Everything works fine except for grouping by date fields. My underlying DBMS i PostgreSQL.
To give a minimal example, here's my entity class:
#Entity
#Table(name = "receipts")
public class DbReceipt {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date sellDate;
// Many other fields
}
This example discusses grouping my "month" interval (therefore grouping by year+month), but in the end I'm looking for a solution that would let me group by any interval, such as "year", "day" or "minutes".
What I'm trying to achieve is the following query, but using Criteria API:
SELECT TO_CHAR(sell_date, 'YYYY-MM') AS alias1 FROM receipts GROUP BY alias1;
My attempt to do so is this:
#Service
public class ReceiptServiceImpl extends ReceiptService {
#Autowired
private EntityManager em;
#Override
public void test() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
Root<?> root = query.from(DbReceipt.class);
Expression<?> expr = cb.function("to_char", String.class, root.get("sellDate"), cb.literal("YYYY-MM"));
query.groupBy(expr);
query.multiselect(expr);
TypedQuery<Object[]> typedQuery = em.createQuery(query);
List<Object[]> resultList = typedQuery.getResultList();
}
}
The reason I use to_char function and not MONTH and similar is that I need entities like 2019-05 and 2020-05 to not be grouped together. I also narrow this example down to only year and month to keep things short, but the goal is to group by any date interval.
The code above creates the following query (SQL logging enabled) which results in an error:
Hibernate: select to_char(dbreceipt0_.sell_date, ?) as col_0_0_ from receipts dbreceipt0_ group by to_char(dbreceipt0_.sell_date, ?)
24-05-2020 12:16:30.071 [http-nio-1234-exec-5] WARN o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 0, SQLState: 42803
24-05-2020 12:16:30.071 [http-nio-1234-exec-5] ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - ERROR: column "dbreceipt0_.sell_date" must appear in the GROUP BY clause or be used in an aggregate function
Position: 16
which to me is caused by the fact that the whole expression is put into the 'group by' part of the query, rather than just an alias. Now, I've tried to assign an alias to the expression (which returns Selection<T> and groupBy accepts expressions, therefore I can only really use that in the multiselect), but that didn't affect how the query is performed - nothing changed.
How do I achieve grouping by year and month as described above using Criteria API? Maybe there's a different way other than using to_char? Maybe there's a way to give an alias to the groupBy method that would cause it to group by an alias instead of the whole expression?
I think it's a bug in PostgreSQL (the error comes from there, not from Hibernate). I have tried a slightly modified version of your code with EclipseLink + Derby and works perfectly.
Note that I had to use numbers instead of strings because Derby DB doesn't have an equivalent of TO_CHAR function.
Expression<Integer> year = cb.function("YEAR", Integer.class, root.get("sellDate"));
Expression<Integer> month = cb.function("MONTH", Integer.class, root.get("sellDate"));
Expression<Integer> expr = cb.sum(month, cb.prod(12, year));
query.groupBy(expr);
query.multiselect(expr);
This returns the following SQL:
SELECT (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))
FROM MY_DATE_TABLE
GROUP BY (MONTH(MY_DATE) + (12 * YEAR(MY_DATE)))
Note that there are no portable solutions for manipulating dates in JPA criteria queries. If the number of groups to be queried simultaneously is not too high I'd go with a more practical approach where you find the dates in Java and pass them as literals to the query builder.
Another workaround is to query with a groupBy(root.get("sellDate")) and then aggregate the results in Java according to the desired time period.
Post Scriptum: I don't think it's relevant, however I modified the query's return type from Object[] to Object.

How to use '::' in spring boot?

how to use a query from the Postgre to Spring boot?
I used this query to get data in postgre
"SELECT * FROM mytable WHERE delivery_date::date = '2019-10-28' limit 10;"
I'm trying to make a rest fire with an entity like this
#Entity
#Table(name ="mytable")
public class DeliveryDataEntity implements Serializable {
#Column(name="delivery_date")
private Date delivery_date;
#Column(name="delivery_name")
private String delivery_name;
#Id
#Column(name="delivery_id")
private int delivery_id;
}
and my repo code like this
#Repository
public interface DeliveryDataRepository extends PagingAndSortingRepository<DeliveryDataEntity, Date>{
#Query(value="SELECT * FROM mytable WHERE delivery_date = ?1", nativeQuery = true)
Page<DeliveryDataEntity> findByUserAndStatusOrderByCreatedAtDesc(Date delivery_date, PageRequest pageRequest
);
}
If I run there are no errors, but the data isn't readable.
I tried changing the query to be like this
#Query(value="SELECT * FROM mytable WHERE delivery_date >= ?1", nativeQuery = true)
and
Query(value="SELECT * FROM accounts_data WHERE updated \\= ?1", nativeQuery = true)
but, if I run it will get null results.
how do i solve this problem?
thanks
Assuming that the likely problem be the double colon (::) cast operator, which Spring Boot doesn't like, you could try refactoring your query to use a range instead:
SELECT *
FROM mytable
WHERE delivery_date >= '2019-10-28' AND delivery_date < '2019-10-29'
ORDER BY <some column>
LIMIT 10;
The logic in the WHERE clause simply states to find all records whose delivery dates occur on or after midnight of 28-OCT-2019, and strictly before midnight of the following day.
JPA/Hibernate might also support using the CAST function, since Postgres does implement CAST, so this version might also work:
SELECT *
FROM mytable
WHERE (CAST delivery_date AS date) = '2019-10-28'
ORDER BY <some column>
LIMIT 10;
I think you do not need the (::) operator. If you want to consider only dates (without times), you can use the #Temportal annotation, as explained in this Baeldung tutorial.
Also, I think you don't need to use native queries.

JPQL get difference in days between a column and now

I'm using hibernate and jpa in a spring boot application.
I have a hibernate entity with 2 columns, type (string) and date (LocalDate).
I want to select all the columns and the difference of days between my date column and now.
I succeeded in PostgreSQL: select date, date - current date from table;
I'm using an interface which extends JpaRepository. Until now i was successfully to insert new queries through #Query annotations.
I can't figure it out how to select my columns and a column containing the difference of days in jpql.
Kind regards,
Indeed you can use query creator. You have to create a new entity which has the same columns as the original table and in addition the calculated difference between the date column and now. You have to be careful which date type is your column.
In my experience only Date from java.util can be used for differences with CURRENT_DATE (i may be wrong).
public List<NewEntity> getTest() {
String queryStr = "select new com.entity.NewEntity(n.type, n.date - CURRENT_DATE as
time) from Table as n"
TypedQuery<PunteInspectieTimpRamas> query = entityManager.createQuery(queryStr,
NewEntity.class);
return query.getResults()
}
Not sure you can do this with jpql.
But you can use your query as a native query, directly in you repository:
#Query(value = "SELECT ... ", nativeQuery = true)
ClassToReturn methodName();
see https://www.baeldung.com/spring-data-jpa-query

Date comparison using the JPA criteria API

I got a range date picker with two dates: start and end, where both can be empty. I would like to filter a table, where the entity has exact one date: date.
So, here are some examples. I'd like to match. Imaging the date to match is the current date (17/07/2016).
null - 17/07/2016 -> match
17/07/2016 - null -> match
17/07/2016 - 17/07/2016 -> match
null - null -> match all
Those are the edge cases in my opinion and the reason why I am struggling so much.
Actually my code looks like:
CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Transaction> cq = cb.createQuery(Transaction.class);
Root<Transaction> root = cq.from(Transaction.class);
List<Predicate> predicates = new ArrayList<>();
Predicate startPredicate = cb.greaterThanOrEqualTo(root.get(Transaction_.date), start);
Predicate endPredicate = cb.greaterThanOrEqualTo(root.get(Transaction_.date), end);
predicates.add(startPredicate);
predicates.add(endPredicate);
cq.select(root).where(predicates.toArray(new Predicate[] {}));
TypedQuery<Transaction> query = getEm().createQuery(cq);
query.setFirstResult(firstRow);
query.setMaxResults(maxRow);
List<Transaction> resultList = query.getResultList();
I'd like to get a query like this:
SELECT * FROM transaction
WHERE ((cast(date AS DATE) >= '2016-07-16' OR cast(date AS DATE) IS NULL))
AND ((cast(date AS DATE) <= '2016-07-17' OR cast(date AS DATE) IS NULL))
Please note: The static date is to simulate a start and end date. and date is the table column.
I know that my code is wrong. It matches only ranges, without considering null values. Also, if start and end is the same day, I will get zero results.
Do you have you any idea? How can I edit my code to match all the mentioned patterns?
I have an existing database table named discount with two columns of type TIMESTAMP named discount_start_date and discount_end_date in a MySQL database. So, please adjust your query according to the name of the table and respective columns in that table.
The complete criteria query based on the SQL statement given in the question can be constructed as follows (I hope the code would be self-explanatory).
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Discount> criteriaQuery = criteriaBuilder.createQuery(Discount.class);
Root<Discount> root = criteriaQuery.from(entityManager.getMetamodel().entity(Discount.class));
SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
java.util.Date startDate = dateFormat.parse("24-02-2016");
java.util.Date endDate = dateFormat.parse("24-03-2016");
ParameterExpression<java.util.Date> parameter = criteriaBuilder.parameter(java.util.Date.class);
Predicate startDatePredicate = criteriaBuilder.greaterThanOrEqualTo(root.get(Discount_.discountStartDate).as(java.sql.Date.class), parameter);
Predicate endDatePredicate = criteriaBuilder.lessThanOrEqualTo(root.get(Discount_.discountEndDate).as(java.sql.Date.class), parameter);
Predicate startDateOrPredicate = criteriaBuilder.or(startDatePredicate, root.get(Discount_.discountStartDate).isNull());
Predicate endDateOrPredicate = criteriaBuilder.or(endDatePredicate, root.get(Discount_.discountEndDate).isNull());
Predicate and = criteriaBuilder.and(startDateOrPredicate, endDateOrPredicate);
criteriaQuery.where(and);
List<Discount> list = entityManager.createQuery(criteriaQuery)
.setParameter(parameter, startDate, TemporalType.DATE)
.setParameter(parameter, endDate, TemporalType.DATE)
.getResultList();
It produces the following SQL query of your interest (both fields are inclusive as you stated).
select
discount0_.discount_id as discount1_15_,
discount0_.discount_code as discount2_15_,
discount0_.discount_end_date as discount3_15_,
discount0_.discount_percent as discount4_15_,
discount0_.discount_start_date as discount5_15_
from
project.discount discount0_
where
(
cast(discount0_.discount_start_date as date)>=?
or discount0_.discount_start_date is null
)
and (
cast(discount0_.discount_end_date as date)<=?
or discount0_.discount_end_date is null
)
Tested on Hibernate 4.3.6 final but average ORM frameworks should produce the same query without any modifications.
In this method setParameter(parameter, startDate, TemporalType.DATE), the last parameter i.e. TemporalType.DATE is only needed, if you have a column of type DATETIME or TIMESTAMP in your database and you want to compare dates ignoring the time portion of such columns. You can simply exclude that parameter, if your columns do not have a time portion like DATE (MySQL).
You can, if necessary, also use other date (time) handling APIs like java.time or JodaTime replacing Date along with SimpleDateFormat

Lambda expression for Entity Framework query

I'm stuck trying to map this sql:
select dt.Id, dateadd(dd,datediff(dd,0,dt.CreatedAt),0) as Ct, DId, Amount
from dt, ad
where dt.ADId = ad.ADId and ad.Id = '13B29A01-8BF0-4EC9-80CA-089BA341E93D'
order by dateadd(dd,datediff(dd,0,dt.CreatedAt),0) desc, DId asc
Into an Entity Framework-compatible lambda query expression. I'd appreciate any help.
I think something like the code below should work.
Guid dtId = new Guid("13B29A01-8BF0-4EC9-80CA-089BA341E93D");
DateTime compareDt = new DateTime(...);
var q = from dt in dts
where dt.id == dtId
orderby dt.Ad.CreatedAt, Did
select new
{
dt.Ad,
(dt.CreatedAt - compareDt).Days,
DId,
Amount
};
dtId has to be outside of the query, because the entity framework mapper doesn't understand constructors that take parameters, similar for the DateTime. I couldn't completely figure out the datediff/dateadd part, looks like you're determining the total amount of days since a given datetime, which is what I assumed.
It could be that the datetime subtraction followed by TimeSpan.Days doesn't work in the query. If that's the case, pull it outside like so:
var q = from dt in dts
where dt.id == dtId
orderby dt.Ad.CreatedAt, Did
select new
{
dt.Ad,
dt.CreatedAt,
DId,
Amount
};
This creates an IEnumerable of objects that have a DateTime CreatedAt property. If you call .ToList() now, the query is executed, and you can calculate stuff that Entity Framework doesn't support. Again, if the first attempt worked, that's the best solution if the number of days since X is what you need.
from item in q.ToList()
select new
{
dt.Ad
(dt.CreatedAt - compareDt).Days,
DId,
Amount
};