TechRepublic’s Oracle newsletter covers automating Oracle utilities, generating database alerts, solving directed graph problems, and more. Automatically subscribe today!
A common problem developers face
when building custom search engines is to implement some kind of paging
functionality; that is, to allow a user to issue a query that returns a large
number of rows but only show the first 20 or so matches. These are displayed
until the user clicks on a link to request the next 20, or a previous set of 20
records from a database application.
The problem that arises with database access is that
requests from a Web site are stateless. It’s very inefficient for the database
to keep a cursor available while waiting for the user to request the next set
of hits. It’s possible to code an HTML page where the cursor lives on the
client side, but cursors are a limited resource, so it’s better to close them
once the page is finished loading.
Oracle cursors don’t support moving backward through a rowset; and it’s always possible for a user to back up in
the browser or request a rowset that is out of
sequence. Clearly, it’s the database server’s responsibility to return the
limited set of rows.
The pseudocolumn that contains the
current row number is ROWNUM. Many first attempts at returning a subset of
records from somewhere in the middle of the table don’t
work:
select * from all_objects where rownum between 30 and 49;
This fails because the ROWNUM column is applied to rows only
as they’re being fetched and filtered. When the first row is fetched, it’s
thrown out because its ROWNUM is 1. Then, the next row is fetched; it’s also
thrown out because it’s the new “1” and so on, until all the rows are used.
This query will never return any rows. The solution is that you always have to
fetch rows 1 through 30 before you can even see 30 to 50:
select * from all_objects where rownum <= 49;
Then, you can use it as a subquery
and filter out all the rows before your start point (notice that I had to provide an alias for “rownum” in order for it to
compile). For an example, view
Listing A.
To ensure that it’s efficient, try to use bind variables for
the limits. This will make all such requests textually the same, which eliminates reparsing the query each time a different range is requested. For an example, view
Listing B.
If you’re working in a language that returns rowsets through stored procedures, the database will
automatically handle the bind variables for you internally. Then, the
application code will receive only the records it requested and not have to decide which rows to retrieve. For an example, view
Listing C.
Note that ROWNUM is calculated after sorting, so using an
ORDER BY clause will still work on the new order of rows. However, ROWNUM is
used by the rule-based optimizer to “short-circuit” queries and return rows to
the next part of the query as soon as the ROWNUM clause is satisfied.