←Part 26  Part 28→

The HP 3000--For Complete Novices
Part 27: IMAGE/SQL Application Logic--Using DBFIND and DBGET Together

Feature
by George Stachnik
In the previous article in this series, we continued to explore how IMAGE/SQL applications work. We saw examples of how IMAGE/SQL uses the DBFIND and DBGET intrinsics to find chains of records. This time around, we're going to take a closer look at the capabilities of DBGET.

The last installment ended with a first look at DBGET. DBGET is used to read records (or data entries, if you prefer the IMAGE/SQL terminology) from a dataset in a database. DBGET is one of the most powerful tools in the IMAGE/SQL programmer's arsenal, because it offers several different ways to access your data. Here is a list of DBGET's most useful capabilities:
  • DBGET supports serial access--reading the records in a particular dataset in the order that they are physically stored in the dataset, either forwards or backwards.
  • DBGET supports direct access--reading a particular record in a dataset by specifying its relative record number. (For example, you can tell DBGET to get the 125th record in a particular dataset).
  • DBGET supports keyed access--reading a particular record in a master dataset by specifying a particular value for a search item.
  • DBGET supports chained access. A chain is a set of records in a detail dataset that share a common search item. An application program that wants to access a chain of records must first use DBFIND to locate the chain, as we saw in the previous installment. Once DBFIND has located the chain, DBGET can then be used to read the records in the chain, forward or backward.
In order to understand DBGET properly, we're going to use a picture of a sample database to walk through some examples of code.

Sample Database

Figure 1 is a representation of a very simple IMAGE/SQL database. This database is made up of just two datasets:
  • CUSTOMER-MASTER (a manual master dataset).
  • ORDER-NUMBER-DETAIL (a detail dataset).
Let's begin by examining the data that's in this database, and seeing how it's organized.

Figure 1: Database

The CUSTOMER-MASTER dataset is shown on the left side of the figure. As its name implies, it is a master dataset--(a manual master, to be absolutely specific). It contains one record for each customer that we are currently doing business with. The figure shows that our company has just four customers, named GEORGE, KEN, ANDREA, and GREG. (Presumably, we're one of those dot-com startups. No matter--the profits will come when they're needed.)

Each customer record contains information pertinent to that particular customer. For the sake of simplicity, all we're showing in the figure is the customer name, but we can assume that additional information such as address, phone, e-mail address, and so forth would also be stored in these records. CUSTOMER-NAME is an important data item in the CUSTOMER-MASTER dataset because it's a search item. This means that IMAGE/SQL has built the database so that we can quickly find records in CUSTOMER-MASTER using CUSTOMER-NAME as a search argument. For example, we can find the record with CUSTOMER-NAME = GREG.

The ORDER-NUMBER-DETAIL dataset is shown on the right side of the figure. Every time our company takes an order, one record is added to this dataset. Again, for the sake of simplicity, the figure shows only one data item in each record--the order number. But assume that each record also contains other information you might need to know about that order, including the particular products or parts that were ordered, the name of the customer who placed the order, and so forth.

One of the fields in ORDER-NUMBER-DETAIL is CUSTOMER-NAME. As we'll see, this data-item is the link between the two datasets, and the building block from which IMAGE/SQL will create chains of records.

Chains of Records

We've been discussing the concept of chains of records in the last few installments of this series of articles. Figure 1 illustrates this concept in more detail than we've seen up until now.

Each of the four records in CUSTOMER-MASTER is associated with a chain of related records in ORDER-NUMBER-DETAIL. For example, the GREG record in CUSTOMER-MASTER is associated with a set of records in ORDER-NUMBER-DETAIL. These records represent all of the orders Greg has placed with our company. Every time Greg calls in an order, a new record representing that order is placed in the ORDER-NUMBER DETAIL dataset. Then, the record is also added to the Greg chain. In Figure 1, we've colored all of GREG's records red, to make them easier to find. The Greg Chain is nothing more than the set of records in ORDER-NUMBER-DETAIL that correspond to GREG's orders.

Every chain of records is arranged in a particular sequence or order. The GREG chain begins (or is rooted) with Greg's record in the CUSTOMER-MASTER dataset. Figure 1 represents the linkages among Greg's records using red arrows. If you follow those arrows from the GREG record in CUSTOMER-MASTER to the records in ORDER-NUMBER-DETAIL, you'll find that the GREG chain is made up of three records. They have order numbers 0001, 0004 and 0009, in that order.

Note, that in Greg's case, the sequence of records in the GREG chain is the same sequence in which the records are physically stored in the database. That is, the GREG chain is made of order numbers 0001, 0004, and 0009, and the chain seems to be sorted in order number sequence. In practice, chains don't have to be sorted in any particular sequence.

For example, look at the Andrea chain of records in Figure 1. The ANDREA records are all shown in green, and the arrows that show the linkages in the ANDREA chain are also green in color.

Like Greg's chain, Andrea's chain is rooted in the ANDREA record in CUSTOMER-MASTER. This record contains a pointer to the corresponding chain of records in ORDER-NUMBER-DETAIL. If you follow the green arrows, you'll see that that the ANDREA chain in ORDER-NUMBER-DETAIL begins with order number 0006, continuing with 0012, and ending with 0002. As you can see, the ANDREA records are in no particular order.

When this IMAGE/SQL database was first defined in a schema, the chains were also defined in that file. We began our discussion of IMAGE/SQL several months ago by showing you how to create and compile a schema.

There was very little discussion of chains at that time, so we'll review the concept now, as it applies to the schema. There are sections in every schema to define database passwords, data items, and datasets. But there is no chains section. That's because the chains are implied by the relationships among the items in the schema.

Recall that every schema contains a SETS: section in which we define the various datasets that make up the database. Then, for each dataset, the various data items (or fields, if you prefer) that make up that dataset are defined in the schema. Some of these data items are identified as search items. Each search item that is defined in the schema results in a corresponding chain of records.

In our example in Figure 1, CUSTOMER-NAME is a search item. In the schema that defined this database, the CUSTOMER-MASTER and ORDER-NUMBER-DETAIL datasets were defined in the SETS portion of the schema, as shown in Figure 2.

Figure 2: SETS Part of Schema
SETS:
NAME:             CUSTOMER-MASTER,      MANUAL(4,8,15/8,15);

ENTRY:            CUSTOMER-NAME(1),
                  STREET,
                  CITY,
                  EMAIL-ADDRESS
                  ZIPCODE;
CAPACITY:          201;

NAME:             ORDER-NUMBER-DETAIL,  DETAIL(4,8,15/8,15);

ENTRY:            CUSTOMER-NAME(CUSTOMER-MASTER),
                  ORDER-NUMBER,
                  DATE;
                  PART-NO,
                  DATE,
The first three lines of the code fragment shown in Figure 2 tell us that we're defining a manual master dataset called CUSTOMER-MASTER. (The parenthesized list of numbers following the word MANUAL is the read/write list of user classes.) The ENTRY section begins on the fourth line shown in the figure.

The ENTRY keyword signals that we're about to see a list of data entries (or fields) that will make up this particular dataset. Note that the data item CUSTOMER-NAME is followed by a 1 in parentheses. This 1 in the schema identifies CUSTOMER-NAME as a search item, and furthermore says that for every unique CUSTOMER-NAME that appears in CUSTOMER-MASTER, there will be a pointer to exactly one detail dataset containing a chain of records for that customer.

So, in a schema, a number next to an item name in a master dataset effectively defines a chain. In the example shown in Figure 1, for every record in CUSTOMER-MASTER, there is (or at least could be) a chain of one or more records in ORDER-NUMBER-DETAIL that corresponds to that record. The number that appears in the schema tells how many detail datasets will contain chains of records corresponding to this item. Note that it doesn't tell which detail datasets; to find that out, we have to look elsewhere in the schema.

Returning to Figure 2, let's take a look at the definition of the ORDER-NUMBER-DETAIL dataset. The first data item in this dataset is CUSTOMER-NAME, and the data item name is followed in the schema by the words CUSTOMER-MASTER in parenthesis. In a detail dataset definition, each search item name must be followed by the name of the corresponding master dataset name in parenthesis.

The schema's definition of a chain, then, has two parts. The first part is in the description of the master dataset that will hold the root of the chain. There, a number must be coded next to the search item name, which tells how many detail datasets contain chains that are rooted in this master. The second part is in the description of the detail dataset (or datasets). There, the name of the corresponding master dataset is coded next to the search item name.
ENTRY:              CUSTOMER-NAME(CUSTOMER-MASTER),
The above line of code from Figure 2 is saying that CUSTOMER-NAME is a search item. IMAGE/SQL will link all the records in this dataset that share a common value in CUSTOMER-NAME (for example, all the GREG records--see Figure 1) into a chain. The root of this chain will reside in the specified master dataset, in this case, CUSTOMER-MASTER.

Sorted and Unsorted Chains

Chains can be defined to be either sorted or unsorted.

When a record is added to an unsorted chain, it is always linked to the end of any chains that the record might be part of. Consequently, unsorted chains are in no particular order (other than temporal)--that is, the records in the chain appear in the order in which they were added to the chains.

When a record is added to a sorted chain, IMAGE/SQL will insert the record in the appropriate place in the chain in order to maintain the chain in the order that was specified in the schema.

In order to define a sorted chain, you name the sort-item in the schema. For example, suppose we wanted the CUSTOMER-NAME chains to be sorted by delivery date. When we defined that chain, we'd specify the sort field as follows:
ENTRY:             CUSTOMER-NAME(CUSTOMER-MASTER(DATE)),
                   ORDER-NUMBER,
                   DATE;
This would tell IMAGE/SQL that chains of records that share a common CUSTOMER-NAME must be sorted by DATE. However, bear in mind that there is a lot of overhead associated with the maintenance of sorted chains. The impact on database performance may be significant; most experienced IMAGE/SQL database administrators avoid them unless they are absolutely necessary.

Picturing A Database

Let's go back to Figure 1 for a moment, and wrap up this conceptual explanation of IMAGE/SQL databases, and make sure we're clear on how chains work. One way of thinking of an IMAGE/SQL database is to imagine each record in CUSTOMER-MASTER as an index card with a piece of string tied to it. That piece of string represents a chain.

Imagine that each record in ORDER-NUMBER-DETAIL is also represented by an index card, but this time, each card has a hole punched in the corner. The string that's tied to the GREG card in CUSTOMER-MASTER is threaded through the holes in each of the cards in ORDER-NUMBER-DETAIL that represent one of GREG's orders.

It may be helpful to think of chained access this way. If you wanted to find all of GREG's orders, you'd pull the GREG card from the CUSTOMER-MASTER box. This card would tell you everything you needed to know about GREG--but nothing about his orders.

The information about GREG's orders would be mixed in with all the other orders in the ORDER-NUMBER-DETAIL cards. By pulling the string, you could pull GREG'S orders quickly and easily.

In the example shown in Figure 1, the string would be threaded from the GREG record in CUSTOMER-MASTER through the cards in ORDER-NUMBER-DETAIL with order numbers 0001, 0004, and 0009. (Remember, the figure represents the fact that these are GREG's orders by color coding them red, to match GREG's record in CUSTOMER-MASTER. Similarly, ANDREA'S records are color-coded green, Ken's are yellow, and George's are Olive.)

Accessing Chains from a Program

Suppose we wanted an application program to access all the orders associated with a particular customer--GREG. We would begin by having the program use the DBFIND intrinsic to locate the chain of records in the ORDER-NUMBER-DETAIL dataset with a search-item value of CUSTOMER-NAME = GREG.

Figure 3 contains some sample COBOL code that demonstrates one way that this might be done. The call to DBFIND is shown at the top of the figure--with the actual procedure call occupying lines 53000 through 53500. This is quite similar to the sample code that we used last time, so we're not going to walk through this code in detail now.

Rather, we want to focus on how DBFIND is used together with DBGET. At the bottom of Figure 3 (line 55000 through 55600) we see a call to the DBGET intrinsic. This intrinsic call will retrieve the first record in the GREG chain.

Figure 3: Accessing Chains
052300   MOVE "GREG"                 TO    SEARCH-ITEM-VALUE.
052500   MOVE 1                      TO    DB-MODE.
052600   MOVE "ORDER-NUMBER-DETAIL;" TO    SET-NAME.
052700   MOVE "CUSTOMER-NAME;"       TO    SEARCH-ITEM-NAME.
052900
053000   CALL INTRINSIC "DBFIND" USING DB-NAME,
053100                       SET-NAME,
053200                       DB-MODE,
053300                       STATUS-ARRAY,
053400                       SEARCH-ITEM-NAME,
053500                       SEARCH-ITEM-VALUE.
053600
053800   IF CONDITION-CODE NOT= 0
053900     IF CONDITION-CODE = 17
054000       DISPLAY "The Customer does not exist."
054100     ELSE
054200       PERFORM IMAGE-ERROR
054300   ELSE
054500
054600     MOVE "@;" TO ITEMS-LIST
054700     MOVE 5    TO DB-MODE
054900
055000     CALL INTRINSIC "DBGET" USING DB-NAME,
055100                        SET-NAME,
055200                        DB-MODE,
055300                        STATUS-ARRAY,
055400                        ITEMS-LIST,
055500                        ORDER-DETAIL-BUFFER,
055600                        IGNORED-PARM
055700
056000    IF CONDITION-CODE NOT= 0
056200      IF CONDITION-CODE = 15
056300        DISPLAY "There are no more entries for that customer."
056400         ELSE
056500           PERFORM IMAGE-ERROR
056600       ELSE
056700         DISPLAY "Customer              = ",CUSTOMER-NAME
056800         DISPLAY "Order Number          = ",ORDER-NO-VALUE
056900         DISPLAY "Part Number           = ",PART-NO-VALUE
057000                                     OF ORDER-DETAIL-BUFFER
This business of chaining records together can get pretty confusing, so we're going to refer back to Figure 1 and see a picture of the records we're actually accessing as we execute each intrinsic.

DBFIND located the beginning of the GREG chain. Each chain in ORDER-NUMBER-DETAIL has a corresponding record (sometimes called the root of the chain) in CUSTOMER-MASTER. Each record in CUSTOMER-MASTER will contain the address of the first record in that chain. These addresses are the pointers that we represented as arrows in Figure 1.

This way of structuring things allows DBGET to retrieve the first record in the chain quickly. The addresses of chained records are placed in the datasets automatically by IMAGE/SQL--your program doesn't need to contain any logic to calculate them or to maintain them.

In Figure 1, the GREG record in CUSTOMER-MASTER contains the address of the first GREG record in ORDER-NUMBER-DETAIL. The address is represented in Figure 1 by the arrow pointing from the GREG record CUSTOMER-MASTER to the 0001 record in ORDER-NUMBER-DETAIL.

When we call DBGET, as shown in Figure 3, it will retrieve the 0001 record in ORDER-NUMBER-DETAIL, because that's the first record in the GREG chain. Following the Greg chain shown in Figure 1, a second call to DBGET would retrieve the next record in the chain. The arrow in Figure 1 shows that this next record is order number 0004.

There are two or three important points that must be made here.
  • First of all, each record in the ORDER-NUMBER-DETAIL dataset has a CUSTOMER-NAME field. And each record that corresponds to one of GREG's orders bears his name in that field.
  • That having been said, you might be tempted to guess that once IMAGE/SQL had located the first GREG record in ORDER-NUMBER-DETAIL, it would locate the next one by simply reading forward, one record at a time, examining the CUSTOMER-NAME field, and stopping only when it found the name GREG. Unfortunately, if you guessed that way, you'd be dead wrong. In fact, each record in the ORDER-NUMBER-DETAIL dataset contains a pointer to the next record in its chain. This means that IMAGE/SQL doesn't need to perform a time-consuming search operation each time it wants to find the next record in a chain. It knows exactly where to find it, and goes directly to it.
Once your application program has used DBGET to retrieve the first GREG record in ORDER-NUMBER-DETAIL, it can call it again to retrieve the next GREG record, which happens to be order number 0004. A third call to DBGET can then be used to retrieve record number 0009, which is the third and final GREG record.

At this point, if you were to use DBGET to attempt to retrieve a fourth record in the GREG chain, DBGET would return an error condition, because there are only three records in this chain. Order-number 0009 represents the last order that GREG has placed with our company, and the end of the GREG chain.

Referring back to Figure 3, Line 56200 tests the condition code returned by DBGET. If this condition code contains a value of zero, it means that DBGET has successfully retrieved a record that matches the criteria that were passed to it. The value of 15 that this line of code is testing for indicates that DBGET encountered an end-of-chain condition. Our application program tests for that condition-code value explicitly. If it finds it, an error message is displayed:

"There are no more entries for that customer."

Other Uses for DBGET

Throughout this discussion, we've been using DBGET to read the records in a particular chain. If that's all that DBGET could do, it would be a useful tool. But DBGET contains many more capabilities than that.

Refer once again to Figure 3, and let's take a closer look at the sample COBOL code that calls DBGET. Earlier, we talked about the fact that DBGET can be used to access data in several ways--serially, using direct access, keyed access, or chained access. Application programs must use the parameters that are passed to the DBGET intrinsic to tell it which kind of access to use.

In Figure 3, the statement that calls DBGET begins on 55000 and continues through 55600. In preparation for the CALL statement, our sample code establishes an access mode of 5 (line 54700). The integer value 5 is telling DBGET that we want to do chained access.

Table 1 shows the various values that can be passed to DBGET in this parameter. Note that a value of 5 literally tells DBGET Read the next record in a chain previously located by DBFIND.

Table 1: DBGET Access Mode Summary
Mode DBGET Action
1 Re-read the current record.
2 Read the next record in the database (serial access)
3 Read the previous record in the database (backward serial access)
4 Read a specified record number (direct access)
5 Read the next record in a chain previously located by DBFIND
6 Read the previous record in a chain previously located by DBFIND
7 Read the record with the specified key value
8 Read the contents of the specified hash location

The code shown in Figure 3 was designed to locate the orders in ORDER-NUMBER-DETAIL that correspond to a particular customer. This is the kind of logic typically found in an online application. Suppose you wanted to design an application program to read all the orders in ORDER-NUMBER-DETAIL in order-number order (without regard for which customers placed the orders).

One way you might achieve this would be to call DBGET and pass it a value of 2 in the DB-MODE parameter. A value of 2 tells DBGET to read the data serially, starting with the first record (the 0001 record--see Figure 1), and continuing through records 0002, 0003, 0004 and so on through the last record in ORDER-NUMBER-DETAIL.

Take a moment now to read through the other values that can be passed to DBGET in the DB-MODE parameter, and make sure that you understand each of them. The only one that we haven't really discussed is mode 8, Read the contents of the specified hash location. We'll return to this in a future article.

Updating a Database

We will briefly visit a new intrinsic in this installment called DBUPDATE. DBUPDATE is used to make changes to an existing record in an IMAGE/SQL database. We'll wind up this installment by quickly glancing through an example of how you might use DBUPDATE to make changes to an existing record in a database.

Figure 4 contains some sample COBOL code that makes use of this intrinsic. Note that this sample code does not use the same database that we've been using up until now, so the data definitions are different.

The sample code begins with some DISPLAY and ACCEPT statements (lines 58500 through 59600). This is standard COBOL logic that is prompting the user to enter a part number. On line 59600, we use the ACCEPT verb so that the user can specify which part number he wants to update.

Lines 59900 through 61200 all support the calling of the DBGET intrinsic. Remember, before you can use DBUPDATE to change the contents of a record, you must first use DBGET to retrieve the record that you want to update. In this way, your application program can be sure that you are updating the right record. It can also examine the contents of the record before it changes them.

In preparation for the CALL statement on 60400, our sample code establishes an access mode of 7 (line 59900). Referring to Table 1, we can see that mode 7 forces DBGET to retrieve the record using keyed access. So the code that appears in Figure 4 is retrieving the record in the PARTS dataset that has a part-number matching the one entered by the end user.

Next, the code in Figure 4 checks for a variety of error conditions, and then prompts the user for changes to the record that it retrieved (lines 62100 through 63500).

Figure 4: Using DBUPDATE
058500 UPDATE-PART-INFORMATION.
058800
059400   DISPLAY "Please enter the Part Number to update: "
059500       WITH NO ADVANCING.
059600   ACCEPT SEARCH-ITEM-VALUE FREE.
059800
059900   MOVE 7         TO DB-MODE.
060000   MOVE "@;"      TO ITEMS-LIST.
060100   MOVE "PARTS;"  TO SET-NAME.
060200
060400   CALL INTRINSIC "DBGET" USING DB-NAME,
060500                     SET-NAME,
060600                     DB-MODE,
060700                     STATUS-ARRAY,
060800                     ITEMS-LIST,
060900                     PARTS-BUFFER,
061000                     SEARCH-ITEM-VALUE.
061200
061300   IF CONDITION-CODE NOT = 0
061500     IF CONDITION-CODE = 17
061600       DISPLAY "The Part does not exist."
061700     ELSE
061800       PERFORM IMAGE-ERROR
061900   ELSE
062100     DISPLAY "Current Part Number = ",PART-NO-VALUE
062200                                OF PARTS-BUFFER
062300     DISPLAY "          New Value : "
062400        WITH NO ADVANCING
062500     ACCEPT PART-NO-VALUE OF PARTS-BUFFER FREE
062700     DISPLAY "Current Description = ",PART-DESCRIPTION-VALUE
062800     DISPLAY "          New Value : "
062900        WITH NO ADVANCING
063000     ACCEPT PART-DESCRIPTION-VALUE FREE
063200     DISPLAY "Current Quantity in Stock = ",QTY-IN-STOCK-VALUE
063300     DISPLAY "               New Value  : "
063400        WITH NO ADVANCING
063500     ACCEPT QTY-IN-STOCK-VALUE FREE
063700
063800     MOVE 1 TO DB-MODE
063900
064100     CALL INTRINSIC "DBUPDATE" USING DB-NAME,
064200                          SET-NAME,
064300                          DB-MODE,
064400                          STATUS-ARRAY,
064500                          ITEMS-LIST,
064600                          PARTS-BUFFER
064800
064900     IF CONDITION-CODE NOT= 0
065100        IF CONDITION-CODE = 41
065200           DISPLAY "The value of the Part Number",
065201                   " cannot be changed"
065210        ELSE
065300           IF CONDITION-CODE = 42
065400             DISPLAY "You do not have the ability to",
065500                      " update the data item."
065600           ELSE
065700              PERFORM IMAGE-ERROR
065800     ELSE
065900       DISPLAY "The Part was successfully updated".
066000
066100   DISPLAY SPACE.
Finally, starting at line 64100, we call DBUPDATE to apply the changes to the database. In the next article, we'll take a closer look at DBUPDATE and see how it differs from DBPUT--the other intrinsic that can be used to write to a database.


George Stachnik works in technical training in HP's Network Server Division.
  ←Part 26  Part 28→
     [3khat]3kRanger   [3khat]3kMail   Updated