将参数传递到子查询中以提高无效标识符

时间:2019-04-30 16:31:56

标签: sql oracle oracle12c

我有一个查询,其中我想按某些字段分组并汇总CSV字符串中的最后一个字段。如果您像我一样来自SQL Server,则可以使用FOR XML PATH('')。但是在Oracle 12c中则完全不同:

表定义

CREATE TABLE HCF (
ID NUMBER,
HCF_DATE DATE,
HCF_TYPE_1 NUMBER,
HCF_TYPE_2 NUMBER)

样本数据

ID  HCF_DATE    HCF_TYPE_1 HCF_TYPE_2
272 27/02/18    1          1
279 28/02/18    15         2
280 28/02/18    15         2
283 28/02/18    5          1

我正在使用的查询

WITH CTE_HCF AS (
SELECT HCF_DATE, HCF_TYPE_1, HCF_TYPE_2, COUNT(ID)
FROM HCF
GROUP BY HCF_DATE, HCF_TYPE_1, HCF_TYPE_2
HAVING COUNT(ID) > 0
)

SELECT a.*, b.*
FROM CTE_HCF a
CROSS APPLY (
    SELECT LTRIM(MAX(SYS_CONNECT_BY_PATH(ORDRE_ID,',')) KEEP (DENSE_RANK LAST ORDER BY curr),',') AS ids
    FROM ( SELECT HCF_DATE, HCF_TYPE_1, HCF_TYPE_2, ID,
            ROW_NUMBER() OVER (PARTITION BY HCF_DATE ORDER BY HCF_TYPE_1, HCF_TYPE_2) AS curr,
            ROW_NUMBER() OVER (PARTITION BY HCF_DATE ORDER BY HCF_TYPE_1, HCF_TYPE_2) -1 AS prev
            FROM CTE_HCF
            WHERE HCF_DATE = a.HCF_DATE AND HCF_TYPE_1 = a.HCF_TYPE_1 AND HCF_TYPE_2 = a.HCF_TYPE_2
        )
    CONNECT BY prev = PRIOR curr
    AND HCF_DATE = PRIOR HCF_DATE
    AND HCF_TYPE_1 = PRIOR HCF_TYPE_1
    AND HCF_TYPE_2 = PRIOR HCF_TYPE_2
    START WITH curr = 1 ) b

错误

ORA-00904: "a"."HCF_TYPE_2" :  invalid identifier

所需的输出

HCF_DATE    HCF_TYPE_1 HCF_TYPE_2 IDS
27/02/18    1          1          272
28/02/18    15         2          279,280
28/02/18    5          1          283

我认为问题在于原始查询的字段在CROSS APPLY子查询的子查询中不可见。

PS:我尝试了this article中所述的其他方法,但是由于多种原因而失败,例如LISTAGG超过了xK个字符。而且我没有足够的特权来创建函数,XMLAGG可能会导致您的Oracle实例崩溃。

更新 Oracle版本是:Oracle Database 12c Enterprise Edition Release 12.1.0.2.0

2 个答案:

答案 0 :(得分:1)

如果您超出listagg的字符数限制,则如何处理取决于您要显示的内容。

如果您使用的是12.2,则可以使用onrun truncate子句来修剪超出限制的字符。

select hcf_date, hcf_type_1, hcf_type_2, 
       listagg ( id, ',' 
         on overflow truncate 
       ) within group ( 
         order by id
       ) csv
from   hcf
group  by hcf_date, hcf_type_1, hcf_type_2;

HCF_DATE               HCF_TYPE_1   HCF_TYPE_2   CSV       
27-FEB-2018 00:00:00                1             1 272        
28-FEB-2018 00:00:00                5             1 283        
28-FEB-2018 00:00:00               15             2 279,280 

如果您使用的是12.1,则有some other workarounds

您可以使用行模式匹配(match_recognize)查找每个额外行的CSV长度。并返回字符串限制内的那些值:

我添加了一些额外的行,并将字符数限制设置为10以显示该原理:

insert into hcf values ( 281, to_date('28/02/18', 'dd/mm/yy'), 15, 2);
insert into hcf values ( 282, to_date('28/02/18', 'dd/mm/yy'), 15, 2);

with grps as ( 
  select *
  from   hcf match_recognize (
    partition by hcf_date, hcf_type_1, hcf_type_2
    order by id
    measures 
      sum(lengthb(s.id) + lengthb(';')) as len
    all rows per match
    after match skip past last row
    pattern (s+)
    define 
      s as 1=1
  )
)
  select hcf_date, hcf_type_1, hcf_type_2,
         listagg ( id, ',' ) 
           within group ( 
             order by id
           ) csv
  from   grps
  where  len <= 10
  group  by hcf_date, hcf_type_1, hcf_type_2;

HCF_DATE               HCF_TYPE_1   HCF_TYPE_2  CSV       
27-FEB-2018 00:00:00            1            1  272        
28-FEB-2018 00:00:00            5            1  283        
28-FEB-2018 00:00:00           15            2  279,280    

或者,当达到字符数限制时,您可以将行分成不同的组。并将它们显示为单独的CSV:

with grps as ( 
  select *
  from   hcf match_recognize (
    partition by hcf_date, hcf_type_1, hcf_type_2
    order by id
    measures 
      match_number() as grp
    all rows per match
    after match skip past last row
    pattern (s csv*)
    define csv as 
      lengthb(s.id) + sum(lengthb(csv.id) + lengthb(';')) < = 10
  )
)
  select hcf_date, hcf_type_1, hcf_type_2,
         listagg ( id, ',' ) 
           within group ( 
             order by id
           ) csv
  from   grps
  group  by hcf_date, hcf_type_1, hcf_type_2, grp;

HCF_DATE               HCF_TYPE_1   HCF_TYPE_2  CSV       
27-FEB-2018 00:00:00            1            1  272        
28-FEB-2018 00:00:00            5            1  283        
28-FEB-2018 00:00:00           15            2  279,280    
28-FEB-2018 00:00:00           15            2  281,282  

如果您要返回的整个CSV列表的长度超过了varchar2的限制,则需要返回clob。您可以使用XML进行哪些操作

select hcf_date, hcf_type_1, hcf_type_2,
       substr (
          xmlcast ( 
            xmlagg (
              xmlelement(s, ',' || id)
              order by id
            ) as clob
          ), 2
        ) csv
from    hcf
group  by hcf_date, hcf_type_1, hcf_type_2;  

HCF_DATE               HCF_TYPE_1   HCF_TYPE_2   CSV               
27-FEB-2018 00:00:00            1             1  272                
28-FEB-2018 00:00:00            5             1  283                
28-FEB-2018 00:00:00           15             2  279,280,281,282 

答案 1 :(得分:0)

最后,我设法要求DBA创建一个将CSV中的id值连接起来的函数,我个人认为这是返回CLOB值的最佳方法。

功能:

create or replace FUNCTION concatenate_list (p_cursor IN  SYS_REFCURSOR)
  RETURN  CLOB
IS
  l_return  CLOB; 
  l_temp    CLOB;
BEGIN
  LOOP
    FETCH p_cursor
    INTO  l_temp;
    EXIT WHEN p_cursor%NOTFOUND;
    l_return := l_return || ',' || l_temp;
  END LOOP;
  RETURN LTRIM(l_return, ',');
END;

查询

WITH CTE_HCF AS (
SELECT HCF_DATE, HCF_TYPE_1, HCF_TYPE_2, COUNT(ID)
FROM HCF
GROUP BY HCF_DATE, HCF_TYPE_1, HCF_TYPE_2
HAVING COUNT(ID) > 0
)

SELECT a.*
  , concatenate_list(CURSOR(SELECT id FROM HCF WHERE HCF_DATE = a.HCF_DATE AND HCF_TYPE_1 = a.HCF_TYPE_1 AND HCF_TYPE_2 = a.HCF_TYPE_2)) AS CSV
FROM CTE_HCF a

Source

PS:如果您不需要CLOB,那么LISTAGG是您的最佳选择。