SQL Server: how to imitate oracle keep dense_rank query?
I don't think that your particular query will run SQL Server. But you can achieve the same result doing this:
SELECT id, SomeId
FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY someId ORDER BY UpdateDate DESC, id DESC) Corr
FROM MyTable) A
WHERE Corr = 1
I return and return to this question and the answer. Unfortunately there are several situations when migration using the "window function for ranking" become very complex. Those situations are:
- many KEEP-DENSE_RANK constructions in the select part of Oracle query based on different orders
- grouping by grouping sets/rollups
Therefore I will add to the answer additional information. Original data SQLFIDDLE: http://sqlfiddle.com/#!6/e5c6d/6
1. Reading oracle function:
select max(m.id), m.someId keep (DENSE_RANK FIRST ORDER BY m.UpdateDate desc)
from MyTable m
groupBy m.someId
there we select max of m.id in the group (someId, UpdateDate) where UpdateDate is biggest it the group (someId)
2. straight forward way doesn't work because of error: Column 'MyTable.UpdateDate' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
SELECT FIRST_VALUE(id) OVER(PARTITION BY someId ORDER BY UpdateDate DESC, id DESC) first_in_orderedset , someId
FROM MyTable
GROUP BY someId
3. improoved 'straight forward' is noneffective
SELECT someId, MIN(first_in_orderedset)
FROM
( SELECT FIRST_VALUE(id) OVER(PARTITION BY someId ORDER BY UpdateDate DESC, id DESC) first_in_orderedset , someId
FROM MyTable ) t
GROUP BY someId;
4. cross apply:
SELECT grouped.someId, orderedSet.FirstUpdateDate, maxInSet.first_in_orderedset FROM
(
SELECT mt.someId
FROM MyTable mt
GROUP BY mt.someId
) grouped CROSS APPLY
(
SELECT top 1 mt2.UpdateDate as FirstUpdateDate
FROM MyTable mt2
WHERE mt2.someId=grouped.someId
ORDER BY UpdateDate desc
) orderedSet CROSS APPLY
(
SELECT max(mt3.id) as first_in_orderedset
FROM MyTable mt3
WHERE mt3.someId=grouped.someId and mt3.UpdateDate=orderedSet.FirstUpdateDate
) maxInSet;
5. Now lets get the more complex table and more complex query: ORACLE : http://sqlfiddle.com/#!4/c943c/23 SQL SERVER: http://sqlfiddle.com/#!6/dc7fb/1/0 (data is pregenerated and it is the same in both sandboxes - it is easy to compare results) Table:
CREATE TABLE AlarmReports (
id int PRIMARY KEY,
clientId int, businessAreaId int , projectId int, taskId int,
process1Spent int, process1Lag int, process1AlarmRate varchar2(1) null,
process2Spent int, process2Lag int, process2AlarmRate varchar2(1) null,
process3Spent int, process3Lag int, process3AlarmRate varchar2(1) null
)
Oracle query:
SELECT clientId, businessAreaId, projectId,
sum(process1Spent),
sum(process2Spent),
sum(process3Spent),
MIN(process1AlarmRate) KEEP (DENSE_RANK FIRST ORDER BY process1Lag DESC),
MIN(process2AlarmRate) KEEP (DENSE_RANK FIRST ORDER BY process2Lag DESC),
MIN(process3AlarmRate) KEEP (DENSE_RANK FIRST ORDER BY process3Lag DESC)
FROM AlarmReports
GROUP BY GROUPING SETS ((),(clientId),(clientId, projectId),(businessAreaId),(clientId,businessAreaId))
SQL query:
(to be continued)
actually there I have planned to put my custom aggregate wroted with c#. if somebody are interested, please contact me... custom aggregate is the best solution of such problems but it is not unviersal in terms of varchar lengths. for each varchar length you would be obligated to create "specialised" aggreate function
That will work absolutely. Try first, then argue. When you have multiple order by you can do this(example made on Oracle):
-- this one with keep dense_rank
WITH a AS (SELECT 1 s1, 4 s2, 'a' c, 10 g FROM dual UNION all
SELECT 2 s1, 2 s2, 'b' c, 10 g FROM dual UNION ALL
SELECT 3 s1, 1 s2, 'c' c, 20 g FROM dual UNION ALL
SELECT 4 s1, 3 s2, 'd' c, 20 g FROM dual)
SELECT g,
MAX(c) KEEP (DENSE_RANK FIRST ORDER BY s1) s1,
MAX(c) KEEP (DENSE_RANK FIRST ORDER BY s2) s2
FROM a
GROUP BY g
-- This one without keep dense_rank
WITH a AS (SELECT 1 s1, 4 s2, 'a' c, 10 g FROM dual UNION all
SELECT 2 s1, 2 s2, 'b' c, 10 g FROM dual UNION ALL
SELECT 3 s1, 1 s2, 'c' c, 20 g FROM dual UNION ALL
SELECT 4 s1, 3 s2, 'd' c, 20 g FROM dual)
SELECT g,
MAX(DECODE(s1, 1, c)) s1,
MAX(DECODE(s2, 1, c)) s2
FROM (SELECT g,c,
ROW_NUMBER() OVER (PARTITION BY g ORDER BY s1) s1,
ROW_NUMBER() OVER (PARTITION BY g ORDER BY s2) s2
FROM a) b
GROUP BY g