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
Related
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?
I tried to get value from sub query after ordering the records of it but the following occurred when execute the query :
ORA-00907: missing right parenthesis
The Query is :
select S.value , nvl((select D.value from D
join T on D.subID = t.SubID
where D.subid2 = s.subid2 and t.subid3 = s.subid3 and rownum = 1 order by t.id),0 ) value
from S
You can't have ORDER BY clause in a subquery.
See if something like this helps: use a CTE (as it looks somewhat nicer; could be a normal subquery, if you want) which calculates ordinal number for all rows, sorted by t.id column value. In outer (main) query, select row whose rn = 1 (which should act just like your ORDER BY t.id + rownum = 1).
WITH
temp
AS
(SELECT s.VALUE s_value,
d.VALUE d_value,
ROW_NUMBER () OVER (ORDER BY t.id) rn
FROM d
JOIN t ON d.subid = t.subid
JOIN s
ON s.subid2 = d.subid2
AND s.subid3 = t.subid3)
SELECT s_value, NVL (d_value, 0) d_value
FROM temp
WHERE rn = 1
If you are on Oracle 12c or higher, you can use the FETCH FIRST... clause.
SELECT S.VALUE,
NVL (( SELECT D.VALUE
FROM D JOIN T ON D.subID = t.SubID
WHERE D.subid2 = s.subid2 AND t.subid3 = s.subid3
ORDER BY t.id
FETCH FIRST 1 ROWS ONLY),
0) VALUE
FROM S
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);
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)";
Please see my code below as it is running too slowly with the CROSS APPLY.
How can I remove the CROSS APPLY and add something else that will run faster?
Please note I am using SQL Server 2008 R2.
;WITH MyCTE AS
(
SELECT
R.NetWinCURRENCYValue AS NetWin
,dD.[Date] AS TheDay
FROM
dimPlayer AS P
JOIN
dbo.factRevenue AS R ON P.playerKey = R.playerKey
JOIN
dbo.vw_Date AS dD ON Dd.dateKey = R.dateKey
WHERE
P.CustomerID = 12345)
SELECT
A.TheDay AS [Date]
,ISNULL(A.NetWin, 0) AS NetWin
,rt.runningTotal AS CumulativeNetWin
FROM MyCTE AS A
CROSS APPLY (SELECT SUM(NetWin) AS runningTotal
FROM MyCTE WHERE TheDay <= A.TheDay) AS rt
ORDER BY A.TheDay
CREATE TABLE #temp (NetWin money, TheDay datetime)
insert into #temp
SELECT
R.NetWinCURRENCYValue AS NetWin
,dD.[Date] AS TheDay
FROM
dimPlayer AS P
JOIN
dbo.factRevenue AS R ON P.playerKey = R.playerKey
JOIN
dbo.vw_Date AS dD ON Dd.dateKey = R.dateKey
WHERE
P.CustomerID = 12345;
SELECT
A.TheDay AS [Date]
,ISNULL(A.NetWin, 0) AS NetWin
,SUM(B.NetWin) AS CumulativeNetWin
FROM #temp AS A
JOIN #temp AS B
ON A.TheDay >= B.TheDay
GROUP BY A.TheDay, ISNULL(A.NetWin, 0);
Here https://stackoverflow.com/a/13744550/613130 it's suggested to use recursive CTE.
;WITH MyCTE AS
(
SELECT
R.NetWinCURRENCYValue AS NetWin
,dD.[Date] AS TheDay
,ROW_NUMBER() OVER (ORDER BY dD.[Date]) AS RN
FROM dimPlayer AS P
JOIN dbo.factRevenue AS R ON P.playerKey = R.playerKey
JOIN dbo.vw_Date AS dD ON Dd.dateKey = R.dateKey
WHERE P.CustomerID = 12345
)
, MyCTERec AS
(
SELECT C.TheDay AS [Date]
,ISNULL(C.NetWin, 0) AS NetWin
,ISNULL(C.NetWin, 0) AS CumulativeNetWin
,C.RN
FROM MyCTE AS C
WHERE C.RN = 1
UNION ALL
SELECT C.TheDay AS [Date]
,ISNULL(C.NetWin, 0) AS NetWin
,P.CumulativeNetWin + ISNULL(C.NetWin, 0) AS CumulativeNetWin
,C.RN
FROM MyCTERec P
INNER JOIN MyCTE AS C ON C.RN = P.RN + 1
)
SELECT *
FROM MyCTERec
ORDER BY RN
OPTION (MAXRECURSION 0)
Note that this query will work if you have 1 record == 1 day! If you have multiple records in a day, the results will be different from the other query.
As I said here, if you want really fast calculation, put it into temporary table with sequential primary key and then calculate rolling total:
create table #Temp (
ID bigint identity(1, 1) primary key,
[Date] date,
NetWin decimal(29, 10)
)
insert into #Temp ([Date], NetWin)
select
dD.[Date],
sum(R.NetWinCURRENCYValue) as NetWin,
from dbo.dimPlayer as P
inner join dbo.factRevenue as R on P.playerKey = R.playerKey
inner join dbo.vw_Date as dD on Dd.dateKey = R.dateKey
where P.CustomerID = 12345
group by dD.[Date]
order by dD.[Date]
;with cte as (
select T.ID, T.[Date], T.NetWin, T.NetWin as CumulativeNetWin
from #Temp as T
where T.ID = 1
union all
select T.ID, T.[Date], T.NetWin, T.NetWin + C.CumulativeNetWin as CumulativeNetWin
from cte as C
inner join #Temp as T on T.ID = C.ID + 1
)
select C.[Date], C.NetWin, C.CumulativeNetWin
from cte as C
order by C.[Date]
I assume that you could have duplicates dates in the input, but don't want duplicates in the output, so I grouped data before puting it into the table.