46
O ctober 1996 OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait, OUTER JOINs are finally supported in Visual FoxPro 5.0! This article discusses the syntax and techniques to employ this powerful new feature. For years, one of my most frequent complaints about the SQL implementation in FoxPro has been its lack of support for OUTER JOINs. I have really come to appreciate Jerry Ela's article, "Stalking the Outer Join," in the September 1992 issue of FoxTalk for e xplaining how to simulate an OUTER JOIN in FoxPro 2.0. (Thanks, Jerry, wherever you are!) I'd grab this article off the shelf several times a year, having memorized its location. Eventually I memorized the technique (I'm a slow learner), and it was a red-l etter day when I successfully created (without referring to Jerry's article!) a three-table OUTER JOIN in the Command window, and it ran correctly the first time! This technique involved performing multiple queries, or use of subqueries, and performing a U NION of the results (yuck!). It worked, but it wasn't pretty. Now, with the release of Visual FoxPro 5.0, Microsoft has expanded its SQL implementation to include OUTER JOINs. What is an Outer Join? When performing a simple two-table query in versions of FoxPro prior to Visual FoxPro version 5.0, the type of join between the two tables is called a "natural" or "inner" join. If you apply the ideas of set theory to the operation, you're producing a resu lt set that is an intersection of two tables. This can be represented by a Venn diagram, as shown in Figure 1 . Thus, the result set for an INNER JOIN ignores any record from either table that doesn't "join," that is, that doesn't satisfy the join condition specified in the WHERE clause of the SELECT statement. In other words, parent records without children and chi ld records without a corresponding parent are not included in an INNER JOIN. Prior to the release of Visual FoxPro 5.0, the syntax for the simplest example of a two-table "natural" or INNER JOIN was accomplished as shown in the following pseudo-code: SELEC T <field list> ; FROM <table1>, <table2> ; WHERE <table1.fieldName> = <table2.fieldName> However, many times you want to produce a result set that not only includes the records from one (or both) of the tables that satisfy the join condition but also from records that do not satisfy the join condition. OUTER JOINs allow us to include records t hat do not meet the join

OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

  • Upload
    others

  • View
    21

  • Download
    0

Embed Size (px)

Citation preview

Page 1: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

October 1996OUTER JOINs in Visual FoxPro 5.0Stephen A. Sawyer (1)

After a long wait, OUTER JOINs are finally supported in Visual FoxPro 5.0! This article discussesthe syntax and techniques to employ this powerful new feature.

For years, one of my most frequent complaints about the SQL implementation in FoxPro hasbeen its lack of support for OUTER JOINs. I have really come to appreciate Jerry Ela's article,"Stalking the Outer Join," in the September 1992 issue of FoxTalk for explaining how tosimulate an OUTER JOIN in FoxPro 2.0. (Thanks, Jerry, wherever you are!) I'd grab this articleoff the shelf several times a year, having memorized its location. Eventually I memorized thetechnique (I'm a slow learner), and it was a red-letter day when I successfully created (withoutreferring to Jerry's article!) a three-table OUTER JOIN in the Command window, and it rancorrectly the first time! This technique involved performing multiple queries, or use ofsubqueries, and performing a UNION of the results (yuck!). It worked, but it wasn't pretty.

Now, with the release of Visual FoxPro 5.0, Microsoft has expanded its SQL implementation toinclude OUTER JOINs.

What is an Outer Join?

When performing a simple two-table query in versions of FoxPro prior to Visual FoxPro version5.0, the type of join between the two tables is called a "natural" or "inner" join. If you apply theideas of set theory to the operation, you're producing a result set that is an intersection of twotables. This can be represented by a Venn diagram, as shown in Figure 1.

Thus, the result set for an INNER JOIN ignores any record from either table that doesn't "join,"that is, that doesn't satisfy the join condition specified in the WHERE clause of the SELECTstatement. In other words, parent records without children and child records without acorresponding parent are not included in an INNER JOIN. Prior to the release of Visual FoxPro5.0, the syntax for the simplest example of a two-table "natural" or INNER JOIN wasaccomplished as shown in the following pseudo-code:

SELECT <field list> ; FROM <table1>, <table2> ; WHERE <table1.fieldName> = <table2.fieldName>

However, many times you want to produce a result set that not only includes the records fromone (or both) of the tables that satisfy the join condition but also from records that do not satisfythe join condition. OUTER JOINs allow us to include records that do not meet the join

Page 2: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

condition.

For example, if you look at the sample data that ships with Visual FoxPro (the TESTDATAdatabase located in the \VFP\SAMPLES\DATA subdirectory), there is an EMPLOYEE tablethat can be joined to the ORDERS table (using the Emp_ID field), allowing a query to reportorder information related to the employee who wrote the order. If a new employee hadn't yetwritten an order, such a query wouldn't include that employee in the result set. It may bedesirable, simply for the sake of good order, to report on all employees and their activities,without having to make inferences about the absence of an employee's name from such a report.This requirement makes a natural or "inner" join inadequate to report this information becauseunless the employee has written at least one order, there is no link between the employee and theORDERS table.

LEFT, RIGHT -- Is that my RIGHT or your RIGHT?

When constructing an OUTER JOIN on two tables, it's not sufficient to simply call it an"OUTER JOIN," as the non-matching information could come from one table, the other, or both.Hence, there are three types of OUTER JOINs: a LEFT OUTER JOIN, a RIGHT OUTER JOIN,and a FULL JOIN.

In Venn diagrams like the ones shown in Figure 2a, Figure 2b, and Figure 2c, it's easy tovisualize the records involved in a RIGHT, LEFT or FULL join.

A LEFT OUTER JOIN as illustrated in would include all records from the CUSTOMER table(on the left) and only the matching records from the ORDERS table (on the right). illustrates aRIGHT OUTER JOIN whose result set will include all of the records from the PRODUCTStable (on the right) and only the matching records from the ORDITEMS table on the left.illustrates a FULL OUTER JOIN, in which all records from both the REPS table and theCUSTOMER table are returned in the result set, without regard to whether they meet the joincondition.

The two tables diagrammed in (REPS and CUSTOMER) are included in the accompanyingDownload file. Their contents and structures are shown in Table 1a and Table 1b.

Table 1a. CUSTOMER table.

CCOMPANY CREP_IDMegaCorp 000004Momenpop Int'l 000000MicroCorp 000000Big Spaceships, Inc. 000005Little Scooters, Ltd. 000000General Products 000006

Table 1b. REPS table.

Page 3: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

CREPNAME CREP_IDLarry 000001Moe 000002Curly 000003Bullwinkle 000004Rocky 000005Droopy 000006

The situation represented by these tables is one in which Larry, Moe, and Curly are in-housesales reps. They aren't assigned to specific accounts; rather, they are responsible for servicing the"house" accounts. "House" accounts don't have permanently assigned sales representatives.Bullwinkle, Rocky, and Droopy, on the other hand, are the only representatives who servicecertain large accounts.

The following query illustrates a FULL join:

SELECT customer.cCompany, ; reps.cRepname ; FROM reps ; FULL OUTER JOIN customer ; ON reps.cRep_ID = customer.cRep_ID

Table 2 illustrates the resulting appearance of data that is returned from "non-matching" recordsin the result set.

Table 2. Result set.

CCOMPANY CREPNAME.NULL. Larry.NULL. Moe.NULL. CurlyMegaCorp BullwinkleBig Spaceships, Inc. RockyGeneral Products DroopyMomenpop Int'l .NULL.MicroCorp .NULL.Little Scooters Ltd .NULL.

The SQL SELECT statement requests the contents of the cCompany field from the CUSTOMERtable. When there is no record in CUSTOMER that matches a record in the REPS table, thequery returns a value of .NULL. for the requested field. Likewise, the three house accounts haveno full time sales rep assigned; hence, the appearance of .NULL. for the cRepName field forthose customer records. .NULL. values can be seen as "placeholders" that allow the display of

Page 4: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

records from the table on the OUTER side of a join.

While .NULL. may mean a lot to you, it won't mean much to your clients and users. You canclean this up a bit by modifying your query to use Visual FoxPro's NVL() function:

SELECT NVL(customer.cCompany,"House Accounts") ; AS Company, ; NVL(reps.cRepname,"House") ; AS Representative ; FROM reps ; FULL OUTER JOIN customer ; ON reps.cRep_ID = customer.cRep_ID

The result set is shown in Table 3.

Table 3. Result set.

Company RepresentativeHouse Accounts LarryHouse Accounts MoeHouse Accounts CurlyMegaCorp BullwinkleBig Spaceships, Inc. RockyGeneral Products DroopyMomenpop Int'l HouseMicroCorp HouseLittle Scooters Ltd House

The ease with which you can now perform these kinds of queries has important implications foryour database designs. In this example, it's convenient to have a Rep ID for house accounts("000000") and still have Rep IDs for individual inside sales representatives! Without easyaccess to OUTER JOINs, you'd be likely to create a "House Account" customer record, have thesame Rep ID for each inside sales representative, have two tables for sales reps (one for inside,one for outside sales), overload the sales rep table by including an inside/direct field, by havingtwo Rep ID fields -- one for inside, one for direct sales representatives -- or some combination ofseveral of these techniques.

The New FROM/JOIN syntax in Visual FoxPro 5.0

In versions of FoxPro prior to version 5.0, the join conditions were specified in the WHEREclause. This caused confusion for some, particularly those who were first learning FoxPro's SQLimplementation, as this is also where (no pun intended) the filter conditions were placed.

Although VFP 5.0 still supports specifying join conditions in the WHERE clause (backwardcompatibility, remember?), it's a good idea to ensure that all newly written queries make use ofthe new extensions to the FROM clause, reserving the WHERE clause to its proper role of

Page 5: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

filtering the results. To take advantage of OUTER JOINs, you need to adopt the new syntax.

The new syntax is seen in the FROM clause. Up to now, the FROM clause has simply included acomma-delimited list of tables, and aliases if needed, following the FROM keyword. All thesetables are opened, and then both joined and filtered in the WHERE clause. In versions of FoxProthrough 3.0b, the minimum executable two-table SQL statement was as follows:

SELECT <field list> FROM table1,table2 ; WHERE table1.field = table2.Field

If you need a Cartesian product (where every record in table1 is matched with every record intable2) or you aren't concerned about running out of disk space, you could even eliminate theWHERE clause.

Here's the equivalent minimalist SQL statement using the new syntax:

SELECT <field list> FROM table1 JOIN table2 ; ON table1.field = table2.field

To produce a Cartesian product you'd eliminate the ON clause:

SELECT <field list> FROM table1 JOIN table2

Thus in normal circumstances (when you're not looking for a Cartesian product) when you'requerying multiple tables, there is one FROM keyword, one JOIN keyword for each table afterthe first (which follows the FROM keyword), and one ON keyword and join expression for eachJOIN keyword.

Some of the join-type specifiers are optional, as shown in Table 4.

Table 4. Equivalent JOIN keywords.

Keyword Is Equivalent toJOIN INNER JOINRIGHT JOIN RIGHT OUTER JOINLEFT JOIN LEFT OUTER JOINFULL JOIN FULL OUTER JOIN

As of this writing (mid-July 1996), the documentation for the FROM clause in a SQL statementreads as follows:

Page 6: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

FROM [DatabaseName!]Table [Local_Alias][[INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER] JOIN][, [DatabaseName!]Table [Local_Alias] [[INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER] JOIN] ...] [ON [DatabaseName!]Table [Local_Alias] .Column_Name =[DatabaseName!]Table [Local_Alias] .Column_Name]

Perfectly clear, right?

To make matters worse, the Query Designer makes use of a rather counter-intuitive structurethat, according to Microsoft, complies both with the ANSI SQL '92 standard and producesqueries in the form that ODBC expects. My advice in the past has been to rely heavily on theQuery Designer when learning to write SQL statements, but after getting the hang of it, theQuery Designer can be abandoned in favor of the greater flexibility of "hand coded" queries.Assuming that some current problems with the Query Designer are resolved in time for theproduct's release, there may be some advantage to hand coding some queries as you learn howthe new syntax behaves.

Here is a simplified (if less rigorous) example of the structure that you find in a three-table querygenerated by the Query Designer:

SELECT table1.FieldName, ; table2.FieldName, ; table3.FieldName, ; FROM table3 ; JOIN table2 ; JOIN table1 ; ON table1.FieldName = table2.FieldName ; ON table3.FieldName = table2.FieldName

I'll refer to this as the ANSI '92 or "nested" structure (note how the ON clauses and the JOINclauses match up in a "nested" pattern).

At this point, another alternative structure that works consistently and accurately is a FROMclause structure that I call "sequential." I'd like to mention here that Chin Bae went down thisroad before me, and discovered that this structure works, and (as you can see) is much moreintuitive. Chin suggested the following rewrite of the syntax, which illustrates this alternativestructure:

Page 7: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

FROM [DataBase!]Table [LocalAlias] [[INNER|LEFT [OUTER]|RIGHT [OUTER]|FULL [OUTER] JOIN DataBase!]Table [LocalAlias] ON [DataBase!]Table|[LocalAlias].ColumnName = [DataBase!]Table|[LocalAlias].ColumnName] ...] [, [DataBase!]Table [LocalAlias] [[INNER|LEFT [OUTER]|RIGHT [OUTER]|FULL [OUTER] JOIN [DataBase!]Table [LocalAlias] ON [DataBase!]Table|[LocalAlias].ColumnName = [DataBase!]Table|[LocalAlias].ColumnName] ...] ...]

The way this translates into actual use, using this "pseudocode" example is much clearer:

SELECT table1.FieldName, ; table2.FieldName, ; table3.FieldName, ; FROM table1 ; JOIN table2 ; ON table2.FieldName = table1.FieldName ; JOIN table3 ; ON table3.FieldName = table2.FieldName

When first working with this new syntax, I think that this structure is much more intuitive thanthe "nested" structure. I encourage using this "sequential" structure at first, then adopting theapparently "standard" ANSI '92 structure once you're comfortable with its behaviors.

Keep the following points in mind when you construct your FROM clause and join conditions:

• The JOIN keyword is the reference point for RIGHT OUTER and LEFT OUTER JOINs.A RIGHT OUTER JOIN will return all records from the table to the RIGHT of the JOINkeyword, and a LEFT OUTER JOIN will return all records from the table to the LEFT ofthe JOIN keyword. This is more evident in the ANSI '92 standard structure, in whichJOIN separates table names, without intervening ON clauses.

• The JOIN keyword has the effect of opening the table that immediately follows it, just asthe FROM keyword opens the table (or tables) immediately following. You can'treference a table in the ON clause until it has been opened. Thus, the following rewrite ofthe previous query will trigger an error, "SQL: Column <fieldName> is not found":

Page 8: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT table1.FieldName, ; table2.FieldName, ; table3.FieldName, ; FROM table1 ; JOIN table3 ; ON table3.FieldName = table2.FieldName ; JOIN table2 ; ON table2.FieldName = table1.FieldName

This error occurs because the SQL command is evaluated from left to right, and the first ONclause refers to table2, which isn't opened until the second JOIN is evaluated.

• Each JOINON clause creates an intermediate result, which is then used by any otherJOINON clauses that the query may include.

How does the new syntax work?

The most important thing to remember in understanding how to apply the new syntax is the lastpoint I made in the preceding section: each JOINON clause creates an intermediate result.

To understand this, consider the pseudocode example I used earlier. Here I've rewritten it, butonly to change the indenting and the location of the line-continuation characters (;):

ANSI '92/Nested structure

SELECT table1.FieldName, ; table2.FieldName, ; table3.FieldName, ; FROM ; table3 JOIN ; table1 JOIN table2 ; ON table1.FieldName = table2.FieldName ON table3.FieldName = table2.FieldName

"Sequential" structure

SELECT table1.FieldName, ; table2.FieldName, ; table3.FieldName, ; FROM table1 JOIN table2 ; ON table2.FieldName = table1.FieldName ; JOIN table3 ; ON table3.FieldName = table2.FieldName

At first glance, a subtle point made in these two examples might pass unnoticed. Because eachJOINON clause will create an intermediate result, and the command is evaluated fromleft-to-right, the order in which the JOINON clauses appear is critical to getting expected (andcorrect) results! If you examine the previous "sequential" structured query, as the command is

Page 9: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

evaluated, you'll see that the first pair of tables linked by a JOINON clause is table1 and table2.This JOIN produces an intermediate result, which is then JOINed with table3 in the nextJOINON clause, using the condition table3.FieldName = table2.fieldName.

Compare this to the ANSI '92/Nested structure. As that command is evaluated from left-to-right,the first JOIN <table> clause is followed by another JOIN, rather than an ON clause. Thereforethe intermediate results of the second or innermost join must be evaluated first. The first joinevaluated is as follows, just as it is in the sequential structure:

table1 JOIN table2 ON table2.fieldName = table1.fieldName

The effect is for Visual FoxPro to evaluate the FROM expression from the "inside out,"producing an intermediate result from the innermost JOINON clause, then applying the nextoutermost JOIN to that intermediate result set. It's possible to get the same result set from eitherstructure, but the order in which the tables are named with their associated JOIN clauses andawareness of the intermediate result sets is critical to getting the result sets you're looking for.

This is a surprise to those of us familiar with SQL syntax in versions of FoxPro through 3.0b,where the order of the tables named in the FROM clause and the order of join conditions in theWHERE clause had no effect on the result set.

Because this is such a new way to approach SQL commands, I'll include two examples for eachquery (where applicable) in the rest of this article, including the more intuitive "sequential"structure and the less intuitive, but more orthodox structure that adheres to the ANSI SQL '92standard. I'm finding it to be both a challenge and a way to sharpen my skills in using the newSQL syntax to write all queries using both structures.

The formatting you will see me use, particularly for the ANSI '92 structure, is designed to helpconceptualize exactly what is happening:

FROM ;<table1> JOIN ;<table2> JOIN <table3> ;ONON

What the foregoing is trying to show is that the innermost expression (which is evaluated first)JOINs table2 and table3, and then table1 is joined to this intermediate set.

One last, but very important point to make regarding the new FROMJOINON syntax is that thetwo structures I describe here are not mutually exclusive. It's not a matter of "either/or," as thetwo structures can be combined in the same query, as shown in this bit of pseudocode:

Page 10: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT <stuff> ; FROM <table1> JOIN <table2> ; ON <condition> JOIN <table3> ; JOIN <table4> ; ON <condition> ; ON <condition3>

Here the join between table1 and table2 is made first, and the intermediate result is joined withthe result of joining table3 and table4.

Use of parentheses (which are allowed in Visual FoxPro 5.0 queries, simply as aself-documenting notation) or a slightly different way of formatting the query can also help tounderstand how the query is executed:

SELECT <stuff> FROM ; (<table1> JOIN <table2> ON <condition>) ; JOIN ; (<table3> JOIN <table4> ON <condition>) ; ON <condition>

My thanks to Matt Peirse on the Beta forum for getting me to see this!

Application of OUTER JOINs

Let's take a look at a fairly straightforward query that provides information without making useof OUTER JOINs. Drawing on the TESTDATA database that ships with Visual FoxPro, thefollowing query generates a result set that shows each product in the PRODUCTS table and thetotal units sold for each product:

SELECT SUM(orditems.quantity) AS purchased, ; products.product_id, ; products.eng_name ; FROM testdata!orditems ; JOIN testdata!products ; ON orditems.product_id = products.product_id ; GROUP BY products.product_id ; ORDER BY products.product_id

When you run this query, you'll see that there have been purchases for every product. As a result,you can modify the query as follows to do a RIGHT OUTER JOIN between the ORDITEMStable and the PRODUCTS table:

Page 11: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT SUM(orditems.quantity) AS purchased, ; products.product_id, ; products.eng_name ; FROM testdata!orditems ; RIGHT OUTER JOIN testdata!products ; ON orditems.product_id = products.product_id ; GROUP BY products.product_id ; ORDER BY products.product_id

The results are identical! This is because in the join between the ORDITEMS table andPRODUCTS table, for every record in each table, there exists a matching record in the table towhich it is joined. In set terminology, the intersection is equal to the union. Therefore, anOUTER JOIN and an INNER JOIN will both yield the same result set. (See Figure 3.)

To properly see the effect of this OUTER JOIN, let's assume for the moment that one of theproducts is a new product offering and that your query will show how sales are running for theentire product line, including products that may show no sales to date. To simulate this situation,open the ORDITEMS table and issue the following commands in the Command window:

DELETE FOR Product_ID = " 60"SET DELETED ON

Now when you run the query, the "Purchased" column will show .NULL for Pierrot Camembert(the English name for product_id 60).

To look at something a bit more complex, you can create a query that reports product sales bycustomer:

SELECT customer.company, ; SUM(orditems.quantity) AS purchased ; products.product_id, ; products.eng_name ; FROM testdata!customer ; JOIN testdata!orders ; ON customer.cust_id = orders.cust_id, ; JOIN testdata!orditems ; ON orders.order_id = orditems.order_id ; JOIN testdata!products ; ON orditems.product_id = products.product_id ; GROUP BY customer.cust_id, products.product_id ; ORDER BY customer.company, products.product_id

Here is the same query, but conforming to the ANSI '92 standard:

Page 12: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT customer.company, ; SUM(orditems.quantity) AS purchased ; products.product_id, ; products.eng_name ; FROM ; testdata!products JOIN ; testdata!orditems JOIN ; testdata!orders JOIN testdata!customer ; ON customer.cust_id = orders.cust_id ; ON orditems.order_id = orders.order_id ; ON orditems.product_id = products.product_id ; GROUP BY customer.cust_id, products.product_id ; ORDER BY customer.company, products.product_id

If you examine the product_id numbers for an individual customer, the list is discontinuous --that is, some of the 77 products are missing. Obviously, this is because not all products arepurchased by all customers. Rather than inferring which products are not being purchased by agiven customer (after all, not all product lines have a conveniently continuous sequence ofproduct IDs!), it would be helpful to explicitly list all products and show their sales as .NULL.(or 0) if no sales of that product have been made to a particular customer.

You could try to introduce the OUTER JOIN, requesting all records from the PRODUCTS table:

SELECT customer.company, ; SUM(orditems.quantity) AS purchased ; products.product_id, ; products.eng_name ;FROM testdata!customer ; INNER JOIN testdata!orders ; ON customer.cust_id = orders.cust_id ; INNER JOIN testdata!orditems ; ON orders.order_id = orditems.order_id ; RIGHT OUTER JOIN testdata!products ; ON orditems.product_id = products.product_id ; GROUP BY customer.cust_id, products.product_id ; ORDER BY customer.cust_id, products.product_id

Here's the equivalent query using the ANSI '92 compliant syntax. This rewrite requires that youuse a LEFT OUTER JOIN because the PRODUCTS table is now to the left of the JOINkeyword:

Page 13: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT customer.company, ; SUM(orditems.quantity) AS pruchased, ; products.product_id, ; products.eng_name ; FROM ; testdata!products LEFT OUTER JOIN ; testdata!orditems JOIN ; testdata!orders JOIN testdata!customer ; ON customer.cust_id = orders.cust_id ; ON orditems.order_id = orders.order_id ; ON orditems.product_id = products.product_id ; GROUP BY customer.cust_id, products.product_id ; ORDER BY customer.company, products.product_id

Running this query yields almost the same result set as the prior example, which did not makeuse of the OUTER JOIN! A clue to the reason that you're getting this result is that a record forProduct_ID 60, Pierrot Camembert, now appears as the first record (see Table 5).

Your OUTER JOIN joins the ORDITEMS table and the PRODUCTS table. Because allPRODUCTS records have at least one match in the ORDITEMS table, again, the OUTER JOINis equivalent to the INNER JOIN except for Product_ID 60. Thus, the only additional record thatthe OUTER JOIN placed in the result set is the only product that hasn't been purchased by anycustomer.

Table 5. An OUTER JOIN adds a record to the result set.

Company Cust_id Product_id Eng_name Purchased.NULL. .NULL. 60 Pierrot Camembert .NULL.

To get the results you're seeking -- that is, a result set that shows the entire product line alongwith a particular customer's total purchases of each product -- you can rewrite the query asfollows:

SELECT customer.company, ; SUM(orditems.quantity) AS purchased, ; products.product_id, ; products.eng_name ;FROM testdata!customer ; INNER JOIN testdata!orders ; ON customer.cust_id = orders.cust_id ; INNER JOIN testdata!orditems ; ON orders.order_id = orditems.order_id ; RIGHT OUTER JOIN testdata!products ; ON orditems.product_id = products.product_id ; WHERE customer.cust_id = "BOTTM" ; GROUP BY products.product_id ; ORDER BY products.product_id

Page 14: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Or using the ANSI SQL '92 structure, rewrite it as follows:

SELECT customer.company, ; SUM(orditems.quantity) AS pruchased, ; products.product_id, ; products.eng_name ; FROM ; testdata!products LEFT OUTER JOIN ; testdata!orditems JOIN ; testdata!orders JOIN testdata!customer ; ON customer.cust_id = orders.cust_id ; ON orditems.order_id = orders.order_id ; ON orditems.product_id = products.product_id ; WHERE customer.cust_id = "BOTTM" ; GROUP BY products.product_id ; ORDER BY products.product_id

This query makes two changes to the previous query. First, you filter the results to examine anindividual customer (in the WHERE clause) and, second, you no longer group or order by thecustomer, since you're examining one customer at a time. Because you're filtering the result setto include only those ORDITEMS records that apply to a particular customer (Bottom-DollarMarkets), you get the results you're looking for, a portion of which is shown in Table 6.

Table 6. A result set using an OUTER JOIN and WHERE filter.

Company Product_id Eng_name PurchasedBottom-Dollar Markets 1 Dharamsala Tea 60.000Bottom-Dollar Markets 2 Tibetan Barley Beer 30.000Bottom-Dollar Markets 3 Licorice Syrup 20.000.NULL. 4 Chef Anton's

Cajun Seasoning.NULL.

.NULL. 5 Chef Anton's Gumbo Mix .NULL.Bottom-Dollar Markets 6 Grandma's

Boysenberry Spread12.000

Bottom-Dollar Markets 7 Uncle Bob's Organic Dried Pears

20.000

Bottom-Dollar Markets 8 Northwoods Cranberry Sauce

16.000

.NULL. 9 Mishi Kobe Beef .NULL

To "clean up" the result set, you can again make use of the NVL() function to substitutemeaningful values for the .NULL. values that the OUTER JOIN produces. A sample of the resultset can be seen in Table 7.

Page 15: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT NVL(customer.company,"Bottom-Dollar Markets") ;AS company, ; products.product_id, ; products.eng_name, ; NVL(SUM(orditems.quantity),0000) AS purchased ; Etc

Table 7. Result set with .NULL. values replaced.

Company Product_id Eng_name PurchasedBottom-Dollar Markets 1 Dharamsala Tea 60Bottom-Dollar Markets 2 Tibetan Barley Beer 30Bottom-Dollar Markets 3 Licorice Syrup 20Bottom-Dollar Markets 4 Chef Anton's

Cajun Seasoning0

Bottom-Dollar Markets 5 Chef Anton's Gumbo Mix 0Bottom-Dollar Markets 6 Grandma's

Boysenberry Spread12

Bottom-Dollar Markets 7 Uncle Bob's Organic Dried Pears

20

Bottom-Dollar Markets 8 Northwoods Cranberry Sauce

16

Bottom-Dollar Markets 9 Mishi Kobe Beef 0

My friend Anders Altberg demonstrated how to significantly improve the performance of eventhis simple query by removing the filter (WHERE) condition, including this condition as part ofthe JOIN condition on the CUSTOMER table instead:

SELECT customer.company, ; SUM(orditems.quantity) AS pruchased, ; products.product_id, ; products.eng_name ; FROM ; testdata!products LEFT OUTER JOIN ; testdata!orditems JOIN ; testdata!orders JOIN testdata!customer ; ON customer.cust_id = orders.cust_id AND ; customer.cust_id = "BOTTM"; ON orditems.order_id = orders.order_id ; ON orditems.product_id = products.product_id ; GROUP BY products.product_id ; ORDER BY products.product_id

In my testing on a 486/66 with 16M, running Visual FoxPro 5.0 on Windows NT 3.51, the queryran in the neighborhood of 2.7 seconds when placing the filter condition in the WHERE clause,but just under a second when the filter condition was included as a JOIN condition instead!

Page 16: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Anders explained that this is because the filter condition gets evaluated only for that intermediateset, not for all records in all intermediate sets.

Before moving on, I'll share with you the query sent to me by Matt Peirse, which successfullygenerates a result set that succeeds where one of my earlier queries failed. It shows allcustomers, their total purchases of each product, and .NULL.s for the products that a particularcustomer hasn't purchased:

SELECT company, ; SUM(quantity) as purchased, ; products.product_id, ; products.eng_name ; FROM ; customer JOIN products on .t.; LEFT JOIN ; orders JOIN orditems ; ON orders.order_id = orditems.order_id) ; ON customer.cust_id=orders.cust_id ; AND products.product_id=orditems.product_id ; ORDER BY company,products.product_id ; GROUP BY Company,products.product_id

This query combines (as I described earlier) the "sequential" and "nested" syntax, and makes aninteresting use of a Cartesian product in the first JOIN between CUSTOMER and PRODUCTS.If you examine this query carefully, and can explain how it works, you'll be in good shape inapplying the new syntax in your own work.

The last example using OUTER JOINs illustrates the use of local aliases with the new JOINsyntax. Local aliases are used whenever a single table must be opened more than once to allowJOINs to be established to use two different join conditions.

The data for this example comes from our company's line of business. Our product line includesalmost 1,000 part numbers. It's often necessary to interchange one of our part numbers to acompetitor's part number, or vice versa. To permit this, our system includes an interchange tablewith the structure shown in Table 8a. A sample of the INTERCHG.DBF data in theaccompanying Download file appears in Table 8b.

Table 8a. Structure for table INTERCHG.DBF.

Field Field Name Type Width Description1 CCOMP_ID Character 6 The competitor's ID - Foreign Key2 CPART Character 6 Our part number - Foreign Key3 CCOMPPART Character 10 The competitor's part number -

Foreign Key

Table 8b. Sample of data in INTERCHG.DBF.

Page 17: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Ccomp_id Cpart Ccomppart000000 6363 18-4611000000 6365 18-4612000000 6364 18-461300000A 6300 424300000A 6301 424400000A 6302 429100000A 6303 429200000A 6306 423100000A 6307 4232

The cComp_ID field allows the table to be related into a file with competitor information. Table9a shows the structure, and Table 9b the contents of this table.

Table 9a. Structure for table COMPETIT.DBF.

Field Field Name Type Width Description1 CCOMP_ID Character 6 The competitor's ID - Primary Key2 CCOMPET Character 15 The competitor's name

Table 9b. Data contained in COMPETIT.DBF.

Ccomp_id Ccompet000000 ABC Company00000A PQR Company00000F XYZ Company

The cPart field in the INTERCHG table allows the table to be joined into our product table,OURLINE, with the structure shown in Table 10a, and a sample of its contents is illustrated inTable 10b.

Table 10a. Structure for table OURLINE.DBF.

Field Field Name Type Width Dec Description1 CPART Character 6 Our part number - Primary

Key2 CAPPLIC Character 60 Vehicle application3 ION_HAND Integer 4 Qty. on hand4 YJOBBER Currency 8 4 Jobber price5 YCORE Currency 8 4 Core value6 CPOPCODE Character 3 Popularity code

Table 10b. Sample of data in OURLINE.DBF.

Page 18: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Cpart Capplic6300 Mustang, T-Bird Turbo Coupe 1987-896301 Mustang, T-Bird Turbo Coupe 1987-896302 Jeep Cherokee, Comanche, Wagoneer, Wrangler6303 Jeep Cherokee, Comanche, Wagoneer, Wrangler6304 Century, Ciera 91-95, Mini-Van 91, "C' Bodies6305 Century, Ciera 91-95, Mini-Van 91, "C' Bodies6306 Regal, Cutlass, Grand Prix 886307 Regal, Cutlass, Grand Prix 886308 Thunderbird / Cougar 89-916309 Thunderbird / Cougar 89-916310 Caravan, Voyager, Chry Lebaron 89-906311 Caravan, Voyager, Chry Lebaron 89-906314 Caravan, Voyager w/Long W.B. 87-896315 Caravan, Voyager w/Long W.B. 87-896316 Shadow, Sundance Rear 886317 Shadow, Sundance Rear 886318 Chry Lebaron, Daytona Rear 89-906319 Chry Lebaron, Daytona Rear 89-906320 Chry Lebaron Rear 89-906321 Chry Lebaron Rear 89-90

Printed reports of interchange information have, in the past, presented some difficulties. First, inorder to provide a report that shows which of our part numbers have not yet been interchanged tothe competitor's number, it was necessary to jump through the hoops I mentioned earlier tosimulate an OUTER JOIN.

This was complicated further by the frequent need to provide interchange information for morethan one competitor in the report. Now, with FoxPro support of OUTER JOINs, this task can beaccomplished with a single, simple query.

In the following SELECT statement, note that because we want to report interchangeinformation for two competitors, the INTERCHG table is opened twice, once with an alias of"ABC" and again with an alias of "XYZ." The LEFT OUTER JOINs ensure that we retrieve allrecords from the OURLINE table, without regard to whether or not we've interchanged that partfor a particular competitor, and the query is filtered on the competitor ID number for only thetwo competitors that are of immediate interest. The SELECT command also makes use of theNVL function to present a string of dashes instead of a .NULL. value:

Page 19: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

SELECT ourline.cpart AS Our_No, ; NVL(abc.ccomppart,"-----") AS ABC_No, ; NVL(xyz.ccomppart,"-----") AS XYZ_No ; FROM ourline ; LEFT OUTER JOIN interchg ABC ; ON ourline.cpart = ABC.cpart ; LEFT OUTER JOIN interchg XYZ ; ON ourline.cpart = XYZ.cpart ; WHERE ABC.ccomp_id = "000000" ; AND XYZ.ccomp_id = "00000F" ; ORDER BY ourline.cpart

And again, using the ANSI SQL '92 structure:

SELECT ourline.cpart AS Our_No, ; NVL(abc.ccomppart,"-----") AS ABC_No, ; NVL(xyz.ccomppart,"-----") AS XYZ_No ; FROM ; interchg XYZ RIGHT OUTER JOIN ; ourline LEFT OUTER JOIN interchg ABC ; ON ourline.cpart = ABC.cpart ; ON ourline.cpart = XYZ.cpart ; WHERE ABC.ccomp_id = "000000" ; AND XYZ.ccomp_id = "00000F" ; ORDER BY ourline.cpart

And finally, using the trick of including WHERE conditions as JOIN conditions instead:

SELECT ourline.cpart AS Our_No, ; NVL(abc.ccomppart,"-----") AS ABC_No, ; NVL(xyz.ccomppart,"-----") AS XYZ_No ; FROM ; interchg XYZ RIGHT OUTER JOIN ; ourline LEFT OUTER JOIN interchg ABC ; ON ourline.cpart = ABC.cpart ; AND ABC.ccomp_id = "000000" ; ON ourline.cpart = XYZ.cpart ; AND XYZ.ccomp_ID = "00000F" ; ORDER BY ourline.cpart

The result set is shown in Table 11.

Table 11. Result set.

Our_no Abc_no Xyz_no6300 18-4257 40796300 18-4257 4099

Page 20: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

6301 18-4258 40786301 18-4258 40986302 18-4339 10136303 18-4340 10126304 ----- 21436305 ----- 21426306 18-4275 21356307 18-4276 21346308 18-4311 40956309 18-4312 40946310 18-4293 30556311 18-4294 30566314 18-4504 30596315 18-4505 30586316 18-4307 -----6317 18-4308 -----6318 18-4305 30676319 18-4306 3066

As a developer who once found it useful to memorize the month, year, and page number of JerryEla's FoxTalk article on how to simulate OUTER JOINs, I can attest to their utility. Microsofthas placed the ability to perform this important function at our fingertips with the release ofVisual FoxPro 5.0, and it's well worth your time to learn to apply it in your applications.

Steve Sawyer is a corporate developer for KarCal Company, Inc. and is author of The Visual FoxProForm Designer in Pinnacle Publishing's The Pros Talk Visual FoxPro [email protected].

What's a Developer Supposed to Do?Whil HentzenIf Visual FoxPro 5.0 isn't out now, it's either right around the corner or an earthquake recentlyleveled the entire left coast of the country. Doesn't it feel like 3.0 came out just a few monthsago? According to our surveys, a fair number of you have yet to migrate to Visual FoxPro,similar to the way that much of corporate America is still using some brand of Windows 3.1. Butdon't think that you'll be able to rest for long. The development team for Visual FoxPro will beon vacation for the weekend, then they'll start work on the next version. Regardless of whetherit's called "6.0" or "Developer Studio FoxPro" or "SchmeeBase," we have to be ready for itpretty soon.

Is your head spinning yet? It should be -- and that's just if you're using FoxPro. Some of you are

Page 21: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

also developing in other languages: Visual Basic, Delphi, PowerBuilder, C++, and Java to namea few likely candidates. And each of these is going through frequent revision cycles as well.

How in the world are you going to keep up with all of these tools?

I could say something condescending like "just keep reading FoxTalk because that's all you'llever need to stay cutting edge." But you've heard that elsewhere, and you really wouldn't believeit, would you? Another thing I could recommend is that you stick with FoxPro and stay awayfrom all those other tools -- who needs 'em, for example -- and trying to do so sure complicatesyour life, right? Besides seeming self-serving, it doesn't address the issue of incessant revisionsof FoxPro itself, and thus I'm still searching for "the perfect answer" as well. So let me tell youwhat we're doing at my shop, and see if you can find a nugget or two of wisdom in theresomewhere.

First, let me explain that when I took over FoxTalk about 10 months ago, there were two of us inthe shop, plus an occasional college intern doing odd jobs and the like. We now have somethinglike eight folk: four or five full-time developers, a couple of support folk, and my assistant. Sonot only have I had to deal with the constant introductions of new tools, but I'm also working ongetting and keeping everyone else up to speed as well. This double-edged sword puts additionalpressure on me to find a solution to the ever-changing environment we're working in.

Funny enough, our customers still expect high quality applications, regardless of the tool we useto build them. Because of the higher and higher levels of pressure, it's easier to let substandardcode get shipped out to customers. One solution would be to exhort my staff to "work smarter,not harder" or some other Dilbert-like mantra. But even if this approach would work, it wouldmerely be a Band-Aid to a deeper problem.

The solution I've decided on is to focus on the process of software development -- tools maycome and tools may go, but the steps by which we create software remain the same. If theprocess is solid, then the degree of expertise with a specific tool is inconsequential as far as thecustomer is concerned. The process will prevent (or reduce the likelihood of) shipping junk tothe customer. In other words, the customer is shielded from the inevitable mistakes made as youcome up to speed on a new tool or a new version of a current tool. Of course, if you are avirtuoso in a particular language, this will still work. It's just that the margin you can commandwill be greater.

We all know various pieces of the process, but hardly anyone has taken the time to spell out eachstep and document how each step is performed. This means, with each new project, the stepsfollowed by the developer change, just as when you cook from memory instead of from a recipe.One time the salt is added first, another time it's added fourth (but twice as much is used), andyet another time it's completely forgotten. And just as with cooking, the results you get will varyeach time. Unfortunately, while uneven results with cooking may result in new and tastyconcoctions, uneven results with software development are not desirable. As any quality guruwill tell you, one of the cornerstones of quality is repeatability.

So we're formally documenting our process, examining it, determining where we can measurethe process and which of those measurements are of interest, and then performing the

Page 22: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

measurements. At the same time, we can use this documented process to train new developers.Once we have established a sufficient quantity of measurements, we can begin to determine howto improve the process, and thus improve the quality of our product. My goal for the next fiveyears (through the year 2001 -- sort of eerie, isn't it?) is for our shop to become reasonablyproficient at the process of development.

In retrospect, this seems like a lot of work. Is it really worth it? In my (oh-so-humble) opinion, itdefinitely is. First, software is becoming more complex. By way of analogy, you can build adoghouse or panel your basement over the summer just by winging it. If, however, your job wasto build a series of 25-story skyscrapers, and you had shorter and shorter time spans with whichto work, your margin for error becomes much smaller and the repercussions of those errorsbecome much more serious. You'll need proper tools, more knowledge, and a process to managethe construction.

And once you have that construction process down, you'll be able to adapt to new materials andtools and techniques a lot easier. You'll be able to finish the building on time, within budget andwith much greater certainty than if you were just guessing.

Second, it's a survival mechanism to handle the revision problem. Third, it's a way ofdifferentiating yourself from the competition. And, finally, it's a route to a healthier bottom line.Quality is free in the long run because the time spent on a better process will result in fewerproblems down the road -- and that's money in the bank.

It's not my intent to write an epistle on the software development process in this article -- we'llcover various topics in the future. I merely want to persuade you to think about this as a possibleavenue in your quest to deal with the frantic world we're living and working in. It was reallyunnerving to hear an executive from Sun Microsystems use the term "perpetual beta" whenreferring to the products that Sun and their competitors are working on, but it underscores thatthe pace won't let up.

So that's our plan, and I'd like to suggest that you think about concentrating at least some of yourefforts on doing the same.

Your First Peek at Visual FoxPro 5.0John V. PetersenWelcome to the 12th Dr. FoxPro's Answer Clinic column. Over the past year, it has been a pleasuretackling the issues that face you in your day-to-day development activities. I look forward toanother successful year. Going into our second year, we'll kick things off with a new arrival, VisualFoxPro 5.0. Read here for your first peek. And as always, keep those questions coming!

VFP 5.0 sports many improved and new features over its predecessor, Visual FoxPro 3.0.Beginning this month and continuing for several issues, I'll introduce you to many of the newfeatures of VFP 5.0 along with some practical tips for their application. In addition to answers to

Page 23: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

your questions, you can expect to see two or three features highlighted each month.

Right-click shortcut menu support

While both Visual FoxPro versions, 3.0 and 5.0, operate in Windows 95, version 5.0 has nativesupport for context-sensitive shortcut menus. Since shortcut menus are a standard inprogramming applications in Windows 95, it's important that development environments supportthe programmer's ability to incorporate them into an application. In 3.0, you can do this byhammering out code to define and activate a pop-up and attaching the method code to theRightClick() Event to a specific object, such as a form. Several limitations exist in VFP 3.0,however. The first deals with appearance. Prior to 5.0, pop-up menus have a flat, non-3-Dappearance. All other applications (Word, Excel, and Visual FoxPro itself) have consistentlooking shortcut menus.

The other problem deals with placement. Typically when defining a pop-up, the first line of codewould be as follows:

DEFINE POPUP mypopup FROM MROW(),MCOL()

When attempting to use right-click menus with forms, it's difficult to place the pop-up activateprecisely at the same location of the mouse pointer. The functions MCOL() and MROW() onlyrecognize the Foxel scalemode. In 5.0, these functions also recognize the Pixel scalemode. Now,creating a shortcut menu is as simple as creating a regular menu, and best of all, no Foxel toPixel conversion is required for proper placement.

Now for a practical application note. With all of the advances in 5.0, you still can't attach methodcode to the _SCREEN object. At the same time, it would be nice to create a right-click shortcutmenu associated with the screen that facilitates access to development tools. The following codecontains a sample shortcut menu that will be activated only when the mouse is over _SCREEN:

Page 24: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

**devmenu.mpr*Menu setup code

LOCAL loObjectloObject = SYS(1270)IF TYPE('loObject') = 'O' AND !ISNULL(loObject) IF loObject.Name <> "Screen" RETURN ENDIFELSE RETURNENDIF

* Menu Definition

DEFINE POPUP shortcut shortcut FROM MROW(),MCOL()DEFINE BAR 1 OF shortcut PROMPT "Debugger"DEFINE BAR 2 OF shortcut PROMPT "Class Browser"DEFINE BAR 3 OF shortcut PROMPT "Other Tools"ON SELECTION BAR 1 OF shortcut Activate Window DEBUGON SELECTION BAR 2 OF shortcut DO (_BROWSER)

Activate POPUP shortcut

All that's required to access our new development shortcut menu is to issue an ON KEYLABEL:

ON KEY LABEL rightmouse DO devmenu.mpr

Universal Naming Convention support

Universal Naming Convention (UNC) support has long been a requested feature from the FoxProDevelopment community. Assume you have an NT Server called NT_ADV_SERVER whereyou have a directory called DATA. In previous versions of FoxPro, you had to assign a specificdrive letter to access this directory. This one issue alone has caused many problems. Imagine aWAN environment in which you can't be sure what the drive letter will be. The only pieces ofinformation you can be sure of are the name of the server and where on that server the filesreside. Now, you can use the UNC directly. For example, assuming you have a database calledCUSTOMERS in the DATA directory, you can now open the data as follows:

OPEN DATA \\NT_ADV_SERVER\DATA\Customers SHARED

Version information support

Often, when you activate the Properties dialog box of a file in Explorer, you see two page tabs.The first is for general information such as the date and time the file was created, the file's type,

Page 25: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

and the attributes of the file (archive, read-only, hidden, or system). The second page, labeledVersion, contains other pieces of information such as the version number, copyrights, comments,and company name. In Visual FoxPro 5.0, you can bind this information into your distributedexecutables.

When you choose to build an .EXE, the Version button on the Build Options dialog box becomesenabled. When this button is pressed, a dialog box appears that allows you to documentinformation regarding the application. In addition, a version numbering scheme can bemaintained. If you choose the auto-increment option, the version number will automatically beupdated every time an .EXE is built. Most importantly, this information is readily available toclients via Explorer.

In addition to being available to the user, this information can be gleaned from the .EXE via anew FOXTOOLS.FLL function called GETFILEVERSION(). This function accepts twoarguments. The first is a string denoting the name of the file. The second is an array ([12,1]),which is passed by reference. In addition to .EXE files, other files such as DLLs and FLLs canprovide this information. Therefore, to learn about the VFP.EXE file, you can issue thefollowing:

DIMENSON aFileVers[12,1]SET LIBRARY TO HOME()+foxtools.fll ADDITIVEgetfilevers(HOME()+"VFP5.EXE",@laFileVers)

When you display memory, the contents of the laFileVers array is as follows:

LAFILEVERS Pub A ( 1, 1) C "" ( 2, 1) C "Microsoft Corporation" ( 3, 1) C "Microsoft Visual FoxPro for Windows" ( 4, 1) C "5.0" ( 5, 1) C "VFP" ( 6, 1) C "Copyright © 1996, Microsoft Corporation" ( 7, 1) C "" ( 8, 1) C "VFP.EXE" ( 9, 1) C "" ( 10, 1) C "Microsoft Visual FoxPro for Windows" ( 11, 1) C "5.0" ( 12, 1) C ""

The following describes the contents of each array element:

Element Description1 Comments2 Company Name3 Internal Name4 File Version Number

Page 26: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

5 Filename6 Legal Copyright7 Legal Trademarks8 Original Filename9 Not Used10 Product Name11 Product Version Number12 Not Used

Now, let's answer a question.

Is there a function in FoxPro that returns thevolume name of a disk or drive?

Yes there is. The function is called ADIR(). The first and second arguments of the ADIR()functions are an array that holds the results of the function call and a file skeleton, respectively.The third argument passed in ADIR() specifies the attribute you wish to inquire about. In thecase of determining a volume label of a disk (fixed or floppy), the third argument is the letter V.Therefore, assuming you wish to find out the volume label of the C Drive, you'd issue thefollowing:

=ADIR(laVolArray,"C:\","V")

Beginning with version 5.0 of VFP, you no longer need to preface a function call with an = sign.Therefore, the function call can be shortened to the following:

ADIR(laVolArray,"C:\","V")

Displaying the contents of the resulting one-element array is as follows:

LAVOLARRAY Pub A ( 1) C "DRIVEC"

John V. Petersen, MBA, is director of software development for Pearl Computer Systems Inc., a MountLaurel, New Jersey, Microsoft Solution Provider and maker of ultiMAINT computerized maintenancemanagement software. John is active in the FoxPro community and has been a speaker at user groupmeetings and at the 1996 Database Development Workshop in Richmond, Virginia. John is also acoauthor of Developing Visual FoxPro Enterprise Applications by Prima [email protected], 609-983-9265.

Page 27: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Stonefield Database ToolkitKelly G. Conway (2)

With the release of Visual FoxPro, Microsoft added a data dictionary to our favorite developmenttool. Or did they? I suppose that depends upon your definition of a data dictionary. Users of theStonefield Data Dictionary for FoxPro 2.x would beg to differ. The Stonefield Database Toolkit fillsa number of gaps in the VFP data dictionary.

Sure, the database container (DBC) is a welcome addition to Fox. It provides a foundation that isextensible so that we, Microsoft, or third-party developers can add other features to it. But theDBC leaves out several features that many of us would consider essential parts of a datadictionary æ especially those of us who use Stonefield's 2.x tools.

But we need not do without, because Stonefield has released a new data dictionary tool for VFP.The Stonefield Database Toolkit expands upon Microsoft's DBC to provide the features that adata dictionary should have æ features that Stonefield's customers have taken for granted for thelast few years. SDT adds to the DBC features such as database documentation, additionalproperties (such as field input masks), and the information needed to recreate table structures andindex tags. SDT also provides several methods that your applications can call to use thisinformation at runtime.

Getting started

Starting SDT is simple enough. The product comes with an .APP file that you can run from thecommand window or from a startup program such as VFPSTART.PRG. Either way, you'll see asplash screen and then notice that two options have been added to the VFP Tools menu pad.Those options allow you to start SDT and to change SDT settings to suit your preferences.

But, before you start up SDT, you'll need to create a VFP database. You might find this a bitannoying æ why wouldn't SDT go ahead and provide an option to do something as simple ascreating the DBC? The answer is that Stonefield didn't try to reinvent anything that they felt VFPalready handled well. So users of SDT will still find themselves using the native VFP tools to dosome database management functions. Although I think this is a nice design from the standpointof allowing me to continue to use VFP for things that it allows me to do, it would be nice if SDTcontained some push buttons that allowed me to quickly call up those VFP tools (for example,MODIFY DATABASE to see a graphical view of the DBC) without exiting SDT.

You start SDT by choosing the Tools/Stonefield Database Toolkit menu option from the VFPmenu. You then see a form that lists all of the tables in the current DBC. The initial SDT formalso contains several command buttons and a few fields for entering information, such as acaption and comments for each table. Figure 1 shows SDT with the VFP sample database open.

Page 28: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

A quick tour

A quick tour of the command buttons should give you an idea of the functionality they offer. Thetool tips note that the command buttons allow you to add an existing table, create a new table,remove a table, copy a table, modify a table (structure), browse a table, reindex a table, update atable's structure, edit stored procedures, pack the database, refresh metadata, producedocumentation, and set SDT preferences. Most of these descriptions are self-explanatory. Butcontext-sensitive Windows help is available (along with printed documentation) to explain all ofthe features.

The controls that appear on the right side of the form allow you to change settings (properties)for each individual table. By clicking on a table in the list to the left, you can see and modify itsproperties on the right. The properties that you see are only those that Stonefield has predefined.One of SDT's most powerful features is the ability to create extended table properties. You'll finda command button for that purpose at the right of the screen. Figure 2 shows the extended tableproperty dialog with sample properties.

These properties have no effect on VFP's use of the tables (for example, removing the mark fromthe Reportable check box for a table doesn't keep you from being able to define VFP reports forthat table). But your application can read and set these properties at runtime and change itsbehavior accordingly. When you consider the fact that these extended properties can also beadded to fields and index tags, the possibilities for extending the DBC boggle the mind.

For example, your application's reporting option might let the user create a quick report byselecting from a list of reportable tables. Or you might add a security-level extended property toall fields that your application could use to remove fields from forms based on the current user'ssecurity level. SDT also comes with some methods -- such as OpenAllTables() and SelectTag()-- that your application can call and that use the predefined properties to drive their appearance.

Most of SDT's functionality can be found by clicking the Modify Table button. This buttonopens a tabbed dialog that contains a list of the selected table's fields on the left and a plethora ofproperties on the right side of the first three tabs. The fourth tab contains a list of index tags andtheir properties. The final tab contains table-level validation and trigger properties.

The controls on the field-level page frames of this form allow you to specify properties for eachfield such as the caption, comments, input mask, output mask, report headings, tool tip, and helptext. You also can create extended field properties the same way I described for tables earlier.New fields can be added to the table and existing fields can be copied or deleted.

The page frame for indexes contains a list of the index tags defined in the table's structural indexfile (.CDX). Index properties include the ones that are accessible through VFP's MODIFYSTRUCTURE command (Name, Type, Expression, Filter, and Descending), as well as Caption,Comments, and Selectable. The Selectable property is used by the built-in SelectTag() method sothat the user can select a tag only from those that the developer has marked as Selectable. Ofcourse, this page frame also contains a command button that brings up the extended propertyeditor for index tags.

Page 29: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

A very useful feature on the index tag page frame is a command button that calls the indexwizard. This wizard can be used to create a tag on the DELETED() function, create a tag onevery field, and create all character-field tags by including the UPPER() function. For mosttables, it's easiest to run the index wizard with all of the options selected and then use theRemove Index command button to delete the tags you don't want.

If all of these features seem overwhelming, the SDT documentation contains a tutorial that walksyou through their use. There's also a SAMPLE subdirectory that contains samples of using SDTextended properties and methods. Another aspect of SDT that I haven't touched on yet is thelibrary of reusable functions and methods that Stonefield provides. These include methods toopen a table, open all tables, recreate index tags, repair corrupted table headers, get and setextended properties, and update table structures. I'll look at a few of these later.

Another feature of SDT that I also haven't discussed may be the best feature of the entirepackage, a builder named AutoSize. In short, AutoSize is a builder that uses field-levelproperties to help you create data-bound forms in an instant. I'll demonstrate AutoSize later.

OK, now what do I do with all of this?

So, now that you have all of this functionality, how can you use it? I'll walk you through thecreation of a very small application and describe how I used the SDT tools to enhance andaccelerate the development process.

This simple example application uses the VFP sample DBC and tables contained in theSAMPLES\DATA subdirectory. It contains a screen for maintaining customers and another formaintaining employees. It also contains utility options for rebuilding index tags and updatingtable structures. I won't use every SDT feature, but I hope you get an idea of the kinds of thingsyou can do with SDT.

Preliminary tasks

First, I create a new directory structure for my application. The top level, called \TESTSDT,contains a subdirectory for DATA. Then, I copy all of the files from the SAMPLES\DATAsubdirectory of my VFP HOME() directory into \TESTSDT\DATA. To keep things simple, I'llcreate all of the application objects in the \TESTSDT directory.

Then, I create a simple menu to run the application's options. To keep things really simple, I putall of the options on one menu pad, named TestSDT, and select the option to append that menuto the VFP menu. The options for the menu (and the corresponding commands) are CustomerForm (do form customer), Employee Form (do form employee), Rebuild Index Tags... (doRebuildIndexTags in MAIN), Update Table Structures... (do UpdateStructures in MAIN), andUnload (release pad TestSDT of _msysmenu). I save my menu as MAIN in the OTHERsubdirectory.

So that I can test these things while I build them, I'm going to create a program that puts up themenu, instantiates SDT (through DBCx æ we'll discuss DBCx more later), and opens the tables Ineed. Here's the code for MAIN.PRG:

Page 30: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

*-- Main.PRG*-- *-- Main program for TESTSDT application*--

set path to data, c:\vfptools\sdt, 'c:\vfptools\sdt\source, c:\vfptools\sdt\dbcxset classlib to dbcxmgr

release oMetapublic oMetaoMeta = createobject('MetaMgr')

if type('oMeta') # 'O' or isNull(oMeta) =Messagebox('Unable to instantiate MetaMgr class.') return .F.endif

if not oMeta.oSDTMgr.OpenData('TESTDATA') =Messagebox('Unable to open the database.') return .F.endif

if not oMeta.oSDTMgr.OpenAllTables() =Messagebox('Unable to open one or more tables.') return .F.endif

do main.mpr

return

Procedure RebuildIndexTags close database open database testdata exclusive oMeta.oSDTMgr.Reindex() close database open database testdata sharedEndProc

Procedure UpdateStructures close database open database testdata exclusive oMeta.oSDTMgr.Update() close database open database testdata sharedEndProc

In this main program, I instantiate the DBCx manager and make sure that it's alive and well.Then, I open the database and, if that's successful, also open all of the tables that I marked to beopened "automatically." After adding the Test SDT menu pad to the VFP menu, MAIN.PRGreturns and leaves me to develop and test the application. The two procedures at the bottom of

Page 31: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

MAIN.PRG are there to wrap calls to SDT's Reindex() and Update() methods with code thatopens the database for exclusive use, performs the required functionality, and then reopens thedatabase for shared use.

Extending the DBC

After saving MAIN.PRG, I go back to the command window to OPEN DATABASEDATA\TESTDATA EXCLUSIVE, and MODIFY DATABASE. I'm going to set the fieldcaptions with VFP's table designer so that SDT will use those captions to set acceptable defaultvalues for several of its extended field properties. I right-click on the Customer table and selectModify... from the context menu. After setting the caption of each field in the customer table, Ido the same thing for Employee.

Then, I use SDT to extend TESTDATA.DBC and its associated tables. I'll DO SDT.APP toensure that the SDT options appear at the bottom of my VFP tools menu. Then I choose theStonefield Database Toolkit... option from the Tools menu. Because this is the first time I'veused SDT on this DBC, I`m prompted to make sure I want to create SDT extensions. I select"Yes," and SDT spends a few seconds building the data dictionary tables.

Setting table, field, and index properties

Now I want to modify each table to see that the field properties are properly set and to make sureI have all the index tags I want (and that they have descriptive captions, too). I select thecustomer table and click the Modify Table button. After a few seconds, the extended tabledesigner form appears with the customer table information (Figure 3 shows the table designerwith the VFP sample customer table loaded). By clicking on various field names and movingamong the first three page frames, I notice that SDT did a nice job of filling several propertieswith the captions I just entered into the DBC. Some of these properties will be used immediatelyfor things like tool tips and status bar messages. Others (dialog prompts and list prompts, forexample) won't be used until I (or someone else) build some tools or controls that access them.

On the Index tab I notice that some index tags already exist (Microsoft created them and thesample data that these tables contain). But there's no index tag on DELETED(), and none of thecharacter index tags use the UPPER() function to aid in sorting and incremental searching. Idecide to remove all the existing index tags (except for the CUST_ID primary tag) and use theSDT index wizard to create my tags. With three clicks the offending tags are gone, and withthree more clicks I've created several new tags. I remove two or three of these new tags andleave the rest. I check the captions and notice that SDT carried over the corresponding fieldcaptions so that I don't need to update them. I also notice that each tag is marked as beingselectable (by the user as the current table order), except the tag on DELETED(). That's just howI want it.

I go through the same steps for the employee table. As with the customer table, SDT takes thetime to create my new index tags when I click the Save button (the disk icon). After setting thetables up the way I want them, I might want to generate some SDT reports to use as systemdocumentation. Then I'm ready to create my first form.

Page 32: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Forms meet the AutoSize builder

I'll create some very simple forms, but they'll include the following features that use SDTproperties and methods. Every data-bound control automatically will be sized and namedappropriately for its data source. Each control also will be able to enforce an input mask as wellas provide appropriate clues via status bar messages and tool tips. A button on each form willallow users to select, from a list of index tag descriptions, the order in which they wish to viewthe data.

Much of this functionality will be accomplished by using the AutoSize builder. Without goinginto the details of using a VFP builder, I'll just mention that SDT includes a program thatregisters the AutoSize builder with VFP for you. This makes using SDT a snap. Just be sure toDO REGAUTO one time after you install SDT.

To create my customer form, I issue CREATE FORM CUSTOMER in the command window. Ithen open the data environment and add the customer table. I move the DE window so that I cansee both the customer table and the customer form. Then, I click on the CUST_ID field and dragit onto my form. I right-click on the new control in the form and choose Builder... from thecontext menu. VFP presents me with a list of builders that are registered for the text box control.From that list I choose AutoSize and see that my text box control has been sized correctly for theCUST_ID field and named txtCust_ID. A label with the caption Customer ID has been created tothe left of the control.

But the common advice is to use your own subclass of the VFP base classes, right? In otherwords, I want to create a subclass of the VFP text box control (and the others) and drop thatsubclass onto my form. AutoSize left the control as a standard VFP text box, so maybe it isn't socool after all. A quick check of the documentation (Chapter 13) makes everything cool again.AutoSize will let me create controls that are based on any class I want. I just have to do a littlesetup work first.

Running AUTOSIZE that first time created a table, AUTOSIZE.DBF, in my VFP homedirectory. BROWSEing that table, I see a dozen records æ one for each VFP-supported fieldtype. Each record provides fields where I can enter a class (and its class library and location) thatI want AUTOSIZE to use for controls that are bound to that type of data. For example, I'llcomplete the character-type (C) record so that it uses the cTextBox control of my CBASE.VCXclass library. If I had a custom class for entering dates, I could enter the information for that classin the data-type (D) record and automatically base all dates on that class. Very nice.

So, now that I have AUTOSIZE.DBF set up to use my classes, I'll try this again. I go back to mycustomer form and remove the controls I added earlier. With the DE window open, I again dragthe CUST_ID field onto the form. I then right-click on the text box control, choose Builder...,then AutoSize. Things look pretty much the same as the first time. But on closer inspection I seethat the new controls are based on the classes I specified (cTextBox for the text box control andcLabel for the label).

Looking back at the third tab on the extended table designer form, I now understand what thecontrol labels named AutoSize Options: are for. If I leave them blank for a field, that field will

Page 33: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

use the default settings for the type of its bound data source when I run the AutoSize builder. ButI can modify the settings for specific fields so that those fields override the defaults and use aspecific class (for example, a cPhoneTextField that contains the InputMask setting and any otherproperties and methods I would want for a phone number field).

Now I can drag each field from the customer table onto the form and run the AutoSize builder onthem. But there's a faster way. If I grab the icon that appears next to the word "Fields" in thecustomer table icon, I find that I can drag all of the fields from the customer table onto my form.Of course, since they're cascaded and overlapped (why, Microsoft, why?), I need to use thelayout toolbar to line them up in a more useful manner. But then, when I run the AutoSizebuilder (with all of the fields still selected), all of the fields are processed at once.

"But that isn't all," he said, "no, that isn't all." If I turn on Builder Lock (the icon in the FormControls toolbar that looks like a magic wand, not the one that looks like a lock), AutoSizeprocesses each field as I drag it onto the form (or, if I drag and drop the Fields icon, all fields atonce). If that aspect of VFP's builder interface doesn't impress you, nothing will.

Running the form, I see that AutoSize did a pretty good job of sizing the fields for me (andeverything else). But there's one minor problem. Fields bound to data that have a width of lessthan about three aren't wide enough. This is a minor annoyance because I can simply enlargethese controls after running AutoSize.

Setting control properties at runtime

I also notice that no tool tips or status bar messages appear for the controls. That's because, eventhough SDT allowed me to enter this information (actually, SDT entered default information forme from the captions that we entered), I still have to tell my controls to do something with theinformation.

Back in the form designer, I look at the properties of my txtCust_ID control. I see that there areproperties for ToolTipText and for StatusBarText, both of which are empty. I probably couldplace a function call in those properties to return the appropriate character strings for theseproperties (or I could get the values from an SDT report and enter them in design mode). But Ican do something much more reusable than that. I can enhance my data-bound control classes sothat they dynamically fill these properties when they're instantiated.

I'll place the code to perform this functionality in the Init() method of each control class. Butfirst, I'll save some time in testing by simply adding the code to one of the controls on my form. Iopen the Init() method for my txtCust_ID control and add the following code:

Page 34: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

*-- If this is a bound control and oMeta is around, set *-- up the properties based on the DBCx properties of *-- the data source.if not empty(This.ControlSource) and type ;('oMeta') = 'O' and not isnull(oMeta)liID = oMeta.DBGETDBCKEY(dbc(), 'Field', ;This.ControlSource)This.ToolTipText = oMeta.DBCXGetProp('CBmToolTip', ; liID)This.StatusBarText = oMeta.DBCXGetProp('CBmMessage', ; liID)This.InputMask = oMeta.DBCXGetProp('CBmInFormat', ;liID)endif

What on earth does all that mean? Don't worry. I'm no DBCx expert, but I picked up enoughfrom SDT's documentation that I was able to write and debug this code in about 15 minutes. AndI only had to do it once to gain this functionality for all of the controls I drop on a form. In anutshell, we're using the control's data source to access the properties that we set for that field inSDT. The only really tricky part is figuring out the name of the property that we want to access.For example, the name of the tool tip property turns out to be CBmToolTip because it's part ofthe Codebook DBCx extension and Flash named it mToolTip. But that information is providedin nice tables in Appendix A of the SDT manual, so isn't that big of a deal.

I save my form and run it again and the status bar message shows up for the Cust_ID field (EnterCustomer ID). Pausing the mouse over the text box control shows a similar tool tip (youprobably wouldn't want to turn that on all the time, would you?). If your tool tips don't show up,make sure you set the ShowTips property of the form to .T. In fact, you could include a buttonon your form or toolbar so the user can toggle the form's ShowTips property and that would beall that's needed to turn tool tips on or off for all controls on the form.

Now all that's left is to cut the Init() code from txtCust_ID and paste it into the Init() method ofmy cTextBox class (and any other classes that need this functionality). Now all of the controlson my form display a helpful message in the status bar when they receive focus and each alsodisplays helpful information in the form of a tool tip when I pause the mouse cursor over them.

OK, it isn't all that helpful yet because I'm just echoing the text that's in the control's label. But Icould modify the default settings of the Tool Tip and Message properties in the SDT extendedtable designer to provide more information to the user without cluttering the form with lengthylabels. Since the code I placed in the cTextBox.Init() method is executed at runtime, I canchange these properties any time and see the new messages the next time I run the form.

May I please take your order?

Remember that I said that these forms were going to contain a command button that allows theusers to select the order in which they wish to view the data? I'll do that now. All I have to do isdrop a command button onto my form, set its caption to Set Order, and place the following

Page 35: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

command into that button's Click() method:

oMeta.oSDTMgr.SelectTag()

That's it (see Figure 4). Clicking that command button in my running customer form brings up adialog box that allows me to select an order from the customer table index tags that I marked asselectable (set the Selectable property to .T.). Back in design mode, I click on the cmdSetOrdercontrol, choose Save as Class... from the File menu, and create a cmdSetOrder class in myCCONTROL.VCX class library.

Just to make sure this was no fluke, I CREATE FORM EMPLOYEE and follow the same stepsthat I took to create the customer form. I created a working employee form with all of thefeatures of the customer form in less than one minute. This includes opening the CCONTROLlibrary and dropping an instance of the cmdSetOrder class onto the form.

Other uses for SDT properties and methods

SDT properties and methods don't have to be used just from forms and classes. Reports can getin on the action, too. One sample that comes with SDT demonstrates this well. The customerreport in the SAMPLES subdirectory includes fields in the page header that make calls toDBCxGetProp() to get the heading for each column from the data dictionary. Other than wherethe code is placed, the code to accomplish this is basically the same as the code I wrote topopulate the tool tip and message properties of my text box control.

The simple application I built also contains examples of using SDT's database utility methods.The Rebuild Index Tags... and Update Table Structures... menu options (and the procedures thatthey call in MAIN.PRG) demonstrate how simple it is to use these features. With a couple ofmenu options and a few lines of code you can include these necessary features in any applicationthat uses SDT as its data dictionary.

What is DBCx?

Now that I've talked about it so much, I feel compelled to elaborate a little on what DBCx is. Agroup of third-party developers (including Flash Creative Management, Neon Software,MicroMega Systems, and Stonefield Systems Group) collaborated to create a data dictionaryscheme for Visual FoxPro. SDT's manager class is subclassed from a DBCx class. These classes,together with the Codebook manager class, cooperate to provide SDT users with the tools thatallow us to do the types of things I've demonstrated.

By using DBCx, SDT has become a "Codebook compatible" product. That means that SDT canbe used together with Codebook or other Codebook-compatible products without having tomaintain separate data dictionaries. Additional products (such as FoxFire! from MicroMegaSystems and Visual FoxExpress from Neon Software) are expected to use DBCx in the future.When that happens, developers should be able to use those products in an application whilemaintaining only one data dictionary.

Page 36: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

The idea of being able to mix and match these third-party tools is a dream come true for manydevelopers. In the past, FoxPro developers have had to maintain separate data dictionaries foreach add-on product or write and run conversion programs to keep both dictionaries updated. Ofcourse, few things in software development work out as easily as originally promised. I'll wait(optimistically) until other Codebook-compatible products are released before I declare DBCx tobe the great add-on product integrator that it appears to be.

In fact, if you don't plan to utilize multiple Codebook-compatible add-on products, you may findthat DBCx adds unwanted complexity to SDT. Modifying SDT (not that you'd want or need to,but the source code is provided) and hunting down problems when an SDT error is generated areboth complicated by the fact that SDT subclasses DBCx classes and uses them to read and writedata-dictionary information.

Room for improvement

SDT is a great tool. But all tools have room for improvement, especially in their first version.Here're some items I would like to see added to or improved in SDT. With Stonefield'sreputation for responding to customer feedback, don't be surprised if some of these featuresaren't already in the product by the time you pick it up.

First, a few features that are in Stonefield Data Dictionary 2.6 are missing from SDT. Most aredocumented in the manual, usually with a chunk of code that produces an equivalent result. Butsome SDD features are flat-out missing. For example, I needed to bring about 20 tables from aFoxpro 2.x/DOS application into a new VFP database. SDD has a feature that would haveallowed me to batch add all of these tables by specifying the directory that contains them. WithSDT, I had to add each table one at a time (although I suppose I could have batch added thetables into SDD 2.6 and then used the supplied MIGRATE.PRG to convert the SDD tables intoSDT tables).

Another possible area for improvement is speed. SDT is noticeably slower than SDD in someareas. Most noticeable on my machines is the time it takes to open the extended table designerform after I click on SDT's Modify Table Command button.

A nice improvement to the extended table designer interface would be to allow me to select adifferent table without closing that form and returning to the main SDT form. The table namealready appears in a long text box at the top of the form. Turning that text box into a combo boxcontaining a list of all tables would make it easier when I need to make property changes tofields in multiple tables or when I want to run the index wizard on each table in turn.

Possibly the biggest omission in the initial version of SDT was support for views. Version 3.0cnow supports views, but they still are not treated as the equals of tables. Extended properties forviews are added to the metatables and the Autosize builder will utilize those properties when youdrag view fields onto a form. But since views still do not show up in the list of database tableswithin SDT's visual interface, these properties must be set for views programatically.

Should you buy it?

Page 37: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

If you think that your Visual FoxPro development efforts could benefit from an extended datadictionary tool (few should think otherwise), I strongly suggest that you look at SDT. Even withthe few warts mentioned here, I find SDT to be a welcome addition to my VFP toolbox.Stonefield has a reputation for providing superb support and for issuing timely upgrades thatinclude both bug fixes and new features.

Speaking of support, Stonefield's is provided via CompuServe, fax, and phone. Product updatesalso are available on the Internet at . Priority support, free for the first 60 days, is available for$199 per year. This plan includes version updates and allows customers to contact Stonefield byphone, fax, or CompuServe. Standard support is free and provided only on CompuServe.Standard support customers pay for version updates (typically $99).

Kelly Conway works as a software developer at Dimoco Manufacturing Company (Lee's Summit,Missouri) and spends all of his "spare" time climbing the VFP, OOP, and Web development learningcurves. He has developed software for 10-plus years, working with FoxPro since version 2.0.816-525-5325, [email protected], [email protected].

Stonefield Database Toolkit $249 US with complete source code

($199 upgrade for SDD 2.6 users)

Stonefield Systems Group

800-563-1119 (306-586-3341 outside the US and Canada)

Fax 306-586-5080

[email protected]

Tip: Closed WindowsPaul RussellIn a recent application we wanted to prevent the user from running any application other than ourFoxPro for Windows application. When they start Windows, we want the FoxPro application tostart; we don't want them to be able to access anything else; and when they exit from the FoxProapplication, we want them to exit from Windows.

We found that the easiest way to do this is to change the SYSTEM.INI file. Before we made anychanges the SYSTEM.INI file looked like this:

Page 38: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

[boot]shell=progman.exe

We modified the file to look like this:

[boot]shell=foxapp.exe; shell=progman.exe

One caveat: When you start the .EXE, the directory that Windows is installed in will be thecurrent directory. You'll either have to change the FoxPro PATH or issue a SET DEFAULT nearthe top of the application. You also have to have the .ESL file in the DOS PATH.

Tip: Run Down Your Batteries, Not YourselfWhil HentzenNotebook computers with nickel cadmium batteries are subject to the "memory effect," wherethey won't hold a charge as long if you don't run the batteries all the way down before chargingthem up again. Here's a little Fox program that will run the batteries down:

* BAT_DOWN.PRG* adds data to a file in order to run the battery downclose databasesselect 0create table BAT_DOWN (nX N(10))for i = 1 to 1000000 wait wind nowait "Adding record number " ;+ alltrim(str( i )) insert into BAT_DOWN (nX) values (m.i) flushendfor

By writing to a table instead of just displaying a lot of information on the screen, it helps run thebatteries down faster. Just make sure you delete the BAT_DOWN table after closing theprogram.

Dynamically Modify Grid Column Alignment

Page 39: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

and Other PearlsBarbara Peisch

Modify Grid Column Alignments

Be careful when dynamically assigning the ControlSource property of a grid's column whenusing default alignment. With default alignment, VFP evidently determines which way to alignwhen it is instantiated. If the datatype of the ControlSource changes dynamically, the alignmentwill be wrong. However, the following code will cause the column to re-evaluate the alignment:

WITH ThisForm.grdMyGrid.Column1 .ControlSource = "MyTable.cOtherCol" .Alignment = .AlignmentENDWITH

We've run into this problem on forms where we determine the RecordSource of the grid in theInit() of the form.

Mark Nadig

Compare Object References, Not Just Objects

Want to compare two object references to determine if they both reference the same object?Using the VFP function COMPOBJ(), you can compare the properties of two objects and returntrue (.T.) if their properties and property values are identical. This is not a comparison ofreferences, but properties of two objects only. Unfortunately, COMPOBJ() compares onlyproperties. If you have two different objects with the same properties, you can't compare themnatively. Here is a function called SameObj() that explicitly compares two object references andreturns .T. if both reference the same object:

Page 40: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

* Program...........: SAMEOBJ.PRG* Author............: Ken R. Levy*) Description.......: Compares reference of two objects.* Returns .T. if both objects* reference the same object.

LPARAMETERS toObject1,toObject2LOCAL lcObjName1,lcTempObjName,llMatch

*-- Quick check to deterimine if references are*-- the same object.IF NOT toObject1.Name==toObject2.Name OR ; NOT toObject1.Class==toObject2.Class OR ; NOT toObject1.ParentClass==toObject2.ParentClass OR ; NOT toObject1.BaseClass==toObject2.BaseClass OR ; NOT toObject1.ClassLibrary==toObject2.ClassLibrary RETURN .F.ENDIF*-- Get first object's name property.lcObjName1=toObject1.Name*-- Create temporary object name.lcTempObjName='_'+lcObjName1*-- Set temporary object name.toObject1.Name=lcTempObjName*-- If both objects have new name, same reference.llMatch=(toObject1.Name==toObject2.Name)*-- Restore first object's name property.toObject1.Name=lcObjName1*-- Return .T. if both references are same object.RETURN llMatch

Here's an example for SameObj():

Page 41: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

LOCAL oObject1,oObject2,oObject2b

oObject1=CREATEOBJECT("Custom")oObject2=CREATEOBJECT("Custom")

* Force both objects to have the same properties.oObject2.Name=oObject1.Name

* Create a second reference to oObject2.oObject2b=oObject2ACTIVATE SCREEN?? "COMPOBJ:"

* Returns .T. since properties are the same? COMPOBJ(oObject1,oObject2)

* Returns .T. since properties are the same? COMPOBJ(oObject1,oObject2b)

* Returns .T. since properties are the same? COMPOBJ(oObject2,oObject2b)?? "SameObj:"

* Returns .F. since they don't reference the same object? SameObj(oObject1,oObject2)

* Returns .F. since they don't reference the same object? SameObj(oObject1,oObject2b)

* Returns .T. since they reference the same object? SameObj(oObject2,oObject2b)

RETURN

Ken Levy

Return Multiple Variables From Forms

One problem that FoxPro programmers have been dealing with forever is returning multiplevalues from dialog forms. In FoxPro 2.x, you would pass an array by reference to the screen, butthis tends to cause scoping problems in Visual FoxPro. You also have to remember what arrayelement equals which variable. In Visual FoxPro, with its object orientation, you have someadditional options. I've defined a custom class that handles returning multiple values from aform. It has five components: two custom array properties and three custom methods. Here's howthe custom class is defined.

To a custom class, add a custom array property named aReturnArray[1,2] and a numericproperty called nArrayLen. aReturnArray is going to store the return values from the form.nArrayLen stores the number of elements used for variables. Second, add the following custom

Page 42: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

methods:

• AddValue

• SetValue

• GetValue

The AddValue method is used to add a row to the aReturnArray. The SetValue method is used inthe form to set one of the return values. The GetValue method is used in the dialog form's callingprogram/method.

The code for these methods is as follows:

Page 43: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

PROCEDURE AddValue* Add a variable to the return value array.LPARAMETER cVariable

cVariable = UPPER(cVariable)WITH This .nArrayLen = .nArrayLen + 1 DIMENSION .aReturnValue[.nArrayLen,2] .aReturnValue[.nArrayLen,1] = cVariableENDWITHRETURN

PROCEDURE SetValueLPARAMETER cVariable, xValue* Returns .T. if the value was set properly.cVariable = UPPER(cVariable)LOCAL lFoundlFound = .F.WITH This FOR I = 1 TO .nArrayLen IF .aReturnValue[I,1] == cVariable .aReturnValue[I,2] = xValue lFound = .T. EXIT ENDIF ENDFORENDWITHRETURN lFound

PROCEDURE GetValueLPARAMETERS cVariablecVariable = UPPER(cVariable)LOCAL xReturnWITH This FOR I = 1 TO .nArrayLen IF .aReturnArray[I,1] = cVariable xReturn = .aReturnArray[I,2] EXIT ENDIF ENDFORENDWITHRETURN xReturn

Here's an example of using this:

Page 44: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

PROCEDURE CallDialog* Example of using multiple return value custom object.* In this example, the return value object has been* named cusRetValx = CREATEOBJECT('cusRetVal')WITH This x.AddValue("NAME") x.AddValue("ADDRESS")ENDWITH

DO DIALOGFORM WITH x

You need to take a few steps within the dialog form to make everything work properly. First off,you must have a custom property where you can store the object reference until it's actually usedwithin the Unload event of the form. I usually call this oReturn. You must also have a place tostore values until they get loaded into the return object. Use whatever technique feels mostcomfortable to you. I use custom properties on the dialog form to store the value. For thisexample, name the custom properties cName and cAddress. The dialog will load those propertiesand the Unload event will do the following:

PROCEDURE UNLOAD

WITH This.oReturn .SetValue("NAME", This.cName) .SetValue("ADDRESS", This.cAddress)ENDWITH* Always clean up after yourself with object* references. Better safe than sorry.This.oReturn = .NULL.* You don't need to return the object reference* to the calling program as it* already has a reference (X in this case).

Back in the calling program, use the following:

WITH X cName = .GetValue("NAME") cAddress = .GetValue("ADDRESS")ENDWITH

You now have all of the pertinent information from the dialog form as painlessly as possible.

William J. O'Conner

The Need for Speed

Page 45: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

Les PinterWhen I was writing FoxPro for DOS applications, my clients used to tell me how they preferredit because Windows was too slow. Well, I just got my new Pentium 166, and I'm here to tell you,Windows isn't slow; your computer is. And it's costing you -- and your clients -- a bundle!

How much does software cost? It costs something to write, and it costs something to use. Yourclient or employer doesn't care how slow your computer, or the ones his or her employees use,is? Let's talk.

I'm a Visual FoxPro (and sometimes Delphi) developer. These are both Windows platforms --there are no DOS versions. FoxPro for Windows requires 16M to run as fast as FoxPro for DOS.So you have to get 16M to do your development -- maybe even a faster computer. That's going tocost someone lots of money, right?

Fry's Electronics in Palo Alto is selling 16M chips for $99 today, unless the price went downagain since I wrote this. So your chip upgrade's going to cost someone anywhere from a fewminutes' to a few hours' pay. And you can buy a Pentium 133 motherboard and CPU for around$450. So the problem is to recover an outlay of $500 or $600 per machine.

Simply going to Windows will return your investment in no time. What's the difference inprogrammer productivity in DOS and Windows? In DOS you have 24 lines and 80 columns, soyour screen design options are limited. I've spent days reworking screens when a few new fieldscropped up at the last minute. In Windows, there's always room for another field or two, or a nicepage frame to add pages to. So just in terms of design time, the additional memory will pay foritself the first time your user changes his or her mind. Even if doing things faster the first timeisn't worth the money, it's definitely cheaper the second time around to have more flexibility.And I don't know about you, but I build my apps perhaps three or four hundred times a day. At aminute each on a slow machine, that also pays for an upgrade in a week.

What about user productivity? There isn't a user out there who doesn't appreciate thecolor-coding, use of icons and graphics, and interoperability of Windows. As programmers, itgives us a little more to work with, too. We're also users. So software that's easier to use, easierto navigate, and all works the same way also pays its way. If Windows requires 16M, thebenefits clearly outweigh the minuscule cost to add memory. And we haven't talked aboutlookups and reports, both of which benefit from additional memory.

A faster CPU can enhance performance even more. How many times have I heard that acompany, a medical school, or a government installation has rooms full of 286s that can't beupgraded or replaced because they "haven't outlived their usefulness?" Considering that the$35,000-a-year people sitting at those computers could be doing twice as much, making a coralreef out of those 286s would give you more for your tax dollar than just about anything I canthink of. And I'm not so sure that some of those slow-working bureaucrats don't like to workslowly. Let's give them a faster computer and see if their feet smoke when the computers dragthem, shall we?

Slow computers are a waste of time and money. If you're developing software on a fixed bid,

Page 46: OUTER JOINs in Visual FoxPro 5 - dFPUG-Portalportal.dfpug.de/dfpug/Dokumente/FoxTalk/PDF2000/FT1096.pdf · OUTER JOINs in Visual FoxPro 5.0 Stephen A. Sawyer (1) After a long wait,

you're losing money; if you're charging by the hour, your client is. And if you're salaried, thestockholders are footing the bill. So take this afternoon off and get a faster machine; Americacould use a little competitive advantage -- even if we buy it from Taiwan.

Les Pinter publishes the Pinter FoxPro Letter . His new novel, The Valley, will be out this fall.415-344-3969, [email protected], .