【问题标题】:Oracle SQL - LISTAGG will be called even condition of CASE statement will not be fullfilledOracle SQL - 即使 CASE 语句的条件不满足,也会调用 LISTAGG
【发布时间】:2021-09-26 22:57:56
【问题描述】:

我必须在版本为Oracle Database 12c Release 12.1.0.1.0 - 64bit Production 的Oracle DB 服务器上工作。因此在使用 LISTAGG 函数时我不能使用ON OVERFLOW ...。为了克服大小大于 4000 字节的列的聚合,我想使用提到的建议解决方案 here,这样我就不会收到错误 01489. 00000 - "result of string concatenation is too long"

不幸的是,这种方法对我不起作用。即使条件为 1=1 也不应该调用 LISTAGG 函数,但我得到了上述错误。

这里是查询:

select distinct t.id,
    (case when 1=1 then
        'Test'
    else
        LISTAGG(mh.RNAME1, '; ') WITHIN GROUP (ORDER BY mh.RNAME1)
    end) as RNAME1LIST,
    (case when 1=1 then
        'Test'
    else
        LISTAGG(mh.RNAME2, '; ') WITHIN GROUP (ORDER BY mh.RNAME2)
    end) as RNAME2LIST, 
    t.email1 as EMAIL1
from mh 
join mp on MH.MID = MP.MID
RIGHT JOIN T ON mh.T_ID = T.ID
group by t.id, t.email1;

我正在寻找对此行为的解释和替代方法,这样我就不会收到上述错误。

【问题讨论】:

  • 也许 Oracle 正在做一些预编译,然后基于此使您的代码失败。
  • 对于您的语句 case 在聚合之后进行评估(因为它包含聚合函数),但聚合本身是在 group by 期间完成的。所以Oracle没有可能避免它
  • 此处无需执行 SELECT DISTINCT,您的 GROUP BY 不会返回重复项。
  • case 表达式.

标签: sql oracle case distinct listagg


【解决方案1】:

你可以使用 CLOB

drop function listagg_clob;
drop type listagg_clob_t;

create or replace package list_const_p
is
  list_sep varchar2(10) := ',';
end list_const_p;
/
sho err

create type listagg_clob_t as object(
  v_liststring varchar2(32767),
  v_clob       clob,
  v_templob    number,

  static function ODCIAggregateInitialize(
    sctx IN OUT listagg_clob_t
  ) return number,
  member function ODCIAggregateIterate(
    self IN OUT listagg_clob_t, value IN varchar2
  ) return number,
  member function ODCIAggregateTerminate(
    self IN OUT listagg_clob_t, returnValue OUT clob, flags IN number
  ) return number,
  member function ODCIAggregateMerge(
    self IN OUT listagg_clob_t, ctx2 IN OUT listagg_clob_t
  ) return number
);
/
sho err

create or replace type body listagg_clob_t is

static function ODCIAggregateInitialize(sctx IN OUT listagg_clob_t)
return number is
begin
  sctx := listagg_clob_t('', '', 0);
  return ODCIConst.Success;
end;

member function ODCIAggregateIterate(
  self IN OUT listagg_clob_t,
  value IN varchar2
) return number is
begin
  if nvl(lengthb(v_liststring),0) + nvl(lengthb(value),0) <= 4000 then
    self.v_liststring:=self.v_liststring || value || list_const_p.list_sep;
  else
    if self.v_templob = 0 then
      dbms_lob.createtemporary(self.v_clob, true, dbms_lob.call);
      self.v_templob := 1;
    end if;
    dbms_lob.writeappend(self.v_clob, length(self.v_liststring), v_liststring);
    self.v_liststring := value || list_const_p.list_sep;
  end if;
  return ODCIConst.Success;
end;

member function ODCIAggregateTerminate(
  self IN OUT listagg_clob_t,
  returnValue OUT clob,
  flags IN number
) return number is
begin
  if self.v_templob != 0 then
    dbms_lob.writeappend(self.v_clob, length(self.v_liststring), self.v_liststring);
    dbms_lob.trim(self.v_clob, dbms_lob.getlength(self.v_clob) - 1);
  else
    self.v_clob := substr(self.v_liststring, 1, length(self.v_liststring) - 1);
  end if;
  returnValue := self.v_clob;
  return ODCIConst.Success;
end;

member function ODCIAggregateMerge(self IN OUT listagg_clob_t, ctx2 IN OUT listagg_clob_t) return number is
begin
  if ctx2.v_templob != 0 then
    if self.v_templob != 0 then
      dbms_lob.append(self.v_clob, ctx2.v_clob);
      dbms_lob.freetemporary(ctx2.v_clob);
      ctx2.v_templob := 0;
    else
      self.v_clob := ctx2.v_clob;
      self.v_templob := 1;
      ctx2.v_clob := '';
      ctx2.v_templob := 0;
    end if;
  end if;
  if nvl(lengthb(self.v_liststring),0) + nvl(lengthb(ctx2.v_liststring),0) <= 4000 then
    self.v_liststring := self.v_liststring || ctx2.v_liststring;
    ctx2.v_liststring := '';
  else
    if self.v_templob = 0 then
      dbms_lob.createtemporary(self.v_clob, true, dbms_lob.call);
      self.v_templob := 1;
    end if;
    dbms_lob.writeappend(self.v_clob, length(self.v_liststring), self.v_liststring);
    dbms_lob.writeappend(self.v_clob, length(ctx2.v_liststring), ctx2.v_liststring);
    self.v_liststring := '';
    ctx2.v_liststring := '';
  end if;
  return ODCIConst.Success;
end;
end;
/
sho err

CREATE or replace FUNCTION listagg_clob (input varchar2) RETURN clob
PARALLEL_ENABLE AGGREGATE USING listagg_clob_t;
/
sho err;
;

测试sql

SELECT listagg_clob(n)
  FROM (SELECT LEVEL * 100 n FROM DUAL CONNECT BY LEVEL<=10000);

http://www.itpub.net/forum.php?mod=viewthread&tid=2094642&extra=&highlight=concat&page=1

【讨论】:

  • 你可以使用 clob 来打破 4000 字节的限制
【解决方案2】:

这是因为聚合实际上是即时发生的,当 Oracle 评估行组时,case 表达式在 select 列表中,因此在结果数据集上进行评估。

您期望的处理应该缓冲所有输入并维护交叉引用(聚合到源行)。或者处理复杂的依赖树,因为在select 列表中,您可以在同一表达式中的group by 列旁边使用聚合结果,这也可能引入循环依赖。

下面是一些日志记录函数的示例,它在聚合步骤中被评估,而不管case 结果如何。并且 case 在聚合之后进行评估。

/*Logging table*/
create table t (
  /*To have a sequence*/
  id number GENERATED ALWAYS as IDENTITY(START with 1 INCREMENT by 1)
  , val number
  , src varchar2(100)
)
/*Logger to check what is going on here*/
create function f_test(
  p_val in number
  , p_src in varchar2
) return varchar2
as
  pragma autonomous_transaction;
begin
  insert into t(val, src)
  values (p_val, p_src);
  commit;
  
  return p_val;
end;
/
/*Log aggregation function invocation and case/where evaluation*/
with a as (
  select
    level as l
    , mod(level, 3) as grp
  from dual
  connect by level < 7
)
select
  grp
  , case
      when f_test(grp, 'CASE CONDITION') > 0
      then f_test(grp, 'CASE RESULT')
      else max(f_test(grp, 'AGG'))
    end as res_agg
from a
where f_test(grp, 'WHERE') = grp
group by grp
玻璃钢 | RES_AGG --: | :------ 2 | 2 0 | 0 1 | 1
select *
from t
order by id
身份证 |价值 | SRC -: | --: | :------------- 1 | 1 |在哪里 2 | 1 | AGG 3 | 2 |在哪里 4 | 2 | AGG 5 | 0 |在哪里 6 | 0 | AGG 7 | 1 |在哪里 8 | 1 | AGG 9 | 2 |在哪里 10 | 2 | AGG 11 | 0 |在哪里 12 | 0 | AGG 13 | 2 |案例条件 14 | 2 |案例结果 15 | 0 |案例条件 16 | 1 |案例条件 17 | 1 |案例结果

db小提琴here

但是,您可以像在 Oracle 12c+ 中一样解决此问题,您可以在 with 子句中声明本地函数。通过这种方式,您可以将数据聚合为集合类型,然后在 PL/SQL 中将其连接起来。或者作为一种通用且更快的方法:为 clob 数据类型定义您的自定义 listagg,如另一个答案中所述。

with function f_listagg_clob(
  p_vc2_tab in sys.odcivarchar2list
  , p_sep in varchar2 default ', '
)
  return clob
as
  r_clob clob;
begin
  for i in 1..p_vc2_tab.count loop
    r_clob := r_clob || case when i > 1 then p_sep end || p_vc2_tab(i);
  end loop;

  return r_clob;
end;

a as (
  select
    dbms_random.string('X', 100) as str
  from dual
  connect by level < 1000
)
select length(f_listagg_clob(cast(collect(str) as sys.odcivarchar2list))) as result_len
from a
| RESULT_LEN | | ---------: | | 101896 |

db小提琴here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 2015-07-12
    • 2015-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-04
    相关资源
    最近更新 更多