![]() |
|
Published 1997-10-01 Printer-friendly version
In this article, I'm going to cover some of the theory, background and tricks concerning Clarion's VIEW technology. This is especially important, with Clarion 4's increasing dependence on this powerful tool. Before we get too deep, though, let's take a look at the big picture. As programmers we have to deal with many details. Sometimes this is necessary, but our goal should be to let the computer worry about the details, and let us simply "direct the action".
There are always situations that require hand-coded file access. This involves our remembering the files, keys, fields, relationships, etc. If we forget any of these elements, we may be lucky enough to get corrected quickly by a compiler error. Or worse, the program doesn't work properly, but we don't notice until later. Even more terrible, it may work fine for a while until the database structure is changed to accommodate a new requirement. Suddenly our code becomes invalid and the whole house of cards comes crashing down.
It's these things that we, as programmers, need to minimize. Well, VIEWs allow us to do just that. It is an abstracted file access facility that worries about the details, and changes its strategy when the underlying rules change. We can always depend on it to do the best job that it can. That doesn't mean, however, that we should be ignorant of the strength and shortfalls of VIEWs. By understanding what they do and how they work, we will be better equipped to optimize our own implementations.
If you remember back a couple of months to my OOP article, I stated that something was "abstracted" when the details and complexity were hidden and it could be perceived as a high-level entity. Although the VIEW itself is not an OOP construct, this particular concept still applies. We don't care about keys, key components, relationships, linking fields, etc. All we want is a set of entries from one or more related files, possibly served up in a specific order. Even if you are accessing only a single file, it is helpful to use a VIEW. When working with multiple related files, it is a godsend.
When you declare a VIEW, all that is required is the primary file. You can optionally request fields from that file using the PROJECT attribute. Additional related files can be added using an embedded JOIN structure. Each of those related files can have its own projected fields.
Regarding projected fields: Even though the VIEW accesses all specified files under the covers, it is only updating the PROJECTed field buffers. If you care about a particular field, make sure that you project it in the VIEW. Do this for all files, even the primary file! (Trust me on this.) If you are using a Process or Report procedure to create the View, you can PROJECT fields by adding them as "Hot Fields".
VIEWs can also have a FILTER attribute. This controls which records are retrieved by the VIEW. Whenever possible, the VIEW engine will use keys in the primary file to speed the search for matching data. This includes boolean expressions like Name='X', Salary<25000, etc. Of course keys will not help if you are searching for a substring. A filter expression can be any valid Clarion expression that would work in an IF statement. Don't forget to BIND all elements of your filter.
The other helpful feature is the ORDER attribute. This specifies the sequence of records processed by the VIEW. Again, it will use keys if possible. However, if a FILTER can use a key to narrow down the solution set, the VIEW will normally use this first. It will then sort the subset of records in memory before giving you the first record. Each element of the ORDER statement must be separated by a comma, and preceded by a plus (+) or minus (-). You can also use expressions, but don't forget to BIND all of the elements.
Here are some sample VIEW structures:
ViewCus VIEW(Customer).
ViewCusInv VIEW(Customer),FILTER('Inv:Date'),ORDER('Cus:Name')
PROJECT(Cus:Name)
JOIN(Inv:CusKey, Cus:No)
PROJECT(Inv:Date)
END
END
ViewInvCus VIEW(Invoice),FILTER('Inv:Date'),ORDER('Cus:Name')
PROJECT(Inv:Date)
JOIN(Cus:NoKey, Inv:CusNo)
PROJECT(Cus:Name)
END
END
Notice that the first VIEW is useless, as it doesn't specify which fields to retrieve with PROJECT, it doesn't narrow the record selection with a FILTER, and it doesn't control the processing sequence with ORDER. It is still, however, a valid VIEW structure (i.e.: it will compile without errors). It will even work (for non-SQL/ODBC files).
Although it may not be immediately obvious, the next two structures are functionally identical. That is, they will give you the same output. The first will be faster, assuming there is a key on customer name. The second view structure will pull the records into memory (at least the customer name and invoice date) and sort them in name order. This is one example where optimizing your VIEW structure can make a big difference.
This brings us to the concept of turning VIEWs "inside-out". In our above example, we wanted to print our records in customer name order. Since we didn't filter our records, the customer file is the obvious choice for primary file. If, however, we had decided to print a subset of records based upon invoice date, and there was a date key in the invoice file, then we may have wanted to use the invoice file as the primary file for the VIEW. This can have a huge impact on the speed of your Reports and Processes.
Almost any VIEW structure can be inverted in this fashion. Be careful, though, of the "forced primary record" rule. Clarion's VIEW structures will always give you at least one record for each record in the primary file, regardless of whether there are related records. This is why I've included the FILTER('Inv:Date') above. It tells the VIEW to process only those Customer records where there is at least one Invoice record.
This works because of a handy aspect of the VIEW engine. If there are no related records for a particular primary record, it will clear the related file's fields. By specifying that we want only entries with a non-zero Invoice date, we eliminate customers without invoices. Of course, if your invoice date could be zero, then you may want to use the invoice number instead. Use whatever works in your situation.
When there are more than two files involved, the VIEW structures can start to look different than each other. Don't worry, they will still work properly. Here is another example:
View VIEW(Customer)
JOIN(Inv:CusKey, Cus:No)
PROJECT(Inv:No)
PROJECT(Inv:Date)
JOIN(Itm:InvKey, Inv:No)
PROJECT(Itm:PrdNo)
END
END
END
View VIEW(Invoice)
JOIN(Cus:NoKey, Inv:CusNo)
PROJECT(Cus:Name)
END
JOIN(Itm:InvKey, Inv:No)
PROJECT(Itm:PrdNo)
END
END
There's something interesting that you should note here. In the above VIEWs, we are dealing with Customers, Invoice Headers and Invoice Line Items. We intuitively understand that there are many Invoices for each Customer, and many Items on each Invoice. It would be very natural for you to assume that the VIEW needs to know this too, but you would be wrong! The VIEW engine doesn't care. It just finds all related records where the linking fields are the same. If there is only one record, fine. If there are thousands, that's still fine. If there are none, then that's fine too.
The only thing that you must realize is that it cannot do multi-branch retrieval. For example, you cannot process all the Calls for a Customer and all the Sales for a Customer using the same VIEW. Instead, you must create two VIEWs to handle the situation, and combine the results manually.
Let's look at a coding example. We want to find all customers without any sales. Our hand-code might look something like this:
SET(Customer)
LOOP
NEXT(Customer)
IF ERRORCODE() THEN BREAK.
CLEAR(Inv:Record,-1) !**
Inv:CusNo = Cus:No !**
SET(Inv:CusKey, Inv:CusKey) !**
NEXT(Invoice)
IF ERRORCODE() OR Inv:CusNo <> Cus:No !**
Count# += 1
END
END
The four lines marked with !** require an intimate understanding of the file structure. Also, this code could easily be broken by changing the key definition. Let's look at the same example using a VIEW:
View VIEW(Customer), FILTER(Inv:Date=0)
JOIN(Inv:CusKey, Cus:No)
PROJECT(Inv:Date)
END
END
CODE
OPEN(View)
LOOP
NEXT(View); IF ERRORCODE() THEN BREAK.
Count# += 1
END
CLOSE(View)
This code is much cleaner. Even the VIEW structure contains fewer details about the files. (The engine still has an intimate knowledge of what's going on.) In most cases you can arrange for the VIEW structure to be written by a template (more about this later).
Let's just recap quickly: a VIEW enables us to process one or more related files, restricting the records with a FILTER and sorting them in ORDER. The VIEW engine optimizes the search to the best of its ability, and we can optimize this via our choice of the primary file.
The ViewManager is a CLASS in Clarion 4's new Application Builder Classes (ABC). It wraps itself around a VIEW, allowing much more flexibility. It is used by the Process and Browse procedure templates to perform all file access. (All of that code that was written into every Process and Browse procedure is now hidden within the ViewManager.) Here's where the class sits with the other ABC classes:

Let's look at each of these very briefly to understand their basic purpose.
The ViewManager handles multiple sorts (think of the tabs on a Browse window). Each of these sorts has an ORDER clause, and optional FILTER clauses. That right - you have more than one filter! These are combined based upon their specified priority. Higher priority filters are likely to help with key selection (like range selectors).
The ViewManager class has a virtual function called ValidateRecord. This allows you to have your own hand-coded record restrictions. After a record has made it through the regular FILTER expression, your virtual function gets to perform a final check on it.
To use the ViewManager, you must define your VIEW, instantiate the class, initialize the object, and add one or more sort orders. Each of these sorts can have one or more order field segments, and zero or more prioritized filter segments. After all of this is done, you can get down to the work of actually using the VIEW to process records.
Now you're likely thinking that using the ViewManager class is too hard. Well, if you're hand-coding, then you're right! Here's where templates can save the day. The templates can ask us a few simple questions, then handle the complex interface with the class. Clarion has the best of both worlds: the benefits of OOP coupled with the simplicity of templates.
This is an extension template that I wrote to simplify my VIEW usage. I describe the desired VIEW in much the same way as you would for a Process. I had a couple of extra requirements like additional field pairs and an optional ORDER clause. Finally, I was intending to access the VIEW from my own hand-code.
This extension may be added to any procedure, including a Browse, Process, Report, Window or Source. (If you are using it in a Source procedure, don't forget to include the mhSourceFiles extension too.)
The file schematic is specified in the same way as all other templates in the Files window. I can change the default name from "View", in case there is more than one instance used in the same procedure. I can specify a filter, Range limits, and additional field pairs with multiple comparison operators (e.g.: Pre:Field=Loc:Field, Pre:Field<Loc:Field, etc.). My goal for coding was to use the following algorithm:
For example:
DO OpenView LOOP DO NextView IF ERRORCODE() THEN BREAK. !Do something END DO CloseView
If I wanted to check whether a record existed, the code might look like this:
DO OpenView DO NextView Exists# = CHOOSE(NOT ERRORCODE(), True, False) DO CloseView
As you can see, this code is very simple, and very abstract. It doesn't contain any details of the file structures, and it doesn't need them. The VIEW will handle it for me. Its structure is written by the template, which is driven by the dictionary. If the dictionary changes, so does the VIEW. This makes for very dependable code.
If you are interested in this template, it's free! Go www.BoxsoftDevelopment.com, and look for MHTPL???.ZIP in the Download section. As of October 1, 1997, it doesn't support the new ABC templates. (I just got C4 Beta 2, and I haven't been able to get the template moved over yet.) Look for new versions of the template in the coming weeks and months.
I hope this has given you a better understanding and comfort level with Clarion's VIEW technology. It is a powerful tool that enables you perform minor miracles, while not worrying about the details of keys, components, relationships, etc. It also means that your code will age well, which will give you fewer headaches down the road.
It seems that each Clarion Developers Conference generates a "pet phrase" that is continually bandied about the sessions. In Arizona 1996, it was "Does it do page numbers?" In Florida 1997, it was "You don't know, and you don't care!" The bottom line is that you can depend on VIEWs to get the job done. If you don't optimize them correctly they'll be slow, but they'll still work. The same claim cannot be made for hand-code.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $189
(includes all back issues since '99)
Renewals from $139
Two years: $289
Renewals from $239