How Can I construct native query in spring boot with dynamic param as placeholder based on condition? - spring

I am using MSSQL server as DB and the method in the repository layer of my spring boot project:
List<Object[]> SummaryData(#Param("startDate") LocalDateTime startDate,
#Param("endDate") LocalDateTime endDate, List<Integer> cNoList);
here cNoList may contain only 0 or some values.
i want when cNoList contains values such as (45,30,20 etc.) then the below query will execute:
SELECT 'CUSTOMER' as title_key, COUNT(*) as total_count FROM LOGIN_TABLE lg left join USER_TABLE ut on lg.u_id = ut.tid_no where lg.user_type = 'CUSTOMER' and lg.is_verified = 1 and
lg.sys_time BETWEEN ?1 AND ?2 AND cId in (?3)
when cNoList contains only 0 then the last part of the above query --> "AND cId in (?3)" will not execute just like below:
SELECT 'CUSTOMER' as title_key, COUNT(*) as total_count FROM LOGIN_TABLE lg left join USER_TABLE ut on lg.u_id = ut.tid_no where lg.user_type = 'CUSTOMER' and lg.is_verified = 1 and lg.sys_time BETWEEN ?1 AND ?2
i have made an attempt like below:
"DECLARE #part VARCHAR(100) = ''; \n" +
"DECLARE #region_no INT = 420;\n" +
"DECLARE #start Datetime = '2022-07-01 00:00:00';\n" +
"DECLARE #end Datetime = '2023-01-17 23:59:59';\n" +
"DECLARE #tp VARCHAR(100) = 'CUSTOMER';\n" +
"SET #part = (CASE WHEN #region_no != 0 THEN 'and region_no = ' + CAST(#region_no AS VARCHAR(100)) ELSE '' END);\n" +
"EXEC ('SELECT ''' + #tp + ''' as title_key, COUNT(*) as total_count FROM LOGIN_TABLE lg left join USER_TABLE ut on lg.u_id = ut.tid_no where lg.user_type = ''' + #tp + ''' and lg.is_verified = 1 and \n" +
"lg.sys_time BETWEEN\n" +
"CONVERT(DATETIME, ''' + #start + ''', 120) \n" +
"AND\n" +
"CONVERT(DATETIME, ''' + #end + ''', 120) ' + #part)", nativeQuery = true)
List<Object[]> SummaryData(#Param("startDate") LocalDateTime startDate,
#Param("endDate") LocalDateTime endDate, List<Integer> cNoList);
this query executes well with the static value #region_no, #start, #end but these values will be assigned dynamically.
issues I am facing :
i tried putting a placeholder "?3" but not working. how can I put a placeholder like "?3" instead of 420 in this line "DECLARE #region_no INT = 420" ?
(the value of #region_no needs to be something of type array of int but i guess it is of type only int here)
how can i use the placeholder for "DECLARE #start Datetime = '2022-07-01 00:00:00';\n", i want to use something like
"DECLARE #start Datetime = ?1;"

You need to use :param instead of ?1.
For example:
"DECLARE #part VARCHAR(100) = ''; \n" +
"DECLARE #region_no INT = :cNoList;\n" +
"DECLARE #start Datetime = :startDate;\n" +
"DECLARE #end Datetime = :endDate;\n" +
"DECLARE #tp VARCHAR(100) = 'CUSTOMER';\n" +
"SET #part = (CASE WHEN #region_no != 0 THEN 'and region_no = ' + CAST(#region_no AS VARCHAR(100)) ELSE '' END);\n" +
"EXEC ('SELECT ''' + #tp + ''' as title_key, COUNT(*) as total_count FROM LOGIN_TABLE lg left join USER_TABLE ut " +
"on lg.u_id = ut.tid_no where lg.user_type = ''' + #tp + ''' and lg.is_verified = 1 and \n" +
"lg.sys_time BETWEEN\n" +
"CONVERT(DATETIME, ''' + #start + ''', 120) \n" +
"AND\n" +
"CONVERT(DATETIME, ''' + #end + ''', 120) ' + #part)", nativeQuery = true)
List<Object[]> SummaryData(#Param("startDate") LocalDateTime startDate,
#Param("endDate") LocalDateTime endDate,
#Param("cNoList") List<Integer> cNoList);

Related

Equivalent of Select #variable in Spring Data Jpa

I need to run a long query on a spring data jpa method call.
What is the equivalent of Select #sql in spring jpa.
I am getting exception when i try to execute it as a native query.
Below is the query that is working fine in SQL server
Declare #sql varchar(max) = 'select '
select #sql = #sql + 'sum(' + c.name + ')' + ' AS ' + c.name + ','
from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
inner join sys.types ty on
c.user_type_id = ty.user_type_id
where
ty.name in ('bigint') and
t.name = 'my_used_features'
select #sql = LEFT(#sql,LEN(#sql)-1) + ' from my_used_features '
EXEC (#sql)
I want to write the same query as native query in my repository class.But I am getting exception like ....... EXEC (#sql)> starts a quoted range at 342, but never ends it.I am not able to figure out the issue.Can anyone please help me on what am I doing wrong.
#Query
(value="Declare #sql varchar(max) = 'select '\r\n"
+ " \r\n"
+ " select #sql = #sql + 'sum(' + c.name + ')' + ' AS ' + c.name + ',' \r\n"
+ " from sys.columns c\r\n"
+ " inner join sys.tables t on c.object_id = t.object_id\r\n"
+ " inner join sys.types ty on\r\n"
+ " c.user_type_id = ty.user_type_id\r\n"
+ " where\r\n"
+ " ty.name in ('bigint') and\r\n"
+ " t.name = 'my_used_features'\r\n"
+ " \r\n"
+ " select #sql = LEFT(#sql,LEN(#sql)-1) + ' from my_used_features ' \r\n"
+ " \r\n"
+ " EXEC (#sql)",nativeQuery=true)
Map<String,Integer> findSum();

JPA #Query SpEL throwing EL1007E despite null checks

I have this query:
#Query(value =
"select distinct t from TimesheetReport t "
+ "where t.month = :month and t.year = :year and t.approvedByMaster = false "
+ "and (:#{#rule.employee} is null or :#{#rule.employee.id} is null "
+ "or t.account.id = :#{#rule.employee.id}) "
+ "and t.id in "
+ "(select distinct dt.timesheetReport.id from DailyTime dt "
+ "where (:#{#rule.project} is null or :#{#rule.project.id} is null "
+ "or dt.project.id = :#{#rule.project.id}) "
+ "and (:#{#rule.client} is null or :#{#rule.client.id} is null "
+ "or dt.client.id = :#{#rule.client.id})) "
+ "or t.id in "
+ "(select distinct cdt.timesheetReport.id from CustomDailyTime cdt "
+ "where (:#{#rule.project} is null or :#{#rule.project.id} is null "
+ "or cdt.project.id = :#{#rule.project.id}) "
+ "and (:#{#rule.client} is null or :#{#rule.client.id} is null "
+ "or cdt.client.id = :#{#rule.client.id}) "
+ ")"
)
Set<TimesheetReport> findTimesheetsMatchingManualApprovalRule(LeaderManualApprovalRulesDTO rule, int month, int year);
I know this is caused by employee object being null.
This happens despite first checking if employee in rule is null and then checking if employee object id field is null. I still get "Property or field 'id' cannot be found on null".
Is it because SpEL needs to be able to evalute every parameter before firing a query?

JPA query to work with daterange of postgresql

Actual DB table:
Column | Type | Collation | Nullable | Default
--------+-----------+-----------+----------+---------
room | text | | |
during | daterange | | |
Working DB query:
select * from room_reservation where during && daterange('[2020-10-15,2020-11-14)');
Entity Mapping:
#Column(name = "during", columnDefinition = "daterange")
private Range<Date> during;
Jpa Repository:
#Query(value = "select r.room from Room_Occupancy r where r.during && daterange('[?1, ?2)')", nativeQuery = true)
List<Long> findOccupiedRooms(#Param("d1") Date d1, #Param("d2") Date d2);
Exception:
Caused by: org.postgresql.util.PSQLException: ERROR: invalid input syntax for type date: "?1"
Position: 65
How can I write same query in JPA?
Here, '[?1, ?2)' inside string literal parameter can't be replaced. You can use || to concat as string where the parameters can be replaced.
#Query(value = "select r.room from Room_Occupancy r where r.during &&"
+ " daterange('[' || :d1 || ',' || :d2 || ')')", nativeQuery = true)
List<Long> findOccupiedRooms(#Param("d1") Date d1, #Param("d2") Date d2);
As it's a native query use ? as parameter placeholders:
#Query(value = "select r.room from Room_Occupancy r where r.during && daterange('[?, ?)')", nativeQuery = true)
List<Long> findOccupiedRooms(#Param("d1") Date d1, #Param("d2") Date d2);
Try this code:-
#Query(value = "select r.room from Room_Occupancy r where r.during >= ?1 and r.during < ?2", nativeQuery = true)
List<String> findOccupiedRooms(Date d1,Date d2); //d1 < d2
#Query(value = "select r.room from Room_Occupancy r where r.during && daterange(''||'[' || :d1 ||',' || :d2 ||')' || '')", nativeQuery = true)
List<Long> findOccupiedRooms(#Param("d1") Date d1, #Param("d2") Date d2);
This worked

Pass date parameter to native query

A user can perform actions based on an occurrence value. When this value is equal to 'DAILY', I would like to retrieve all daily actions that have not been completed the last 24 hours.
The working SQL query:
SELECT distinct a.* FROM action as a LEFT OUTER JOIN history as h
ON a.id = h.action_id
AND h.user_id= <user> WHERE a.occurrence = 'DAILY' AND (h.id is NULL OR h.entry_date < TIMESTAMP 'yesterday')
The equivalent native query:
#Query(value =
"SELECT distinct a.* FROM action a "
+ "LEFT OUTER JOIN history h "
+ "ON a.id = h.action_id "
+ "AND h.user_id = :userId "
+ "WHERE a.occurrence='DAILY' AND (h.id IS NULL OR h.entry_date < :yesterday) ", nativeQuery = true)
public List<Action> findAllAvailableActions(#Param("userId") Long userId, #Param("yesterday") ZonedDateTime yesterday);
How it is called in my service :
ZonedDateTime today = ZonedDateTime.now(ZoneOffset.UTC);
ZonedDateTime yesterday = today.minus(1,ChronoUnit.DAYS);
Long userId = userDTO.getId();
List<Action> result = actionRepositoryCustom.findAllAvailableActions(userId, yesterday);
However, I do get the wrong results in my tests (actions that have already been completed are returned). I am afraid this is linked to the date parameter. The attribute entry_date is declared as ZoneDateTime in my entity. What am I doing wrong ?
hibernate : 5.2.4
You can't pass a ZonedDateTime into a native SQL query. You need to convert it to Calendar:
#Query(value =
"SELECT distinct a.* FROM action a "
+ "LEFT OUTER JOIN history h "
+ "ON a.id = h.action_id "
+ "AND h.user_id = :userId "
+ "WHERE a.occurrence='DAILY' AND (h.id IS NULL OR h.entry_date < :yesterday)", nativeQuery = true)
public List<Action> findAllAvailableActions(#Param("userId") Long userId, #Param("yesterday") Calendar yesterday);
And you can convert your ZonedDateTime this way:
public Calendar convertToDatabaseColumn(ZonedDateTime entityAttribute) {
if (entityAttribute == null) {
return null;
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(entityAttribute.toInstant().toEpochMilli());
calendar.setTimeZone(TimeZone.getTimeZone(entityAttribute.getZone()));
return calendar;
}
This approach is described here: link

jpql native query not setting parameter

#Repository
public interface GroupRepository extends JpaRepository<Group, String> {
//Other queries....
#Query(value = "with cte(group_id, parent_group_id, group_name) as( "
+ "select group_id, parent_group_id, group_name "
+ "from hea.hea_group "
+ "where group_id = ?1 "
+ "union all "
+ "select g.group_id, g.parent_group_id, g.group_name "
+ "from hea.hea_group g "
+ "inner join cte on cte.group_id = g.parent_group_id "
+ "where g.parent_group_id is not null "
+ ") select * from cte", nativeQuery = true)
List<Object> getChildGroups(String groupId);
}
Above is the query that I have written that should return the parent group and all of its children. The query does what it is suppose to do when I replace the ?1 with a hard coded group id value and change the method to have no parameters, but when I try to run it as above it returns nothing even though I'm passing in the exact same value that I was hard coding.
Below is the sql that is being generated by the query. When I replace the ? with a group id an run it on a test database it returns the results that it should.
with cte(group_id, parent_group_id, group_name) as( select
group_id,
parent_group_id,
group_name
from
hea.hea_group
where
group_id = ?
union
all select
g.group_id,
g.parent_group_id,
g.group_name
from
hea.hea_group g
inner join
cte
on cte.group_id = g.parent_group_id
where
g.parent_group_id is not null ) select
*
from
cte
The variables are zero based so ?0 is what you should use.

Resources