如何为表中多条记录分别实现构造结果集(一)
上一篇 / 下一篇 2008-04-14 21:22:47 / 个人分类:ORACLE
前一段用SQL实现了一个复杂的功能,但是留下了一个问题一直没有真正的解决,那就是如何同时为表中的多条记录分别实现构造结果集。
问题源自:一个复杂问题的求解过程(一):http://yangtingkun.itpub.net/post/468/456641
感觉直接用文字描述比较困难,下面通过一个例子说明打算实现什么样的功能。
在写一个复杂的SQL的时候,经常会涉及到构造结果集的功能,而构造的功能一般是通过两种方法来实现,第一种是借助一张已经存在的大表,比如DBA_OBJECTS,通过访问这张表来获取需要的记录,第二种方式是利用CONNECT BY方式直接从DUAL表获取。
第一种方法比较直观,但是需要依赖被访问的表有足够的记录。而第二种方式除了DUAL表外不需要借助任何的对象,而且没有记录条数的限制。
比如希望构造一个1到10的结果集,采用两种方法分别为:
SQL> SELECT ROWNUM RN FROM ALL_OBJECTS WHERE ROWNUM <= 10;
RNITPUB个人空间LzyU1A9|s9D
----------
T^3M Ywic0 1
8x9^2pz @0 2
(y9o
A5{I4}mp/ofk0 3
%b{-}
U:od-\0 4
Q%y}B*M*G5i z%k7g(L0 5
&YlI2{ wd9Yp0 6ITPUB个人空间[ y*vII
w&L
7
k"qvu0J:c)P|0 8ITPUB个人空间.c7p)d#A S
9ITPUB个人空间6p_l*a"t
10
已选择10行。
前面已经提到了,这种方法虽然方便但是需要访问一张大表,还需要确保构造的记录数小于大表中的记录总数。
第二种方法是利用CONNECT BY来访问DUAL表:
SQL> SELECT ROWNUM RN FROM DUAL CONNECT BY LEVEL <= 10;
RNITPUB个人空间mC^O6I,ru-}4T1X
----------ITPUB个人空间qZ K ~,|B
1
o$C/wy;O\/I0 2ITPUB个人空间+Q~)f5@{(t y
3
K,Cy|y4x%EF0 4ITPUB个人空间{j CEL2a T
5
{7qYe'@mO*PAh0 6
8c&j.[ EI!D0 7ITPUB个人空间_(AKN C
8ITPUB个人空间3{%MTon#\
9ITPUB个人空间Rl^%a DZ
10
已选择10行。
DUAL表是Oracle数据库必然存在的表,Oracle的很多数据字典都依赖这张表。而且这种方式不需要担心记录数超过表中记录的情况,有更好的普遍性。因此一般实现构造结果集的时候都倾向于选择这种方式。
不过现在碰到的问题不是构造一个结果集,而是为表中配置记录分别构造结果集:
SQL> CREATE TABLE T (ID NUMBER, VALUE NUMBER, POWER NUMBER);
表已创建。
SQL> INSERT INTO T VALUES (1, 2, 3);
已创建1行。
SQL> INSERT INTO T VALUES (2, 1, 2);
已创建1行。
SQL> INSERT INTO T VALUES (3, 4, 4);
已创建1行。
SQL> INSERT INTO T VALUES (4, 5, 2);
已创建1行。
SQL> INSERT INTO T VALUES (5, 4, 3);
已创建1行。
SQL> COMMIT;
提交完成。
SQL> SELECT * FROM T;
ID VALUE POWERITPUB个人空间+oO+At1v
---------- ---------- ----------
ivB(M%g.@0 1 2 3
f+ha
M,rZ0 2 1 2
dX8L'xWuS#N0 3 4 4ITPUB个人空间l{8~#E jEN
4 5 2ITPUB个人空间"nP/r!p]
5 4 3
现在的问题是,为5条记录分别生成结果集,每个ID对应的结果集POWER从0开始递增到POWER中现有的值(不包括当前值),最后在乘以VALUE的值。简单的说,最终需要得到的结果如下:
ID VALUE POWER RESULTITPUB个人空间8Sp4?H3eJK)U*M
---------- ---------- ---------- ----------ITPUB个人空间F+z{
ng*Jr
1 2 3 0ITPUB个人空间m}&?%VD;V(X(R
1 2 3 2
K
KTsC0 1 2 3 4
&C#zHX){:p@rP0 2 1 2 0ITPUB个人空间x RAalY2~/o9e
2 1 2 1
F7U2V$eFE7G0 3 4 4 0ITPUB个人空间iW(M G8N:d#?&Q
3 4 4 4
vgj5o4]MY}0 3 4 4 8
9uN ?8x
qx0x#?j0 3 4 4 12
LHk&nF8Y1LJ0 4 5 2 0ITPUB个人空间+L2Kx8^z+^@[z
4 5 2 5
4imn5Or J0 5 4 3 0ITPUB个人空间,D"hZ6r I,C
P
5 4 3 4
aU6~vMF$}5_0 5 4 3 8
也就是说,现在需要做的是为一组记录生成结果集,且每条记录结果集大小是不固定的,这个大小有这条记录上的数据来限制。
这个时候显然不能直接使用CONNECT BY来实现,因为表中包含了多条记录,直接使用CONNECT BY LEVEL < POWER的方式会导致表中数据之间产生树形查询,从而导致结果集出现大量的重复记录:
SQL> SELECT T.ID, T.VALUE, POWER, T.VALUE * (LEVEL - 1) RESULTITPUB个人空间CG$WP%f9UI
2 FROM T
T^1`C[n0 3 CONNECT BY LEVEL <= T.POWER
6f6YMXlqey0 4 ORDER BY 1, 4;
ID VALUE POWER RESULTITPUB个人空间ufh5g|V/}l
---------- ---------- ---------- ----------ITPUB个人空间-LYc vc{q
On
1 2 3 0
/yV1z CMQ/I8}
a#O0 1 2 3 2
QdN:Xf|&K q0 1 2 3 2ITPUB个人空间
m2n1nQCEj3A
1 2 3 2
Vwb@"}7]2]9D}Wq0 1 2 3 2
'f5S*hM3V f3F-tIA-p0 1 2 3 2ITPUB个人空间.W+_x8TJ3B:h)E Hc-iy
1 2 3 4ITPUB个人空间'\-yOj'VwXT%L)Z5G
1 2 3 4ITPUB个人空间
i,wD(w,Xlk)|
1 2 3 4
-k#za0rJ/k0 1 2 3 4ITPUB个人空间Vg(F'w l
1 2 3 4ITPUB个人空间I1Y&k-@&G"_&pj~-`&L
1 2 3 4
qf3aPmN,fu0 1 2 3 4
tC:{)L9bd~0.
Y:p"V1za0.
W H,DqYP8mG6oO[0.
:j}/Z8r;`0 1 2 3 4ITPUB个人空间NX1E^ |@9Q_O
2 1 2 0ITPUB个人空间DF-Rr*K\3J-\$?0]
2 1 2 1
.F7uJg,g(w^0 2 1 2 1
5r/hvA#r0 2 1 2 1
E1r2VC-B6@u'~v.X0 2 1 2 1
.](\~\N
Ek AV0 2 1 2 1ITPUB个人空间G*`{}._
3 4 4 0ITPUB个人空间)k8b"h e&Gh!i k+Q
3 4 4 4ITPUB个人空间k&sr!uY&C:Tf:Sm
3 4 4 4ITPUB个人空间\LCv|_
3 4 4 4ITPUB个人空间n*j{d u I
3 4 4 4ITPUB个人空间Hp!F(I:Gc
3 4 4 4ITPUB个人空间*\jB&S9a'b
3 4 4 8
0c Y!SA7aP0 3 4 4 8ITPUB个人空间/Ea#d2qMwqtT
3 4 4 8
3]fX?1Vf+Of9m,~0 3 4 4 8ITPUB个人空间
]U{.Hv,uW
3 4 4 8
9us,H2t_:q G/G0.
;`JC
qyv){q0.ITPUB个人空间;LK^C#rC,|:W
.ITPUB个人空间'x,WnW#hQ6WGLF~
ITPUB个人空间{s4hO;VM
3 4 4 12
I?$i@6b(?3W4MN0 4 5 2 0ITPUB个人空间e+O9D4Le*{E&s
4 5 2 5
d_h/Kzr0bEaI0 4 5 2 5
9V4D9laF5kj7V0 4 5 2 5ITPUB个人空间/E)|*Q`!R'~6\
4 5 2 5ITPUB个人空间'L4A*qF+t5g(P,H^
4 5 2 5
8d5A NH3e4~!Uy'R(t0 5 4 3 0ITPUB个人空间(C,lxJB(D
5 4 3 4
?w)W4x:N&wx0 5 4 3 4
#|1]!LQk[1z.z7a0 5 4 3 4ITPUB个人空间r-J$|'@Y
5 4 3 4
"D7_/a:Li-v ^#b0 5 4 3 4ITPUB个人空间H ZQG^PY{
5 4 3 8
E)Qg:cEy&_)f6EgK0 5 4 3 8ITPUB个人空间'g
qwRvC J7}m @
5 4 3 8
BKw/sLm6s"]
IO0 5 4 3 8
8z"RO6b9y
A2g2e0.ITPUB个人空间8bAW/CB
.ITPUB个人空间;b4nU[*B9T8O[
.ITPUB个人空间P:O bn4?$uC
5 4 3 8ITPUB个人空间]?W5],E1eL
5 4 3 8
已选择180行。
当然可以简单的采用DISTINCT和GROUP BY的方法来消除这个问题,比如:
SQL> SELECT T.ID, T.VALUE, POWER, T.VALUE * (LEVEL - 1) RESULT
a7l{5eq0 2 FROM T
.k@
m E)]s*k0 3 CONNECT BY LEVEL <= T.POWER
-j3Sb@:}z0 4 GROUP BY ID, VALUE, LEVEL, POWER
`*Q#@dq0 5 ORDER BY 1, 4
&y(NnQ_7R0 6 ;
ID VALUE POWER RESULT
Isa6m_K4q7E0---------- ---------- ---------- ----------
;Y3L8hBEW0 1 2 3 0ITPUB个人空间^G@,sv,o
r+v
1 2 3 2ITPUB个人空间`
RY#F+P
m5DG
1 2 3 4ITPUB个人空间 } sW
GNPcG
2 1 2 0
8](c_$US+J(Gk0 2 1 2 1ITPUB个人空间\Yu!P,Qo6p
3 4 4 0
1zC(kF;@0 3 4 4 4
0r[*E q{{0 3 4 4 8ITPUB个人空间*{,mt7N[ljrW
3 4 4 12
A)v;l O0z0 4 5 2 0ITPUB个人空间7fj9@6{Ax
{zcK`%b
4 5 2 5ITPUB个人空间&c!Gy6Sj;OK,Gu
5 4 3 0ITPUB个人空间)ubA$[3l(I7M
5 4 3 4
)hyN(a,ya6Rh[h0 5 4 3 8
已选择14行。
SQL> SELECT DISTINCT T.ID, T.VALUE, POWER, T.VALUE * (LEVEL - 1) RESULT
vl9?4ah c
yI0 2 FROM T
0O"E*O@1j0 3 CONNECT BY LEVEL <= T.POWERITPUB个人空间/pqL6f#Cr:J;c
4 ORDER BY 1, 4;
ID VALUE POWER RESULTITPUB个人空间/a;xbw2oe:|W
---------- ---------- ---------- ----------ITPUB个人空间Ux1Z8t'P6BAx
1 2 3 0ITPUB个人空间e:SW-A{o'kD
1 2 3 2ITPUB个人空间/p9Eu"wK,oW0{m
1 2 3 4
6['UF2Y xd(x'?0 2 1 2 0
4D1m%cJ1c,R(Q0 2 1 2 1
!p9T7q9^di-b vv0 3 4 4 0
R#P2G8N`1p/JL0 3 4 4 4ITPUB个人空间b"GVz8\"swS
3 4 4 8
AFw'Z$tDsH0 3 4 4 12
9US \y1TXi.`0 4 5 2 0
bu,PHZ$L0 4 5 2 5ITPUB个人空间9k5A+P}$j'a0J1y
5 4 3 0
+^5t)Ue0r.w'^L0 5 4 3 4
-d:BK)n!X
O0 5 4 3 8
已选择14行。
这两种方式似乎都可以圆满的解决这个问题,而且由于当时没有找到更好的方法在我前面的文章中给出的SQL解决方法中也是采用的这种方法:http://yangtingkun.itpub.net/post/468/456778
但是这种方法埋藏着一个严重的隐患,那就是性能问题。
由于这种方法存在着记录之间的树形查询,导致大量的重复记录出现,而采用DISTINCT或GROUP BY的方式,实际上只是为了最终结果的正确性,而并没有从根本上消除重复记录的问题,因此一旦数据库增大一点,就会导致需要DISTINCT的结果成数量级的增大。
比如将T中的记录增大到40条,在执行上面的查询:
SQL> INSERT INTO T SELECT 5 + ID, VALUE, POWER FROM T;
已创建5行。
SQL> INSERT INTO T SELECT 10 + ID, VALUE, POWER FROM T;
已创建10行。
SQL> INSERT INTO T SELECT 20 + ID, VALUE, POWER FROM T;
已创建20行。
SQL> SET AUTOT TRACE
;T&l.Y"Wx5bl0SQL> SET TIMING ON
HQ5K+{%at}9}0SQL> SELECT T.ID, T.VALUE, POWER, T.VALUE * (LEVEL - 1) RESULTITPUB个人空间)jU4w!s9d\}P
2 FROM T
.HaJ/]2S)b9s!vo0 3 CONNECT BY LEVEL <= T.POWER
Bf gn5Y|8B"X-RQ[0 4 GROUP BY ID, VALUE, LEVEL, POWER
Dl brj){
wf0 5 ORDER BY 1, 4
}hj&q6h.E"bj0 6 ;
已选择112行。
已用时间: 00: 00: 04.04
执行计划ITPUB个人空间2V/[XN&w
----------------------------------------------------------ITPUB个人空间r9oD q_*fO/o"m
Plan hash value: 2909186630
---------------------------------------------------------------------------------------ITPUB个人空间7D'_'xF~5lE
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |ITPUB个人空间 {T)e5M~$Q gZ
---------------------------------------------------------------------------------------ITPUB个人空间^z,N'I'U8b6@
| 0 | SELECT STATEMENT | | 40 | 1560 | 5 (40)| 00:00:01 |
.VqL'QL.M0CD0| 1 | SORT ORDER BY | | 40 | 1560 | 5 (40)| 00:00:01 |
xP$vu)x5c;q0| 2 | HASH GROUP BY | | 40 | 1560 | 5 (40)| 00:00:01 |
8HG
[KO4R'f0| 3 | CONNECT BY WITHOUT FILTERING| | | | | |
#ujLm`x0| 4 | TABLE ACCESS FULL | T | 40 | 1560 | 3 (0)| 00:00:01 |