【问题标题】:LISTAGG oracle function incorrect resultsLISTAGG oracle 函数不正确的结果
【发布时间】:2022-11-30 17:39:10
【问题描述】:

我想咨询聚合函数 LISTAGG 的这个问题:

select 
r.id, 
(
select
    LISTAGG(
        r.id || '##' || cco.id  
            , 
        ' '
    )
    from 
    (
        SELECT
            co.id
       FROM conditions co
       START WITH ID = (select cons.id from conditions cons where cons.role_id = r.id)
       CONNECT BY PRIOR co.id = co.parent_condition_id
    ) cco
) conditions_export

from roles r
where r.id in (570, 571, 569)
--r.id between 569 and 571



-- table roles has 2 fields: id (number), name (varchar2)
-- table conditions has 4 fields: id, parent_condition_id, role_id (number), rule (varchar2)

当我在条件 r.id in (570, 571, 569) 中使用单个 ID(或显式 ID 列表)时,结果符合预期。

当我不使用 where 子句或使用某些动态范围(r.id between 569 and 571r.id > 500r.id in (select rr.id from roles rr))时,结果包含每行相同的聚合值 - 结果是错误的(与预期不同)。

我的数据库中的示例:

表角色中的值:

id   name
---  -----
569  ROLE1
570  ROLE2
571  ROLE3

表条件中的值:

id    parent_condition_id   role_id  rule
------------------------------------------
1657  NULL                  569      deny
1659  NULL                  570      allow
1667  NULL                  571      and
1674  1668                  NULL     match
1673  1670                  NULL     allow
1672  1671                  NULL     allow
1671  1670                  NULL     and
1670  1669                  NULL     and
1669  1668                  NULL     and
1668  1667                  NULL     and

查询:... r.id in (570, 571, 569) 结果:

569 569##1657
570 570##1659
571 571##1667 571##1668 571##1669 571##1670 571##1671 571##1672 571##1673 571##1674

查询:... r.id between 569 and 571 结果:

569 569##1657
570 570##1657
571 571##1657

使用此聚合的原因是将当前配置从数据库导出到文本文件。

问题:你有什么想法,如何解决这个问题?

数据库版本为Oracle 19c

【问题讨论】:

  • 我没有在 18c、19c (19.16) 或 21c 中看到 ... r.id in (570, 571, 569) 的结果;与... r.id between 569 and 571 相同。但是单独运行它们我可以看到这应该是你从所有这些中得到的结果 - 所以我猜你遇到了一个错误。

标签: oracle hierarchical-data


【解决方案1】:

您可以使用递归子查询分解而不是分层查询:

with rcte (role_id, condition_id) as (
  select r.id, c.id
  from roles r
  left join conditions c on c.role_id = r.id
  where r.id in (570, 571, 569)
  union all
  select rcte.role_id, c.id
  from rcte
  join conditions c on c.parent_condition_id = rcte.condition_id
)
select rcte.role_id,
  listagg(rcte.role_id || '##' || rcte.condition_id, ' ')
from rcte
group by rcte.role_id
order by rcte.role_id;
ROLE_ID LISTAGG(RCTE.ROLE_ID||'##'||RCTE.CONDITION_ID,'')
569 569##1657
570 570##1659
571 571##1667 571##1668 571##1674 571##1669 571##1670 571##1673 571##1671 571##1672

fiddle 包括您的各种初始查询及其结果。

锚点成员获取角色和初始条件;然后递归成员查找任何子条件。


第一个问题是结果条件的顺序不同

您的原始代码没有任何条件排序,因此结果是不确定的。

在您的示例输出中,它们似乎是按条件 ID 排序的,因此您可以明确地这样做:

select rcte.role_id,
  listagg(rcte.role_id || '##' || rcte.condition_id, ' ')
    within group (order by rcte.condition_id) as conditions
from rcte
ROLE_ID CONDITIONS
569 569##1657
570 570##1659
571 571##1667 571##1668 571##1669 571##1670 571##1671 571##1672 571##1673 571##1674

正如您提到的级别,您可能希望按该级别排序,尽管它为您的示例提供了不同的结果。您可以通过跟踪 CTE 中的虚拟列来获取级别(我将其称为 lvl,因为 level 保留用于分层语法):

with rcte (role_id, lvl, condition_id) as (
  select r.id, 1, c.id
  from roles r
  left join conditions c on c.role_id = r.id
  where r.id in (570, 571, 569)
  union all
  select rcte.role_id, rcte.lvl + 1, c.id
  from rcte
  join conditions c on c.parent_condition_id = rcte.condition_id
)
select rcte.role_id,
  listagg(rcte.role_id || '##' || rcte.condition_id, ' ')
    within group (order by rcte.lvl) as conditions
from rcte
group by rcte.role_id
order by rcte.role_id;
ROLE_ID CONDITIONS
569 569##1657
570 570##1659
571 571##1667 571##1668 571##1669 571##1674 571##1670 571##1671 571##1673 571##1672

或者您可以按级别排序,然后按条件排序,其中同一级别有多个条件(即多个条件具有相同的父项);有了这些数据,它没有什么区别,但如果有多个数据,它会保持确定性:

select rcte.role_id,
  listagg(rcte.role_id || '##' || rcte.condition_id, ' ')
    within group (order by rcte.lvl, rcte.condition_id) as conditions
from rcte
ROLE_ID CONDITIONS
569 569##1657
570 570##1659
571 571##1667 571##1668 571##1669 571##1674 571##1670 571##1671 571##1673 571##1672

fiddle

第二个问题是我无法访问“LEVEL”变量

您问题中的代码并未引用它,但如上所示,您可以将其生成为 lvl 虚拟列。目前还不清楚您将如何使用它 - 如果您希望它出现在选择列表中,那么您将不得不按它进行分组,这会得到非常不同的结果。您可以改为包含 max(lvl);或将 lvl 连接到条件中(例如 571#1#1667)。目前还不清楚你想用它做什么。

【讨论】:

  • 感谢这个提议,它有 2 个问题,为什么我不能使用它(所以我可能切换到 plsql 而不是单一查询)。第一个问题是结果条件的不同顺序,第二个问题是我无法访问“LEVEL”变量,所以我无法确定 YAML 输出的缩进
  • @tomas - 您的原始查询没有任何明确的排序,使其具有不确定性,因此我没有向我的查询中添加任何内容。根据您要应用的逻辑,我已经用几种明确订购的方式更新了我的答案。我还展示了如何获得 level 的等价物,但你的问题也没有使用它,所以我不确定你想用它做什么。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-02
  • 2011-09-19
  • 1970-01-01
相关资源
最近更新 更多