4:**多个相关表中的计数/总和行

时间:2014-05-13 14:32:00

标签: sql oracle11g

我有一个复杂的选择 - 当简化时 - 看起来像这样:

select m.ID,
  (select sum(AMOUNT) from A where M_ID = m.ID) sumA,
  (select sum(AMOUNT) from B where M_ID = m.ID) sumB,
  .....
from M;

表A,B,......具有指向表M的外键M_ID。 问题是这个选择非常慢。我想用表连接重写它,但我不知道怎么做,因为

select m.ID
  sum(a.AMOUNT),
  sum(b.AMOUNT),
  .....
from M
join A on a.M_ID = m.ID
join B on b.M_ID = m.ID
....
group by m.ID;

给出不正确(高得多)的和结果,因为A或B中的每一行都可以多次计数。

有没有办法如何使用例如最佳选择来编写选择分析函数或其他一些方法?

修改 原始(非简化)选择的解释计划如下所示:

|   0 | SELECT STATEMENT              |                           |
|   1 |  SORT AGGREGATE               |                           |
|*  2 |   FILTER                      |                           |
|*  3 |    TABLE ACCESS BY INDEX ROWID| WORKITEM                  |
|*  4 |     INDEX SKIP SCAN           | WORKITEM_U01              |
|*  5 |    FILTER                     |                           |
|*  6 |     TABLE ACCESS FULL         | RPRODUCT_INVENTORY_MASTER |
.....
|  31 |  SORT AGGREGATE               |                           |
|* 32 |   FILTER                      |                           |
|* 33 |    TABLE ACCESS BY INDEX ROWID| WORKITEM                  |
|* 34 |     INDEX SKIP SCAN           | WORKITEM_U01              |
|* 35 |    FILTER                     |                           |
|* 36 |     TABLE ACCESS FULL         | RPRODUCT_INVENTORY_MASTER |
|  37 |  SORT GROUP BY                |                           |
|  38 |   TABLE ACCESS FULL           | RPRODUCT                  |

这就是我想优化它的原因。此外,AWR报告显示此选择具有50000获取/执行。

Edit2,3: 整个选择看起来像这样:

SELECT rprd.ID,
  rprd.NAME,
  (select sum(AMOUNT) from WORKITEM
    where ACTION='REMOVE'
      and trunc(CREATED_DATE) = to_date(:1,'DDMMYYYY')
      and PAYEE_ID in
          (select rim.RPRODUCT_ID from RPRODUCT_INVENTORY_MASTER rim
            where  rprd.ID = rim.RPRODUCT_ID
              and rim.INVENTORY_DATE = to_date(:2,'DDMMYYYY')),
  .....
  (select sum(AMOUNT) from WORKITEM
    where ACTION='COLLECT'
      and trunc(CREATED_DATE) < to_date(:11,'DDMMYYYY')
      and PAYEE_ID in
          (select rim.RPRODUCT_ID from RPRODUCT_INVENTORY_MASTER rim
            where  rprd.ID = rim.RPRODUCT_ID
              and rim.INVENTORY_DATE < to_date(:12,'DDMMYYYY'))
FROM RPRODUCT rprd 
GROUP BY rprd.ID, rprd.NAME 
ORDER BY rprd.ID
;

我没有写:-),我正要重写它。注意,在比较运算符中,在ACTION值中,在将INVENTORY_DATE与。

进行比较的日期中存在差异

Edit4: 我试图像这样重写查询(并且exec计划看起来更好),但是已经遇到了&#34;行多重性&#34;上述问题:

with RPRODUCT_INVENTORY_MASTER# as (
    select RPRODUCT_ID, min(INVENTORY_DATE) INVENTORY_DATE
      from RPRODUCT_INVENTORY_MASTER
      group by RPRODUCT_ID),
  WORKITEM# as (
    select AMOUNT, PAYEE_ID, ACTION, trunc(CREATED_DATE) CREATED_DATE
      from WORKITEM
      where ACTION in ('REMOVE','ADD','COLLECT')
  )
select rprd.ID,
  rprd.NAME,
--  sum(wip2.AMOUNT), -- this is singular because of '=' in inventory_date comparison
  sum(abs(wip4.AMOUNT)),
  .....
  sum(wip12.AMOUNT)
from RPRODUCT rprd
left join RPRODUCT_INVENTORY_MASTER# rim4 on rim4.RPRODUCT_ID = rprd.ID 
  and rim4.INVENTORY_DATE <= to_date(:4 ,'DDMMYYYY')
left join WORKITEM# wip4 on wip4.PAYEE_ID = rim4.RPRODUCT_ID
  and wip4.ACTION='REMOVE'
  and wip4.CREATED_DATE = to_date(:3 ,'DDMMYYYY')
.....
left join RPRODUCT_INVENTORY_MASTER# rim12 on rim12.RPRODUCT_ID = rprd.ID 
  and rim12.INVENTORY_DATE < to_date(:12 ,'DDMMYYYY')
left join WORKITEM# wip12 on wip12.PAYEE_ID = rim12.RPRODUCT_ID
  and wip12.ACTION='COLLECT'
  and wip12.CREATED_DATE < to_date(:11 ,'DDMMYYYY')
group by rprd.ID, rprd.NAME 
order by rprd.ID
;

RPRODUCT_INVENTORY_MASTER#总是为每个rprd.ID提供最多一行。 WORKITEM#可以为每个RPRODUCT_ID = rprd.ID提供任意数量的行。

3 个答案:

答案 0 :(得分:3)

是的,这是一个典型的问题。为了清晰起见,我喜欢你的原始查询。但是,如果在性能问题上运行,则必须考虑其他选项。

这是一个选项。当A和B相乘时,您可以简单地将总和除以相关计数。嗯,诚然,这看起来有些奇怪。

select m.ID
  sum(a.AMOUNT) / count(distinct b.id),
  sum(b.AMOUNT) / count(distinct a.id),
  .....
from M
join A on a.M_ID = m.ID
join B on b.M_ID = m.ID
....
group by m.ID;

另一个选项,我更喜欢建立群组,以便首先不是每个m.id有多个A和B:

select m.ID
  a_agg.SUM_AMOUNT,
  b_agg.SUM_AMOUNT,
  .....
from M
join (select M_ID, sum(AMOUNT) as SUM_AMOUNT from A group by M_ID) a_agg
  on a_agg.M_ID = m.ID
join (select M_ID, sum(AMOUNT) as SUM_AMOUNT from B group by M_ID) b_agg
  on b_agg.M_ID = m.ID

编辑:如果M_ID可能没有任何A或任何B,则必须在两个查询中用LEFT JOIN替换连接。然后在第一个查询中选择:

nvl(sum(a.AMOUNT), 0) / greatest(count(distinct b.id), 1),
nvl(sum(b.AMOUNT), 0) / greatest(count(distinct a.id), 1),

在第二个查询中:

nvl(a_agg.SUM_AMOUNT, 0),
nvl(b_agg.SUM_AMOUNT, 0),

编辑:这是您修改的查询。诀窍是加入不同的轮辋。

SELECT 
  rprd.ID,
  rprd.NAME,
  nvl(same_date.SUM_AMOUNT, 0),
  .....
  nvl(earlier_date.SUM_AMOUNT, 0)
FROM RPRODUCT rprd 
LEFT JOIN
(
  select rim.RPRODUCT_ID, sum(w.AMOUNT) as SUM_AMOUNT
  from
  (
    select distinct RPRODUCT_ID 
    from RPRODUCT_INVENTORY_MASTER
    where INVENTORY_DATE = to_date(:2,'DDMMYYYY')
  ) rim
  left join WORKITEM w 
    on w.PAYEE_ID = rim.RPRODUCT_ID
    and w.ACTION = 'REMOVE'
    and trunc(w.CREATED_DATE) = to_date(:1,'DDMMYYYY')
) same_date on same_date.RPRODUCT_ID = rprd.ID
LEFT JOIN
(
  select rim.RPRODUCT_ID, sum(w.AMOUNT) as SUM_AMOUNT
  from
  (
    select distinct RPRODUCT_ID 
    from RPRODUCT_INVENTORY_MASTER
    where INVENTORY_DATE < to_date(:12,'DDMMYYYY')
  ) rim
  left join WORKITEM w 
    on w.PAYEE_ID = rim.RPRODUCT_ID
    and w.ACTION = 'REMOVE'
    and trunc(w.CREATED_DATE) < to_date(:11,'DDMMYYYY')
) earlier_date on earlier_date.RPRODUCT_ID = rprd.ID
GROUP BY rprd.ID, rprd.NAME 
ORDER BY rprd.ID
;

答案 1 :(得分:1)

这应该有效

select m.ID,
a.aamount,
b.bamount
from M
inner join 
(
select M_ID,sum(AMOUNT) as aamount
from A group by M_ID
) a
on a.M_ID = m.ID
inner join 
(
select M_ID,sum(AMOUNT) as bamount
from B group by M_ID
) b
on b.M_ID = m.ID;

答案 2 :(得分:0)

无论A,B,C,......表中的m_id行数是多少,这都应该有效:

select
  M.id,
  sum(decode(u.src, 'A', u.sumx, 0)) sum_a,
  sum(decode(u.src, 'B', u.sumx, 0)) sum_b,
  sum(decode(u.src, 'C', u.sumx, 0)) sum_c,
  ...
from M,
  (select 'A' src, m_id, sum(amount) sumx from A group by m_id
  union all
  select 'B', m_id, sum(amount) from B group by m_id
  union all
  select 'C', m_id, sum(amount) from C group by m_id
  ...
  ) u
where
  M.id=u.m_id
group by
  M.id;