SQL Server:как имитировать oracle keep dense rank query?
у меня есть запрос Oracle
select max(m.id),
m.someId keep (DENSE_RANK FIRST ORDER BY m.UpdateDate desc)
from MyTable m
groupBy m.someId
который для таких данных:
id UpdateDate someId
1 20-01-2012 10
2 20-01-2012 10
3 01-01-2012 10
4 10-02-2012 20
5 01-02-2012 20
6 01-04-2012 30
вернет мне именно этот:
2 10
4 20
6 30
Итак, для каждого someId он ищет последнее updateDate и возвращает соответствующий id
. (И если есть несколько кодов за последний период он принимает последний код).
но для SQL server этот запрос будет работать так же? Я имею в виду эту конструкцию keep (dense_rank first order by ..)
?
4 ответов
Я не думаю, что ваш конкретный запрос будет запускать SQL Server. Но вы можете достичь того же результата, делая это:
SELECT id, SomeId
FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY someId ORDER BY UpdateDate DESC, id DESC) Corr
FROM MyTable) A
WHERE Corr = 1
Я возвращаюсь и возвращаюсь к этому вопросу и ответу. К сожалению, существует несколько ситуаций, когда миграция с использованием "оконной функции для ранжирования" становится очень сложной. Вот такие ситуации:
- многие конструкции KEEP-DENSE_RANK в выбранной части Запрос Oracle на основе различных заказов
- группировка по группам наборов / rollups
поэтому я добавлю к ответу дополнительную информацию. Оригинальный SQLFIDDLE данных : http://sqlfiddle.com/#!6/e5c6d/6
1. чтение функции oracle:
select max(m.id), m.someId keep (DENSE_RANK FIRST ORDER BY m.UpdateDate desc)
from MyTable m
groupBy m.someId
там мы выбираем максимум m.id в группе (someId, UpdateDate), где UpdateDate является самым большим, это группа (someId)
2. прямой путь не работает из-за ошибки: Столбец " MyTable.UpdateDate ' недопустим в списке выбора, поскольку он не содержится ни в агрегатной функции, ни в предложение Group by.
SELECT FIRST_VALUE(id) OVER(PARTITION BY someId ORDER BY UpdateDate DESC, id DESC) first_in_orderedset , someId
FROM MyTable
GROUP BY someId
3. импровизация "прямо вперед" не эффективна
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. крест применить:
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. теперь давайте получим более сложную таблицу и более сложный запрос: Оракул : http://sqlfiddle.com/#!4/c943c/23 SQL SERVER:http://sqlfiddle.com#!6 / dc7fb/1/0 (данные предварительно генерируются и одинаковы в обеих песочницах - их легко сравнить результаты) Таблица:
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:
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 запрос:
(to be continued)
на самом деле я планировал поставить свою пользовательскую совокупность, написанную с C#. если кто-то заинтересован, пожалуйста, свяжитесь со мной... пользовательский агрегат является лучшим решением таких проблем, но он не является нежизнеспособным с точки зрения длин varchar. для каждой длины varchar вы будете обязаны создать "специализированную" функцию aggreate
SQL Server не поддерживает конструкцию "keep", поэтому вам нужно использовать подзапрос:
select m.*
from (select *, row_number() over (partition by m.someid ORDER BY m.UpdateDate desc) as seqnum
from MyTable m
) m
where seqnum = 1
это находит первую строку для каждого m.id с последним UpdateDate. Затем он выбирает эту строку во внешнем запросе. Обратите внимание, что вам не нужна group by с помощью этого метода.
это будет работать абсолютно. Сначала попробуй, потом спорь. Когда у вас есть несколько заказов, вы можете сделать это(пример, сделанный на Oracle):
-- этот с 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
-- этот без 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