Oracle SQL 按日期和 ID 计算 Children 和 Parents

Oracle SQL Count Children and Parents by Date and ID

背景: 我需要一种方法来计算我们将在存档过程中 运行 记录的记录数。我的查询低于我提出的查询,但我觉得必须有更好的方法(过去 90 天 运行 大约需要 20 分钟)

查询:我们有一个名为Table_Main(非真名)的table,其中包含大量记录(3亿+)并且是不断被添加到(因此存档)。 table 包含 parent 个具有 TR_ID 的记录,这些记录将它们链接到另一个 table,而 children 没有 TR_ID。 children 通过 Parent_ID 附加到 parent。这里棘手的部分是我需要根据通过 TR_ID 链接的 Trans table 中包含的值来计算记录。

 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;

Question/Idea:有什么方法可以通过 Table_Main 和它自身之间的一个连接来简化这个问题吗?例如,保留 parent 的第一条记录加上所有已加入的 children 的连接?我正在尝试类似下面的查询但没有得到任何结果。

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

在此先感谢您提供的任何帮助。

Oracle 设置:

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;

结果:

SOME_DATE      XX_ID   COUNT(*)
--------- ---------- ----------
01-DEC-15          4          1 
01-DEC-15         45          4 
22-DEC-15         23          8 
20-DEC-15          6          3 

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

这是一种方法:

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

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

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

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

然后连接到 trans table 并进行汇总计数是一件简单的事情。


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

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