有没有办法在一个简单的查询中做到这一点?

时间:2020-02-16 12:35:14

标签: sql oracle group-by subquery

我有一个BIG(2300万行)表CONTRATOS,其中包含以下列:

SELECT
    CONTRATO,
    CODIGO_ORIGEN,
    ORIGEN
FROM CONTRATOS;

CODIGO_ORIGEN列有多个具有相同值的行:

CODIGO_ORIGEN     CONTRATO       ORIGEN 
-------------    ----------     --------
     1               345          CONT
     1               363          BKP
     1               645          BKP
     1               365          CONT 

每个CODIGO_ORIGEN我只需要获得一份CONTRATO,但始终优先考虑'CONT'值。因此,在此示例中,值将为 365

的CONTRATO。

我正在尝试这样的事情:

SELECT  
   CODIGO_ORIGEN,
   CASE
    WHEN ORIGEN = 'CONT' THEN
        FIRST_VALUE (CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY CONTRATO DESC) 
    WHEN ORIGEN = 'BKP' THEN
        FIRST_VALUE (CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY CAMPO1 DESC, CAMPO2 DESC, CONTRATO DESC) 
   END AS CONTRATO
FROM CONTRATOS;

如果ORIGEN是'CONTR',我应该按最高的CONTRATO排序并获得CONTRATO列。 如果ORIGEN是'BKP',我应该按不同的列排序并获得CONTRATO列。

最后,我应该可以使用CODIGO_ORIGEN-> CONTRATO(1:1)排成一排。

有一种简单的方法(1个查询)吗?

谢谢!

3 个答案:

答案 0 :(得分:0)

您可以使用ROW_NUMBER来按codigo_origen对行进行排名。这是编写其ORDER BY子句的一种方法。也有其他人。

select *
from
(
  select  
     c.*,
     row_number() over (
       partition by codigo_origen 
       order by 
         case when origen 'BKP' then campo1 end desc,
         case when origen 'BKP' then campo2 end desc,
         origen desc
     ) as rn
  from contratos c
) ranked
where rn = 1
order by codigo_origen;

答案 1 :(得分:0)

我想我明白了

      SELECT 
        C.contrato, C.codigo_origen,  NVL2(MX.CONTRATO, NULL, 'F') AS ESTADO, NVL2(MX.CONTRATO, NULL, '4008') AS ERROR
      FROM MGR_CUENTA_CTL C
      LEFT JOIN
      (
          select /*+ PARALLEL */
            DISTINCT CODIGO_ORIGEN,
            CASE
              WHEN ORIGEN_TABLA = 'CONT' THEN FIRST_VALUE(CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY CONTRATO DESC) 
              WHEN ORIGEN_TABLA = 'BKP' THEN FIRST_VALUE(CONTRATO) OVER (PARTITION BY CODIGO_ORIGEN ORDER BY CAMPO1 DESC, CAMPO2 DESC) 
            END AS CONTRATO
          FROM
          (  
             SELECT /*+ PARALLEL */ ctl.*
             FROM mgr_cuenta_ctl ctl
             LEFT JOIN 
             (
                   select /*+ PARALLEL */ CODIGO_ORIGEN
                   from mgr_cuenta_ctl
                   group by codigo_origen having count(distinct origen) > 1
             ) CTL2 ON ctl.CODIGO_ORIGEN = ctl2.CODIGO_ORIGEN
             WHERE (CTL2.CODIGO_ORIGEN IS NOT NULL AND CTL.ORIGEN <> 'BKP') OR (CTL2.CODIGO_ORIGEN IS NULL)
          )
      ) MX ON MX.CONTRATO = C.CONTRATO;

我认为它确实可以解释我的意图。

这对您有意义吗?

谢谢!

答案 2 :(得分:0)

检查是否有origen = 'CONT',使用解析count()。如果是,则将解析函数与第一种排序方法一起使用,否则,将解析函数与第二种排序方法一起使用:

select mgr_cuenta_ctl.*,
       case 
       when count(case origen when 'CONT' then 1 end) over (partition by codigo_origen) > 0
       then first_value(contrato) over (
            partition by codigo_origen
            order by case origen when 'CONT' then 1 end, contrato desc)
       else first_value(contrato) over (
            partition by codigo_origen
            order by case origen when 'BKP' then 1 end, campo1 desc, campo2 desc, contrato desc)
       end as best
  from mgr_cuenta_ctl

如果只希望分组的值而不包含详细信息,请删除partition by子句:

select codigo_origen,
       case when count(case origen when 'CONT' then 1 end) > 0
       then max(contrato) keep (dense_rank first 
                order by case origen when 'CONT' then 1 end, contrato desc) 
       else max(contrato) keep (dense_rank first 
                order by case origen when 'BKP' then 1 end, campo1 desc, campo2 desc) 
       end as best
  from mgr_cuenta_ctl
  group by codigo_origen

dbfiddle demo

顺便说一句。我试图分析您的查询,但是它引发了一些奇怪的错误,我放弃了。在这里,您只需触摸表格一次,就不会自我连接,因此应该更快。