我有一个用SQL Server编写的T-SQL例程.我们正在迁移到Oracle,因此我尝试将其移植到PL/SQL.这是T-SQL例程(简化); 请注意使用表值变量,在Oracle中,它将成为"嵌套表"类型的PL/SQL变量.我的问题的主要内容是在PL/SQL中使用这种"集合"对象的最佳方法.移植代码中的几个操作(下面的第二个代码示例)非常笨拙,在SQL Server原始版本中它们似乎更容易:
DECLARE @MyValueCollection TABLE( value VARCHAR(4000) ); DECLARE @valueForThisRow VARCHAR(4000); DECLARE @dataItem1Val INT, @dataItem2Val INT, @dataItem3Val INT, @dataItem4Val INT; DECLARE theCursor CURSOR FAST_FORWARD FOR SELECT DataItem1, DataItem2, DataItem3, DataItem4 FROM DataTable; OPEN theCursor; FETCH NEXT FROM theCursor INTO @dataItem1Val, @dataItem2Val, @dataItem3Val, @dataItem4Val; WHILE @@FETCH_STATUS = 0 BEGIN -- About 50 lines of logic that evaluates @dataItem1Val, @dataItem2Val, @dataItem3Val, @dataItem4Val and constructs @valueForThisRow SET @valueForThisRow = 'whatever'; -- !!! This is the row that seems to have no natural Oracle equivalent INSERT INTO @MyValueCollection VALUES(@valueForThisRow); FETCH NEXT FROM theCursor INTO @dataItem1Val, @dataItem2Val, @dataItem3Val, @dataItem4Val; END; CLOSE theCursor; DEALLOCATE theCursor; -- !!! output all the results; this also seems harder than it needs to be in Oracle SELECT * FROM @MyValueCollection;
我已经能够移植几乎所有东西,但在两个地方(参见代码中的注释),逻辑比旧的SQL Server方式复杂得多,我想知道在Oracle中是否会有更优雅的东西那种躲避我的方式:
set serveroutput on; -- needed for DBMS_OUTPUT; see below DECLARE TYPE StringList IS TABLE OF VARCHAR2(4000); myValueCollection StringList; dummyTempCollection StringList; -- needed for my kludge; see below valueForThisRow VARCHAR2(4000); BEGIN -- build all the sql statements FOR c IN ( SELECT DataItem1, DataItem2, DataItem3, DataItem4 FROM DataTable; ) LOOP -- About 50 lines of logic that evaluates c.DataItem1, c.DataItem2, c.DataItem3, c.DataItem4 and constructs valueForThisRow valueForThisRow := 'whatever'; -- This seems way harder than it should be; I would rather not need an extra dummy collection SELECT valueForThisRow BULK COLLECT INTO dummyTempCollection FROM dual; -- overwrites content of dummy temp myValueCollection := myValueCollection MULTISET UNION dummyTempCollection; -- merges into main collection END LOOP; -- output all the results... again, there's no shorter/easier/more-compact/single-line equivalent? IF myValueCollection.COUNT > 0 THEN FOR indx IN myValueCollection.FIRST .. myValueCollection.LAST LOOP DBMS_OUTPUT.PUT_LINE(myValueCollection(indx)); END LOOP; END IF; END; /
在此先感谢您的帮助!
就个人而言,我将采用"50行逻辑",将其移动到您在SQL语句中调用的函数中,然后执行一个简单的BULK COLLECT
操作将数据加载到本地集合中.
假设您确实希望逐个元素地将数据加载到集合中,您可以简化加载集合的代码
DECLARE TYPE StringList IS TABLE OF VARCHAR2(4000); myValueCollection StringList := StringList(); valueForThisRow VARCHAR2(4000); BEGIN -- build all the sql statements FOR c IN ( SELECT DataItem1, DataItem2, DataItem3, DataItem4 FROM DataTable; ) LOOP -- About 50 lines of logic that evaluates c.DataItem1, c.DataItem2, c.DataItem3, c.DataItem4 and constructs valueForThisRow valueForThisRow := 'whatever'; myValueCollection.extend(); myValueCollection( myValueCollection.count ) := valueForThisRow; END LOOP; -- output all the results... again, there's no shorter/easier/more-compact/single-line equivalent? IF myValueCollection.COUNT > 0 THEN FOR indx IN myValueCollection.FIRST .. myValueCollection.LAST LOOP DBMS_OUTPUT.PUT_LINE(myValueCollection(indx)); END LOOP; END IF; END; /
如果将集合声明为关联数组,则可以避免调用extend
以增加集合的大小.如果您知道要加载到集合中的元素数量,则可以将其传递给extend
循环外的单个调用.潜在地,您还可以消除valueForThisRow
局部变量,并仅对集合中的元素进行操作.
至于处理集合的代码,你真正想做的是什么?生产代码写入dbms_output
并期望任何人在正常处理期间都能看到输出是非常不寻常的.这将影响您编写该代码的方式.假设你的意图真的只是打电话dbms_output
,知道通常会将数据发送到以太网
FOR indx IN 1 .. myValueCollection.count LOOP dbms_output.put_line( myValueCollection(indx) ); END LOOP;
当您拥有密集集合时(所有索引在1和count
集合之间存在且具有值),这都适用.如果你可能有一个稀疏集合,你会想使用FIRST
,NEXT
以及LAST
在一个循环,但是这是一个有点更多的代码.