home *** CD-ROM | disk | FTP | other *** search
/ 8bitfiles.net/archives / archives.tar / archives / genie-commodore-file-library / Information / RELATIVELY-EASY-PART-2 < prev    next >
Encoding:
Text File  |  2019-04-13  |  13.4 KB  |  279 lines

  1. RELATIVE FILES MADE RELATIVELY EASY Part 2
  2.  
  3. by Bill Brier 
  4. DELPHI Mail:   TROUBLESOME
  5.  
  6. V.  READING A RELATIVE FILE RECORD
  7.  
  8. In RELATIVE FILES MADE RELATIVELY EASY Part 1 we learned what a
  9. RELative file is, how to organize such a file, how to create a file and
  10. how to position to a given record.  In Part 2 we'll now look at merging
  11. all of this information together to make a cohesive program.
  12.  
  13. Reading a RELative file record requires that your program operate in a
  14. logical sequence.  Essentially , it goes like this.  First, you OPEN
  15. the error channel, then the RELative file itself.  Next, you check for
  16. a possible disk error.  If one exists you CLOSE your files and branch
  17. to a routine that evaluates the error and takes appropriate action.
  18. Otherwise, you position to the desired record and field and use INPUT#
  19. or GET# to read in the data.  The position and read operation is
  20. performed for each field if the entire record is to be read in, or just
  21. for one field if you are searching for a particular item of data.  When
  22. you have read in the data you close the RELative file and lastly, close
  23. the error channel.  It's actually quite simple once you see how it's
  24. done.
  25.  
  26. Reading in the whole record is best done by setting up a subroutine for
  27. that purpose.  Let's write such a subroutine for reading in one record
  28. from our mailing list.  The subroutine will start at line 8400 in our
  29. program.  The position routine, you may recall, is located at line 8100
  30. and falls through to line 8200 to check the disk status.  Prior to
  31. actually calling the record reading routine you must OPEN the error
  32. channel (as demonstrated in Part 1) by a GOSUB6900, OPEN the RELative
  33. file by GOSUB7000 and check the error channel for a possible error by
  34. using the errorchecking routine (GOSUB8200).  If an error occurred, the
  35. program will return from 8200 with the error "flag" EF set to 1 and the
  36. variables X and X$ containing the error number and text, respectively.
  37. In such a case no attempt should be made to read a record or else the
  38. program will crash.
  39.  
  40. Once the preliminary operations have been performed, you should set the
  41. record number variable J to the desired record number.  Now you are
  42. ready to read in the record:
  43.  
  44.     8400 P=1:GOSUB8100:IF EF THEN 8470
  45.     8405 INPUT#2,NA$
  46.     8410 P=34:GOSUB8100:IF EF THEN 8470
  47.     8415 INPUT#2,AD$
  48.     8420 P=60:GOSUB8100:IF EF THEN 8470
  49.     8425 INPUT#2,CT$
  50.     8430 P=79:GOSUB8100:IF EF THEN 8470
  51.     8435 INPUT#2,SA$
  52.     8440 P=82:GOSUB8100:IF EF THEN 8470
  53.     8445 INPUT#2,ZI$
  54.     8450 P=88:GOSUB8100:IF EF THEN 8470
  55.     8455 INPUT#2,AC$
  56.     8460 P=92:GOSUB8100:IF EF THEN 8470
  57.     8465 INPUT#2,TN$
  58.     8470 RETURN
  59.  
  60. Several patterns emerge from this example.  First, each call to the
  61. position subroutine at 8100 is preceded by assigning a value to the
  62. variable P, which is of course the field position.  These values were
  63. derived from the table that we discussed in Part 1.  Also, notice that
  64. after each call to the position subroutine, the generic error flag EF
  65. is checked.  EF is set to 0 if there is no error and is set to 1 if an
  66. error occurs.  The actual error number and message are contained in the
  67. variables X and X$.  However, at this point in the program we only want
  68. to know whether an error actually occurred.  Later on an evaluation
  69. will take place.
  70.  
  71. If EF is equal to 1 the expression IF EF THEN 8470 will succeed and the
  72. routine will automatically abort.  The importance of making an error
  73. check after each disk command cannot be overemphasized.  If an error
  74. occurs and your program ignores it, a crash of some sort is inevitable.
  75.  
  76. Assuming that no error occurs, the next step is to actually read the
  77. field into a variable.  Never use numeric variables for working with
  78. disk files.  Reading numeric data into a string variable will never
  79. cause any problem but reading string data into a numeric variable will
  80. cause the program to crash.  Also, INPUT# restricts the maximum length
  81. of a data string to 88 characters and assumes that the field is
  82. terminated by a carriage return.  If the field is longer than 88
  83. characters, use GET# to retrieve the characters one at a time and then
  84. concatenate the characters into one string variable.
  85.  
  86. In looking at our record reading subroutine it becomes obvious that the
  87. data has to be read in a certain order.  This is because when the
  88. record is written out to the disk it is also written in a certain
  89. order.  A subroutine to do that will be presented later in this
  90. article.
  91.  
  92. Once all of the fields have been read in, the routine will reach the
  93. RETURN in line 8470 and exit back to the rest of the program.  If an
  94. error occurred at some point the variable EF will be set to 1 upon exit
  95. from the routine and X and X$ will contain the actual error (review
  96. Part 1 if you aren't sure as to how the values for EF, X and X$ were
  97. obtained).
  98.  
  99. The previous example demonstrated the means by which a RELative file
  100. record can be read into variables for use within your program.  But, a
  101. RELative file allows more than just reading a record.  The real power
  102. in a RELative file lies in the structuring of the records into fields.
  103. It is possible to examine individual fields and thus perform searches
  104. for different data items.
  105.  
  106. VI.  SEARCHING ON FIELDS
  107.  
  108. Let's suppose that you are going to make a bulk mailing of advertising
  109. flyers to customers.  The post office has advised you that you can get
  110. a reduced rate if you bundle all of your flyers into groups sorted by
  111. zip codes.  Obviously, when the mailing labels are printed the records
  112. should be retrieved by zip code.  Such a search means searching on the
  113. zip code field.
  114.  
  115. To do that requires that you set a string variable equal to the zip
  116. code that you wish to retrieve, read the zip code field in each record
  117. and, if the zip code in that record is the same as the one that you
  118. want, read in the whole record and print the label.  Sounds
  119. complicated?  Not at all.  Here's an example, which assumes that you've
  120. already OPENed the error channel and RELative file, and that the value
  121. of J has been set to the record to be read:
  122.  
  123.     REM SEARCH FOR '606' AS THE ZIP CODE FRAGMENT
  124.     :
  125.     ZS$="606":L=LEN(ZS$):REM ZS$ IS THE SEARCH VALUE
  126.     P=82:GOSUB8100:IF EF THEN 9500:REM 9500 IS ERROR ROUTINE
  127.     INPUT#2,ZI$:REM READ IN THE ZIP FIELD FROM THE RECORD
  128.     IF LEFT$(ZI$,L)<>ZS$ THEN TRY NEXT RECORD
  129.     :
  130.     GOSUB8400:REM READ IN ENTIRE RECORD FOR PRINTING
  131.  
  132. A FOR-NEXT loop would be used to cycle through all the records on file.
  133. With the above routine, each time a record was found with the proper
  134. zip code, the entire record would be fetched and used to print a label.
  135. Because only the zip code field is actually checked rather that the
  136. whole record, time is not wasted in reading in fields that aren't
  137. needed.  Only when the zip field matches the search value or parameter
  138. is the whole record fetched.
  139.  
  140. It is also possible to make multiple field searches using the same
  141. technique.  You would simply read each field that is being searched and
  142. if all fields match then read in the whole record.  The possibilities
  143. are limited more by the imagination then anything else.
  144.  
  145. VI.  WRITING A RECORD
  146.  
  147. Writing a record entails program activity similar to that required for
  148. reading a record.  You must OPEN the error channel and the RELative
  149. file, position to each field within the record and write out the data.
  150. As with reading a record there are some rules to be followed.
  151.  
  152. Although reading a single field of a record can be done in a random
  153. fashion, writing a field may not.  This is due to the manner in which
  154. the DOS handles RELative file records.  For this reason and others, a
  155. subroutine to write the whole record to the disk should be constructed.
  156. Here is how we would write our mailing list record:
  157.  
  158.     8300 P=1:GOSUB8100:IF EF THEN 8375 :REM TRAP FOR ERROR
  159.     8305 PRINT#3,NA$:REM WRITE NAME FIELD
  160.     8310 P=34:GOSUB8100:IF EF THEN 8375
  161.     8315 PRINT#3,AD$
  162.     8320 P=60:GOSUB8100:IF EF THEN 8375
  163.     8325 PRINT#3,CT$
  164.     8330 P=79:GOSUB8100:IF EF THEN 8375
  165.     8335 PRINT#3,SA$
  166.     8340 P=82:GOSUB8100:IF EF THEN 8375
  167.     8345 PRINT#3,ZI$
  168.     8350 P=88:GOSUB8100:IF EF THEN 8375
  169.     8355 PRINT#3,AC$
  170.     8360 P=92:GOSUB8100:IF EF THEN 8375
  171.     8365 PRINT#3,TN$
  172.     8370 P=1:GOSUB8100
  173.     8375 RETURN
  174.  
  175. If the above routine looks similar to the routine for reading a record,
  176. it is because the record must be written out in the exact same manner
  177. as you wish to read it back.  Obviously, the program wouldn't work
  178. correctly if the name field was written to the wrong place in the
  179. record.
  180.  
  181. Several precautions must be heeded when writing the record to avoid a
  182. possible corruption of the data.  The writing of the record must always
  183. start at the lowest field position (normally 1), as shown in the
  184. example.  If you were to first write the zip field (position 82) and
  185. then the name field (position 1) the zip field would be destroyed.  For
  186. that reason, the record must be written sequentially from the
  187. beginning.
  188.  
  189. If your program is modifying one field (such as the zip code) you must
  190. read in the whole record, change the variable that corresponds to the
  191. field to be altered and then write the whole record back.  Reading in
  192. the zip code, modifying it and writing it back to the record alone
  193. would destroy the area code and telephone number fields.
  194.  
  195. Another thing to be careful of is the content of each variable.  The
  196. INPUT# statement will not handle nulls or leading blanks (CHR$(32)).
  197. If a variable is to be unused in a particular record it must be padded
  198. with a suitable character that your program will recognize as a blank
  199. field.  For example, in our mailing list you could make the area code
  200. field an optional entry that the user could default.  A default would
  201. normally result in AC$ having an ASCII value of zero.  This is not
  202. acceptable for use in a disk file.  In such a case change AC$ so that
  203. it has an ASCII value of 160 (a SHIFTed space).  INPUT# will retrieve
  204. such a character without any trouble.  If a variable containing a
  205. SHIFTed space is printed to the screen it will have the same effect as
  206. a regular space.  Incidentally, some Centronics printer interfaces,
  207. such as the Card/?+G by CardCo, have trouble handling a SHIFTed space.
  208.  
  209. Leading blanks are equally troublesome.  A leading blank will cause
  210. INPUT# to malfunction and result in the program "locking up", with the
  211. disk file left open.  For this reason, you must prevent a leading blank
  212. from being written to the disk.  A simple way to detect a leading blank
  213. in a variable is to test the ASCII value of the variable.  ASCII will
  214. always return the value of the first character in the variable, no
  215. matter what length the variable is.  To test for a leading blank in
  216. NA$, for example, you could do this:
  217.  
  218.     A=ASC(NA$+CHR$(0))
  219.  
  220. Even if NA$ is twenty characters in length A will containing the ASCII
  221. value of the first character only.  The CHR$(0) part is needed to
  222. prevent a program crash in case NA$ is a null.
  223.  
  224. INPUT# terminates fetching of characters from the disk once a carriage
  225. return (CHR$(13)) is received.  Unfortunately, a comma or a colon will
  226. do the same thing.  Therefore, your program must trap out these
  227. characters.  If you must write them as part of the record, use GET# to
  228. fetch the characters one at a time, and concatenate a string variable.
  229. You will have to evaluate each character as it comes in so you may
  230. detect the CHR$(13) that acts as the delimiter.  Because of this, you
  231. will find GET# to be rather slow.
  232.  
  233. Because you have defined the length of each field, your program must
  234. prevent a variable length from exceeding the corresponding field
  235. length.  This is where the LEN function becomes useful.  Simply check
  236. the LENgth of the variable and if it exceeds the field size limit, chop
  237. off the excess with the LEFT$ function.
  238.  
  239. For example, here is how to limit the name to 32 characters:
  240.  
  241.     IF LEN (NA$) > 32 THEN NA$=LEFT$(NA$,32)
  242.  
  243. Actually, a better way to do this is to write an input routine which
  244. can control how many characters the user can type in.  This way the
  245. user knows when his input is being restricted.  Using the LEFT$
  246. function to limit the variable length does not let the user know
  247. exactly what he is sending to the disk.
  248.  
  249. Finally, after your program has written out the record to the disk it
  250. should reposition back to the first field of the record before CLOSEing
  251. the file.  This will prevent a DOS bug, known as the SPAN-SPILL bug,
  252. from corrupting certain records.  That is the function of line 8370 in
  253. the example.  Do not actually write anything with PRINT#...simply
  254. reposition back to the start of the field.
  255.  
  256. VII.  RELATIVE REVIEW
  257.  
  258. Let's look back a bit at what has been discussed.  We have developed
  259. techniques to create a relative file, we have seen how to position to a
  260. given record and field, we have seen how to read and write to a record,
  261. and we have seen a simple technique for searching records for a
  262. particular item of data.  We have learned how to correctly structure
  263. our records, calculate the approximate storage space needed on the disk
  264. and how to deal with disk errors.
  265.  
  266. Obviously, RELATIVE FILES MADE RELATIVELY EASY is not an exhaustive
  267. treatment of the subject, nor is it a manual on database programming.
  268. It is hoped however that the budding programmer will be able to
  269. construct an efficient database using this information.  The only sure
  270. route to programming knowledge is via experimentation and observation.
  271. Experiment with the routines described above on a scratch disk and
  272. watch what does or doesn't happen.  Don't be afraid to make a mistake.
  273. And, above all, remember the KISS philosophy:  (K)eep (I)t (S)imple,
  274. (S)tupid!
  275.  
  276.  
  277. W.J. Brier (Delphi mail: TROUBLESOME)
  278.  
  279.