【问题标题】:Oracle SQ Identify Siblings via siblings and calculate related ratioOracle SQL 通过兄弟姐妹识别兄弟姐妹并计算相关比率
【发布时间】:2020-03-02 21:29:30
【问题描述】:

我有一组相关记录及其相关比率。不幸的是,并非所有比率都被捕获,而是可以从其他比率中得出。正如我可能需要 A -> D 的比率,但是我没有直接的比率可以参考,但我可以从下面的示例中通过 A->C (0.5) 和 C-> 的比率得出它D (3) 因此 0.5 * 3 = 1.5。或者我可以通过反转 A->B (1/2 = 0.5) 的比率来推导出 B,然后取 A->C (0.5) 的比率,因此 (1/2) * 0.5 = 0.25 依此类推.

例如

FROM_UNIT_ID  TO_UNIT_ID  RATIO
A             B           2
A             C           0.5
C             D           3

我想要做的是计算所有可能路径及其比率的完整列表,如下所示。

FROM_UNIT_ID  TO_UNIT_ID  RATIO
A             B           2
A             C           0.5
C             D           3
A             D           1.5
B             C           0.25
B             D           0.75
B             A           0.5
C             A           2
C             B           4
D             A           0.666666667
D             B           1.333333333
D             C           0.333333333

使用下面的stack overflow question 我已经能够推导出一个口粮的所有同级以获得可能路径的列表,但是我无法调整这个查询来计算比率。我正在使用 Oracle 11g。

SELECT
       CONNECT_BY_ROOT( FROM_UNIT_ID ) AS FROM_UNIT_ID,
       TO_UNIT_ID
FROM   (
  SELECT FROM_UNIT_ID, TO_UNIT_ID, RATIO FROM RATIOS 
  UNION
  SELECT TO_UNIT_ID, UNIT_ID, 1/RATIO AS RATIO FROM RATIOS 
)
WHERE  CONNECT_BY_ROOT( FROM_UNIT_ID ) <> TO_UNIT_ID
CONNECT BY NOCYCLE
       PRIOR TO_UNIT_ID = FROM_UNIT_ID
ORDER BY FROM_UNIT_ID, TO_UNIT_ID;

【问题讨论】:

  • 对于您的简单示例,没有冲突。但如果你的桌子上有B C 10,答案会是什么?
  • 您的 Oracle 版本是 11g 还是只有 11gR2?后者有 CTE。
  • 正如@TheImpaler 所说 - 这是我们必须知道您拥有的详细 Oracle 版本的一种情况。 11 还可以,但是 11.1 和 11.2 还是有很大区别的。当你select banner from v$version 时你看到了什么?
  • Oracle 数据库 11g 版本 11.2.0.4.0

标签: sql oracle plsql oracle11g graph-algorithm


【解决方案1】:

在 Oracle 11.2 及更高版本(递归 WITH 子句可用)中,您可以这样做(假设输入表中的数据不自相矛盾):

with
  test_data (from_unit_id, to_unit_id, ratio) as (
    select 'A', 'B', 2   from dual union all
    select 'A', 'C', 0.5 from dual union all
    select 'C', 'D', 3   from dual
  )
, symm_data (x, y, r) as (
    select case when h.source = 1 then from_unit_id else to_unit_id   end,
           case when h.source = 1 then to_unit_id   else from_unit_id end,
           case when h.source = 1 then ratio        else 1/ratio      end
    from   test_data cross join 
                     (select 1 as source from dual union all select 2 from dual) h
  )
, all_nodes (x) as (
    select distinct x
    from   symm_data
  )
, rec (x, y, r) as (
    select x, x, 1
      from all_nodes
    union all
    select r.x, s.y, r.r * s.r
      from rec r join symm_data s on r.y = s.x
  )
  cycle x, y set cycle to 'Y' default 'N'
select x as from_unit_id, y as to_unit_id, round(r, 4) as ratio 
from   rec
where  cycle = 'N' and x != y
order  by x, y
;

输出:

FROM_UNIT_ID TO_UNIT_ID        RATIO
------------ ------------ ----------
A            B                     2
A            C                    .5
A            D                   1.5
B            A                    .5
B            C                   .25
B            D                   .75
C            A                     2
C            B                     4
C            D                     3
D            A                 .6667
D            B                1.3333
D            C                 .3333

注意——我在最后的SELECT中使用了ROUND;通常应该只在最终报告中使用,否则你不应该在任何地方四舍五入。

【讨论】:

  • 我设法得到这个来完美地计算它。我确实必须使用alter session set cursor_sharing = EXACT; 来解决我认为在我的Oracle 版本上循环绑定变量的错误。如果比率不是固定的而是每天都在变化,您会使用这种方法吗?
  • @garyamorris - 我看不出固定比率和它们每天都在变化之间的区别。那有什么意思?无论如何,是的,我会使用相同的方法,无论您所说的“比率每天都在变化”。
  • 因为比率像汇率一样每天都在变化,我还是尝试了这种方法,正如你所说的那样它确实有效。最后一个问题,为什么在 symm_data 中使用交叉连接而不是联合?
  • @garyamorris - UNION ALL 确实是自然选择(更容易理解)。 CROSS JOIN 事情是一种交易技巧,基于了解事情的实际完成方式。对于UNION ALL,基础数据必须读取两次——“读取数据”非常耗时。使用交叉连接技巧,相同的数据只需要读取一次;一行输入一次产生两行输出。 是为了处理速度,没有其他原因;但它确实对大量输入有很大帮助。
【解决方案2】:

您可以通过计算所有单个 unit_id 的值来做到这一点,假设其中一个为 1,然后使用它来计算比率:

--Creating the sample table

Create table ratios as
(select 'A' as FROM_UNIT_ID,'B' as  TO_UNIT_ID,cast(2 as float) as  RATIO from dual union
select 'A','C',0.5 from dual union
select 'C', 'D',3 from dual);

--Creating a list of unique unit_ids

Create table unit_id as
select a.*,CAST(NULL as float) as Val from
(select distinct from_unit_id as unit_id from ratios
UNION
select distinct to_unit_id as unit_id from ratios)a;

--Updating the first value to be 1

UPDATE unit_id
SET Val=1
WHERE rownum=1;

--Updating the other values based on the known values

UPDATE unit_id
SET Val=
(select Calc from 
(Select a.Unit_ID,c.Val*b.ratio as Calc
FROM unit_id a
INNER JOIN ratios b on a.unit_id=b.to_unit_id
INNER JOIN unit_id c on c.unit_id=b.from_unit_id) b 
WHERE Unit_ID.Unit_Id=b.Unit_Id)
WHERE Unit_ID.Val is null;

--Updating the other values based on the known values
--You can repeat this step a couple more times if needed

UPDATE unit_id
SET Val=
(select Calc from 
(Select a.Unit_ID,c.Val*b.ratio as Calc
FROM unit_id a
INNER JOIN ratios b on a.unit_id=b.to_unit_id
INNER JOIN unit_id c on c.unit_id=b.from_unit_id) b 
WHERE Unit_ID.Unit_Id=b.Unit_Id)
WHERE Unit_ID.Val is null;

select a.Unit_id as from_unit_id,
      b.unit_id as to_unit_id,
      trunc(a.val/b.val,2) as ratio
from unit_id a
cross join unit_id b
where a.unit_id<>b.unit_id
order by a.unit_id,b.unit_id

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2019-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    相关资源
    最近更新 更多