Informaton courtesy of the duick.com Advantage Gen / COOL:Gen Index - http://www.duick.com/gen/
 Universal Paging Algorithm

Summary: Many times data needs to be retrieved in 'groups' for display on a screen, web page or window. Bill Todd presents a useful white paper on how to 'page' through data consistently. The text is illustrated with sample code.
(written by Bill Todd)

Historical overview

Paging through lists of data is a time-honored tradition in the Information Technology world and over time, many methods for accomplishing this paging have been used. While paging forward is relatively simple, paging backwards has always been a bit more involved.

One paging technique reads the database from beginning to end and stores the key for the first item on each page in a separate repeating group. It then uses those keys to rebuild each page when paging backwards. Variations include algorithms to handle cases such as an item being added to the list since the page was first built and its key stored. These tend to be very complex and difficult to maintain.

Another technique decrements the starting value for the read each until the proper number of records to fill the page are returned.   If the result isn’t correct (the read went too far back or not far enough) the starting value is decremented again and the process starts over.

Time for a change

These techniques have two things in common - they are complex and they do not fully address the needs of a dynamic environment - one in which items can added or deleted at will. With the power of the latest hardware and software, especially database engines, it is time to find a simpler and more universal way to page through information. The basic requirements are:

  • Start at any point in the data store.
  • Page backward or forward to the end of the database without asking the user to enter a new starting value.
  • Automatically adjust for  additions and deletions performed by other transactions.
  • Have minimal overhead, both for database access and for program construction and maintenance.
  • Work in both block mode and GUI (Graphical User Interface) environments.
  • Be scaleable to work in any situation, simple enough to provide a good answer for small tables and robust enough to work with very large tables

A new way to page

As noted in the introduction, paging forward has always been simple. The standard method for a READ EACH NEXT module, expressed in IEF style pseudo-code, looks like this:

Set subscript of repeating_group to 0

(one less than the start of the page)

= READ EACH entity
| SORTED BY ascending key
|

(sort going up)

| WHERE entity key greater than key of last item on current page

|

(start the next page at the next higher item)

| Set subscript of repeating_group to subscript of repeating_group + 1
|

(point to the next higher position on the page)

|-- If subscript of repeating_group
|                    greater than MAX of repeating_group
| |

(there are more items than will fit on the page - set an indicator and get out of the read loop)

| |
| | Set export plus_flag to ‘+’
<-- Escape
|--
|
| Move entity to export repeating_group entity
=

Notice that one more item is read than will fit in the list. This enables the ‘More data’ indicator or control, whatever it might be, from a ‘+’ on a 3270 block mode screen to the ‘more data’ button on the AOL browser on the Internet. An explicit subscript is used since the implicit method only allows a READ_EACH until the repeating group is full, and to have this control we need to read one beyond full.

Since this works so well for paging forward, what happens if we turn it upside-down and try paging backwards? In other words, read backwards (sort descending) and fill the page from the bottom to the top. Let’s have a look - here is the code for the READ EACH PREVIOUS module:

Set subscript of repeating_group to MAX of repeating_group + 1

(one more than the end of the page - we are starting at the bottom)

= READ EACH entity
| SORTED BY descending key

(sort going down)

| WHERE entity key less than key of the first item on current page
|

(start the next page at the next lower item)

| Set subscript of repeating_group to subscript of repeating_group - 1
|

(point to the next lower position on the page)

| -- If subscript of repeating_group less than 1
| |

(there are more items than will fit on the page - set an indicator and get out of the read loop)

| |
| | Set export minus_flag to ‘-’
<-- Escape
|--
|
| Move entity to export repeating_group entity
=

 

Now let’s try ‘playing computer’ with the code. Assume a database with a very simple index - a single number and the following entries:

index data
010 ------------- data --------------
020 ------------- data --------------
030 ------------- data --------------
040 ------------- data --------------
050 ------------- data --------------
060 ------------- data --------------
070 ------------- data --------------
080 ------------- data --------------
090 ------------- data --------------
100 ------------- data --------------
110 ------------- data --------------
120 ------------- data --------------

Let’s further assume a window/screen that displays 5 entries and that it is currently displaying items 070 through 110. If the page back button is pressed the sequence of events is:

  • Subscript is set to 6 (MAX plus 1)
  • Record 060 is read (sort descending less than the first entry on the current page)
  • Subscript is set to 5 (sub = sub - 1)
  • Record 060 is moved to line 5
  • Record 050 is read
  • Subscript is set to 4
  • Record 050 is moved to line 4
  • Record 040 is read
  • Subscript is set to 3
  • Record 040 is moved to line 3
  • Record 030 is read
  • Subscript is set to 2
  • Record 030 is moved to line 2
  • Record 020 is read
  • Subscript is set to 1
  • Record 020 is moved to line 1
  • Record 010 is read
  • Subscript is set to 0
  • Minus_flag is set to ‘-‘
  • READ_EACH loop is escaped

It looks like the code works in this specific case, but what happens if we start with record 020? Record 020 would go on line 5, Record 010 would go on line 4 and then we would be out of records before we were out of lines.

There actually are a few situations where this would be desirable behavior, but in most cases the user would want to have record 010 on line 1 and the rest following in order.

The easiest way to cause that to happen is to add a use statement to call the READ EACH NEXT action block. It will start at the beginning of the database (Record 010), read the rest in order and place them starting on the first line to the end of the screen.

The use statement can be triggered by the subscript. If it is not less than 2, the view was not filled and the READ EACH NEXT action block should be called.

But what about efficiency?

It may be counter-intuitive, but DB/2 and other relational databases don’t really care whether they are asked to sort ascending or descending, especially on indexes. The sort overhead only applies to the number of records returned to fill the repeating group and that is usually fewer than 20-25.

If, however, you are working in an IMS or other non-relational environment and are reading forward on a primary key (identifier), you will need to add a "9’s complement" index to read backwards as efficiently as forward. Creating a "9’s complement" index is just a matter of subtracting the real key from high-values and storing the result in the "descending index" when creating a record.

Miscellaneous considerations

There is a third component needed, in addition to the READ EACH NEXT and READ EACH PREVIOUS components previously described. It is the READ EACH STARTING AT component and it is identical to the READ EACH NEXT module with two exceptions.

The first is that the read qualifier needs to say greater than or equal to in order to start with a specified key. This is used for both user entered starting values or values selected from a list.

The second is that the READ EACH STARTING AT component has to support the ‘More previous’ control. Since the module could be started at any point in the database, it must look for any record with a lower index than the starting value. If one is found, the ‘More previous’ flag is set or the page back button is enabled.

The Universal Paging Algorithm

These three components, taken together, form the Universal Paging Algorithm (UPA). When used together as described, they will simplify the original construction and the on-going maintenance of any system that delivers lists of sorted data to users. As always, we are anxious to hear about your experiences and discoveries in the application of this algorithm.

see also - Concatenated Key Processing