Dynamically change the Spring Data query strings - spring-boot

I have the following Spring Data Repository method:
#Query(value = "SELECT * FROM movies m " +
"WHERE m.approved = true " +
"AND NOT exists (SELECT user_id FROM movie_lists ml WHERE ml.movie_id = m.id and ml.user_id = :userId)",
countQuery = "SELECT count(*) FROM movies m " +
"WHERE m.approved = true " +
"AND NOT exists (SELECT user_id FROM movie_lists ml WHERE ml.movie_id = m.id and ml.user_id = :userId)",
nativeQuery = true)
Page<Movie> findAllApprovedNotListedInUser(#Param("userId") Long userId, Pageable pageable);
or another one
#Query(value = "SELECT m.* FROM movies m " +
"WHERE m.approved = true " +
"AND m.release_year >= :minReleaseYear AND m.release_year <= :maxReleaseYear " +
"AND exists (SELECT movie_id FROM movies_genres mg WHERE mg.movie_id = m.id AND mg.genre_id IN :likedGenres LIMIT 1) " +
"AND NOT exists (SELECT movie_id FROM movies_genres mg WHERE mg.movie_id = m.id AND mg.genre_id IN :dislikedGenres LIMIT 1) " +
"AND NOT exists (SELECT user_id FROM movie_lists ml WHERE ml.movie_id = m.id and ml.user_id = :userId LIMIT 1)",
countQuery = "SELECT count(*) FROM movies m " +
"WHERE m.approved = true " +
"AND m.release_year >= :minReleaseYear AND m.release_year <= :maxReleaseYear " +
"AND exists (SELECT movie_id FROM movies_genres mg WHERE mg.movie_id = m.id AND mg.genre_id IN :likedGenres LIMIT 1) " +
"AND NOT exists (SELECT movie_id FROM movies_genres mg WHERE mg.movie_id = m.id AND mg.genre_id IN :dislikedGenres LIMIT 1) " +
"AND NOT exists (SELECT user_id FROM movie_lists ml WHERE ml.movie_id = m.id and ml.user_id = :userId LIMIT 1)",
nativeQuery = true)
Page<Movie> findAllApprovedNotListedInUser(#Param("userId") Long userId, #Param("minReleaseYear") int minReleaseYear, #Param("maxReleaseYear") int maxReleaseYear, #Param("likedGenres") Set<Long> likedGenres, #Param("dislikedGenres") Set<Long> dislikedGenres, Pageable pageable);
I'd like to dynamically in runtime to be able to set Query.value and Query.countQuery strings. Is it possible in some way? I'd like to be able to dynamically change the query strings.
I have 6 different parameters and based on presence or absence of values for them I'd like to change the WHERE part of the query.

Can you try this (I removed the countQuery to help keep it readable):
#Query(value = "SELECT m.* FROM movies m " +
"WHERE m.approved = true " +
"AND ( :minReleaseYear is null OR m.release_year BETWEEN :minReleaseYear AND :maxReleaseYear )" +
"AND ( :likedGenres IS NULL OR exists (SELECT movie_id FROM movies_genres mg WHERE mg.movie_id = m.id AND mg.genre_id IN :likedGenres LIMIT 1) ) " +
"AND ( :dislikedGenres IS NULL OR NOT exists (SELECT movie_id FROM movies_genres mg WHERE mg.movie_id = m.id AND mg.genre_id IN :dislikedGenres LIMIT 1) ) " +
"AND NOT exists (SELECT user_id FROM movie_lists ml WHERE ml.movie_id = m.id and ml.user_id = :userId LIMIT 1)",
nativeQuery = true)
Page<Movie> findAllApprovedNotListedInUser(#Param("userId") Long userId, #Param("minReleaseYear") int minReleaseYear, #Param("maxReleaseYear") int maxReleaseYear, #Param("likedGenres") Set<Long> likedGenres, #Param("dislikedGenres") Set<Long> dislikedGenres, Pageable pageable);

Related

How to turn this native SQL query with left join and subquery into JPQL query in spring-jpa?

This query works fine for me but I need to turn this native query into a JPQL query so I can write unit tests on it. This query enriches a table tableA with some extra columns fetched from tableB and tableC. There are some subqueries involved to preprocess tableB and tableC. I have not found a way to remove these subqueries (without changing the functionality of the query). Right now, JPQL query does not support subquery.
#Query(value =
"SELECT a.id AS id, " +
" b.y1 AS y1, " +
" b.y2 AS y2, " +
" c.z AS z, " +
"FROM tableA a " +
" LEFT JOIN (SELECT y1, y2 FROM tableB WHERE date = :date) b ON a.id = b.id " +
" LEFT JOIN (SELECT id, " +
" date, " +
" ROW_NUMBER() OVER ( PARTITION BY id " +
" ORDER BY date DESC ) row_number " +
" FROM tableC) c ON a.id = c.id " +
" AND c.row_number = 1 ", nativeQuery = true)
How do I turn this into a JPQL query?

T-SQL to PL-SQL - Using count to return the number of records and the search result using the same query

I'm using that query in SQL Server, and it's working great:
SELECT *
FROM Person " +
"WHERE (#Nome IS NULL OR Nome LIKE #Nome + '%') " +
"ORDER BY [Nome] " +
"OFFSET " + pageSize * (pageNumber - 1) + " ROWS " +
"FETCH NEXT " + pageSize + " ROWS ONLY " +
" " +
"SELECT COUNT(Id) FROM Person " +
"WHERE (#Nome IS NULL OR Nome LIKE #Nome + '%') AND Ativo = 1";
In this search I return the results along with the total of these results. The output is a all the records with a "No column name" with the total.
But I couldn't find a way to return this result using Oracle.
In the query below, I return the results, but I didn't find a way to use "count" to return the total, along with the values.
ORACLE:
SELECT *
FROM
(SELECT ROWNUM rnum, b.*
FROM
(SELECT *
FROM Person
WHERE (:Nome IS NULL OR NOME LIKE ':Nome%')
) b
)
WHERE RNUM between :PageSize * (:PageNumber - 1) AND (:PageSize * :PageNumber)";

row_number() over partition in hql

What is the equivalent of row_number() over partition in hql
I have the following query in hql:
select s.Companyname, p.Productname, sum(od.Unitprice * od.Quantity - od.Discount) as SalesAmount FROM OrderDetails as od inner join od.Orders as o inner join od.Products as p " +
"inner join p.Suppliers as s" +
" where o.Orderdate between '2010/01/01' and '2014/01/01' GROUP BY s.Companyname,p.Productname"
I want to do partition by s.Companyname where RowNumber <= n.
As far as I know you cannot use row_number() neither in HQL nor in JPQL. I propose to use a native SQL query in this case:
#PersistenceContext
protected EntityManager entityManager;
...
String sQuery = "SELECT q.* FROM (" +
"SELECT s.company_name, " +
"p.product_name, " +
"sum(od.unit_price * od.quantity - od.discount) as SalesAmount, " +
"row_number() OVER (partition by s.company_name) as rn " +
"FROM OrderDetails od " +
"INNER JOIN Orders o ON o.id = od.order_id " +
"INNER JOIN Products p ON p.id = od.product_id " +
"INNER JOIN Suppliers s ON s.id = p.supplier_id " +
"WHERE o.order_date between '2010/01/01' and '2014/01/01') as q " +
"WHERE rn <= :n";
List<ResultDbo> results = new ArrayList<>();
Query query = entityManager.createNativeQuery(sQuery);
query.setParameter("n", n);
List<Object[]> resultSet = query.getResultList();
for (Object[] resultItem : resultSet) {
ResultDbo result = new ResultDbo();
result.setCompanyName((String) resultItem[0]);
result.setProductName((String) resultItem[1]);
result.setSalesAmount((String) resultItem[2]);
results.add(result);
}
If you ever try to use OVER() in HQL you'll almost certainly get some validation exception like java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: OVER near line 1, column 42 ...
Row number by partition looks like this:
row_number() over (partition by s.Companyname)
You can not use window function row_number in where clause, so you have to do subquery with filter by its value:
select * from (
-- here is your query
select
...,
row_number() over (partition by s.Companyname) as rowNum
from ...
where ...
) as res
where rowNum <= n

Dynamic query to dynamic data

I am new to the oracle database, I am trying to execute the following query
select o.id as ovaid ,
(case when(select count(m.cid) from ovamapper m where m.id = o.id and m.solutionid = 1)>0 then 1 else 0 end) as sol1,
(case when(select count(m.cid) from ovamapper m where m.id = o.id and m.solutionid = 2)>0 then 1 else 0 end) as sol1,
(case when(select count(m.cid) from ovamapper m where m.id = o.id and m.solutionid = 3)>0 then 1 else 0 end) as sol1 from ovatemplate o order by o.id
Instead of static values for solutionid , I would like to select it from other table.
Any help on this is really appreciated
you could use
join
to table that contain the solutionid. ex
Select * from ovatemplate JOIN solutiontable ON (solutiontable.ovaid=ovatempate.ovaid)
after that, change the static values to solutionid
Try this query
select o.id as ovaid ,
count(case when solutionid = 1 then m.cid else null end) as sol1 ,
count(case when solutionid = 2 then m.cid else null end) as sol2 ,
count(case when solutionid = 3 then m.cid else null end) as sol3
from ovamapper m , ovatemplate o
where m.id = o.id
group by o.id
order by o.id
If you dont need the aggregations as columns you should probably do that instead
select o.id as ovaid , solutionid , count(*) as sol
from ovamapper m , ovatemplate o
where m.id = o.id
and m.solutionid in (1,2,3)
group by o.id , solutionid
order by o.id

Is it true that writing LINQ queries is not easy for complex queries?

Is it true that writing LINQ queries is not easy for complex queries?
select a.sNavID,
a.sNavText,
a.sNavText as EName,
' '+a.sNavText as NameDisplay
from ContentPageNav as a
where a.navID=0
union
select b.sNavID,
a.sNavText + ' >> ' + b.sNavText as Name,
b.sNavText as EName,
' ' + b.sNavText as NameDisplay
from ContentPageNav as a
inner join ContentPageNav as b on a.sNavID=b.navID and b.catNo=1
union
select c.sNavID,a.sNavText + ' >> ' + b.sNavText + ' >> ' + c.sNavText as Name,
c.sNavText as EName,
' ' + c.sNavText as NameDisplay
from ContentPageNav as a
inner join ContentPageNav as b on a.sNavID=b.navID and b.catNo=1
inner join ContentPageNav as c on b.sNavID=c.navID and c.catNo=2
LINQ can pretty much handle and query you want to toss at it.
... Fully Composed Query ...
var query = (
from a in ContentPageNav
where a.navID == 0
select new
{
a.sNavID,
Name = a.sNavText,
EName = a.sNavText,
NameDisplay = " " + a.sNavText,
}).Concat(
from a in ContentPageNav
join b in ContentPageNav on a.navID equals b.navID
where b.catNo == 1
select new
{
b.sNavID,
Name = a.sNavText + " >> " + b.sNavText,
EName = b.sNavText,
NameDisplay = " " + b.sNavText,
}).Concat(
from a in ContentPageNav
join b in ContentPageNav on a.navID equals b.navID
where b.catNo == 1
join c in ContentPageNav on b.navID equals c.navID
where b.catNo == 1
select new
{
c.sNavID,
Name = a.sNavText + " >> " + b.sNavText + " >> " + c.sNavText,
EName = c.sNavText,
NameDisplay = " " + c.sNavText,
});
... Here is a version that has been decomposed into smaller parts ...
var rootRecords = ContentPageNav.Where(r => r.navID == 0);
var cat1Records = ContentPageNav.Where(r => r.catNo == 1);
var cat2Records = ContentPageNav.Where(r => r.catNo == 2);
var rootComposed =
from a in rootRecords
select new
{
a.sNavID,
Name = a.sNavText,
EName = a.sNavText,
NameDisplay = " " + a.sNavText,
};
var cat1Composed =
from a in rootRecords
join b in cat1Records on a.navID equals b.navID
select new
{
b.sNavID,
Name = a.sNavText + " >> " + b.sNavText,
EName = b.sNavText,
NameDisplay = " " + b.sNavText,
};
var cat2Composed =
from a in rootRecords
join b in cat1Records on a.navID equals b.navID
join c in cat2Records on b.navID equals c.navID
select new
{
c.sNavID,
Name = a.sNavText + " >> " + b.sNavText + " >> " + c.sNavText,
EName = c.sNavText,
NameDisplay = " " + c.sNavText,
};
var composedQuery = rootComposed.Concat(cat1Composed).Concat(cat2Composed);
You just used SQL anti-pattern #1.
Is it true that writing LINQ queries is not easy for complex queries?
When one uses Linq for data access, one tends to write simple queries that get the job done. It is a mistake to write complex queries in the first place. In that sense, it is true. It was also true for SQL.

Resources