我正在尝试提高我已经写过的概念证明的表现并且没有运气。我认为这种方法可能有缺陷,但我很难找到另一种解决方案。我已经介绍了我能找到的所有Ask Tom文章和论坛帖子。
我们正在运行Oracle 10g R2。
我们有以层次结构排列的项目。数量是根据关系定义的。层次结构中有两种类型的对象:作为逻辑分组的程序集和表示实际项目的项目。因此,如果我们代表一个完整的工具集,我们将有一个代表整个工具集的根,以及一个代表实际工具的叶子。所以:
工具集 - >螺丝刀 - >平头螺丝刀 - >小平头螺丝刀
可以在层次结构中重用程序集,也可以在项目中重复使用。
我需要展平层次结构,以便项目的每个实例都有一行和数量。任何关系都可以有一个数量> = 1.要获得一个项目的数量,我们需要从根到叶子的所有关系中得到数量的乘积。
我的解决方案有效,但效果不佳。针对实际数据运行大约需要8分钟来生成6000多行,并且我们的层次结构将产生5万多行。理想情况下,这将在10秒或更短的时间内完成,但我知道......乐观;)
我的解决方案和简化数据集如下。任何反馈都将非常感谢!
CREATE TABLE ITEMHIER
(
PARENT VARCHAR2(30 BYTE),
CHILD VARCHAR2(30 BYTE),
QUANTITY NUMBER(15,2),
ISLEAF NUMBER
);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY005','ITEM001',2,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY005','ITEM002',1,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY005','ITEM003',5,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY006','ITEM002',10,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY006','ITEM004',3,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY007','ITEM005',12,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY007','ITEM006',1,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY008','ITEM006',2,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY008','ITEM005',5,1);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY002','ASSY005',2,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY002','ASSY007',1,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY003','ASSY006',3,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY003','ASSY008',2,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY004','ASSY007',1,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY004','ASSY005',3,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY004','ASSY006',2,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY001','ASSY002',1,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY001','ASSY003',2,0);
INSERT INTO ITEMHIER (PARENT, CHILD, QUANTITY, ISLEAF) VALUES ('ASSY001','ASSY004',1,0);
COMMIT;
/
CREATE OR REPLACE FUNCTION GETQTY(P_NAVPATH IN VARCHAR2,
P_STARTWITH IN VARCHAR2) RETURN INTEGER AS
R_QTY INTEGER;
BEGIN
SELECT EXP(SUM(LN(QUANTITY)))
INTO R_QTY
FROM (
SELECT QUANTITY, SYS_CONNECT_BY_PATH(CHILD,'/') NAV_PATH
FROM ITEMHIER
START WITH PARENT = P_STARTWITH
CONNECT BY PRIOR CHILD = PARENT
)
WHERE INSTR(P_NAVPATH, NAV_PATH) = 1;
RETURN R_QTY;
END;
/
SELECT 'ASSY001' || SYS_CONNECT_BY_PATH(CHILD,'/') NAV_PATH,
GETQTY(SYS_CONNECT_BY_PATH(CHILD,'/'), 'ASSY001') QTY,
CHILD
FROM ITEMHIER
WHERE ISLEAF = 1
START WITH PARENT = 'ASSY001'
CONNECT BY PRIOR CHILD = PARENT;
---- EDIT
使用WITH
条款,我能够将处理时间减少大约1/2,这是一个很大的收益!还有其他想法吗?
with
h as (
select sys_connect_by_path(child,'/') navpath,
child,
quantity qty,
isleaf
from itemhier
start with parent = 'ASSY001'
connect by prior child = parent
)
select h1.navpath,
h1.child,
(SELECT exp(sum(ln(h2.qty)))
FROM h h2
WHERE instr(h1.navpath, h2.navpath) = 1) qty
from h h1
where isleaf = 1
编辑2
jonearles建议使用sys_connect_by_path构建算术表达式,然后使用PL / SQL来评估它似乎是要走的路。运行我最大的数据集,我能够在55秒内产生77k行输出。
我也试图使用并行性,但正如他所指出的那样,几乎没有性能提升。
答案 0 :(得分:2)
Podiluska的建议很好。如果您有Oracle 11g R2,则可以使用公用表表达式。新语法的递归性质允许您放弃sys_connect_by_path
与instr
相结合,这将严重损害您的表现。
试试这个:
select
child,
sum(total_quantity) total_quantity
from (
with h (parent, child, isleaf, quantity, total_quantity) as (
select
parent,
child,
isleaf,
quantity,
quantity total_quantity
from
itemhier
where
parent = 'ASSY001'
union all
select
ih.parent,
ih.child,
ih.isleaf,
ih.quantity,
ih.quantity * h.total_quantity total_quantity
from
itemhier ih
join
h on h.child = ih.parent
)
select * from h
where isleaf = 1
)
group by child;
这是sqlfiddle:http://sqlfiddle.com/#!4/9840f/6
答案 1 :(得分:1)
您应该查看WITH
语句和公用表表达式/子查询因子,这将允许您在单个SQL语句中遍历层次结构。它也可能更快。
例如:
找到'assy002'的所有叶子
with cte as
(
select * from #ITEMHIER
union all
select i.PARENT, cte.CHILD, cte.QUANTITY, cte.ISLEAF
from #ITEMHIER i
inner join cte on i.CHILD = cte.PARENT
)
select CHILD,QUANTITY, isleaf from cte
where PARENT='assy002'
and isleaf=1;
答案 2 :(得分:1)
您可以使用SYS_CONNECT_BY_PATH
生成表达式,即分支中所有数量的乘积。然后使用函数动态执行该表达式以获得最终数量。
这不是一个理想的解决方案。 SQL和PL / SQL之间的上下文切换需要一些时间。而且你需要担心SQL注入。但至少你可以避免两次查询同一个表。
(递归CTE,正如Dan A.和podiluska所建议的那样,很可能是最好的解决方案。根据我的经验,即使两种语法做同样的事情并且使用类似的访问路径,递归CTE也可能是显着的比connect by
快。但你需要等到升级到11gR2。)
CREATE OR REPLACE FUNCTION EVALUATE_EXPRESSION(P_EXPRESSION IN VARCHAR2) RETURN NUMBER AS
R_QTY INTEGER;
BEGIN
EXECUTE IMMEDIATE 'SELECT '||P_EXPRESSION||' FROM DUAL' INTO R_QTY;
RETURN R_QTY;
END;
/
SELECT 'ASSY001' || SYS_CONNECT_BY_PATH(CHILD,'/') NAV_PATH,
GETQTY(SYS_CONNECT_BY_PATH(CHILD,'/'), 'ASSY001') QTY,
SUBSTR(SYS_CONNECT_BY_PATH(QUANTITY,'*'), 2) QTY_EXPRESSION,
EVALUATE_EXPRESSION(SUBSTR(SYS_CONNECT_BY_PATH(QUANTITY,'*'), 2)) QTY2,
CHILD
FROM ITEMHIER
WHERE ISLEAF = 1
START WITH PARENT = 'ASSY001'
CONNECT BY PRIOR CHILD = PARENT;
另外,您提到该表具有索引。但是使用索引的查询是什么?你能发布解释计划吗?
最后,如果查询速度很慢,您可能需要研究并行性。不幸的是,我使用并行性和connect by
从来没有太多运气。