Oracle SQL按日期和ID计算子项和父项

时间:2016-01-19 14:24:01

标签: sql oracle join hierarchical-data

背景:我需要一种方法来计算我们将通过存档流程运行的记录数。我在下面提出的问题,但我觉得必须有一个更好的方法(在过去的90天内运行大约需要20分钟)

查询:我们有一个名为Table_Main(非真实姓名)的表,其中包含大量记录(3亿+)并且不断被添加到(因此归档)。该表包含具有TR_ID的父记录,该记录将它们链接到另一个表,而子项没有TR_ID。孩子们通过Parent_ID与父母联系。这里棘手的部分是我需要根据Trans表中包含的值来计算记录,该表通过TR_ID链接。

 Select  B.Some_date, B.xx_ID, count(*)
From
(
select tr.Some_date, tr.xx_ID, p.H_ID
from Table_Main p, Trans tr
where p.TR_ID = tr.TR_ID 
 and tr.Some_date>sysdate-90
     --
UNION ALL
        --
Select Result.Some_date , Result.xx_ID, th.H_ID
from (
select tr.Some_date, tr.xx_ID, p.H_ID
from Table_Main p, Trans tr
where p.TR_ID = tr.TR_ID  and tr.Some_date>sysdate-90
) Result
  inner join Table_Main th on th.Parent_id = Result.H_ID and th.Parent_ID is not null
  ) B
  group by  B.Some_date, B.xx_ID
order by B.Some_date;

问题/想法:有没有什么方法可以通过Table_Main和它自己之间的一次连接来简化这个?例如,保持父母的第一条记录加上所有已加入的孩子的联接?我正在尝试类似下面的查询,但没有到达任何地方。

Select a.Some_date, A.xx_ID, p.H_ID, c.*
from Table_Main p
inner join Trans a on p.TR_ID = a.TR_ID
left join Table_Main c on c.Parent_id = p.H_ID 
where a.Some_date> sysdate-20
order by p.H_ID, c.H_ID

Table_Main

-H_ID
-Parent_ID //Links child to parent
-TR_ID     //Links to Trans Table

-TR_ID     //Link to Table_Main
-xx_ID     //This is used to group on
-Some_Date //Used to group on

示例输入:

Table_Main

H_ID     Parent_ID     TR_ID
1        NULL          1
2        1             NULL
3        NULL          2
4        NULL          3
5        4             NULL
6        4             NULL
7        NULL          4
8        7             NULL
9        NULL          5
10       9             NULL
11       9             NULL
12       9             NULL
13       9             NULL
14       9             NULL
15       9             NULL
16       9             NULL

TR_ID    XX_ID    Some_Date
1        45       12/1/2015
2        4        12/1/2015
3        6        12/20/2015
4        45       12/1/2015
5        23       12/22/2015

期望的输出:

Date      xx_ID Count
12/1/2015   4   1
12/1/2015   45  4
12/20/2015  6   3
12/22/2015  23  8

提前感谢您提供的任何帮助。

2 个答案:

答案 0 :(得分:1)

Oracle安装程序

TRAVERSE

<强>查询

CREATE TABLE TRANS(
  TR_ID     NUMBER(10,0) PRIMARY KEY,
  XX_ID     NUMBER(10,0),
  Some_Date DATE
);

CREATE TABLE TABLE_MAIN (
  H_ID      NUMBER(10,0) PRIMARY KEY,
  PARENT_ID NUMBER(10,0) REFERENCES TABLE_MAIN( H_ID ),
  TR_ID     NUMBER(10,0) REFERENCES TRANS( TR_ID )
);

INSERT INTO TRANS
SELECT 1, 45, DATE '2015-12-01' FROM DUAL UNION ALL
SELECT 2,  4, DATE '2015-12-01' FROM DUAL UNION ALL
SELECT 3,  6, DATE '2015-12-20' FROM DUAL UNION ALL
SELECT 4, 45, DATE '2015-12-01' FROM DUAL UNION ALL
SELECT 5, 23, DATE '2015-12-22' FROM DUAL;

INSERT INTO TABLE_MAIN
SELECT  1, NULL, 1 FROM DUAL UNION ALL
SELECT  2, 1, NULL FROM DUAL UNION ALL
SELECT  3, NULL, 2 FROM DUAL UNION ALL
SELECT  4, NULL, 3 FROM DUAL UNION ALL
SELECT  5, 4, NULL FROM DUAL UNION ALL
SELECT  6, 4, NULL FROM DUAL UNION ALL
SELECT  7, NULL, 4 FROM DUAL UNION ALL
SELECT  8, 7, NULL FROM DUAL UNION ALL
SELECT  9, NULL, 5 FROM DUAL UNION ALL
SELECT 10, 9, NULL FROM DUAL UNION ALL
SELECT 11, 9, NULL FROM DUAL UNION ALL
SELECT 12, 9, NULL FROM DUAL UNION ALL
SELECT 13, 9, NULL FROM DUAL UNION ALL
SELECT 14, 9, NULL FROM DUAL UNION ALL
SELECT 15, 9, NULL FROM DUAL UNION ALL
SELECT 16, 9, NULL FROM DUAL;

<强>结果:

SELECT some_date,
       xx_id,
       COUNT(*)
FROM   Trans t
       INNER JOIN
       (
        SELECT CONNECT_BY_ROOT( TR_ID ) AS TR_ID
        FROM   Table_Main
        START WITH H_ID IS NOT NULL
        CONNECT BY PRIOR H_ID = PARENT_ID
       ) m
       ON ( t.TR_ID = m.TR_ID )
WHERE  some_date > SYSDATE - 90
GROUP BY some_date,
         xx_id;

答案 1 :(得分:1)

感谢您添加输入和预期的输出数据 - 这使我们更容易确保获得您期望的答案!

这是一种方法:

with table_main as (select 1 H_ID, null Parent_ID, 1 TR_ID from dual union all
                    select 2 H_ID, 1 Parent_ID, NULL TR_ID from dual union all
                    select 3 H_ID, NULL Parent_ID, 2 TR_ID from dual union all
                    select 4 H_ID, NULL Parent_ID, 3 TR_ID from dual union all
                    select 5 H_ID, 4 Parent_ID, NULL TR_ID from dual union all
                    select 6 H_ID, 4 Parent_ID, NULL TR_ID from dual union all
                    select 7 H_ID, NULL Parent_ID, 4 TR_ID from dual union all
                    select 8 H_ID, 7 Parent_ID, NULL TR_ID from dual union all
                    select 9 H_ID, NULL Parent_ID, 5 TR_ID from dual union all
                    select 10 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 11 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 12 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 13 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 14 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 15 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 16 H_ID, 9 Parent_ID, NULL TR_ID from dual),
          trans as (select 1 TR_ID, 45 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 2 TR_ID, 4 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 3 TR_ID, 6 XX_ID, to_date('12/20/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 4 TR_ID, 45 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 5 TR_ID, 23 XX_ID, to_date('12/22/2015', 'mm/dd/yyyy') Some_Date from dual)
-- end of mimicking your tables with data in them. See SQL below:                    
select   tr1.some_date,
         tr1.xx_id,
         count(*) cnt
from     (select     h_id,
                     parent_id,
                     max(tr_id) over (partition by connect_by_root(h_id)) tr_id
          from       table_main tm
          connect by prior h_id = parent_id
          start with parent_id is null) tm1
         inner join trans tr1 on (tm1.tr_id = tr1.tr_id)
group by tr1.some_date,
         tr1.xx_id
order by tr1.some_date,
         tr1.xx_id;

SOME_DATE       XX_ID        CNT
---------- ---------- ----------
12/01/2015          4          1
12/01/2015         45          4
12/20/2015          6          3
12/22/2015         23          8

基本上,这首先进行分层查询(connect by...)以链接父行和子行。

然后我们使用connect_by_root函数来识别所有父子行中的顶级h_id。

一旦我们有了这个,我们就可以使用分析函数在每个顶级h_id的所有父行和子行中返回tr_id(我在这里使用了max(),因为看起来只有父行会有一个tr_id)。

然后加入trans表并进行聚合计数就很简单了。

鉴于层次结构只有两个可能的级别,这是一种(希望更快!)修改方式做同样的事情:

with table_main as (select 1 H_ID, null Parent_ID, 1 TR_ID from dual union all
                    select 2 H_ID, 1 Parent_ID, NULL TR_ID from dual union all
                    select 3 H_ID, NULL Parent_ID, 2 TR_ID from dual union all
                    select 4 H_ID, NULL Parent_ID, 3 TR_ID from dual union all
                    select 5 H_ID, 4 Parent_ID, NULL TR_ID from dual union all
                    select 6 H_ID, 4 Parent_ID, NULL TR_ID from dual union all
                    select 7 H_ID, NULL Parent_ID, 4 TR_ID from dual union all
                    select 8 H_ID, 7 Parent_ID, NULL TR_ID from dual union all
                    select 9 H_ID, NULL Parent_ID, 5 TR_ID from dual union all
                    select 10 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 11 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 12 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 13 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 14 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 15 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 16 H_ID, 9 Parent_ID, NULL TR_ID from dual),
          trans as (select 1 TR_ID, 45 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 2 TR_ID, 4 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 3 TR_ID, 6 XX_ID, to_date('12/20/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 4 TR_ID, 45 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 5 TR_ID, 23 XX_ID, to_date('12/22/2015', 'mm/dd/yyyy') Some_Date from dual)
-- end of mimicking your tables with data in them. See SQL below:                    
select   tr.some_date,
         tr.xx_id,
         count(*) cnt
from     table_main tm1
         left join table_main tm2 on (tm1.h_id = coalesce(tm2.parent_id, tm2.h_id) and tm1.parent_id is null)
         inner join trans tr on (tm1.tr_id = tr.tr_id)
group by tr.some_date,
         tr.xx_id
order by tr.some_date,
         tr.xx_id;

SOME_DATE       XX_ID        CNT
---------- ---------- ----------
12/01/2015          4          1
12/01/2015         45          4
12/20/2015          6          3
12/22/2015         23          8

另一个可能的答案不涉及自联接,但确实依赖于分析函数:

with table_main as (select 1 H_ID, null Parent_ID, 1 TR_ID from dual union all
                    select 2 H_ID, 1 Parent_ID, NULL TR_ID from dual union all
                    select 3 H_ID, NULL Parent_ID, 2 TR_ID from dual union all
                    select 4 H_ID, NULL Parent_ID, 3 TR_ID from dual union all
                    select 5 H_ID, 4 Parent_ID, NULL TR_ID from dual union all
                    select 6 H_ID, 4 Parent_ID, NULL TR_ID from dual union all
                    select 7 H_ID, NULL Parent_ID, 4 TR_ID from dual union all
                    select 8 H_ID, 7 Parent_ID, NULL TR_ID from dual union all
                    select 9 H_ID, NULL Parent_ID, 5 TR_ID from dual union all
                    select 10 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 11 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 12 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 13 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 14 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 15 H_ID, 9 Parent_ID, NULL TR_ID from dual union all
                    select 16 H_ID, 9 Parent_ID, NULL TR_ID from dual),
          trans as (select 1 TR_ID, 45 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 2 TR_ID, 4 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 3 TR_ID, 6 XX_ID, to_date('12/20/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 4 TR_ID, 45 XX_ID, to_date('12/01/2015', 'mm/dd/yyyy') Some_Date from dual union all
                    select 5 TR_ID, 23 XX_ID, to_date('12/22/2015', 'mm/dd/yyyy') Some_Date from dual)
-- end of mimicking your tables with data in them. See SQL below:                    
select   tr.some_date,
         tr.xx_id,
         count(*) cnt
from     (select max(tr_id) over (partition by coalesce(parent_id, h_id)) tr_id
          from   table_main) tm1
         inner join trans tr on (tm1.tr_id = tr.tr_id)
group by tr.some_date,
         tr.xx_id
order by tr.some_date,
         tr.xx_id;

SOME_DATE       XX_ID        CNT
---------- ---------- ----------
12/01/2015          4          1
12/01/2015         45          4
12/20/2015          6          3
12/22/2015         23          8