SQL Server: how to imitate oracle keep dense_rank query?

前端 未结 5 1928
既然无缘
既然无缘 2020-12-20 17:46

I have an Oracle query

select max(m.id),
       m.someId keep (DENSE_RANK FIRST ORDER BY m.UpdateDate desc) 
from MyTable m 
groupBy m.someId
相关标签:
5条回答
  • 2020-12-20 17:55

    SQL Server does not support the "keep" construct, so you need to use a subquery:

    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
    

    This finds the first row for each m.id with the most recent UpdateDate. It then chooses that row in the outer query. Note that you don't need a group by with this method.

    0 讨论(0)
  • 2020-12-20 18:02

    In case someone look for Oracle KEEP DENSE_RANK simulation in Postgres:

    CREATE TABLE myt (
      "id" INTEGER,
      "update_date" timestamp,
      "some_id" INTEGER
    );
    
    INSERT INTO myt
      ("id", "update_date", "some_id")
    VALUES
      ('1', '2012-01-20', '10'),
      ('2', '2012-01-20', '10'),
      ('3', '2012-01-01', '10'),
      ('4', '2012-10-02', '20'),
      ('5', '2012-01-02', '20'),
      ('6', '2012-01-04', '30');                         
    
    
    select
        some_id, 
        (array_agg(id order by update_date desc, id desc))[1]
    from myt
    group by some_id
    order by some_id
    

    0 讨论(0)
  • 2020-12-20 18:09

    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
    
    0 讨论(0)
  • 2020-12-20 18:14

    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:

    1. many KEEP-DENSE_RANK constructions in the select part of Oracle query based on different orders
    2. 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

    0 讨论(0)
  • 2020-12-20 18:17

    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
    
    0 讨论(0)
提交回复
热议问题