I have this sql query which I am using native sql to process currently and I need to transform it to use the JPA criteria builder so I can dynamically build up the where conditions. I am currently semi building the conditions dynamically in a janky way in sql (AND (case when 0 in :#{#filter.modelIds} then true else model_id in :#{#filter.modelIds} end)
Also, Im not sure what the best practices are and if I should just leave the code I currently have or move it over to Criteria builder. A lot of articles I read have said not to use criteria builder for advance quires. For me everything works but some people expressed concerns over the maintainability of the code. To me I like looking at sql versus the criteria builder just confuses me :)
Another blocker for me is I'm using the Postgres function generate_series to create all the dates in a date range so that way I can have empty values for dates with no data. I was not sure how to translate this over to the criteria builder I figured out how to use the date trunc function Expression<Date> view_date = builder.function("date_trunc", Date.class, builder.literal("day"), root.get("acquisitionDate")); The only other thought I had was to just execute the sub query and the use java to build up the date array and map my results to it
SQL:
select date, COALESCE(views, 0) as count, model_id
from generate_series(date '2020-05-9',date '2020-05-18',interval '1 day')as t(date)
left join (
select date_trunc('day',acquisition_date) as view_date, count(acquisition_date) as views, model_id
from view
where acquisition_date between '2020-05-9' and '2020-06-19'
group by model_id, view_date
order by view_date asc
) as v on v.view_date = date
order by date asc;
Repository:
#Query(
value = "select date, COALESCE(views, 0) as count, d.model_id as itemId " +
"from generate_series(date (:#{#filter.startDate}),date (:#{#filter.endDate}), CAST('1'||' '||:#{#filter.getInterval()} AS Interval))as t(date)" +
"left join (" +
"select date_trunc((:#{#filter.getInterval()}),acquisition_date) as view_date, count(acquisition_date) as views, model_id " +
"from view " +
"WHERE acquisition_date between (:#{#filter.startDate}) and (:#{#filter.endDate})" +
"AND (case when 0 in :#{#filter.modelIds} then true else model_id in :#{#filter.modelIds} end)" +
"AND (case when '0' in :#{#filter.imageIds} then true else image_id in :#{#filter.imageIds} end) " +
"AND (case when 0 in :#{#filter.objectIds} then true else object_type_id && array[(:#{#filter.getObjectArray()})] end)" +
"group by model_id, view_date " +
"order by view_date asc" +
") as v on v.view_date = date_trunc((:#{#filter.getInterval()}), date) " +
"order by date asc",
nativeQuery = true
)
List<ItemDateCountProjection> getModelViewsByDate(#Param("filter") ViewFilter filter);
Projection
public interface ItemDateCountProjection {
String getItemId();
Integer getCount();
Date getDate();
}
Related
#Query(value =
"SELECT * " +
"WHERE (type REGEXP ?1) " +
"AND status= ?2 " +
"AND date(createdAt)..." +
"ORDER BY id DESC " +
"LIMIT ?4",
nativeQuery = true)
Optional<List<Item>> findLatest(String type, String status, String date, int limit);
I have this query where I'd like to filter by createdAt, only if the date value from the parameter is not null.
If String date = null, the query should not consider the AND date(createdAt)...
However if String date = "today" the query should include something like AND date(createdAt) = CURRENT_DATE
How can I achieve this?
Simplified you can make
If you have more choice you need case when
The idea is when the condition is not met, we let the and part always be true
so that the where calsue is true if all the other conditions are met
SELECT * FROM
A
WHERE
status= 72
AND IF( date = "today" , date(createdAt), current_date) = current_date
The way we usually handle this is by adding a logical check to the WHERE clause which allows a null date.
#Query(value = "SELECT * " +
// ...
"WHERE (type REGEXP ?1) AND " +
" status = ?2 AND " +
" (DATE(createdAt) = ?3 OR ?3 IS NULL) " +
"ORDER BY id DESC " +
nativeQuery = true)
Optional<List<Item>> findLatest(String type, String status, String date);
Note that it is not possible to bind parameters to the LIMIT clause, therefore I have removed it. Instead, you should use Spring's pagination API.
I'm trying to create some type of update endpoint that when you hit it does an insert from one table to another table. Basically it takes from a table named funds which was fundId, reportingfrequency and country. IT retains the fundIds and for the value it gets a List of Dates that are a result of a join with a Postgres stored procedure/function. Here is an example of the SQL query in it's entirety. So ideally if there are 100 rows in the table it would insert all 100 rows with an array of dates into the due_dates table.
INSERT INTO due_dates (fund_id, listdate)
SELECT fundid,
(
SELECT array_agg(weekdays::date)
FROM generate_series(date'2021-01-01', date'2021-12-31', interval '1' day) as t(weekdays)
LEFT JOIN holidays.poland(2021, 2021) f ON (weekdays = f.datestamp)
WHERE f.datestamp IS NULL
AND extract(dow from weekdays) BETWEEN 1 AND 5
)
FROM funds
WHERE reportingfrequency = 'Daily';
Now my issue is I'm not sure how to... programmatically do this so that depending on the rows from the funds table... I'm not sure how to grab the country field from the row to do a specific stored procedure call. For instance if a single row has Poland in it's country field then it would ideally call holidays.poland(2021, 2021)... and if the country was USA it would call holidays.usa(2021, 2021). Here's how my current NativeQuery.
entityManager.createNativeQuery("INSERT INTO due_dates (fund_id, listdate)" +
"SELECT fundid," +
"(" +
"SELECT array_agg(weekdays::date)" +
"FROM generate_series(date'2021-01-01', date'2021-12-31', interval '1' day) as t(weekdays)" +
"LEFT JOIN holidays." + country + "(" + year + "," + year + ") f ON (weekdays = f.datestamp)" +
"WHERE f.datestamp IS NULL" +
"AND extract(dow from weekdays) BETWEEN 1 AND 5" +
")" +
"FROM funds" +
"WHERE reportingfrequency = 'Daily'; ")
.executeUpdate();
Is there something I need to do to tweak the original SQL statement before I can achieve what I want to do?
Below is the simple example which explain my problem
Query(name = "select p.* , pr.actual_date_time , count(*) " +
"from player p " +
"inner join app.player_reports pr ON pr.player_id = p.id " +
"where pr.actual_date_time between now() - interval '?1 day'
and now() - interval '0 day' " ,nativeQuery = true)
List<PlayerEntity> findCheaters(#param("NumberDays") int number )
this is my query, I want to put "NumberDays" between single quotation and substitute with "?1".
I would be glad to help me, and thank you
You can't parametrize the value for the interval constant, but you can simply multiply the base unit with a parameter:
pr.actual_date_time between now() - (interval '1 day' * ?) and now()
Then pass the number of days you want as an integer to the PreparedStatement.
Note that the - interval '0 day' is useless
This question already has answers here:
Jpa namedquery with left join fetch
(2 answers)
Closed 5 years ago.
I'm using spring data JPA and I want to write a SQL query in my repository.
I have a following SQL query (notice the LEFT JOIN):
SELECT * FROM institution LEFT JOIN
(select * from building_institutions where building_institutions.building_id = 1) as reserved_institutions
ON reserved_institutions.institutions_user_id = institution.user_id
WHERE reserved_institutions.institutions_user_id is null;
and i want to execute it in my InstitutionRepository which is as follows:
#Repository
public interface InstitutionRepository extends JpaRepository<Institution, Long>, PagingAndSortingRepository<Institution,Long> {
// doesn't work
//#Query("SELECT b.institutions as bi FROM Building b left join Institution i WHERE bi.building_id not in :id")
Page<Institution> findPotentialInstitutionsByBuildingId(#Param("id") Collection<Long> id, Pageable pageable);
// doesn't work
#Query(
value = "SELECT * FROM kits_nwt.institution LEFT JOIN\n" +
"(SELECT * FROM kits_nwt.building_institutions WHERE kits_nwt.building_institutions.building_id = ?1) AS reserved_institutions\n" +
"ON reserved_institutions.institutions_user_id = kits_nwt.institution.user_id\n" +
"WHERE reserved_institutions.institutions_user_id IS null ORDER BY ?#{#pageable}",
nativeQuery = true)
Page<Institution> findPotentialInstitutionsByBuildingId(Long userId, Pageable pageable);
}
So, I want to get all institutions which are not in building with certain ID (which I will send as a parameter).
Here is my current DB data:
Institutions:
Building institutions:
What I want: (in this query, the id is set on 1, for presentation purposes)
I have looked at many SO questions and answers (such as this one) but I haven't been able to figure out the solution.
So, how do I write this query so that I get what I want?
Edit 1:
#KevinAnderson Currently, I'm trying with:
#Query(
value = "SELECT username, password, description, location, title, user_id FROM (institution INNER JOIN user ON institution.user_id = user.id) LEFT JOIN\n" +
"(SELECT * FROM building_institutions WHERE building_institutions.building_id = 1) AS reserved_institutions\n" +
"ON reserved_institutions.institutions_user_id = kits_nwt.institution.user_id\n" +
"WHERE reserved_institutions.institutions_user_id IS null ORDER BY ?#{#pageable}",
nativeQuery = true)
Page<Institution> findPotentialInstitutionsByBuildingId(Long userId, Pageable pageable);
And I'm getting this exception:
MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE) FROM building_institutions WHERE building_institutions.building_id = 1) A' at line 2
Edit 2:
#StanislavL here it is:
The problem is that your query gets me the institution with ID 25 because it is in both building with ID 1 and building with ID 2. When you do a JOIN, you have 2 rows ON institution ID being 25 in both institution table and building_institutions table. Then, your WHERE condition removes one from those two rows and I get one row where instituiton ID is 25, and I don't want what.
Here is an image for the above:
Edit 3 - this question is not a duplicate because of the following:
My query is with pagination (I added "with pagination" to the question title)
I'm not using #NamedQuery but #Query
My mistake was that I didn't write countQuery parameter to the #Query annotation
I solved it...
The query needs to look like this:
#Query(
value = "SELECT * FROM \n" +
"(institution INNER JOIN user ON institution.user_id = user.id) \n" +
"LEFT JOIN \n" +
"(SELECT * FROM \n" +
"building_institutions \n" +
"WHERE building_id = :userId)\n" +
" AS reserved_institutions \n" +
"ON reserved_institutions.institutions_user_id = kits_nwt.institution.user_id \n" +
" where reserved_institutions.institutions_user_id IS null \n"
+ "ORDER BY ?#{#pageable}"
,
countQuery = "SELECT count(*) FROM \n" +
"(institution INNER JOIN user ON institution.user_id = user.id) \n" +
"LEFT JOIN \n" +
"(SELECT * FROM \n" +
"building_institutions \n" +
"WHERE building_id =:userId)\n" +
" AS reserved_institutions \n" +
"ON reserved_institutions.institutions_user_id = kits_nwt.institution.user_id \n" +
"where reserved_institutions.institutions_user_id IS null \n" +
"ORDER BY ?#{#pageable}",
nativeQuery = true)
Page<Institution> findPotentialInstitutionsByBuildingId(#Param("userId") Long userId, Pageable pageable);
I was getting the error at line 4 near WHERE, when my query looked like this:
#Query(
value = "SELECT username, password, description, location, title, user_id FROM (institution INNER JOIN user ON institution.user_id = user.id) LEFT JOIN\n" +
"(SELECT * FROM building_institutions WHERE building_institutions.building_id = 1) AS reserved_institutions\n" +
"ON reserved_institutions.institutions_user_id = kits_nwt.institution.user_id\n" +
"WHERE reserved_institutions.institutions_user_id IS null ORDER BY ?#{#pageable}",
nativeQuery = true)
Page<Institution> findPotentialInstitutionsByBuildingId(Long userId, Pageable pageable);
and that was because I didn't add the countQuery parameter to the #Query annotation.
Big thanks to all of you who tried to help.
I hope that I save someone else many hours of misery.
Cheers! :)
I am using Spring 3.1.1 with Hibernate 4 and a MSSQL database. Finally, I have been able to query my database with joins in my table that returns the correct answers. Although, it seems that it does not return the entire strings of my messages, but cuts at 29/30 digits. Here is my query:
SQLQuery sql = this.getCurrentSession().createSQLQuery(
"SELECT event.id as eventid, CAST(event_type.type_name AS varchar) as eventtype, event.check_date, event.event_date, event.status, CAST(event_message.message AS varchar) as eventmessage " +
"FROM event_log event " +
"LEFT JOIN event_type " +
"ON (event.event_type_id = event_type.id) " +
"LEFT JOIN event_message " +
"ON (event.event_message_id = event_message.id) " +
"WHERE event.event_type_id = " + jobId +
"ORDER BY eventid");
The result can be:
4506 Database 2014-01-15 14:14:15.02 2014-01-15 14:14:15.02 false Database-connection cannot be
Where the columns are id, task_name, check_date, event_date, status and the message-string at the end. .
The result goes to a ArrayList<Object[]> where I read row[0] etc. to get the entities in the row. The string message gets cut after 29 digits, which is disturbing. Why does it cut the string and how can I fix this? In my database the string exists in it's full and is entitled 400 characters max.
I know this is probably not the best way to query my database, but it will do for my application since it works.
You are using varchar without a length. Never do this!
Replace:
CAST(event_type.type_name AS varchar)
With something like:
CAST(event_type.type_name AS varchar(255))
In some contexts, the default length is 32. In some it is 1. In general, though, always specify the length.
EDIT:
Actually, you probably don't even need the cast. Why not just have event_type.type_name in the select list?