【问题标题】:SQL Server: how to imitate oracle keep dense_rank query?SQL Server:如何模仿oracle保持dense_rank查询?
【发布时间】:2012-06-01 04:07:45
【问题描述】:

我有一个 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。 (如果最新日期有多个 id,则需要最新 id)。

但是对于 SQL Server,这个查询会以同样的方式工作吗?我的意思是这个结构keep (dense_rank first order by ..)

【问题讨论】:

    标签: sql sql-server oracle


    【解决方案1】:

    如果有人在 Postgres 中寻找 Oracle KEEP DENSE_RANK 模拟:

    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
    

    【讨论】:

      【解决方案2】:

      这绝对有效。先尝试,再争论。 当您有多个订单时,您可以这样做(在 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
      

      -- 这个没有keepdense_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
      

      【讨论】:

        【解决方案3】:

        我返回并返回这个问题和答案。不幸的是,在某些情况下,使用“排名窗口函数”进行迁移变得非常复杂。这些情况是:

        1. 的选择部分中有许多 KEEP-DENSE_RANK 结构 基于不同订单的Oracle查询
        2. 按组/汇总分组

        因此,我将在答案中添加其他信息。 原始数据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 服务器: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 长度而言,它并不是 unviersal。对于每个 varchar 长度,您将有义务创建“专门的”聚合函数

        【讨论】:

          【解决方案4】:

          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 someId',它应该如何工作?我的查询总是会返回 3 个条目,因为表中有 3 个不同的 someId。您的查询将返回不依赖于不同 someId 数量的结果,那么它应该是错误的吗?
          • 这行不通,结果是每个 id 都有一行,这不是 op 想要的。
          【解决方案5】:

          我认为您的特定查询不会运行 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
          

          【讨论】:

          • 我不明白.. 如果没有'group by someId',它应该如何工作?我的查询总是会返回 3 个条目,因为表中有 3 个不同的 someId。您的查询将返回不依赖于不同 someId 数量的结果,那么它应该是错误的吗?
          • @javagirl - 你先试过了吗?它会起作用的。至于它是如何工作的,分析函数(OVER() ...)可以有一个PARTITION BY,并且不需要在整个级别上进行分组。它为表中的每一行返回一个值。
          • 我误读了原始数据。问题是通过将 someid 放入分区而不是 id 来解决的。
          • @GordonLinoff - 是的,我知道。这就是为什么我在我的答案中有这个;-)。另外,关于这个问题,当最近日期有多个 id 时,它应该返回最大 id,你没有考虑到这一点
          猜你喜欢
          • 2014-03-17
          • 1970-01-01
          • 2019-03-01
          • 2014-01-26
          • 1970-01-01
          • 2021-10-23
          • 1970-01-01
          • 2016-07-29
          • 1970-01-01
          相关资源
          最近更新 更多