home *** CD-ROM | disk | FTP | other *** search
- RELATIVE FILES MADE RELATIVELY EASY Part 2
-
- by Bill Brier
- DELPHI Mail: TROUBLESOME
-
- V. READING A RELATIVE FILE RECORD
-
- In RELATIVE FILES MADE RELATIVELY EASY Part 1 we learned what a
- RELative file is, how to organize such a file, how to create a file and
- how to position to a given record. In Part 2 we'll now look at merging
- all of this information together to make a cohesive program.
-
- Reading a RELative file record requires that your program operate in a
- logical sequence. Essentially , it goes like this. First, you OPEN
- the error channel, then the RELative file itself. Next, you check for
- a possible disk error. If one exists you CLOSE your files and branch
- to a routine that evaluates the error and takes appropriate action.
- Otherwise, you position to the desired record and field and use INPUT#
- or GET# to read in the data. The position and read operation is
- performed for each field if the entire record is to be read in, or just
- for one field if you are searching for a particular item of data. When
- you have read in the data you close the RELative file and lastly, close
- the error channel. It's actually quite simple once you see how it's
- done.
-
- Reading in the whole record is best done by setting up a subroutine for
- that purpose. Let's write such a subroutine for reading in one record
- from our mailing list. The subroutine will start at line 8400 in our
- program. The position routine, you may recall, is located at line 8100
- and falls through to line 8200 to check the disk status. Prior to
- actually calling the record reading routine you must OPEN the error
- channel (as demonstrated in Part 1) by a GOSUB6900, OPEN the RELative
- file by GOSUB7000 and check the error channel for a possible error by
- using the errorchecking routine (GOSUB8200). If an error occurred, the
- program will return from 8200 with the error "flag" EF set to 1 and the
- variables X and X$ containing the error number and text, respectively.
- In such a case no attempt should be made to read a record or else the
- program will crash.
-
- Once the preliminary operations have been performed, you should set the
- record number variable J to the desired record number. Now you are
- ready to read in the record:
-
- 8400 P=1:GOSUB8100:IF EF THEN 8470
- 8405 INPUT#2,NA$
- 8410 P=34:GOSUB8100:IF EF THEN 8470
- 8415 INPUT#2,AD$
- 8420 P=60:GOSUB8100:IF EF THEN 8470
- 8425 INPUT#2,CT$
- 8430 P=79:GOSUB8100:IF EF THEN 8470
- 8435 INPUT#2,SA$
- 8440 P=82:GOSUB8100:IF EF THEN 8470
- 8445 INPUT#2,ZI$
- 8450 P=88:GOSUB8100:IF EF THEN 8470
- 8455 INPUT#2,AC$
- 8460 P=92:GOSUB8100:IF EF THEN 8470
- 8465 INPUT#2,TN$
- 8470 RETURN
-
- Several patterns emerge from this example. First, each call to the
- position subroutine at 8100 is preceded by assigning a value to the
- variable P, which is of course the field position. These values were
- derived from the table that we discussed in Part 1. Also, notice that
- after each call to the position subroutine, the generic error flag EF
- is checked. EF is set to 0 if there is no error and is set to 1 if an
- error occurs. The actual error number and message are contained in the
- variables X and X$. However, at this point in the program we only want
- to know whether an error actually occurred. Later on an evaluation
- will take place.
-
- If EF is equal to 1 the expression IF EF THEN 8470 will succeed and the
- routine will automatically abort. The importance of making an error
- check after each disk command cannot be overemphasized. If an error
- occurs and your program ignores it, a crash of some sort is inevitable.
-
- Assuming that no error occurs, the next step is to actually read the
- field into a variable. Never use numeric variables for working with
- disk files. Reading numeric data into a string variable will never
- cause any problem but reading string data into a numeric variable will
- cause the program to crash. Also, INPUT# restricts the maximum length
- of a data string to 88 characters and assumes that the field is
- terminated by a carriage return. If the field is longer than 88
- characters, use GET# to retrieve the characters one at a time and then
- concatenate the characters into one string variable.
-
- In looking at our record reading subroutine it becomes obvious that the
- data has to be read in a certain order. This is because when the
- record is written out to the disk it is also written in a certain
- order. A subroutine to do that will be presented later in this
- article.
-
- Once all of the fields have been read in, the routine will reach the
- RETURN in line 8470 and exit back to the rest of the program. If an
- error occurred at some point the variable EF will be set to 1 upon exit
- from the routine and X and X$ will contain the actual error (review
- Part 1 if you aren't sure as to how the values for EF, X and X$ were
- obtained).
-
- The previous example demonstrated the means by which a RELative file
- record can be read into variables for use within your program. But, a
- RELative file allows more than just reading a record. The real power
- in a RELative file lies in the structuring of the records into fields.
- It is possible to examine individual fields and thus perform searches
- for different data items.
-
- VI. SEARCHING ON FIELDS
-
- Let's suppose that you are going to make a bulk mailing of advertising
- flyers to customers. The post office has advised you that you can get
- a reduced rate if you bundle all of your flyers into groups sorted by
- zip codes. Obviously, when the mailing labels are printed the records
- should be retrieved by zip code. Such a search means searching on the
- zip code field.
-
- To do that requires that you set a string variable equal to the zip
- code that you wish to retrieve, read the zip code field in each record
- and, if the zip code in that record is the same as the one that you
- want, read in the whole record and print the label. Sounds
- complicated? Not at all. Here's an example, which assumes that you've
- already OPENed the error channel and RELative file, and that the value
- of J has been set to the record to be read:
-
- REM SEARCH FOR '606' AS THE ZIP CODE FRAGMENT
- :
- ZS$="606":L=LEN(ZS$):REM ZS$ IS THE SEARCH VALUE
- P=82:GOSUB8100:IF EF THEN 9500:REM 9500 IS ERROR ROUTINE
- INPUT#2,ZI$:REM READ IN THE ZIP FIELD FROM THE RECORD
- IF LEFT$(ZI$,L)<>ZS$ THEN TRY NEXT RECORD
- :
- GOSUB8400:REM READ IN ENTIRE RECORD FOR PRINTING
-
- A FOR-NEXT loop would be used to cycle through all the records on file.
- With the above routine, each time a record was found with the proper
- zip code, the entire record would be fetched and used to print a label.
- Because only the zip code field is actually checked rather that the
- whole record, time is not wasted in reading in fields that aren't
- needed. Only when the zip field matches the search value or parameter
- is the whole record fetched.
-
- It is also possible to make multiple field searches using the same
- technique. You would simply read each field that is being searched and
- if all fields match then read in the whole record. The possibilities
- are limited more by the imagination then anything else.
-
- VI. WRITING A RECORD
-
- Writing a record entails program activity similar to that required for
- reading a record. You must OPEN the error channel and the RELative
- file, position to each field within the record and write out the data.
- As with reading a record there are some rules to be followed.
-
- Although reading a single field of a record can be done in a random
- fashion, writing a field may not. This is due to the manner in which
- the DOS handles RELative file records. For this reason and others, a
- subroutine to write the whole record to the disk should be constructed.
- Here is how we would write our mailing list record:
-
- 8300 P=1:GOSUB8100:IF EF THEN 8375 :REM TRAP FOR ERROR
- 8305 PRINT#3,NA$:REM WRITE NAME FIELD
- 8310 P=34:GOSUB8100:IF EF THEN 8375
- 8315 PRINT#3,AD$
- 8320 P=60:GOSUB8100:IF EF THEN 8375
- 8325 PRINT#3,CT$
- 8330 P=79:GOSUB8100:IF EF THEN 8375
- 8335 PRINT#3,SA$
- 8340 P=82:GOSUB8100:IF EF THEN 8375
- 8345 PRINT#3,ZI$
- 8350 P=88:GOSUB8100:IF EF THEN 8375
- 8355 PRINT#3,AC$
- 8360 P=92:GOSUB8100:IF EF THEN 8375
- 8365 PRINT#3,TN$
- 8370 P=1:GOSUB8100
- 8375 RETURN
-
- If the above routine looks similar to the routine for reading a record,
- it is because the record must be written out in the exact same manner
- as you wish to read it back. Obviously, the program wouldn't work
- correctly if the name field was written to the wrong place in the
- record.
-
- Several precautions must be heeded when writing the record to avoid a
- possible corruption of the data. The writing of the record must always
- start at the lowest field position (normally 1), as shown in the
- example. If you were to first write the zip field (position 82) and
- then the name field (position 1) the zip field would be destroyed. For
- that reason, the record must be written sequentially from the
- beginning.
-
- If your program is modifying one field (such as the zip code) you must
- read in the whole record, change the variable that corresponds to the
- field to be altered and then write the whole record back. Reading in
- the zip code, modifying it and writing it back to the record alone
- would destroy the area code and telephone number fields.
-
- Another thing to be careful of is the content of each variable. The
- INPUT# statement will not handle nulls or leading blanks (CHR$(32)).
- If a variable is to be unused in a particular record it must be padded
- with a suitable character that your program will recognize as a blank
- field. For example, in our mailing list you could make the area code
- field an optional entry that the user could default. A default would
- normally result in AC$ having an ASCII value of zero. This is not
- acceptable for use in a disk file. In such a case change AC$ so that
- it has an ASCII value of 160 (a SHIFTed space). INPUT# will retrieve
- such a character without any trouble. If a variable containing a
- SHIFTed space is printed to the screen it will have the same effect as
- a regular space. Incidentally, some Centronics printer interfaces,
- such as the Card/?+G by CardCo, have trouble handling a SHIFTed space.
-
- Leading blanks are equally troublesome. A leading blank will cause
- INPUT# to malfunction and result in the program "locking up", with the
- disk file left open. For this reason, you must prevent a leading blank
- from being written to the disk. A simple way to detect a leading blank
- in a variable is to test the ASCII value of the variable. ASCII will
- always return the value of the first character in the variable, no
- matter what length the variable is. To test for a leading blank in
- NA$, for example, you could do this:
-
- A=ASC(NA$+CHR$(0))
-
- Even if NA$ is twenty characters in length A will containing the ASCII
- value of the first character only. The CHR$(0) part is needed to
- prevent a program crash in case NA$ is a null.
-
- INPUT# terminates fetching of characters from the disk once a carriage
- return (CHR$(13)) is received. Unfortunately, a comma or a colon will
- do the same thing. Therefore, your program must trap out these
- characters. If you must write them as part of the record, use GET# to
- fetch the characters one at a time, and concatenate a string variable.
- You will have to evaluate each character as it comes in so you may
- detect the CHR$(13) that acts as the delimiter. Because of this, you
- will find GET# to be rather slow.
-
- Because you have defined the length of each field, your program must
- prevent a variable length from exceeding the corresponding field
- length. This is where the LEN function becomes useful. Simply check
- the LENgth of the variable and if it exceeds the field size limit, chop
- off the excess with the LEFT$ function.
-
- For example, here is how to limit the name to 32 characters:
-
- IF LEN (NA$) > 32 THEN NA$=LEFT$(NA$,32)
-
- Actually, a better way to do this is to write an input routine which
- can control how many characters the user can type in. This way the
- user knows when his input is being restricted. Using the LEFT$
- function to limit the variable length does not let the user know
- exactly what he is sending to the disk.
-
- Finally, after your program has written out the record to the disk it
- should reposition back to the first field of the record before CLOSEing
- the file. This will prevent a DOS bug, known as the SPAN-SPILL bug,
- from corrupting certain records. That is the function of line 8370 in
- the example. Do not actually write anything with PRINT#...simply
- reposition back to the start of the field.
-
- VII. RELATIVE REVIEW
-
- Let's look back a bit at what has been discussed. We have developed
- techniques to create a relative file, we have seen how to position to a
- given record and field, we have seen how to read and write to a record,
- and we have seen a simple technique for searching records for a
- particular item of data. We have learned how to correctly structure
- our records, calculate the approximate storage space needed on the disk
- and how to deal with disk errors.
-
- Obviously, RELATIVE FILES MADE RELATIVELY EASY is not an exhaustive
- treatment of the subject, nor is it a manual on database programming.
- It is hoped however that the budding programmer will be able to
- construct an efficient database using this information. The only sure
- route to programming knowledge is via experimentation and observation.
- Experiment with the routines described above on a scratch disk and
- watch what does or doesn't happen. Don't be afraid to make a mistake.
- And, above all, remember the KISS philosophy: (K)eep (I)t (S)imple,
- (S)tupid!
-
-
- W.J. Brier (Delphi mail: TROUBLESOME)
-