You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@turbine.apache.org by Andy Mitchell <an...@oracle.com> on 2001/09/21 21:04:50 UTC
Here's a Way to Code Offset/Limit in Oracle
I've been using Torque for a couple of weeks and noticed that there
isn't offset/limit capability set for Oracle. It's actually possible to
create these with standard SQL ( rather than using a PL/SQL function to
mimic it ).
The culprit is ROWNUM. This gets bound to the record before the group
or order phases. So if you try :
Example A:
SELECT letter, rownum
FROM alphabet
ORDER BY letter
you may get results like :
Letter Rownum
-----------------------
A 5
B 3
C 23
D 15
...
Also, if you try :
Example B:
SELECT letter, rownum
FROM alphabet
WHERE rownum BETWEEN 10 and 20
you'll get back no results.
But it is possible to get what you want if you're bull-headed enough.
The secret is that Oracle allows you to do "inner views", which are just
SQL statements taking the place of tables in the FROM clause. For
example,
Example C:
SELECT *
FROM ( SELECT letter from alphabet );
To support offset and limit, you actually have to do two inner views
like so :
Example D:
SELECT *
FROM (
SELECT vw_$_.*, Rownum AS rownum_$_ -- "OuterView"
FROM (
SELECT letter -- "InnerView"
FROM alphabet
) vw_$_
)
WHERE rownum BETWEEN 10 AND 20 ;
Why do you need two inner views? The Oracle parser will work from the
inside out. So the parser will create a temporary result set with all
of the data from InnerView first. Then, it'll pass that result set to
OuterView. Then the result set from that query will be past to the
actual select statement. InnerView may contain an ORDER BY so you
can't add the ROWNUM here. If you try to, ROWNUM may not be in
sequential order with order which the records are sent back (like the
example A above). So, you've got to wait until all the results are back
from the innermost view, and then append the ROWNUM. You do that by
wrapping it inside OuterView. OuterView simply selects the result set
from InnerView and adds the ROWNUM field. Then, the actual select
statement grabs that temporary result set from OuterView and performs
the limit/offset filter. If you try to skip the OuterView step, you'll
run into a situation like Example B above where you can't actually
select against the ROWNUM.
To summarize :
- the innermost view (InnerView) gets back the data you want
- the second view (OuterView) adds on the ROWNUM in the correct sequence
- the select statement grabs the result set from the second view. Now,
ROWNUM is not a "magical" Oracle column; it's just a simple number
column that you can use to filter.
Inner views are supported back to version 7.3.4.
This is truly ugly :) but it does work. It also requires two-plus
passes through the data to get the answer, but it should be faster than
shipping all the records across to Turbine and having the user perform
the filter in the code since it's done on the server.
I modified three Turbine files :
org.apache.turbine.util.db.adapter.DB - added LIMIT_STYLE_ORACLE
org.apache.turbine.util.db.adapter.DBOracle - added a getLimitStyle()
method
org.apache.turbine.om.peer.BasePeer - added some code in
createQueryString to handle Oracle limits/offsets. Looks like it should
be done in createPreparedStatement too.
There are a couple of bugs with the code. First, it adds an unexpected
column, ROWNUM, to the final result set. That'll map correctly in the
xxPeer.populateObject, but it may throw off people using the village
records. Also, the inner views mask the names of the columns. If a
user creates a join with two columns of the same name, they'll need to
alias one of them.
---------------------------------------------------------------------
To unsubscribe, e-mail: turbine-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: turbine-dev-help@jakarta.apache.org