147 19 22MB
English Pages 546 [582] Year 1993
=
DBMS Magazine’s Database Foundation Series
Parad0
—~Ouerie A Developer's
Dan
Ehrmann
rv_$1008
Reference
NATIONAL UNIVERSITY LIBRARY SAN DIEGO
QA
76.9
Ehrmann,
Paradox
D3
E354
1993
Dan.
queries
NATIONAL UNIVERSITY LIBRARY 4007 CAMINO DEL RIO SOUTH SAN DIEGO, CA 92168
DEMCO
A Developer's
Reference
Digitized by the Internet Archive in 2023 with funding from Kahle/Austin Foundation
https ://archive.org/details/paradoxqueriesdeOOO00ehrm
DBMS Magazine’s Database Foundation Series
Paradox ueries A Developer's
Reference
NATIONAL UNIVERSITY
LIBRARY
SAN DIEGO
The definitive reference to queries in custom applications Dan
Ehrmann \NCZ
sg. [—]
[—]
MST?3 = 5
co (—' = =~ cs
M&T Books A Division of MIS:Press
A Subsidiary of Henry Holt and Company, Inc. 115 West 18th Street New York, New York 10011
© 1993 by Dan Ehrmann Printed in the United States of America
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage and retrieval system, without prior written permission from the Publisher. Contact the Publisher for information on foreign rights. Limits of Liability and Disclaimer of Warranty The Author and Publisher of this book have used their best efforts in preparing the book and the programs contained in it. These efforts include the development, research, and testing of the theories and programs to determine their effectiveness.
The Author and Publisher make no warranty of any kind, expressed or implied, with regard to these programs or the documentation contained in this book. The Author and Publisher shall not be liable in any event for incidental or consequential damages in connection with, or arising out of, the furnishing, performance, or use of these programs. All brand names, trademarks, and registered trademarks are the property of their respective holders. Library of Congress Cataloging-in-Publication Data (to be assigned) Ehrmann, Dan Paradox Queries: A Developer’s Reference /Dan Ehrmann
p. cm. Includes index. ISBN 1-55851-311-6 96\
95-94
93
4321
Developmental Editor: Michael Miley Project Editor: Mike Welch Technical Editor: Dan Paolini Copy Editor: Peter Weverka Cover Design: Lauren Smith Design The sample tables used throughout this book are adapted from the tables shipped with Paradox for Windows. The original tables are copyright © 1985-1991 by Borland International, Inc. All rights reserved. Used with permission.
The Paradox for DOS Query Tools provided with this book are copyright © 1992-1993 by Kallista, Inc. All rights reserved. Permission is granted for purchasers of this book to use these tools on their own computers. The Paradox for Windows Query Tools described in Chapter 18 are Copyright © 1993 by Kallista, Inc. All rights reserved. Permission is granted for purchasers of this book to use these tools on their own computers and in custom applications that are distributed to other users.
The QuerySave to PAL Conversion script in Chapter 17 is Copyright © 1992 by DataStar International. Used with permission.
To my parents
who taught me how to read; everything follows from this point forward
a
ea
” =a
=e
=
-
re
*
oe
7
=eS9,
'.
— -
.
*
warns
os
i)
Ga
£m
7
ental, ea
,
o62
oh!
: =~
on
& i
Ts tate 2%
=>)
©&
Phy
06
r @@a6 oa
e@
Cae»
5
orn”
f=
e
ot
e&
.—
>
ae
cee 2
7
tema
“hk
me
=y*
_
Peli
ee
8
»
er
te
\ 6.4icee
;
(ws
.
@
Awad
@
Lay S68spr ea
s
» yaaa
hua
od Pa
@
mat
as
C=
fh)
medial gpcellanen
lend
@@
aet
t sobe
ifs eee ee oe gee 2
=
€ -
—
ie
a
tas.
F
Aes
=e
whe
fe
eas
=
a
:
‘ie
ton
oe
mA
@
a
aie
=)
«| bo tal)
=)
'digne ? we
teas
hy
>!
it
Gee to
we
ea) epee)
6) ott Ge
ae
(tiwwee
Mare
te
se
6 O45
és
ho »
oneal
= t: eo
oe
PORE:
PenGeoe Gil @ bape argam (i (igual | wy
ee
bine ier
——
.)
:
7 matiwt
A
o
—
oe
Contents at a Glance Preface by. Rob DickOrson....
City]
Using these examples, the query in Listing 2-3 can be coded slightly differently, as shown in Listing 2-4. Listing 2-4. Using MoveTo to reference tables and fields ; Script
Menu Menu
0204
{Ask} {Ask}
-
Query for selected customers whose orders were shipped on 6/8/19 via DHL
{Customer} {Orders}
MoveTo
[Customer(Q)
MoveTo MoveTo MoveTo MoveTo
[Name] [City] [State/Prov] [Phone]
MoveTo
[Orders(Q)
MoveTo
[Sale
Date]
MoveTo
[Ship
VIA]
MoveTo
[Total
; display ; display ->
Customer
No]
Example
Check Check Check Check ->
Customer
No]
query query
image image
"xxx"
"not
Example
blank"
"xxx"
TMS ftsh//S) IL "DHL"
Invoice]
Check
Do_It! MoveTo
"Customer(Q)"
ClearImage
; remove
query
image
MoveTo
"Orders (Q)"
ClearImage
; remove
query
image
If you ShowPlay this script, you will see that Paradox moves explicitly between the different tables and query images to fill in query criteria and clear images from the Workspace. It is always better to be explicit about where you want Paradox to go, and MoveTo provides the flexibility you need. When you are explicit, scripts are easier to read, especially when you come back to an application that is some months old. You also
43
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
have more flexibility to change the structure of your tables, since your queries no longer rely on the relative position of fields or tables. Note that, in the sample code above, there is no need to issue the command MoveTo
"Orders (Q)"
After the Customer query image is cleared from the Workspace, you will already be positioned on the Orders query image, and this is the image you want to clear anyway. But being specific adds virtually no overhead and, for all the reasons above, it is a much better approach.
Entering Literal Text The line following demonstrates the ability to type in literal text by placing it inside quotation marks in your script. When you are already in the field, this is the simplest way to enter text. MoveTo
[Ship
VIA]
"DHL"
You can also use the field specifier notation to enter a value into a field without actually moving to that field. Instead of moving to the Ship VIA field to enter “DHL”, you could use the following syntax: CSlentoy WAN]
=
Yea
This option only works for selection criteria, query keywords, and example elements. To check a field, you must still move to it. To reference the record number “field” for the Insert, Delete, Set, Find, and Fast operators, use [#]. So the following expression places the “Set” keyword in the leftmost column of the current query image: cial
Ss
geet
BEBE
eB
CHAPTER
2: QUERIES
USING
PAL
CODE
It’s an interesting curiosity that Paradox used to allow you to specify [Total
Invoice]
=
"Check"
and it would process the query as expected. This anomaly was “fixed” in Version 3.0. The PAL command Typeln provides another option. With this command, you are telling Paradox to explicitly type the following text into the field: MoveTo
[Ship
VIA]
TypeIn
"DHL"
In the above example, Typeln is not strictly necessary. But if the selection criterion is a variable, then Typeln is the only option available. Variables in queries are discussed in the next chapter.
Entering Query Operators In a PAL-coded query, query operators are entered into the query as string literals. For example, if you want to query for customers where the Country field is blank, use the following code: MoveTo
[Country]
"blank"
As mentioned previously, if you want to query for a literal value which is the same as a Paradox query operator, you have to surround the literal with quotation marks. For example, to query for customers in Oregon, use the following code: MoveTo
[State/Prov]
"\"OR\""
Paradox types “OR” into the State/Prov field, which tells the program to search for customers in Oregon, and not to use the Or query operator.
45
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Paradox supports the backslash notation used in C and other languages to represent special characters inside a string. A backslash tells Paradox that the next character has a special meaning. Two examples are important in the current context:
¢ Use \" to indicate a quote, as in "John
\"Red\"
Smith"
¢ Use \\ to indicate the backslash itself, as in "C:\\DATA\\ORDERS\\"
Entering Example Elements Paradox is smart enough to know when an example element stops and the remainder of the query expression takes over. Example elements cannot contain spaces, and must consist entirely of letters and numbers. As soon as Paradox sees a different character—and assuming that you have followed correct syntax—it knows that the example element has stopped. To see this in action, perform the following test in which you will increase all stock prices by 10 percent. 1. Display a query image for the STOCK table, and move the cursor to the List Price field.
2. Press [F5] and type xxx. 3. Press the comma key. Notice that it is not highlighted, even though you didn’t do anything to tell Paradox that the example element was finished.
4. Type ChangeTo without the quotation marks. 5. Press [F5] and type xxx*1.1. Notice again that Paradox knows when the example element stops and the remainder of the expression continues.
46
BEeeeee
eB
CHAPTER
2: QUERIES
USING
PAL
CODE
When you implement this query in PAL, you can take advantage of this fact to minimize the typing required. This query is shown in Listing 2-5. Listing 2-5. Typing example elements into Paradox ; Script
0205
-
Increase
Menu {Ask} {STOCK} MoveTo [List Price]
stock
prices
Example
"xxx,
lpdenioulys:
Vowioe
by
10%
ChangeTo" we ake
sh!
Donwe?
ClearAll
In each case, Example tells Paradox to press [F5], and the script then types an expression. Paradox turns off the highlight after the example element xxx without the need for any further instructions. Using direct assignments in queries
Another approach uses direct field assignment to write selection criteria, keywords, and example elements into the query. Criteria and keywords are written using expressions similar to the following examples: (Country ] =
"blank"
Psalleswatre | esa
>— dre
=~STARTDATE,
Date
< ~ENDDATE
|
EndQuery DO meitel!
When you use wildcards of any kind, they must be placed outside the tilde variable. If you know that wildcards will always be a part of the query, you should place them in the field as a selection criterion, as shown in Listing 3-6. But if they may be specified by the user as an optional criterion, you are better served by using PAL code to place them. As you have seen in this discussion, tilde variables are relatively limited. They cannot be used to substitute for a table name in a query, nor for a field name. For example, you cannot use a tilde variable to select the Ship Date or Sale Date field at runtime. Tildes are only suitable when you are querying for a consistent exact value each time you perform the query. If your selection criteria are more varied, a PAL approach to entering variables is easier to manage and debug. Many developers don’t use tilde variables at all, but prefer instead to take an exclusive PAL approach for the greater control that it provides. Querying for an actual tilde One final problem with tilde variables occurs if your data contains the actual tilde character and you need to query for it. Whenever Paradox sees a tilde in a query image, even inside a string, it looks for a variable with the name of the following characters. So if you need to search for all records containing the sequence ~ABC you cannot use the criterion 5
| SINBIOUNG 6
because Paradox will look for a variable called ABC, even though the character sequence is contained within quotation marks.
62
BEBBEBeBee
Bw
CHAPTER
3: VARIABLESIN QUERIES
The solution to this small dilemma is to create a variable and assign it the character sequence. For example, enter Roan * EC
Then use the following variable in the selection criterion as a tilde variable: 50 Pheu
Because Paradox performs the substitution internally and the ~ABC never appears in the field, Paradox will search for the correct value. This technique can be applied generally to every situation where you need to search for a special formatting character that might be misinterpreted by Paradox.
Variables Using PAL Entering variable selection criteria using PAL requires the Typeln command. This command expects one parameter, the value to be typed into Paradox. For example, when you issue the commands SHilne MB rrH. 1
Sra
MoveTo
[Ship
TypeIn
SHIP.METH
VIA]
you are telling Paradox to type the current value of the variable SHIP.METH into itself. When you type into a query image, Paradox displays the value of the variable. Listing 3-11 demonstrates how to use this approach with the query in Listing 3-4. In a real-world application, the variable would likely be defined elsewhere in the program, or typed in by the user, or selected from a table. Listing 3-11. Using the Typeln command to type a value into Paradox DESC.CLASS
=
"Air
Regulator"
Query
Stock
| Stock
No
;
| Claeielle
I
[Equipment
Class]
EndQuery
MoveTo
TypeIn
DESC.CLASS
Do_ te!
63
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
G@
Unlike tilde variables, the selection criterion is actually typed into the query. So you don’t need to worry as much about wildcards, because Paradox interprets wildcards in the selection criterion as wildcards and not as literals. Listing 3-12 shows this approach with the query in Listing 3-8. This time the query returns the expected result—all orders placed in January of 1990. Listing 3-12. Using Typeln to enter a variable criterion into Paradox WING OVNABE,
AI
ASNT
Query
Orders
; Order Check
No
!
EndQuery MoveTo [Sale
Date]
TypeIn
MYDATE
Dowit
As I noted in Chapter 2, you can also use direct field assignment to write an expression into a field in a query image. To enter the value of a variable into a field, use code like the following: SHIP.METH = "DHL" [Ship VIA] = SHIP.METH
Handling reserved words, commas, and single quotes There is a potential problem with this approach if a variable is the same as a reserved word in Paradox. The classic example, one you have seen before, is asking for customers located in Oregon. Listing 3-13 shows a query that will fail because of
a formatting error. Listing 3-13. An invalid query: Paradox reports a formatting error because the Or keyword is misused. WHICH.STATE
Menu
{Ask}
Check Moveto Do_It!
64
=
"OR"
{CUSTOMER}
[State]
Typein
WHICH.STATE
BEeSBSBeEeee
Bw
CHAPTER
3: VARIABLES
IN
QUERIES
A related problem occurs if the search criterion includes commas. Listing 3-14 shows another query that fails because Paradox does not interpret the selection criterion as intended. Listing 3-14. This query is valid, but no matching records are found because of the embedded comma. CUST.NAME = "Diving Adventures, Inc." Menu {Ask} {CUSTOMER} Check Moveto [Name] Typein CUST.NAME DoLit
In this example, Paradox returns an empty ANSWER table. The reason is that the value typed into the Name field is Diving
Adventures,
Inc.
Paradox thinks that this is two selection criteria separated by a comma. Paradox interprets this as an And condition and looks for records where the name is Diving Adventures and, at the same time, where it is Inc. This is patently impossible. What you want instead is for Paradox to treat this as one selection criterion. To force this behavior, you need to surround the criterion with quotation marks, like so: "Diving
Adventures,
Inc."
To do this in your PAL code, use the backslash technique vious chapter. Surrounding the value being typed in is a string tation mark. Paradox requires you to precede that mark with it can clearly interpret where the string starts and ends. This
introduced in the preconsisting of one quoa backslash (\) so that technique is shown in
Listing 3-15. Listing 3-15. Using backslash notation ensures that the embedded comma is treated as a literal. CUST.NAME = "Diving Adventures, Menu {Ask} {CUSTOMER} Check Moveto
[Name]
Typein
"\""
Inc."
+ CUST.NAME
+
"\""
Do_It!
65
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
This technique also works for the previous example, since the State is typed in as “OR” and Paradox does not confuse it with a query keyword. In general, if there is any chance that the field contents will be confused for reserved words, or that formatting characters will cause Paradox to misinterpret the selection criterion, you should use this technique. One potential problem occurs if the value typed into the Name field includes a single quote, usually by mistake. Paradox will type "Diving
Adventures"
Inc."
into the query image, and then display an error message complaining about a missing right quote. If this is a concern, you can use the Match() function to test for the quote, and then precede it with a backslash character, as shown in Listing 3-16. Listing 3-16. Testing for an embedded double quote before typing a value into a query image Menu
{Ask}
{CUSTOMER}
Check MoveTo
[Name]
; test for the " character ; if found, create BEG.VAL LEeMatcehi(yCUST Gy pew
NAME,
S\Se
in the middle of the string and END.VAL on either side
"i Nu.
BEG =VAI
tae
BEG AVAL,
JENDE VAL)
NON Nese ee END) enV
then
Actos
Else
TypeIn EndIf
"\""
+ CUST.NAME
+
"\""
Dowit:!
In Listing 3-16, Paradox splits the value into BEG.VAL and END.VAL if there is a double quote in the string. It then reassembles the value, placing a \” in the middle of the string. Paradox will then read the double quote as part of the text, and not as the end-of-string delimiter. (This technique was first described to me by John Moore, a respected Paradox developer in Southern California.) Other useful PAL commands
Table 3-1 shows some other PAL commands that are useful when you are programming variable information into your queries. With these commands in hand, you 66
SESE
eEe
ee
Bw
CHAPTER
3: VARIABLES
IN
QUERIES
can begin to explore more complex examples of variable queries. (Note that most of these commands have many more options than are discussed here. See your PAL manual for a detailed review of their features and limitations.) Table 3-1. Useful PAL Commands when you are Programming Variable Information into Queries Command
Description
ShowMenu
Allows you to display a custom menu and have the user make a selection from it. To use this command, you will supply a list of menu choices and descriptions, together with a variable to which the selected choice is assigned.
Paradox 4.0 also supports the ShowPopup and ShowPullDown commands to create modal and nonmodal menus.
Accept
Allows you to pause Paradox while the user enters a value that consists of a number, date, or text string. When the user presses [Enter], the value is assigned to a variable you define. You can also press [Esc] to cancel the operation.
If Then
Allows you to perform a logical test (this command is a
Else
fixture in virtually every programming language). If the result of the test is True, Paradox will branch to a desig-
nated block of code, but if the result is False, you can
EndIf
optionally branch to a different block of code.
Switch / Case / EndSwitch
Performs a multibranch test, allowing you to determine one among many outcomes and branch to a block of code associated with that decision.
Wait
Allows you to display a table and have the user browse through it without the script maintaining complete control. When a designated key is pressed, the script takes over
again, and can read information from the table. (In Paradox 4.0, this command also responds to mouse events, sys-
tem-generated actions, and table-based triggers.)
67
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@&
Querying When Fields May Vary The PAL approach to variable queries is necessary when the field being queried may itself vary. This will happen if you have related fields in a table and give the user the option of querying one or the other. For example, suppose that you wanted the ability to search the ORDERS table based on date. However, ORDERS contains both a Sale Date and a Ship Date field, and you might know one but not the other. The example is shown in Listing 3-17. Listing 3-17. Prompting the user for the date field to query ShowMenu "SaleDate" "ShipDate" to
QUERY.DATE
If
QUERY.DATE
; field : : =
"Query "Query "Esc"
on on
the the
date date
Then
of of
to
query
sale", actual
shipment"
; user
pressed
[Esc]
Return
EndIf Clear @ 0,0 ?? "Specify Date: " Accept "D" to DATE.VALUE If RETVAL = False Then
; ; ; ;
blank the screen prompt wait for date user pressed [Esc]
Return
Endlf Menu {Ask} {ORDERS} Check If QUERY.DATE = "SaleDate" MoveTo [Sale Date] Else MoveTo [Ship Date] EndIf TypeIn
StrVal
(DATE. VALUE)
Then
; load query image ; check all fields ; jump to query field
; convert
to
string
IDoyeeliie.!
; process
query
MoveTo "ORDERS (Q) " ClearImage
; back to query ; clear it away
image
This script uses a ShowMenu to select the field to query, and an Accept statement to prompt for the date. Armed with this information, the script can jump to the appropriate field and enter the date as a selection criterion. You have to convert the date to its string equivalent using the StrVal() function. In a query image, every selection criterion and keyword is text and must be treated as such for Paradox to allow it to be entered. 68
CHAPTER
3: VARIABLES IN QUERIES
Querying with a sometime condition
A related example occurs when a selection criterion is only in effect some of the time. You may provide the ability to limit the query in different ways, but only based on a specific choice by the user. For example, suppose you want to query for customers who have placed orders for a specific stock item. But the user also needs the ability to limit the query at times to US customers, instead of all customers. The code to perform this query is shown in Listing 3-18. Listing 3-18. Querying with a condition that is only in effect some of the time ; stock
ID
ORYSTOCKs
-
=
normally
specified
by the
user
13 642
ShowMenu "Yes"
:
"Do
you
want
US
customers
only?",
"No"
: "Do
you
want
US
customers
only?"
"hse"
‘then:
| Order | _XXX
No
Go: US. CUST int US. CuSi==" Return
EndIf Query
Lineitem
Orders
| Order | _XXX
Customer
No
| Customer | -222Z
|
Stock No | ~QRYSTOCK
; Customer Pez zz, No
|
|
No
|
; Name | mGhecken
EndQuery
; only
enter
the
condition
if
"Yes" then Ihe WS (CUSN MoveTo [CUSTOMER -> Country]
the
user
asked
for
it
Us Srvc
EndIf
Do_It!
In Listing 3-18, a menu is used to determine if the query will be limited to US customers, but many other methods could be used. After the query images are displayed 69
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
on the Workspace, Paradox tests the menu variable. If it is “Yes”, then the query is restricted by placing the criterion “U.S.A.” in the Country field. If it is “No”, nothing is done and this field does not limit the query in any way. Linking sometime tables to a query You can extend this example one step further to only link a table into the query under certain conditions. Assume for a moment that you want to find all customers who placed orders on a specific date, perhaps to conduct a survey. But you might provide the additional ability to limit the query to a specific stock item. Without this item, the query requires the CUSTOMER and ORDERS tables only; with this item, you also have to link in the LINEITEMS table. It is important to point out that the ANSWER table will have the same structure in both cases, because the Name field in CUSTOMER is the only one checked. This code is shown in Listing 3-19. Listing 3-19. The LINEITEM table is only linked to the query when requested by the user. ; order
date
ORD.DATE
-
normally
specified
by
the
user
= 2/15/91
ShowMenu
to ct
"OneProduct" "AllProducts" LIMITORY EMEPORYe=
: "Limit the query to one stock item", : "Do not limit the query; include all
—shse
items"
Vane
Return
Endlf
; 1f user ; table If
wants
and
LIMITQRY
View Wait
=
Lt
limit the
the
user
"OneProduct"
to
query, select
display a Stock
the
stock
item.
Then
"STOCK" Table
Prompt
Uncle
to
allow
"Select
hes
RETVAL
a Stock
Item.
[F2]
Continue
; 1d
for
[Esc]
Cancel"
Se. =
"Ese"
Then
ClearImage Return EndIf WHICH.STOCK ClearImage Endif
70
=
[Stock
No]
current
record
BEBeEeEee
; load
eB
query
CHAPTER
images
and
fill
them
3: VARIABLESIN QUERIES
in
Query
Orders
3 Customer pilings
Customer
=No-|)
; Customer bea
Salle Date’ | ~ORD.DATE
No
| |
| Name | Chechen
EndQuery
; add table if ; create links If
LIMITOQRY
MoveTo Menu
=
one product query was requested from new image back to original "OneProduct"
[ORDERS {Ask}
->
Order
ones
Then
No]
Example
"xxx"
{LINEITEM}
MoveTo
[Order
No]
Example
MoveTo
[Stock
No]
Typein
"xxx"
WHICH.STOCK
EndIf Domiity
MoveTo 1 While ImageType() ClearImage EndWhile
=
; first
image
; clear
query
"Query" images
only
In this example, the user first decides whether or not to limit the query to a single product. If the user chooses this option, the script sidetracks to display the STOCK table so that a single item can be selected. After the initial query tables are displayed and filled in, the script again checks to see if the query should be limited; if so, it loads the LINEITEM table and links it to the query as well. If you understand what Paradox needs to fill in a multi-table query image, you can set up the necessary commands in PAL with a little care. For example, notice that the code in Listing 3-19 only places an example element in [ORDERS -> Order No] if it will also link the LINEITEM table on this field. Notice also how the code removes only query images from the Workspace after the query was processed. This script does not care if the query required two or three images; they are all deleted.
71
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Querying for variable table names The PAL coded approach is also required if the table you want to query is variable. In Paradox for DOS, there is no way in a saved query image to specify a variable table name. But, using the PAL approach, you can easily tell Paradox which table to query. When you want to make a menu selection which is defined by a variable, Select is the PAL command you need. For example, the following code displays a query image for the ORDER89 table, containing backed up orders from 1989. QRY.TABLE
Menu
=
"ORDER89"
{Ask} Select
QRY.TABLE
You can also use string concatenation to allow only a portion of the name to be a variable, like so: ORY.YEAR
Menu
=
"89"
{Ask} Select
"ORDER"+QRY.YEAR
Note that the Select command is equivalent to Typeln plus Enter: QRY.TABLE
Menu
=
"ORDER89"
{Ask}
TypeIn
QRY.TABLE
Enter
Querying for ranges Asa final example, Listing 3-20 shows a script that prompts for a date criterion. It allows the user to either specify a date and whether to query for records that equal that date or are greater than or less than that date, or to skip the date selection completely. This example also demonstrates how to use variables to represent query operators including the range operators. Listing 3-20. Using a menu to define the type of range that is entered into the query ; select
type
of
date
criterion
ShowMenu
"GreaterThan" "LessThan"
72
: "Select : "Select
orders orders
greater than the specified date", less than the specified date",
BEBE
ee
to
ee
eB
CHAPTER
"Between" "EqualTo" "NotEqualTo"
: : :
"Select "Select "Select
orders orders orders
"SkipDate"
:
"Ignore
date
3: VARIABLESIN QUERIES
between a specified date range", equal to the specified date", not equal to the specified date",
as
a
selection
criterion"
USE.DATE
iieISHS DAT
=
Mase e hen:
; user
canceled
Return Endlf If
USE.DATE "SkipDate" Then Clear @ 0,0 ?? "Specify Date: " Accept "D" to THIS.DATE If
RETVAL
=
False
Return Else THiS. DALE
or
; ; ; ;
THIS.DATE
=
prompt for date blank screen text wait for user entry
BlankDate()
; user =
ourVadl (LHES
DATE)
Then
cancelled
; Convert
to
text
EndIf EndIf If
USE.DATE = "Between" Then Clear @ 0,0 ?? "End Date: " Accept "D" to END.DATE If RETVAL = False or END.DATE = Or END.DATE
Case
USE.DATE TypeIn ">"
Case
USE.DATE
TypeiIn
"
4!
pre
oe
=
-
=
LJ
he fentenaeing
@& ee
ahi,
or y
hau, progream
,
tt ging
eth Tearulte by
2qQs_G ‘ewmnag Ge cutee
eww etn
it
>
on + oqrtasend Horexample
Rdibding Preisecs & DRUM, tr
es
:
a
if
ate
pe eene ieThvshity
e-
7CAR
Ula
:
7
™
THe
=a
genes
a, edi PR nlew
ne
ots oth scatoneau bbartan 90d.ai the
«9 gobeaes or qed
a,
ef
HI ert
tateh tae bug a
Oy eat
Yrares
ou)
bee bia Sty\ wR. ingg nts. Ral
al =
oe
YS,
onaNie ang
|
CHAPTER
SIX
Understanding QBE Files Before you can include queries in your Paradox for Windows applications, you need to understand how this version saves queries and the various ObjectPAL methods you can use to execute these queries.
The Components of a QBE File Like Paradox for DOS, Paradox for Windows allows you to save a query so that it can be replayed at any time. When you select File / Save or File / SaveAs from the menu while the cursor is in a query window, Paradox prompts you to specify a file name. If you attempt to close a new query, Paradox will prompt you with the same dialog box, shown in Figure 6-1. Saye File As
€:\book\tables\...... SZ
Figure 6-1. The Save File As dialog box
111
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
After you specify a file name and click on the OK button, Paradox saves the query to a file with the .QBE extension. This is a text file and it follows a special format. Consider the query shown in Figure 6-2, which joins four tables in the database to extract customers who have ordered a particular stock item. Figure 6-3 shows the 0602.QBE file opened in the Windows Notepad.
Query : 0602.QBE
Table : :PRIV:ANSWER.DB
Figure 6-2. A query to find customers who have ordered an item
Notice that the format of the QBE file is very similar to the QuerySave script format in Paradox for DOS. There are subtle and important differences, but the fundamental principles are the same. A QBE file contains the components described in the following sections.
112
Beeee
eB
CHAPTER
6: UNDERSTANDING
QBE
FILES
Notepad - 0602.QBE | File
Edit
ANSWER: SORT:
Search
Help
\BOOK\TABLES\TEST .DB CUSTOMER .DB->"State/Prov",
CUSTOMER.DB
| Customer
No
| _EG61 ORDERS.DB
| Order
No
| _EG62
LINEITEM.DB STOCK.DB
CUSTOMER.DB->"City",
CUSTOMER .DB->"'Name",
| Name
| City
| State/Prov
|
| Check
| Check
| Check
|
| Customer
No
| _EG61
|
|
| Order No | Stock No | | _EGe2 | _EG63 |
| Stock
No
| _EG63
| Model
|
| D-356
|
EndQuery
Figure 6-3. The saved QBE file
The Query and EndQuery keywords Query is the ObjectPAL keyword that tells Paradox for Windows that what follows until the EndQuery keyword is a query definition. When Paradox sees this keyword in a QBE file, it opens a new query window, loads the necessary tables into this
window, and defines the various query elements. Blank lines Each query image in the script is separated from adjoining query images, and from the Query and EndQuery keywords, by a blank line. This is Paradox’s message telling itself that a new table is about to be specified. If you delete a blank line and try to play the query, Paradox will respond with various error messages, including Use only Insert, Delete or Set in leftmost column
and Expecting consistent number of columns in all rows of table
113
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
The table name For each image, the table name is the first thing that Paradox sees. Each time Paradox sees a table name, it checks if the table is already present in the query window. If it is, Paradox jumps to that table, since you can only have one query image for a specific table in each query at a time. If the table is not present, Paradox loads it into the query window.
Fields A vertical bar (I) is used to separate one field from the next. On the top line of each table image, Paradox lists the field name for each field with some kind of query operation in it. Notice that not every field from a table is listed; fields that are not used in the query are left out of the saved image. Notice also that at least one blank character appears on either side of the vertical bar. When Paradox sees a field in the table image, it jumps to that field and processes the check marks, example elements, selection criteria, and query operators found there. Check operators If Paradox finds any one of the keywords Check, CheckPlus, CheckDescending, or GroupBy in the field, it places the appropriate Check operator in the check window for that field. Query operators and selection criteria Query elements and selection criteria are placed literally into the field. If Paradox finds an operator under the table name in the saved query image, it is typed into the leftmost column of the query on the Desktop, exactly as you would expect. Example elements Paradox represents example elements by using a leading underscore and the exam-
ple letters or numbers.
Differences with Paradox for DOS If you read Chapter 2 of the companion volume, you know that Paradox for Windows adds a number of significant new capabilities to query processing, including the following:
114
BEEBE
eB
CHAPTER
6: UNDERSTANDING
QBE
FILES
e The name and subdirectory of the table where query results are placed. Unlike Paradox for DOS, you are not limited to ANSWER as the name of the results table. ¢ The table type. Query results can be placed in either a Paradox or a dBASE table, which are the two native formats supported in Paradox for Windows Version 1.0. e The sequence of fields in the results table. ¢ Field properties for each field in the results table, as well as grid, heading, and field title properties. ¢ Sort order for the records placed in the results table. Paradox for Windows allows you to save these changes, so that they are automatically used whenever the query is run. To modify every option except the last one, select Properties / Answer Table / Options... from the menu, or click on the Answer Table Properties speedbar button. Paradox will display the dialog box shown in Figure 6-4. Answer Table Properties
/
\TABLES\TEST.DB
Figure 6-4. The Answer Table Properties dialog box
115
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Specifying the subdirectory and table name By default, Paradox will place the following string at the beginning of the
QBE file: ANSWER:
:PRIV:ANSWER.DB
This parameter tells Paradox to place query results in the private subdirectory in a table called ANSWER. :PRIV: is an alias that represents the Paradox Private directory. (For more information on aliases, see Chapter 9.) If you specify a different alias or an explicit subdirectory, Paradox will use this name in the ANSWER: string. You can also specify any valid DOS file name as the table, but the extension is currently limited to either .DB for a Paradox table or .DBF for a dBASE file. For example, if you specify that query results be placed in a CUSTHOLD table in the working directory, Paradox will save the following ANSWER: string: ANSWER:
:WORK:CUSTHOLD.DB
Specifying the table type
If you click the Paradox radio button in the dialog box, .DB is specified as the file extension. If you click the dBASE radio button, .DBF is specified as the file extension. In addition, Paradox adds a new parameter to the saved query statement: TYPE:
DBASE
Handling field sequence and properties
If you modify the sequence of fields in the ANSWER table, then save the query, Paradox creates a ResultTable.TV file in the same subdirectory as the designated result table, even if this table does not yet exist. For example, if you specify that query results be placed in: WORK:CUSTHOLD.DB, Paradox will create a CUSTHOLD.TV file in the working directory. When you run the query, the settings in this TV file are used to format and arrange the result fields. The TV file is in a binary format and cannot be directly modified. If you need to make changes, you should load the query, update its settings using the dialog box, and resave the query.
116
BEB6e
eB
CHAPTER
6: UNDERSTANDING
QBE
FILES
When you modify properties, such as field alignment, number or date format, color, or font, these settings are also saved into the .TV file.
Handling sort order Unlike Paradox for DOS, Paradox for Windows separates the sequence of fields in the ANSWER table from the sort order used to control the sequence of records. To modify the sort order for query results, select Properties /Answer Table / Sort. Paradox will display the dialog box shown in Figure 6-5.
State/Prov
Figure 6-5. The Sort Answer dialog box
If you specify a different sort order as shown in the dialog box, then save the query, Paradox adds a new parameter to the saved query: SORT:
CUSTOMER.DB->"State/Prov",
CUSTOMER.DB->"City",
This parameter lists each sort field in turn, together with the table name and type from which this field originates. The dash-arrow notation is borrowed directly from
117
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
&
Paradox for DOS. If a linked table is stored in a different subdirectory, Paradox will preface the directory name or alias in front of the table, like so: SORT:
:HISTORY:CUSTHIST.DB->"Archive
Date",
If you sort a query by a calculated field, Paradox uses the field where the calculation is being performed in the SORT: expression. Consider the query in Figure 6-6, which asks for the count and value of all orders grouped by customer. The Sort Answer dialog box specifies a sort by the No of Orders and then by the Customer Sales fields in that order.
Figure 6-6. Querying for customers and the orders they have placed
When you save this query, Paradox generates the QBE file shown in Figure 6-7. This file includes a SORT: expression for each of the two calculations, using the Total Invoice field in the sort expression. Note that Paradox does not appear to distinguish between the two calculated fields in any way. When you run this query, Paradox does not sort the result in the order you specified. Instead, Paradox sorts by the Customer Sales field followed by the No of Orders field. This anomaly represents a minor bug in Paradox’s handling of SORT: expressions.
118
BAe
Be Bw
CHAPTER
oa File
6: UNDERSTANDING
QBE
FILES
Notepad - 0606.QBE Edit
Search
Help
Query ANSWER:
:PRIU: ANSWER .DB
SORT: ORDERS .DB->"Total CUSTOMER .DB->""Name",
CUSTOHER.DB
Invoice",
| Customer
No
| _EG#1 ORDERS .DB
| Customer
No
| Total | Calc
| Name
|
| Check
|
Invoice",
|
| _EGs1 ORDERS .DB
ORDERS.DB->"Total
| Invoice Sum
As
“Customer
Sales",
Calc
Count
All
as
“No
of
Orders’
EndQuery
Figure 6-7. Paradox’s saved QBE file for the query in Figure 6-6
Running a Query Unlike Paradox for DOS, you cannot simply place a Do_It! or RunQuery command after the saved query in the QBE file and expect Paradox to load the query, run it, and automatically display the results. Since ObjectPAL does not drive the user interface the way PAL does, an ObjectPAL script will not perform the same operations that you would perform if you ran the query interactively. To run a saved query, the simplest technique is to use a script, which is a small ObjectPAL program. From the menu, select File /New / Script to create a new script. To open an existing script, select File /Open / Script or click on the OpenScript icon (the one showing three objects in a loop). You can also right-click on this icon and select New or Open from the popup menu. When you open a new script, Paradox displays an empty script window, as shown in Figure 6-8. Within the script, Paradox places the run() method that tells Paradox to run the ObjectPAL commands you type into the window.
119
PARADOX
QUERIES:
A
ee method
DEVELOPER’S
REFERENCE
@
@
@
@
#Scriptl::run run(var
eventInfo
Event)
endmethod
Figure 6-8. A new script window
The Script menu When the script window is displayed, Paradox adds a number of new options to the menu and changes the speedbar. (These options are also available when you are editing ObjectPAL code in a form or a library window.) The following capabilities will be useful as you begin coding in ObjectPAL:
e Select Language / CheckSyntax to verify that the instructions you have typed are correctly formatted. Paradox will flag any errors that would prevent the script from running. You can also click on the check mark speedbar icon to perform the same operation. ¢ Select Debug / Run to run the script. Paradox will first check the syntax and, if everything is correctly formatted, will process your instructions. You can also click on the lightning bolt speedbar icon to perform the same operation. (Pass the cursor over this icon and notice Paradox’s prompt on the bottom line: “Run this form.” Internally, a script is simply a special kind of form.) e When you run a script, it is not saved permanently; you must still explicitly save it using either the File / Save or File /Save As menu choice. You can also click on the return arrow icon to save and exit the editor.
¢ On the Language menu, select Keywords to list basic language elements. Select Types... to list the different object types, and the methods and pro-
120
Bee
e
eB
CHAPTER
6:
UNDERSTANDING
QBE
FILES
cedures available for each. Select Constants to display a dialog of ObjectPAL constants. executeQBEFile(): Running a saved query automatically To run a saved query automatically, use the procedure executeQBEFile(), which
is a member of the Database type. (In ObjectPAL, a procedure is a method in which the object type is implied and is not required.) In its simplest form, this procedure has the following syntax: executeQBEFile(
"Filename.QBE"
)
This procedure returns logical True if the query file was found and run successfully, or False if the query could not be run. Listing 6-1 shows a simple script that uses this procedure. The msgInfo() procedure is used to inform the user what happened when the query file was executed. Listing 6-1. A script to run a query and report on the results ; Script method
0601.SSL
run(var
-
Run
eventInfo
a query Event)
If
executeQBEFile("0606.QBE") Then msgIinfo("Query Results", "Query ran successfully") Else msginfo("Query Results","Query 0606.QBE not found") EndIf endmethod
When you run this script, Paradox displays the message shown in Figure 6-9. The 0606QBE file exists and is correctly formatted, so the query will be executed. However, notice that the ANSWER table is not displayed by Paradox. It has been created on-disk but Paradox does not open a table window for this table and make it the current window. As I mentioned before, such behavior is appropriate in Paradox for DOS, where PAL drives the user interface. But this is not the model in Paradox for Windows, so the response is not appropriate.
121
PARADOX
QUERIES:
=
A
DEVELOPER’S
REFERENCE
@
@
@
@
Query Results Queryransuccessfullp
Figure 6-9. The result of running the script in Listing 6-1
Viewing the ANSWER table
If you want to view the ANSWER table, modify the script as shown in Listing 6-2. The revised script starts by defining a TableView variable, which serves as a way to reference this TableView object within your script. After the query has been run successfully, the script opens a TableView of the ANSWER table and then ends. You are left with the table displayed on-screen. Listing 6-2. Viewing the ANSWER table after a query ; Script method
0602.SSL
run(var
-
Run
eventInfo
a query
and
view
the
ANSWER
table
Event)
var ans
TableView
endvar If
executeQBEFile("0606.QBE")
Then
ans.open(":priv:ANSWER.DB") Else
msgInfo("Query EndIf endmethod
122
Results",
"Query
0606.QBE
not
found")
BeSBBe
eB
CHAPTER
6: UNDERSTANDING
QBE
FILES
If you want the user to browse the TableView and then close it, all under script control, add the following two lines after the ans.open() line: ans.wait() ans.close()
The wait() method suspends the script and allows the user to wait modally on the table. Script execution does not continue until the user closes the window. But if you opened a table under script control, you must also close it under script control, even if the user performed the standard Windows operation to close the window. With the close() method, ObjectPAL is closing the table and not the window.
Attaching a Query to a Form Button Most applications in Paradox for Windows are built around forms because they are the only design object that accepts ObjectPAL code. In addition, users spend most of their time interacting with the application while viewing a form. Paradox makes it easy to attach custom code to a form. For example, button objects have an attached pushButton() method which will execute when the button
is clicked. If you include the code to run a query in this method, the query will fire automatically when the button is clicked.
Sequencing Queries Running two or more queries one after the other is easier in Paradox for Windows than in Paradox for DOS because there is no impact on the screen. You don’t have to worry about clearing ANSWER tables and query images from the Workspace. Figures 6-10, 6-11, and 6-12 show a sequence of three queries to delete orders prior to 1990 from the ORDERS table, sum the Total Invoice field by Customer, and update the Orders To Date field in the CUST_SP table.
123
PARADOX
QUERIES:
see
A
DEVELOPER’S
REFERENCE
@& @
@
@
Query : 0610.QBE oe a Date
fn "Total
CUSTOMER.DB
| Customer | _EGO1
ORDERS.DB
; Customer 1 —EGO1
Invoice"
No
| Name ; Check
No
4
; Total Invoice | Cale Sum As "Customer
Sales"
EndQuery TVAns.open(":PRIV:ANSWER. DB")
endmethod
Unlike Paradox for DOS, you cannot simply place a query in your code and expect it to run without modification. The reason has to do with the underlying Paradox for Windows design. ObjectPAL is an object-based programming language designed to work in the object-based and event-driven environment of Windows and Paradox. Everything in ObjectPAL is an object, including a table, form, field, literal text, button, or string. Objects have properties, which can be modified directly, and methods, which are built-
in behaviors that control the default behavior of the object and can be customized by the programmer. Some methods are part of the ObjectPAL Runtime Library and are associated with a particular object type. Methods serve the same role in ObjectPAL as commands and functions in traditional programming languages. Queries are also objects and have to be referenced using a query variable. You define a query variable using the following notation at the beginning of a script or method: var
MyQry
Query
endVar
The var-endVar construct defines a block of variables of different types. MyQry is the name of the query and Query is its type. In ObjectPAL, variables almost always have a predefined type (although you can also define or create a variable with AnyType).
128
@
@
CHAPTER 7: DEFINING QUERIES
WITHIN
YOUR
CODE
Listing 7-2 shows the modified script from Listing 7-1, using the query variable to reference the query object. When you check the syntax of this script, Paradox reports no syntax errors.
Listing 7-2. Using a query variable to reference a query object ; Seript method
0702.SSL
run(var
-
Referencing
eventInfo
a query
with
a query
variable
Event)
var
MyQry
Query
TVAns
TableView
endvar MyQry
=
Query
ANSWER:
SORT:
:PRIV:ANSWER.DB
ORDERS.DB->"Total
CUSTOMER.DB
; Customer
| _EGO1 ORDERS.DB
| Customer
Invoice"
No
|; Name
bh Checkus| No
1) Seon
| Total
| Calc
Invoice
Sum As
"Customer
Sales"
EndQuery TVAns.open(":PRIV: ANSWER. DB")
endmethod
However, when you save and play this script, the ANSWER table is not created or displayed. The syntax shown in Listing 7-2 simply defines a query object in memory. It does not execute that object or run the query. This behavior is very similar to Paradox for DOS, which requires a Do_It! command to run the query.
Running the Query To run a Paradox for Windows query defined by a query variable, use the method executeQBE(). This method has a number of options, but the simplest syntax is QueryVar.executeQBE( )
129
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
8
@
@
The executeQBE() method is of the Query object type. Whenever you have a query object, usually defined by a query variable as shown above, you can tell this object to execute itself, and Paradox will run the query for you. To associate a method with an object of the appropriate type, you use the familiar dot notation. The above example is translated as follows: For the query object defined by QueryVar, issue the query method executeQBE(). Paradox uses the ANSWER table name and settings defined as part of the query. Listing 7-3 shows the revised script with this command included after the Query ... EndQuery. Listing 7-3. The executeQBE() method runs a query defined by a query variable. method
run(var
eventInfo
Event)
var
MyQry
Query
TVAns endvar
TableView
MyQry
= Query ANSWER: SORT:
:PRIV:ANSWER.DB ORDERS.DB->"Total
CUSTOMER.DB
ORDERS.DB
; Customer | SEGOL | Customer
Invoice"
No
No
1 GOL
; Name 4 (Clavexelle ; Total
| Calc
Invoice
Sum As
"Customer
Sales"
EndQuery
MyQry .executeQBE ( ) TVAns.open(": PRIV: ANSWER .DB" )
endmethod
This script performs the following operations: 1. It starts by defining two variables: MyQry is a query and TVAns is a TableView. 2. The next block of code creates a query object and assigns this object to the query variable MyQry. You can now use this variable as a handle to reference the query object. 130
@
@
CHAPTER
7: DEFINING
QUERIES
WITHIN
YOUR
CODE
3. The next command executes the query defined by MyQry. 4. Finally, Paradox opens the ANSWER table in a TableView window, using the variable TVAns as a handle to reference that Table View object. Alternate syntaxes for ObjectPAL methods Borland has provided an alternate syntax for many ObjectPAL methods. Every object type has methods that belong to that object. executeQBE() is a method of the Query object type, while open() is a method of the TableView object type (and many other types as well). To see a list of object types and the methods associated with each, select Language / Types... while the cursor is in a script, method, or library window. Figure 7-1 shows the dialog box displayed by Paradox, with object types listed on the left side, methods on the right, and a syntax example for each method along the bottom. Types and Methods
Figure 7-1. The ObjectPAL Types and Methods dialog box
131
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Notice that many methods are listed more than once in the list box on the righthand side. Each entry represents an alternate syntax, which you can see in the lower box. For example, the executeQBE() method is shown with four alternate syntaxes. Syntax
Explanation
QryVar.executeQBE()
In this option, Paradox runs the query as defined in the query object definition. Query results are placed in the ANSWER table
or in a different table based on the specified name in the query definition. Paradox returns a logical result to indicate if the query
completed successfully. QryVar.executeQBE(
"AnswerTable"
)
In this option, you specify the name of a table for query results as a string or string variable. Paradox uses this name in place of
any table defined as part of the query object. QOryVar.executeQBE(
TableVar
)
In this option, you specify the name of a table object that has previously been defined, and Paradox places the query results into this table. Note that the table itself is not required to be predefined, only the variable referencing this table. QryVar.executeQBE(
TCursorVar
)
In this final option, you provide the name of a TCursor object that has been previously defined, and Paradox places the query results into this TCursor. A TCursor is a pointer into a virtual table that exists only in memory. When you specify a TCursor, no data is written to disk. TCursors are excellent tools when you
need to manipulate data without it being seen by the user. For example, you may need to perform a query to generate a list that
is then read into an array or dynarray for further processing. The user does not need to see this intermediate ANSWER table, and
writing the results directly to a TCursor solves this problem.
132
@
@
CHAPTER
7:
DEFINING
QUERIES
WITHIN
YOUR
CODE
For example, Listing 7-4 shows a query to list customers who have ordered a specific stock item, with the results placed in a table object as shown in the third syntax. This script uses the attach() method to associate a table variable with the specific table name you wish to use. The expression RESULTS. attach (":WORK:CUST1351.DB")
tells Paradox to attach the table name CUST1351 in the working directory to the RESULTS table variable. This table does not have to exist; Paradox will create it if necessary when the query is processed. Listing 7-4. Query results are placed in a predefined table object. ; Script method
0704.SSL
run(var
-
Querying
eventInfo
into
a table
Event)
var
MyQry
Query
Tbl
Table
endvar
MyQry
= Query ORDERS.DB
|! Order
No
1 Heo! LINEITEM.DB
| Customer
No
|}
1 elisa
| Order 1 JEGOD
No
| Qty teeike
Shin ce
Miteteell
Wiemiea
|
EndQuery MolmabeachiguiwOnk CUS TES 512 DB)")
MyQry.executeQBE(
Tbl
)
endmethod
133
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
&
@
@
@
Understanding Procedure Notation If you review the Types and Methods dialog shown in Figure 7-1, you will notice that Paradox often lists elements called procedures after the method elements. Procedures were added to ObjectPAL to make the language more familiar to users of traditional programming languages such as PAL. Procedures perform the same operations as methods, but their syntax is closer to the functions or commands that many programmers are more comfortable using. For example, when you use executeQBE() as a method, the syntax is QryVar.executeQBE(
"TableName"
)
When you use executeQBE() as a procedure, the syntax is executeQBE(
QryVar,
"TableName"
)
Both options are equally valid, although the first is more object-based in keeping with the Paradox for Windows style and approach. (Note that the procedure notation for executeQBE() is not listed under Query in the Types and Methods dialog box. This is a minor documentation error.)
Reading a Query into Your Code Typing the exact syntax required for a saved query is a difficult task. All versions of Paradox are very particular about the formatting required and will refuse to run the query if the format is incorrect. A better approach is to create the query interactively, then save it to a QBE file. You can then edit a script or method and read the saved query into your code. To perform this operation:
1. Select Edit / Paste From File from the menu. Paradox will prompt with the dialog box shown in Figure 7-2. 2. Type the name of a saved query, including the QBE extension, and click OK. Paradox will merge the query definition into your code at the cursor position.
134
M@ @
CHAPTER
7: DEFINING
QUERIES
WITHIN
YOUR
CODE
Paste From File
WORK:0706.QBE|
Figure 7-2. Pasting a query file into the script at the cursor position
Writing a QBE File You can also perform the opposite operation: saving a query defined in your code as a query file. Another method available for the query type is WriteQBE(), which takes a query object and writes it to disk as a file with the .QBE extension. An example is shown in Listing 7-5. Listing 7-5. Writing a defined query object to disk as a QBE file 7 Script
met ned
0705:SSL
run(var
="Writing
eventInfo
a OBE)
frie
Event)
var
QryVar
Query
endvar QryVar
=
query
Orders
; Order
Ee Lineitem
Ou
No
| Customer
No
|
| Check
; Order No hie OA
; Stock 1 a!
No
|
endquery QryVar.writeQBE
("0705")
endmethod
135
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
8
8
@
@
Note that you do not need to specify the file extension because Paradox adds the .QBE automatically. You can actually specify any file extension, but Paradox will not be able to reference the file later unless it has the correct extension. If the file exists,
it will be overwritten without Paradox prompting for confirmation. WriteQBE() is very useful when your application includes an ad hoc query system. It allows you to provide a facility where users can define a query and then save it to be used again and again. Note that procedure notation is also valid for writeQBE(), even though it is also not listed in the Types and Methods dialog box. Paradox accepts the following syntax without complaint: writeQBE(
QryVar,
"0705")
Referencing Queries with Query Strings Paradox for Windows provides a second technique to reference queries within your applications. A Query string has the same syntax as a Query...EndQuery statement, but it is defined as a text string instead of as a keyword. This option is slightly more difficult to construct and understand, but it does provide more flexibility, especially when you need to define variables as part of your queries. (This topic is the subject of Chapter 8.) The simplest way to generate a Query string is to start with a traditional Query...EndQuery statement and convert it to the string format. Listing 7-6 shows a simple query to find the count of orders shipped using each Ship Via method. Listing 7-6. A query to count orders shipped using each method ; Script method
0706.SSL
run(var
- Counting
eventInfo
orders
by shipping
method
Event)
var QShip
AnsVW endvar QShip
Query
TableView
= Query
Orders .db
Ship Check
136
Via
| |
Order
iF |
Calc
No Count
All
M@@
CHAPTER 7: DEFINING QUERIES
WITHIN
YOUR
CODE
EndQuery
QShip.executeQBE() AnsVW.open(":PRIV:ANSWER"
)
endmethod
A string can be concatenated from a series of smaller strings by using the Plus operator. Paradox does not mind if a long string is split across multiple lines, as long as the operator joins them logically. To convert this saved query to the Query string format, perform the following operations: 1. Change QShip from a query variable to a string variable in the var-endVar block.
2. Place double quotes at the beginning and end of each nonblank line in the query, including the Query and EndQuery statements. 3. Place a \n inside the quotation marks and a + (Plus operator) outside the quotation marks at the end of each line in the query except the last one. Paradox uses the \n to designate a new line sequence. 4. Remove the blank lines and place another \n inside the quotation mark at the end of each previous line. 5. Modify the executeQBE() statement to use the method executeQBEString(). The revised script is shown in Listing 7-7. Listing 7-7. Revised query script using a Query string to define the query ; Script method
0707.SSL
run(var
-
The
query
eventInfo
converted
to
a Query
string
Event)
var QShip AnsVW endvar QShip
String TableView
=
"Query\n\n"
"Orders.db
+
; Ship
"! Check
Via
Order No PE ee C@alkemCoumte AeA Gone
137
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
"EndQuery"
QShip.executeQBEString() AnsVW.open(": PRIV: ANSWER. DB") endmethod
The executeQBEString() method runs a query that was previously defined in a string variable. Paradox assumes that the variable contains a definition in the correct format; if not, Paradox will not be able to detect it as a syntax error. The string is not evaluated to see if it’s a correctly formatted query until the executeQBEString() statement. If there is an error, Paradox returns the error message
An error was triggered in the ‘executeOBEString’ method on an object of Query type. You can see this message by revising the code to read as follows: if QShip.executeQBEString()
then
AnsVW.open(":PRIV: ANSWER. DB") else msgStop("Error",
errorMessage()
)
endif
The errorMessage() method returns the full text of a system error that prevented
Paradox from executing your code as expected. When you use the Query string approach, the whole query is considered as one long string split across many lines for readability. As with any long string, the expression should be formatted with double quotes delimiting each substring and a plus sign to concatenate them together, like so: ubsiesentgaye; AU) ei criingye) 2) "String N#
ee
If you want to include blank lines in the middle of the string definition, you can do so. Paradox also allows strings of any length, even well over 255 characters. 138
M@ @
CHAPTER
7: DEFINING
QUERIES
WITHIN
YOUR
CODE
Using Multi-Line Strings In Paradox for Windows, strings can also run across lines, without the need to delimit them front and back. For example, Paradox accepts the following as a single string: ins easisayer ail Strang 2 String 3"
When you define strings in this fashion, each multi-line string is limited to 255 characters. You can concatenate longer strings, as shown in the Listing 7-8. Also, if you plan to embed ObjectPAL variables in your queries, you will need to fully delimit your strings. For more information on variables in queries, see Chapter 8. Listing 7-8 shows a five-table query defined as a three multi-line string variables. Listing 7-8. A five-table query te count the number of vendors from whom each customer has ordered ; Script method
0708.SSL
run(var
-
A five
eventInfo
table
query
using
string
variables
Event)
var QFive AnsVW endvar QFive
String TableView
=
"Query
ANSWER:
:PRIV:ANSWER.DB
CUSTOMER.DB
"ORDERS.DB
| Order NEEECOD
LINEITEM.DB
"STOCK.DB
VENDORS.DB
; Customer ft EGO!
No
|; Order | _EGO2
| Stock EGOS
No
|; Vendor
| _EGO4
No
; Name | Check
; Customer fe aaxel)al No
; Stock i, EGOS:
| Vendor | _EGO4 No
No
; Vendor
j\n\n"
No
No
+
}
,; iWale
Be
|
Name
ieGalic=Count
f
Aud
|
EndQuery"
139
PARADOX
QUERIES:
A DEVELOPER’S
message("Running
if
REFERENCE
@& @
@
@
Query")
QFive.executeQBEString()
then
AnsVW.open(":PRIV: ANSWER" )
else msgStop("Error",
errorMessage () )
endif endmethod
In Listings 7-7 and 7-8, executeQBEString() is used as a method attached to a string variable. The manual does not mention that this method belongs to the String object type, since the variable defines the query as a string. This is a minor documentation error. You can also perform this operation using the procedure notation with the string variable defined as a parameter of the procedure, like so: if
executeQBEString(
QShip
) then
The Database Object Type All of the methods described above are also members of the Database object type. Paradox for Windows defines a database as a handle or reference to a different subdirectory. It can be used to reference tables stored in that directory without having to change the query. For example, suppose you have an ORDERS table in the C:\DATA subdirectory, which is the Paradox working directory, and you also have an ORDERS table in the C:\DATA\HISTORY subdirectory to represent historical or archived orders. One way to perform this query is shown in Listing 7-9. Listing 7-9. Querying on a table in a different subdirectory ; Script method
0709.SSL
run(var
-
Querying
eventInfo
var QryDB
Database
QShip
Query
AnsVW endvar QShip
140
TableView
=
Query
on
Event)
a table
in a different
subdirectory
M@ @
CHAPTER
7: DEFINING
TEST\Orders.db
QUERIES
| Ship Via | Check
Order
Cale
WITHIN
YOUR
CODE
No
Count
All
EndQuery
if
executeQBE(QShip)
then
AnsVW.open(":PRIV:ANSWER" )
else msgStop("Error", endif
errorMessage()
)
endmethod
But the problem with this approach is that you have to use a different query to reference each ORDERS table. The Database object type provides a simple way to use the same query regardless of which table you plan to query. By default, Paradox considers the active database to be the current subdirectory. If you reference a table in a query or other operation without specifying an explicit subdirectory, Paradox will use the table in the active database. But if you want Paradox to reference a table in a different location, it is a simple matter to open a new database by creating a database variable to represent the new subdirectory. Listing 7-10 demonstrates an example. Listing 7-10. Using a database object to query a table in a different subdirectory ; Script method
0710.SSL
run(var
- Using
eventInfo
an
alias
to
reference
a database
Event)
var QryDB QShip AnsVW endvar
Database Query TableView
; define
a new
alias
and
open
it
addAlias("history", "Standard", QryDB.open("history") ; query
QShip
against
the
new
as
a database
"C:\\DATA\\HISTORY")
database
= Query
Orders.db
Ship Check
Via
Order Cale
No Count
All
141
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
8
@
@
@
EndQuery if
QryDB.executeQBE(QShip)
then
AnsVW. open (":PRIV: ANSWER" )
else msgStop("“Error",errorMessage() )
endif endmethod
The clearest way to understand this example is to work backwards from the query itself: if
QryDB.executeQBE(QShip)
then
This line executes the query defined in the query object QShip using the tables contained in the database variable QryDB. OShaip
= Query.=:
This line, as with all the examples in this chapter, defines a query object in memory. This object references an ORDERS table, but there is no reference to where this table is physically located. addAlias("history", "Standard", QryDB.open("history")
"C:\\DATA\\HISTORY")
These lines define an alias to represent the C:\DATA\HISTORY subdirectory (the double backslashes are used because the name is contained in a quoted string) and then opens this database using the QryDB database variable. To run this same query against an ORDERS table stored in another directory, use a different database variable. To run this query against an ORDERS table in the current working directory, use the method without specifying a database variable, like so: if
executeQBE(QShip)
then
The following methods, already described, are also valid for Database objects:
142
@
@
CHAPTER
7: DEFINING
QUERIES
WITHIN
YOUR
CODE
* executeQBFFile() * executeQBEString() © writeQBE()
The Parsing Bug in Paradox for Windows The original release of Paradox for Windows has one bug in the Query object type that you may inadvertently bump into. Consider the query in Listing 7-11, which generates a frequency distribution of payment methods from the ORDERS table. Listing 7-11. An invalid query? Paradox misunderstands the keyword method in the field. ; Script method
0711.SSL
run(var
-
Counting
eventInfo
the
frequency
of
payment
methods
Event)
var
qq
Query
AnsTV
TableView
endvar
qq = Query Orders
Payment
micheck
Method
Gale
Count
ALD
|
Endquery qq.execut eQBE ( ) AnsTv.open(":PRIV:ANSWER.
DB"
)
endmethod
When you click on the CheckSyntax speedbar icon, Paradox highlights the vertical line after the word Method and generates the following error message:
Error: Identifier Expected The problem is that Method is a reserved word in Paradox that defines a new method for an object. Even though the word is included within a query statement, Paradox sees it and misinterprets the start of a new method. This problem also exists with the keyword Proc, but not with any other keywords or method names.
143
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
You can avoid this problem by renaming the field to Payment_Method or by using an entirely different word. Another approach is to define the query as a Query string with the executeQBEString() method. This approach is shown in Listing 7-12. Listing 7-12. Using a Query string to run this query correctly ; Script method
0712.SSL run(var
-
A workaround
eventiInfo
for
the
"Method"
All
| Nwaitls ;\n\n"
parsing
bug
Event)
var qq AnsTV endvar qq
=
String TableView
"Query\n\n"
"Orders
+
| Payment Method "| Check Cale Count
+
"Endquery"
qq. executeQBESt ring () AnsTv.open(": PRIV: ANSWER. DB")
endmethod
This bug should be fixed in a maintenance release of Paradox for Windows, but you should verify it before creating queries in your applications.
Summary This chapter introduced the concepts of Query objects and Query strings. Query objects are similar to the defined query found in Paradox for DOS. They allow you to create a query in your ObjectPAL code and have it assigned to a query variable. You can then use query methods to operate on this object. Query strings are like any other type of string. But when you tell Paradox to process the string, it looks for something which is formatted like a Query object, and that’s what it creates in memory. You can use the same methods to process this object. You also learned about databases, which allow you to query tables in different subdirectories without modifying the query each time. The next chapter explains how to define variables in your queries using query objects and Query strings. 144
CHAPTER
EIGHT
Programming Variables in Queries As you saw in Chapter 3, the use of variables in queries is an important technique for making your queries significantly more flexible. Variables allow queries to be used over and over again with different selection criteria, or with different fields being queried, or even with different tables added into the query. Paradox for Windows provides two ways to program variables into queries: ¢ Like Paradox for DOS, the Windows version supports the concept of a tilde variable to include the value of a variable within a query. As you will see, the Paradox for Windows implementation is a little more flexible than the one in Paradox for DOS. e Using the Query string technique discussed in the previous chapter, you can embed variables at various places in your queries, including table and field names.
Because Paradox for Windows does not provide for queries that are directly controlled by ObjectPAL, the ability to fill out a query under application control is not available. This is a significant limitation in Paradox for Windows, necessitating workarounds to achieve the same capabilities. This chapter will explore many of the same techniques and problems described in Chapter 3, but it will show you how to address these issues in Paradox for Windows.
How Variables Work in Paradox for Windows In Paradox for DOS, PAL is an interpreted environment, with Paradox processing each line as it is read from a script or procedure library. Variables do not
145
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
need to be declared or typed in advance because Paradox can determine the variable type at runtime. ObjectPAL in Paradox for Windows is a compiled environment. When you run a script, program library, or form, Paradox actually compiles your code into a format that is very similar to a Windows’ dynamic link library (DLL). Although Paradox for Windows does not require that you declare and type variables in advance, you will see significant performance improvements if you do so. When you don’t predeclare variables, Paradox arbitrarily assigns a type of AnyType so that it has a consistent way of managing that variable. As with any programming language, a variable is a named location in memory, a place for temporary storage of some kind of information, such as a text value, date, number, or logical. But in an object-based programming language, variables are subsumed within the more powerful concept of an object type. One way to consider objects is to think of them as complex variables. In addition to simple numbers or dates, an object might be a pushbutton, record, query, or even a complete form. To reference an object, you use a variable that is typed to that object. For example, the previous chapter used variables of type Query or type String to represent the definition of a query in memory. In each of these instances, the variable acts as a handle that provides the means to reference that object in other parts of the application. The concept of a handle to grab an object is used in many programming environments. For example, many pro-
grams that use windows—including Paradox 4.0 for DOS—use the concept of a handle to reference a window that the program or programmer has created on-screen. To define a variable, you must place a var-endVar block at the beginning of the script or method. For example, the expression var
QQ SaleDate CustState, endVar
Query ShipMeth
Date String
defines four variables: a query, a date, and two strings. Paradox for Windows has very detailed and explicit rules about variable scope. They are explained in detail in Chapter 5 of the ObjectPAL Developer’s Guide.
146
@
@
CHAPTER
8: PROGRAMMING
VARIABLES IN QUERIES
Using Tildes to Represent Variable Information Like Paradox for DOS, Paradox for Windows supports the concept of a tilde variable to represent an item of variable information in a defined query object. Consider the query in Listing 8-1, which sums the orders placed by customers in California only. It uses a tilde variable to reference a string defined before the query. Listing 8-1. Querying for customers in CA using a tilde variable ; Script method
0801.SSL
run(var
- Using
eventInfo
a tilde
variable
in a query
Event)
var
aq
Query
OCErING mE Stang AnsTV TableView endVar OSE
=a
CAN
qq = Query Customer
| Customer
No
| —EGO1 Orders
| Customer ! |
No
_EGO1
| Name | Check
State/Prov ~QString
Total Invoice Cale Sum
Endquery
if
qq.executeQBE() then AnsTv.open(":PRIV:ANSWER.DB" )
else errorShow() endif endmethod
Paradox requires that the tilde variable be defined before the query object is defined. If the line QString = “CA” is placed after the query definition qq = Query..., Paradox will respond with the error message Query String is empty
147
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Paradox evaluates all tilde variables at the time the query object is defined and will not accept a query where a tilde variable has not been assigned. To define a tilde variable in a number or date field, you can specify the variable as either a number or date, or as a string. Consider the query in Listing 8-2, which displays the customer name and invoice amount for orders where the outstanding balance is greater than a number. Listing 8-2. A query to find customer and order amount for orders placed on a specific date /
Script
method
0802.SSL run(var
-
Tilde
eventInfo
variables
can
be
of
any
type
Event)
var qq
Query
QBal
Number
AnsTV
TableView
endvar QBal
=
1000
qq = Query Customer
| Customer
No
7 etal Orders
; Customer
| Name
|
We Check No
EGON
; Total
Invoice
| Check
| Balance
| Check
Due
>~QBal
|
|
Endquery if
qq.executeQBE()
then
AnsTv.open(":PRIV:ANSWER. DB")
else errorShow()
endif endmethod
The tilde variable can be specified in either of the following ways, and Paradox will understand the value when the query is processed: var QDate endVar QDate
148
Date
= dateVal
("6/24/88")
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
or var QDate Straong endVar QDate = "6/24/88"
Using Blank Tilde Variables Consider the query in Listing 8-3, which defines two tilde variables, one for the customer’s state and the other to find outstanding balances greater than the designed amount. In the example, Paradox finds four Hawaiian orders with balances greater than $1000. Listing 8-3. Querying for orders from a designated state with outstanding balances greater than a designated amount ;
!
Script
method
0803.SSL
run(var
-
Querying
eventInfo
for
orders
with
two
tilde
variables
Event)
var
qq
Query
QBal
Number
QState AnsTV endvar QBal
String TableView
=
1000
OStatves=sthi!
qq = Query Customer
| Customer
No
ih SEOs: Orders
Customer
No
heECOl
; Name
| State/Prov
mCheck)
|) Check
;} Total
Invoice
| Check
=O0State ; Balance
| Check
4 Due
>~QBal
Endquery
1f qq.executeQBE()
then
AnsTv.open(":PRIV: ANSWER. DB")
else errorShow() endif endmethod
149
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
What happens if the QState variable is specified as a blank, like so: OSCarek—
ee
Paradox for DOS will only find orders where the CUSTOMER State/Prov field is actually blank. But Paradox for Windows behaves differently, finding all orders— regardless of the customer’s state—with an outstanding balance greater than $1000. This is a significant difference, and it suggests a useful technique when you are defining queries that may include variable selection criteria in amy field:
e When you specify the query, place tilde variables in every field that might ultimately contain selection criteria. ¢ Before the query definition, define variables to represent each instance of a tilde in the query, initializing each of these variables to a blank value.
e When the user specifies selection criteria, some of these variables will be changed to hold actual values.
Listing 8-4 shows an example, using the CUSTOMER table to define selection criteria. The query produces correct results even though all tilde variables are initialized to null values except for the customer’s country. Listing 8-4. Tilde variables are placed in every CUSTOMER field, but most of them represent blank values. ; Script method
0804.SSL
run(var
Query
QName, OStreee,
OGney, QStProv,
QZip, QCountry,
QPhone
150
Using
eventInfo
var aq
-
String
tilde Event)
variables
in
every
criterion
field
@
@
CHAPTER
OPUKSECe AnsTV endvar
8: PROGRAMMING
IN
QUERIES
Date TableView
QName OSE ese
ae = wll
QCity
eS
OSERROV
IN
QZip
=e
OCournGry
=
s2USSe A.
QPhone
Seo
ty
OFamstCs
= blank’)
CUSTOMER.DB
ue.
; date
| Customer No | Check _EGO1
CUSTOMER. DB
CUSTOMER. DB
| Name | Check
ray
Check
ORDERS.DB
VARIABLES
~QCity
field
~QName
State/Prov
| Zip/Postal
Check
| Check
~QStProv
Country
i]
Check
| Check
| Customer i GOL
i}
~QCountry No
| Street i} |1 Check ~QStreet
Phone
i
i}
~QPhone
Total Invoice Calc Sum
1 1
Code
|
~QZip
First
| Check’
Contact
i]
~QFirstc
|
!
|
EndQuery
if
qq.executeQBE()
then
AnsTv.open(":PRIV:ANSWER. DB")
else errorShow()
endif endmethod
As you will see in Chapters 13 and 14, the ability to enter selection criteria into any field of the query is an important characteristic of ad hoc queries. Under Paradox for DOS, it is easy to navigate through the query image using PAL to enter selection criteria wherever you need in order to define the query. Paradox for Windows does not have this capability, but you can still use the feature described above to enter selection criteria into any field. In Paradox for Windows, if you want to query for records where a particular field is actually blank, you must specify Paradox’s Blank operator as your selection criteria.
151
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Using Tildes for Other Query Elements Paradox for Windows differs from Paradox for DOS in its use of tilde variables
in a number of other ways. Following is a discussion of the various ways. Specifying wildcards as part of tilde variables In Paradox for DOS, tildes can only be used for exact match selection criteria. Wildcards must be placed outside the tilde expression (see Listing 3-6). But in Paradox for Windows, you can specify wildcards as part of the tilde variable, and Paradox will interpret them correctly. The query in Listing 8-5 asks for all stock items with the word regulator in the Descrip-
tion field. Paradox finds nine stock items representing four different descriptions. Listing 8-5. Unlike Paradox for DOS, Paradox for Windows correctly interprets wildcards in a tilde variable. ; Script method
0805.SSL run(var
-
Using
eventInfo
tilde
variable
with
wildcards
Event)
var
aq
Query
DESC AnsTbl endvar DESC ts
String TableView
"ee regubacoisarne
qq = Query Stock
7 Stock | Check
No)
Deseriptionm | Check ~DESC
| |
endQuery
if
qq.executeQBE() then AnsTbl.open(": PRIV: ANSWER. DB")
else errorShow()
endif endmethod
Specifying query operators with tilde variables Paradox for Windows also allows you to specify a query operator with a tilde variable. In Listing 8-6, the operation is defined by a tilde variable, and Paradox cal152
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
culates the sum or order amounts grouped by country. The field name in the ANSWER table is based on the value of the tilde variable. Listing 8-6. A query operator in a tilde variable ; Script method
0806.SSL
run(var
- A query
eventInfo
operator
in a tilde
variable
Event)
var lere| Q_OP
Query String
AnsTV endVar GLOPe=
TableView
sunt!
qq = Query Customer
Orders
| Customer eeneou
| Customer
No
No
| —_EGO1
; Country | Check
| Total | Cale
|
Invoice
|
~Q_OP
Endquery if
qq.executeQBE()
then
AnsTv.open(":PRIV:ANSWER. DB" )
else errorShow()
endif endmethod
You can even have Paradox perform two operations with one variable. If you change Q_OP in the preceding listing to the expression OnOP*=
Ksum~
cale
count
all”
Paradox generates the Calc Sum and the Calc Count All, adding a new column to the ANSWER table. By now, you should see a pattern emerging. A query object in Paradox for Windows is a construct in memory, and any element of that construct can be represented
153
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
8
@
8
@
by a tilde variable in your code. When Paradox defines the construct, it interprets each element in turn, using the current value of each variable defined in your code.
Tilde variables for field and table names Based on the above explanation, you should be able to use tilde variables for field and table names as well, something that Paradox for DOS simply doesn’t permit. And indeed you can! Listing 8-7 shows a query on the ORDERS table to find the most recent date for each customer. But do you want to query the Sale Date or the Ship Date field? The query allows you to choose which one. Listing 8-7. Using a tilde variable to define the query field ; Script method
0807.SSL
run(var
-
Using
a tilde
eventInfo
variable
Event)
var qq
Query
QDateField AnsTV endVar QDateField qq
=
String TableView
=
"Sale
Date"
Query
Customer
; Customer
No
| _EGO1 Orders
; Customer 1 xeloil
; Name
No
; ~QDateField | Calc Max
Endquery
if
qq.executeQBE()
then
AnsTv.open(":PRIV:ANSWER. DB")
else errorShow()
endif endmethod
154
|
| Glovevete |
to
control
the
field
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
Listing 8-8 shows a truly generic query. It finds the ID value that corresponds to the maximum value in a number or date field. You define the table and fields each time the query is run! Listing 8-8. Using tilde variables for the table name as well as the fields being queried ; Script method
0808.SSL
run(var
-
Defining
eventInfo
the
table
and
query
fields
at
runtime
Event)
var
aq
Query
QTable, QID
Field,
QCalc_Field AnsTV endVar
String TableView
QTable
=
VSTOGK
QNo_Field QCalc_Field
= =
isicorele Ney? “List Price"
qq
=
Query
~OTable
| ~ONomPrelds,
Set | Check
~OCale paeld | JEGOL | Check max_EGO1
'
|
Endquery 1f
qq.executeQBE() then AnsTv.open(":PRIV:ANSWER. DB" )
else
errorShow() endif endmethod
Prompting Users for Values In all of the above examples, the selection criteria and variable information have been hard-coded in the script for demonstration purposes only. More commonly, you will prompt the user for this value, or obtain it from a field in a form or a menu selection.
155
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
8
@
@
G@
The simplest way to prompt users for information is to use the view() method, which is provided as part of the AnyType object. The syntax for this method is Variable.view(
"DialogBoxTitle"
)
If you preassign the value of the variable, it will be displayed in the dialog box window as a highlighted block. For example, the expression CustState
=
"Enter
a
State
CustState.view("Customer
here"
State")
displays the dialog box shown in Figure 8-1. Paradox allows you to modify the value for the following data types: currency, date, date time, logical, long integer, number, point, small integer, string, and time. If you press the OK button, the variable assumes the value you typed. If you press the Cancel button, the variable reverts to the value it held when the dialog box first opened.
=
Customer State
Figure 8-1. The view() dialog box
156
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
Listing 8-9 demonstrates one way to use the view() method in a script. It displays the dialog box without preassigning a value to the QBal variable, and Paradox displays N/A in the dialog window. After the user interacts with the dialog box, the script checks to see if QBal is still unassigned. If it is, the user pressed the Cancel button or did not enter a value, so the script quits. But if a value was entered, the query is processed and an ANSWER table is displayed. Listing 8-9. Prompting the user for a minimum number to be used in the query ; Script method
0809.SSL
run(var
-
Prompting
eventInfo
the
user
for
a selection
criterion
Event)
var
aq
Query
QBal
Number
AnsTV endVar
TableView
QBal.view("Enter
if not
the
minimum
QBal.isassigned()
Balance
Due")
then
return
endif
qq = Query Customer
Orders
| Customer P neexoial Customer
No
; —EGO1
No
| Name | imcheciony
; Total
Invoice
| Check
Balance
Check
Due
>~QBal
Endquery 1£
qq.executeQBE() then AnsTv.open(":PRIV: ANSWER. DB")
else errorShow()
endif endmet hod
Creating a Custom Dialog Box In more elaborate Paradox applications, forms become an important component. In the initial release of Paradox for Windows, forms are the only design object to 157
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
which ObjectPAL code can be attached. Forms are the primary mechanism for interacting with your data, displaying custom menus and dialog boxes, performing queries, and generating reports. Most complex applications are built around forms. Forms are composed of user-interface objects, such as pages, tables, fields, buttons, and other graphic elements. Each object as well as the form itself can have
attached methods that trigger based on different events. For example, you can attach custom ObjectPAL code to the following events (among others) on a field: ¢ MouseEnter and MouseExit ¢ MouseClick and MouseDoubleClick
¢ FieldArrive and FieldDepart ¢ FieldOpen and FieldClose ¢ DataAction ¢ MenuAction
¢ ChangeValue and NewValue Objects on a form also exist within a hierarchy of objects that contain one another. For example, a pushbutton on a form is contained within the current form page, which in turn is contained within the form itself. Code can be attached at different levels of this hierarchy, and if a particular object does not handle an event in a special way, the event bubbles up to the next highest object in the hierarchy, all the way to the form level. This book does not attempt to explain the complete ObjectPAL model for forms, since the subject would require a complete book on its own. The ObjectPAL Developer’s Guide explores this model in great detail, and I will assume that you already understand the basics of the model. Dialog boxes are implemented in Paradox for Windows using forms, with various window properties set or cleared so that the form is treated as a floating dialog. Let’s go over the steps for creating a custom dialog box.
Creating the dialog form Suppose you want to run a query that prompts the user for the Date field to query (Sale Date or Ship Date), and then checks for the maximum value in this field for each
customer. One way to accomplish this task is to use a dialog box like the one shown in Figure 8-2.
158
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
Select Date Field
Figure 8-2. A dialog box for selecting a query field
The first step in setting up this dialog box is to create the custom form shown in Figure 8-2, and attach ObjectPAL code at the appropriate places. You will need to perform the following steps: 1. Create a new form. When Paradox prompts for the design layout, just press the OK button without adding any tables.
2. When Paradox prompts for the Page Layout, specify a custom width of 2.5 inches and a custom height of 1.5 inches, and then press the OK button.
3. Place a field object on the form, right-click on this object, and select Display Type / RadioButtons... from the popup menu. Paradox will display the Define List dialog box shown in Figure 8-3. 4. Add Sale Date and Ship Date to the list and click on the OK button. Paradox changes the field to display radio buttons, with these two options shown.
5. Place two button objects at the bottom of the form. Change the text of one to read OK and the other to read Cancel. 159
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Define List
Figure 8-3. The Define List dialog box for radio buttons
. Use the color, pattern, frame, and font properties on each object to design the form to your taste. . Right-click on the field object, and then click on the top entry in the menu to change the name for this object. Call it WhichDate.
. Right-click on this object again and select Methods. You will see the dialog box shown in Figure 8-4, which lists the built-in event methods for this object. . Select the open method and click on the OK button. This method behaves just like a small script, giving you a place to attach custom code to this event. Any code added to the Open method for an object will be executed when the object is first opened.
160
@@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
0802::WhichDate
Figure 8-4. The Methods dialog box for the field
10. Add the following text on the blank line between the method and endmethod keywords: WhichDate
=
"Sale
Date"
This code tells Paradox to establish “Sale Date” as the default value when
the form is first opened. Close the method window and save your change. 11. Select the OK button you placed on the form and press [Ctrl-Spacebar]. This is another way to display the Methods dialog box (see Figure 8-4). 12. Select the PushButton method, which will trigger when the button is clicked. 13. Add the following text between the method and endmethod keywords: formReturn
(True)
161
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
G@
When this button is clicked, Paradox will return from this form, passing back the indicated value. 14. Perform the same operations on the Cancel button, placing the following text: formReturn
(False)
15. From the menu, select Properties / Form / Window Style.... Paradox will display the dialog box shown in Figure 8-5.
16. Configure the dialog box to match the illustration in Figure 8-5 so that Paradox displays a custom title and treats the dialog box modally. Press OK to accept your selections. 17. Finally, save the form. In the example, it is called USERDATE.FSL.
Form Yindow Properties Window Properties: __
gitawe a % Dialog Box Frame Properties:
_| Dialog Frame
ay Hreicontal Scroll
_| Border: __| Thick Frame
v| Gixe Te FR | Modal —
: litle Bar Properties:
_| Control Menu —
|| Standad Menu
-
-_| Minimize Button —
_| Maximize Button—
Figure 8-5. The Form Window Properties dialog box
162
oe
v| Mouse Activates 2
@@
CH APTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
Creating the script Listing 8-10 shows a script to display the form, allow the user to make a selection, and then react accordingly. Listing 8-10. A script to query a user-defined date field ; Script method
0810.SSL
run(var
- Querying
eventInfo
a user-defined
date
field
Event)
var FormDate
Form
qq
Query
Result QDateField AnsTV endVar ; open ; code
Logical String TableView
dialog box and on pushbuttons
wait on it specifies return
FormDate.open(":WORK:
Result
=
; user
clicked
value
USERDATE.FSL")
FormDate.wait() the
button
if not Result then FormDate.close() return
endif ; user
clicked
QDateField = FormDate.WhichDate FormDate.close() message("Running query on the "
aq
+ QDateField
+
"
field")
= Query
Customer
Customer
No
Orders
| Customer
Ee Om
Name
Check
_EGO1
No
|; ~QDateField
| Calc
Max
Endquery
1f qq.executeQBE()
then
AnsTv.open(":PRIV:ANSWER.DB")
else
163
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
errorShow()
endif endmethod
This script performs the following operations: 1. It defines a series of variables that are used in the script.
2. It opens the specified form and assigns FormDate as a handle to reference this form elsewhere in the script. The form is opened in the mode and with the display options you specified above. “Sale Date” is established as the default value in the radio button field.
3. It waits on this form. Control passes to the form, allowing the user to interact with it in a modal way. The user can perform the following actions: e Select a different date field by clicking on the appropriate radio button. ¢ Click on the OK button to accept the dialog box settings. Paradox passes a logical True back to the calling form, via the formReturn() you previously added to this button.
¢ Click on the Cancel button to cancel the dialog box settings. Paradox passes a logical False back to the calling form, again using formReturn().
4, When the user accepts or cancels the dialog box, the script places the appropriate return value in the variable Result. At this point, control has returned to the script, although the dialog form is not yet closed. ¢ If the Result variable holds a logical False, it means the user clicked on the Cancel button, so the script closes the form and returns, which effectively ends the script. Otherwise, if the user clicked on
164
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
the OK button, the current value of the WhichDate field is saved to a variable before the form is closed.
5. Before running the query using the appropriate field, a custom message is displayed on-screen. After the query has been run, the ANSWER table is displayed. Using these techniques, you allow a user to control any aspects of the query.
Creating a Custom Menu Another way to solve the same problem is to display a custom popup menu with the appropriate options. For example, suppose you want to allow the user to select a Country from which to summarize orders. Listing 8-11 shows the pushButton method from the button on a custom form to perform this operation. Listing 8-11. The selection criterion is obtained from a popup menu, which in turn is generated from another query. ; Form 0811.FSL - pushButton() method ; Using a custom menu to generate a criterion method
pushButton(var
eventInfo
Event)
var QCntry
String
qql,
Query
qq2
CntryList
TCursor
pm
PopupMenu
AnsTbl endVar
TableView
; get list of unique countries ; place the results in a TCursor message("Querying
for
available
countries")
qql = Query CUSTOMER
| Country
| First
| Check
; 2201/01/92
Contact
|
EndQuery
qql.executeQBE(CntryList) ; scan
the
TCursor
and
build
a menu
object
in memory
165
PARADOX
QUERIES: A DEVELOPER’S
scan
REFERENCE
CntryList
pm.addText
(CntryList.Country)
endscan CntryList.close() ; display
the
; close
popup
menu
and
message("Select a country QCntry = pm.show() it wOCn iE Ve = Sabet herd:
or
save
the
the
press
TCursor
menu
choice
[Esc]")
; user
cancelled
return
endif ; query
qq2
using
the
country
menu
choice
= Query
CUSTOMER
ORDERS
; Customer EC Om | Customer | —_EGO1
No
No
; Name | Check
j; Country |; ~QCntry
| Order No Galles Gount
| |
sale
EndQuery
if qq2.executeQBE() message("Viewing
then orders
for
"
+ QCntry
)
AnsTbl.open(":PRIV:ANSWER. DB")
AnsTbl.wait() AnsTbl.close() else errorShow() endif endmethod
This script starts out by querying the CUSTOMER table for unique countries where first contact was made with the customer after the beginning of 1990. If a Secondary index is placed on this field, this will be a very fast query. Query results are generated into a [Cursor since the user does not need to see the actual table. Instead, Paradox scans each record into a popup menu object, which is then presented to the user. When a choice is made, Paradox uses this choice as the selection criterion in a
second query to generate orders just for customers in that country.
166
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
Querying with a Variable Using Query Strings Tilde variables are only valid with query objects defined using the Query...EndQuery construct. When you specify a query as a Query string, you cannot use a tilde variable. However, if you structure your queries as strings, it is a simple matter to substitute a variable in place of a literal string for any information that might change. For example, suppose you need to query for orders placed by a specific customer to show the date and total invoice for each order. Listing 8-12 shows a solution using query strings. Listing 8-12. Querying with a variable using query strings !
Script
i met hod
0812.SSL
run(var
-
Select
eventInfo
a customer
for
whom
to
query
Event)
var CustList ID RetForm QryString AnsTbl endVar ; open
; allow
form string logical string TableView
the
form
the
user
to wait
modally
CustList.open("CUSTLIST")
message("Select
RetForm ; user
a
customer")
= CustList.wait() pressed
the
button
if not RetForm then CustList.close() return endif
; user ; read
pressed the key
field
and
close
the
ID = strVal( CustList.Customer_No CustList.close() ; build
the
QString
=
query
string,
"Query\n\n"
using
form )
the
ID
as
appropriate
+
167
PARADOX
QUERIES:
"CUSTOMER
"ORDERS
A
DEVELOPER’S
; Customer No My) GONE Ma aks
| Customer
No
wm, —HGOL
cee
REFERENCE
@
@
@
@
LN et Ban Wee
| Sale
Date
t Cheek
| Total
| Check
Invoice
;\n"
i\n\n"
+
+
Endquery"
if
QString.executeQBEString() AnsTbl.open(":PRIV:ANSWER.
then DB")
else errorShow()
endif endmethod
This script uses the CUSTLIST form containing a tableframe and two buttons to allow the user to select a customer. If the user presses the OK button, the value of the Customer No field is read into the ID variable, which is then used in the query string.
Adding Variable Tables in a Query When you program ad hoc queries, a common requirement is the ability to add a table to the query only on selected occasions. In Chapter 3, you learned how to do this in Paradox for DOS by using a PAL-driven query. In Paradox for Windows, you have to use Query strings. For example, suppose you want to generate a frequency distribution of shipping methods, sometimes for all customers and other times for U.S.A. customers only. Figure 8-6 shows the DOUSA.FSL custom form with a check box to indicate if US customers should be included and a button to run the query. Customer Query
Figure 8-6. A custom dialog form to set an option and run the query
168
@
@
CHAPTER
8: PROGRAMMING
VARIABLES
IN
QUERIES
The check box is named DoUSA. When the user clicks on the RunQuery button, the script reads the current value of this check box. If the value is True, the box was checked and only US customers are included. If the value is False, the box was not checked and all customers are included. Listing 8-13 shows the ObjectPAL code attached to the pushButton method on the RunQuery button. Listing 8-13. The RunQuery::pushButton() method ; DOUSA.FSL
-
; Including
a table
method
RunQuery::pushButton()
in a query
pushButton(var
only
eventInfo
method
sometimes
Event)
var Qry, OryCust,
QryOrd AnsTbl endVar ; read
String TableView
value
of
the
; 1f
it's
checked
; 1f
not,
set
if
check
variables
DoUSA.Value
box
(=True)
= True
QryCust
=
"CUSTOMER
Orage
Maz
VEO
link to
the
null
Customer
table
strings
then
| Customer "\ _EGO1
No
; Country
|\n" + RURSeAe me wa iat!
else OnvV.Gursitte= QryOrd
—)
en
endif ; ; ; ;
define the query QryCust contains the CUSTOMER table if necessary QryOrd contains the example element if CUSTOMER was linked in otherwise, both variables are null and the table is not linked
Ory
=
"“Query\n\n"
"ORDERS
+ OryCust
+
; Customer No ; Ship VIA er Ony OGd+ muy Cheeks Callie (Count
hae Aloe sivate
"EndQuery" ; run
the
query
169
PARADOX
QUERIES:
if
A
DEVELOPER’S
Qry.executeQBEString()
REFERENCE
#
@
&
@&
then
AnsTbl.open(":PRIV:ANSWER.DB")
else errorShow()
endif endmethod
The QryCust variable is calculated based on the value of this check box. If only US customers should be included, the CUSTOMER table is linked into the query, and the QryOrd variable is assigned to the linking example element. If all customers should be included, both variables are assigned to zero-length strings so that the CUSTOMER table is not linked into the query.
Summary This chapter reviewed the different ways to include variable information in Paradox for Windows queries. Unlike Paradox for DOS, Paradox for Windows does not allow you to manipulate query images directly with ObjectPAL. Fortunately, Paradox for Windows supports tilde variables in a considerably more flexible way than Paradox for DOS. It allows the tilde to substitute for any part of the query, including selection criteria, query operators, field names, and even the table name. Paradox for Windows also supports the concept of a Query string, which is a saved query formatted using string constants and variables. Using this technique, you can build variable information from string variables for any portion of the query image, including complete tables that are only added into the query at certain times.
170
CHAPTER
NINE
Developing Applications As you saw in Chapter 5 with Paradox for DOS, queries are an integral part of most database applications. This chapter discusses how to use queries in complete Paradox for Windows applications. It also introduces a number of other ObjectPAL methods that are relevant to queries.
Structuring Applications Paradox applications are built primarily around forms, with code attached either to objects in the form or to the form itself acting as a top-level object. Paradox also allows you to define custom methods for an object that you can then call from different built-in methods for that object. For custom methods that may be used in different parts of the application, Paradox allows you to define methods in a central library. You can tell your application to open the library and use the methods defined therein. Building and storing custom methods in a library is the recommended approach for designing more complex applications. When you attach code to the menuAction() method of an object so that it responds to the user’s menu choice, or when you want something special to happen on a pushbutton, the best approach is to immediately call a custom method from the library to perform this detailed action. Storing and running queries Queries in an application can be stored and run in any one of three formats (all were discussed in previous chapters):
e Asa
separate, stand-alone .QBE file.
¢ As a Query object that is referenced using a query handle and tilde variables.
171
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
¢ Asa query string that is referenced using methods attached to the string and database object types. Variables are handled as strings, which are concatenated into the query. The first option is not the recommended approach for polished applications. QBE files are stored as straight text files and offer no protection against prying or modification. They are also slower to use because Paradox has to read the file from disk each time the query is executed. Standalone QBE files are good for prototyping queries, but when you are happy with a query as defined, they should be read into a method to fully integrate them into the application. When you include your code within a built-in or custom method, you also make it easier to correctly sequence your queries. Instead of calling a series of QBE files in quick succession, all of the code can be placed in one method, with the necessary execute...() methods and error checking.
The query object and query string techniques provide similar security and performance. In each case, the query is built directly from within the method, either as
a special query object, or as a string that Paradox interprets as a query. The query object technique is slightly faster, since Paradox pre-parses the query to determine proper syntax and formatting. Query strings are parsed at runtime, which provides the flexibility to build the string dynamically. Query objects require that variables be defined before the object. With a query string, on the other hand, you don’t need to define embedded variables until runtime, which is an advantage in some situtations Using a query object is best when the query you need to perform is clearly defined, with few or no variables. Such queries occur more commonly in custom applications in which a particular operation must be performed on a regular basis. Query strings, on the other hand, are best when the query may vary significantly each time it is run. This variation may be in selection criteria, but it might also occur in the fields or tables being queried. If you need to make the queries you are running generic, use the query string approach. Chapter 18 describes a generic query definition and processing system for Paradox for Windows applications that uses query strings. It allows you to specify a query using one or more dynarrays, then to call a custom routine to create the query string for you.
172
Beeee
eB
CHAPTER
9: DEVELOPING
APPLICATIONS
Delivering Objects Paradox for Windows objects, such as forms, reports, scripts, and libraries, are compiled files. Internally, Paradox uses a format very much like the standard Win-
dows DLL format for these files. But when you initially create these files, Paradox stores the source code together with the compiled code in the file. This allows you to debug the code, single-stepping through it to analyze what is happening. When you are ready to deliver these files to a client, Paradox allows you to strip this source code from the file, making it smaller and therefore a little faster, and ren-
dering it secure from modification by a user. To deliver an object, use the commands shown in Table 9-1. Paradox strips the source code from the file and sets a flag that prohibits the user—and you as well— from loading the object in Design mode. Delivered objects have different extensions, as shown in the table. They do not remain linked to the object from which they were created. If you modify or add to the original form, script, or library, you must execute the Deliver command again to update the delivered object. Table 9-1. File Extensions for Original and Delivered Objects Object
Original Extension
Delivered Extension
Deliver Command
Form
PSE
FDL
Form / Deliver
Report
RSL
RDL
Report / Deliver
Script
SSL
SDL
Language / Deliver
Library
CSE
LDL
Language / Deliver
Using Aliases to Manage Directories Paradox provides a convenient way to work with applications that could be stored in different subdirectories. An alias is a handle you can use to reference a subdirectory in your applications. It allows you to reference tables in different subdirectories anywhere on your hard disk or on other devices attached to your computer without having to hard-code the specific directory name into your application. Aliases consist of a leading colon, a name you define, and a trailing colon. Paradox defines the :WORK: alias to represent the current working directory, and the :PRIV: alias to represent a user’s Private directory. Interactively, you set aliases using the File / Aliases... dialog box, which is shown in Figure 9-1.
173
PARADOX
QUERIES:
DEVELOPER’S
A
=
@
@
REFERENCE
@
&
Alias Manager
Database Alias: (LUIS Driver Type: |STANDARD
Alias available for this session.
[=]
Choose Save As to save —
[+]
permanently.
on
Path |C:\BOOK\DATA2
Figure 9-1. The Alias Manager dialog box
Under ObjectPAL control, aliases are set using the addAlias() method. For exam-
ple, to define the alias of ORDINFO, use the following command: addAlias(
"ORDINFO",
"Standard",
"C:\\APPS\\CURRENT\\ORDERS"
)
¢ The first argument is the alias you wish to use.
¢ The second argument is the alias type. In the initial release of Paradox for Windows, you must use “Standard”, but future releases will add SQL support for client-server environments, and other alias types will then be allowed. ¢ The third argument is the subdirectory to which this alias will refer.
Custom applications should never refer to explicit subdirectories. Instead, you should always use aliases so that you only have to change one reference when an application is moved to a different location. Aliases can be prefixed to the table name in every situation where you must specify a table in a method or a saved query. For example, Listing 9-1 shows a saved query to reference a table in the aliased directory defined above. 174
Bee
eB
CHAPTER
9: DEVELOPING
APPLICATIONS
Listing 9-1. Querying with embedded aliases ; Script
qq
0901.SSL
Qq
Query
PV
TableView
-
Querying
with
embedded
aliases
= Query : ORDINFO: ORDERS
Ship VIA Check
, Total Invoice | Calc Sum
EndQuery if
qq.executeQBE( TV.open(
":WORK:ANSWER.DB"
":WORK:ANSWER.DB"
) then
)
else errorShow() endif
When you define a query using an alias, Paradox saves the alias as part of the saved query. The following exercise demonstrates this behavior. 1. Select the File /Working Directory... option to change to the root directory.
2. Using the File / Aliases... option, establish an alias for the directory into which you installed the sample files that accompany this book. 3. Create a new query window. In the alias field of the Select File dialog box, specify the alias you defined above, and then pick the ORDERS table.
4. Define the query shown in Listing 9-1 above, and save this query to a QBE file.
When you view the QBE file or merge it into a method or script, the alias from which the ORDERS table was loaded is prefixed to the table name in the saved query.
175
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
Loading aliases for a session The setAlias() method is a member of the Session object type. With this object you can open new sessions to Paradox’s database engine, perhaps with different settings for the handling of blank values and other configuration options. Session methods are also used to describe the default session that is opened by Paradox when you start the program. Another Session method is enumAliasNames(), which enumerates or loads a list of the current session’s aliases to a table, as shown in Listing 9-2. Listing 9-2. Enumerating aliases to a table
TV
TableView
endVar
enumAliasNames
(":PRIV:ALIASES.DB")
TV.open(":PRIV:ALIASES.DB")
One approach to defining aliases for an application is to use a table such as this one in reverse. If you create a table with the same structure and fill it with your aliases, your application can open it using a TCursor and scan the table to define the aliases it needs. An example of this approach is shown in Listing 9-3. Listing 9-3. Loading aliases for an application from a predefined table ,
Script
TES
0903.SSL
-
Loading
aliases
from
a table
TeCucsor
endVar
TC.open("C:\\ALIASES. DB") scan
=
addAlias(TC.DBName, endscan TC.close()
TC.DBType,
TC.DBPath
)
Placing tables in Private directories Unlike Paradox for DOS, Paradox for Windows requires a Private directory, even for single-user applications. Paradox automatically creates the :PRIV: alias to refer176
Bees
ee
CHAPTER
9:
DEVELOPING
APPLICATIONS
ence your Private directory. You should never reference this directory explicitly; the alias provides a safer and more convenient reference. Paradox’s temporary tables are normally placed in the Private directory, but this is not essential. When you specify query properties, you can define any directory or alias as the destination for the ANSWER table. If an ANSWER table already exists in that directory, it is automatically overwritten. Similarly, you can tell Paradox for Windows to place any table in the Private directory, including intermediate tables created as part of a multi-step operation. When you can specify the destination and name of a holding table as part of the query definition, there is no need for the notorious PrivTables command used in Paradox for DOS. For example, suppose you need to perform three queries in succession, with the intermediate tables saved to the Private directory so that they don’t conflict with other users who are performing the same operation. Listing 9-4 demonstrates the Paradox for Windows solution using an extract from a larger script. Listing 9-4. Placing intermediate tables in the Private directory for a multi-step operation ; write
results
to
qql.executeQBE(
; attach
if not ; if
variable
to
in the
test
":PRIV:HOLDING1.DB"
Tbl.empty() there
name
":PRIV:HOLDING1.DB"
a table
Tbl.attach(
a special
directory
)
if
table
is
empty
)
then
is data
qq2.executeQBE(
Private
to
continue,
perform
":PRIV:HOLDING2.DB"
the
remaining
queries
)
qq3 .execut eQBE ( ) else msgStop("Results",
"No
records
match
these
selection
criteria")
endif
Other Query-Related Methods Paradox for Windows provides other methods to assist you in performing queries. The rest of this chapter describes some of these methods.
177
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
isAssigned(): Determining if a variable has a value The isAssigned() method can be used to determine if a query variable has been assigned a value. You might use this method if the source of a query is outside your application instead of the query being defined within your code. For example, to check if the query variable qq has been assigned a value and, if it hasn’t been assigned one, call a custom method to create the query, use the following code: if
not
qq.isassigned()
then
defineQQ()
endif qq.executeQBEString()
You can also use isAssigned() as a procedure. Paradox accepts the following alternate syntax without complaint: if
not
isassigned(qq)
then
isAssigned() does not check to see if the contents of the variable evaluate to a
valid query, just if the variable has a value. Unfortunately, ObjectPAL does not provide an isValid() method to determine if the value in a query variable or query string conforms to the valid syntax. isBlankZero(): Treating blanks as zeros As you can see in Chapters 19 and 20 of Paradox Queries: The Basics, the com-
panion volume to this book, and in Chapter 4 of this volume, Paradox allows you to treat blanks as if they were zeros for arithmetic calculations in queries. Under program control, you need a way to test if Paradox has been configured to process blanks as if they were zero. This is done with the isBlankZero() method.
Unlike Paradox for DOS, Paradox for Windows also allows you to change this setting under program control. The blankAsZero() method accepts a logical parameter of True if blanks should be treated as if they were zero, or False if they should not. Listing 9-5 provides an example.
178
Beeee
eB
CHAPTER
9: DEVELOPING
APPLICATIONS
Listing 9-5. Controlling the BlankAsZero setting from ObjectPAL ; the
following
code
is placed
in the
var
window
for
a
form
var
BlankSetting
Logical
endVar
; the following is a custom method on the ; called from the form's open() method method
form
InitializeApp()
if not isBlankZero() then BlankSetting = False blankAsZero (True) endif
; save
; set
default
it
the
way
you
need
endmethod ; the
following
; called
method
from
is
the
a custom form's
method
close()
on
the
form
method
CloseAppDown( )
if
BlankSetting then blankAsZero (True) else blankAsZero (False)
; read saved default ; restore it ; restore
it
endif endmethod
This example demonstrates how to save the previous blankAsZero setting at the beginning of an application and restore it when the application has finished, even if you have changed the setting while the application was running. It requires that you set up a global variable at the form level to hold the initial setting. This setting is determined, and the variable set, from a custom method that is used to initialize the application when the controlling form is first opened. You will find a method such as this one in many systems. A similar method is used to shut the application down and restore previously saved defaults.
179
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
fileBrowser(): Displaying the Paradox File Browser The fileBrowser() procedure, which is a member of the System type, allows you
to display the Paradox File Browser under program control to enable the user to select a file. A valuable use of this tool is the ability to control the file type that is shown when the browser window opens, as well the current alias. This is done via a predefined record that you must create in memory. FileBrowserInfo is a special object of type “record” that the procedure will examine to find the settings you have specified. Consider the script in Listing 9-6. FBRec is a FileBrowserInfo record that is defined so that only queries in the SAVEDQBE directory are displayed. When the user selects a query, it is executed and the ANSWER table displayed. Listing 9-6. Using the fileBrowser() procedure to select a QBE file to run ; Script
0906.SSL
-
Displaying
the
fileBrowser
var FBRec oneFile TV endVar
FileBrowserInfo String TableView
FBRec.AllowableTypes = fbQuery FBRec. SelectedType = fbQuery FBRec.Alias = ":SAVEDQBE:" if
; allow queries only ; display queries only ; private dir only
fileBrowser(oneFile, FBRec ) then message("Running query " + oneFile) if oneFile.executeQBEFile() then TV.open(":PRIV:
ANSWER. DB")
endif endif
Summary This chapter explored various issues on developing complete applications in Paradox for Windows. It introduced the subjects of application structure, how to manage directories using aliases, and how to manage intermediate holding tables. You also learned about the isassigned(), isBlankZero(), and blankAsZero() methods, and the fileBrowser() procedure.
180
ee ie fa ©]
> pe© sl
PART
III
BHRHREHBREHEEBEHEHEHEHEHEHBEHHEEHEHEEEHE EG
Application Development Techniques Part III explores various application development techniques. It begins in Chapter 10 with a discussion of database normalization from a developer’s standpoint, and explores ways to normalize and denormalize your data and why you might want to perform these transformations. (For more information on normalization, refer to Chapter 16
in the companion volume.) Chapter 11 continues with a discussion of using queries as tools to control viewing and editing. Paradox integrates queries and reports very closely, and this subject is covered in Chapter 12. The subject of ad hoc queries is covered next in Chapters 13 and 14. These are queries built into your applications. In ad hoc queries, the user defines the selection criteria, fields, and perhaps even the tables involved in the query at runtime. This powerful capability allows users to organize and summarize their data in ways that you, the developer, have not specifically programmed. Chapter 15 describes a stepwise query system that lets you perform recursive queries on the same data. Chapter 16 covers operations that cannot be performed using queries and for which the alternative is a PAL-oriented approach. Finally, Chapters 17 and 18 introduce a number of powerful query tools for Paradox 4.0 and Paradox for Windows.
181
: : =
we. ——
es
a pat
pane ania * wotruli matin
eggs teu baits¥ Samples cw
canine
ileum splinah bitin te
sel MsPan wigbe u vice hee ath iw adiemonsli hte ealeanot renee ee aes a (ertynaqtewslay) watt w/t)
etstaup griaay
7
baie seaoline Lt patadLt . ore
+1 Sen T osiw VU Eafitiprete’ esaee) peli Ua poimng bnreareec 1 ah j camp a. aha asbowed si sa wistol
ne wot An baat seeped girteneteerdaie Wfkaheap eee
ae
uommaadae si eytdeb so. se “ui Pictipoad be t0} adninclingy igo anti Ne
wer, alEsansasnxis WO) 6 ee
se apy bea
AHO cath GY a ks, Wot Sains hae soa hncomes Rell vila Si 0 ait : erates 14 aol) Sonn (ivadizeze torr oend eaveatch b > Beet ab sme sth We 2) arts MLR enews i epitramch 106 bit tethering! bsatretuey? coe settle 2
ie OEDhs.Sterigets stljeal thaedaen tain Ra
Sa eR
> ae! shaw phe a orate detieweet)
Suetiak aa
(eeerlan
-
&.
eve
.
7
CHAPTER
TEN
Normalizing
and Denormalizing Clients make two requests that strike abject fear into the hearts of developers: I like the way you organized the detail records in this database, but I need the reports to look like a spreadsheet, with each month in a different column.... This new database is nice, but I have all this data from the old database that has to be converted or else we can’t use it....
In the first instance, the client needs the report data to be denormalized so that each group of records is aggregated into different fields. One way to do this is with a crosstab, but as you will see, the crosstab facility in Paradox for DOS is not especially powerful and may not do the job. In that case, queries are the only answer. In the second instance, the client most likely has an unnormalized table design, with repeating groups of fields. This data needs to be moved into a detail table, changing each nonblank group into a separate record. This is usually a one-time operation when the application is first installed, but if you are working with data imported periodically from another source, the task may need to be performed on a regular basis.
Denormalizing Data in Paradox for DOS The opposite of normalizing your data into master and detail tables is to denormalize it. Denormalizing data is usually done for reporting purposes. The best way to explore the various techniques is to study a specific example. Assume that the client needs a report of sales volume by product for a specific year. The report needs to list all products in the catalog, whether or not there were sales for a specific month or even for the whole year, together with the monthly sales 183
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
organized as a series of columns across the report. In other words, the client needs a report in spreadsheet format. The report might look something like Figure 10-1.
Figure 10-1. A sample report with denormalized data
To set up this operation in Paradox for DOS, you will need several tables. Following is a description of each of them. Stock report The STOCKREP table has a key field for the Stock No, and twelve additional fields, one for each month in the report, as shown in the following table. You will attach the report from Figure 10-1 to this table, linking back to the STOCK table for the Equipment Class and Description fields. The objective of the following queries will be to fill this table so that you can output the report. Here is the structure of the STOCKREP table: Field #
Name
Type
1
Stock No
N*
2
1
N _ ; January
3
2
N _ ;February
4
3
N — ; March
12
4
N _ ; November
13
*)
N_ _ ; December
-— SS? ee
184
@
@
@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
Grouping by month As you saw in the companion volume, Paradox does not allow you to select or group on a portion of a date. But a linked MONTHS table allows you to group on a month and year from a range of dates. Here is the structure of the MONTHS table: Field #
Name
Type
1
Month
Se
2
Start Date
D
3
End Date
D
+
Month Name
A3
This table must be initialized for each year, so that the Start Date and End Date fields reflect the correct starting and ending dates for the year you have chosen. To do this in a script, use a Paradox for DOS script like the one shown in Listing 10-1. Listing 10-1. Initializing the MONTHS table for a year ; get
year
from
the
user
Clear @ 0,0 ?? "Enter 2-digit Accept "S" Picture "##" If RETVAL = False Then Return EndIf
; load
the
table
CoEdit
"MONTHS"
ROX “EHeom [Month]
eT = X
; first
day
[Start
Date]
based
Year: To YR
on
the
specified
of =
blank screen prompt user wait for entry user pressed [Esc] cancel operation
year
omlZ
7 loop
the
for
12
records
month
DateVal(StrVal
; next day of the following ; except for December Difiexe
; ; ; ; ;
"
(X)+"/01/"+StrVal
month
-
(YR) )
1
< [End
tal?) Then Date]
=
DateVal (StrVal (X+1)+"/01/"+StrVal(YR))
[End
Date]
=
DateVal
-
1
Else ("12/31/"+StrVal (YR) )
Endif
185
PARADOX
QUERIES:
[Month Down
Name]
=
A
DEVELOPER’S
MOY([Start
REFERENCE
@
@
@
@
Date]) ; next
record
EndFor
Do_It! ClearImage
; save results ; clean up
This script prompts for the year, then fills a 12-record table with the starting and ending dates for each month in that year. If your application has obtained the year through some other means, you can drop the first half of the script. Querying for matching records The first query in this sequence is shown in Figure 10-2. It links the MONTHS table (to establish date ranges and the month grouping) with the ORDERS table (for the Sale Date) and the LINEITEM table (for the Stock No and Qty sold).
Figure 10-2. Querying to summarize records by Month and Stock No
When you are running this query under Paradox for DOS, the order of tables on the Workspace is very important. The only way in Paradox for DOS to control the order of fields in the ANSWER table is by the sequence of Check fields in the query images. If the MONTHS table is listed first, the MONTHS -> Month field will be the first one in the ANSWER table, and Paradox will sort by this field (because you have specified a Check query). This sorting is very important, as you will see shortly. Another unusual feature of this query is the use of inclusion operators in the Start Date and End Date fields. It is marginally possible that some months will not have any orders, which will cause problems with subsequent steps in the query. When you specify an outer join, you are telling Paradox to include all months in the ANSWER table, even if there are no orders for those months in the ORDERS table.
186
@
@
@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
Crosstabulating the ANSWER table The next step in generating this report is to perform a crosstabulation. Crosstabs are a powerful analysis tool used to examine the interrelationships between different fields in a table. If you have two fields that vary independently, and a third field whose value is dependent on the values of the other fields, you can tell Paradox to use one of the independent fields for the record labels, the other for the column labels, and the dependent field for the “cells” formed at the intersection of row and column. The transformation you are performing is shown in Figure 10-3.
ANSWER
: CROSSTAB
eee
Figure 10-3. Transforming ANSWER to a crosstabulation
The result is a table that looks like a spreadsheet, with columns containing data in a series, rows containing different instances, and values in the table representing the combination of a series and an instance. This table is certainly not normalized, but it is an important and different view of your data. Crosstabs allow you to go one step further. If there is a third field that varies independently, you may have more than one record in the original table for each unique combination of the independent fields. Therefore, you may have more than one value to place at the intersection. Crosstabs solve this problem by allowing you to generate the Sum, Count, Min, Max, or Average of the values. (Paradox for Windows supports all five operators; Paradox for DOS supports all except Average.)
187
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
Paradox for DOS will perform a crosstab automatically, but the feature is relatively limited. It does not sort the values in either of the independent fields as they are being crosstabulated. This means that if the values in the Month field are not sorted before you run the crosstab, the fields generated by the crosstab will not be sorted either, which would be a disaster.
To crosstabulate the ANSWER table you just created, perform the following steps: 1. Move the cursor to the Month field and press [Ctrl-R] to rotate this field to the end of the table. Stock No becomes the current field. 2. Move the cursor to the Sum of Qty field and press [Ctrl-R] to rotate it to the end. Month is now the current field. 3. From the menu, select Image / Graph / CrossTab.
4. Select 1) Sum to perform a summary crosstab. 5. Paradox for DOS prompts for the field containing the row labels. Move the cursor to the Stock No field and press [Enter]. 6. Paradox prompts for the field containing the column labels (fields in the CROSSTAB table). Move the cursor to Month and press [Enter]. 7. Paradox prompts for the field containing the crosstab values. Move the cursor to Sum of Qty and press [Enter]. The CROSSTAB table, shown in Figure 10-4, is generated relatively quickly. Notice how the months are listed in sequence from 1 through 12, as you require. Notice also that some combinations of Stock No and Month have a zero in the intersection “cell.” When Paradox finds a month where a product was not sold, the sum of values is zero. (If you were doing a Min or Max crosstab, Paradox would leave missing intersections blank, because zero means something entirely different in a Min or Max operation.)
188
@
@
CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
NS
= AWN WW =
Fi
He
? Form
Alt-F9
Cokdit
Seerhoegcereegegn Seqgqgorvrevroegggd ARE & RFOONW NORE
eqgogoggqcescea Meu. TS
oJ saml
Figure 10-4. The CROSSTAB table
The PAL code to perform these operations is shown in Listing 10-2. It assumes that you have just run the query from Figure 10-2. Listing 10-2. Crosstabulating the ANSWER table MoveTo MoveTo
[Month] [Sum of
Qty]
Rotate Rotate
Menu {Image} {Graph} {CrossTab} MoveTo [Stock No] Enter MoveTo [Month] Enter MoveTo
[Sum
of
Qty]
; rotate ; rotate {1)
to to
end end
Sum} ; row labels ; column labels
Enter
; crosstab
values
ClearAll
Updating the STOCKREP table By design, the CROSSTAB table has the same structure as the STOCKREP table. Updating STOCKREP therefore involves nothing more complex than a simple add operation. Listing 10-3 shows the code and query necessary to update STOCKREP. Listing 10-3. Updating STOCKREP from the CROSSTAB table Empty
"STOCKREP"
; remove
old
records
from
table
Query
StockRep
; Stock
insert
iEStock
No
,
189
PARADOX
QUERIES:
Stock
Stock
A
DEVELOPER’S
REFERENCE
@& @
@
G@
No
wstock EndQuery
Powe} Add "CROSSTAB"
id
"STOCKREP"
; load new list of Stock items ; update with crosstab results
You may need to perform one final step. If some stock items were not ordered during the year, their records will be completely blank because there is no entry in the CROSSTAB table for the Add operation. If other items were not ordered in a particular month, those fields will contain a zero. You must decide if you want all empty fields to be blank or to contain zero. If you want blanks, perform a twelve-line query like the one shown in Figure 10-5. Each ChangeTo operation must be on a different line of the query image, because you are looking for fields that independently have a zero value, not for records where every value is zero.
biseai|
Figure 10-5. Changing zeros to blanks for consistency (the fields have been narrowed so more can be seen).
If you want zeros instead of blanks, reverse the query, using the operation blank,
changeto
0
Denormalizing Data in Paradox for Windows 3k
In Paradox for Windows the operation begins in the same way, by initializing the MONTHS table for the specified year. You can use a TCursor for this operation, since there is no need for the user to see what is happening. Listing 10-4 shows the ObjectPAL code to initialize the MONTHS table.
190
@
@
CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
Listing 10-4. Loading the MONTHS table in Paradox for Windows method
run(var
eventInfo
Event)
var
YRG
Xx
SmalliInt
Mon
TCursor
endVar ; prompt NAR
=e
user
to
enter
year
10)
YR.view("Enter
a 2 digit
; user
cancelled
tee SASL
(0) Melakeial
year")
return
endif
!
open
a TCursor
to
update
the
table
Mon.open("MONTHS.DB")
Mon. empty () Mon.edit () 1S
; empty ; allow
OC weicteny
IL qevey ALY
Mon. insertRecord() Mon.Month
’
first
=
day
Mon."Start
; Open
Mon."End
record
X
of
the
Date"
month
=
DateVal
; next day of the following except for December , Die
table changes
(StrVal(X)+"/01/"+StrVal (YR) )
month
-
1
then Date"
=
DateVal (StrVal (X+1)+"/01/"+StrVal (YR) )
Date"
=
DateVal("12/31/"+StrVal (YR) )
1
Else Mon."End EndIf Mon."Month
Name"
=
MOY(Mon."Start
Date")
EndFor
Mon. endEdit () Mon.close()
; save ; clean
results up
endmethod
191
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
G@
The process of generating the crosstab view is considerably simpler in Paradox for Windows, for the following reasons:
° In Paradox for Windows, crosstabs can be placed as an object on a form (but not on a report, unfortunately). ¢ Forms can be built around queries. When you open the form, the query is run automatically and the data displayed on the form reflects the results of the query. When you consider these facts, you will realize that the STOCKREP table is not necessary in Paradox for Windows. Instead, you can display the crosstab directly on a form after running the query shown in Figure 10-4 automatically. To set up the crosstabbed view in Paradox for Windows, perform the following steps:
1. Create a new query like the one shown in Figure 10-6. This query links the MONTHS, ORDERS, and LINEITEM tables. Query : 008, QBE
Jor
|
O
rder as
>=sdate,
ack No-
Figure 10-6. Linking three tables to generate a crosstab
2. Select Properties /Answer Table / Options... and rotate the fields in the ANSWER table to the following order:
192
@
@
@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
¢ Stock No ¢ Month
¢ Sum of Qty Click on the OK button to save these changes.
3. Select Properties / Answer Table / Sort... and specify that the ANSWER table be sorted in the following order: e Stock No ¢ Month
4. Run the query to ensure it generates the correct ANSWER table, and then save it to the query file 1008.QBE.
5. Create a new form. Instead of linking it to tables, select the 1008.QBE query. (Click on the Type drop-down list to select Queries.) Click OK to accept this Data Model. 6. In the Design Layout dialog box, select a “Blank” layout and click OK to accept this layout.
7. Place a crosstab object on the form and resize it to the full width and height of the form. 8. With the cursor positioned in the top-left corner of the crosstab, rightclick and select Define Crosstab. Select 1008.QBE. Paradox automatically uses the Stock No field for the row labels and the Month field for the column labels, and specifies a Sum operation for the field values. 9. Narrow the crosstab columns so that all twelve months are visible. You may also need to adjust the Number format for the row labels and field values, and add a vertical scrollbar. Prettify the crosstab in any other ways you wish.
193
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
G@
10. When you have finished customizing the form, click on the Run Form icon to run it. Paradox runs the query first, then the crosstab on the ANSWER table, before displaying the form. The result is shown in Figure 10-7.
11. Don’t forget to save when you have finished.
Form : 1009.FSL
Figure 10-7. A Crosstab of Stock No by Months
Unfortunately, for some reason that I do not understand, Paradox for Windows does not allow you to place a crosstab object in a report. When you open the form described above as a report, Paradox removes the crosstab object. Instead, Paradox
provides an action constant that allows you to save a crosstab to a CROSSTAB table in your Private directory, and you have to report on this table.
194
@
@
BM CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
Working with Rolling Months Real-world data rarely falls into the neat categories and examples described here. There is always some wrinkle to gum up the works. For example, assume that the client wants a rolling twelve-month report showing sales for the last twelve months. Or perhaps your company is based on a fiscal year from April through March. In both instances, you cannot easily sort based on the month field. Solving these specific problems requires a playing a trick on Paradox. Modify the structure of the MONTHS table to add a new field called Report Month, which will be an S field. As part of your code, you can prompt the user for the starting month, and use this number to fill in the field. Listing 10-5 shows the Paradox for DOS code to do this. Listing 10-5. Filling the MONTHS table with the special month field Clear Clr 0h 22 “Enter Starcang Month: as Accept "S" Min 1 Max 12 To ST.MON If Retval = False Then Return EndIf Clear @ 0,0 ?? "Enter 2-digit Accept "S" Picture "##" If RETVAL = False Then Return EndIf
Empty "MONTH" CoEdit "MONTH" For X from ST.MON [Month] = X [Report
to
Month]
=
[Start Date] = Le xe LO nen
Start To YR
Year:
"
; ; ; ; ;
blank screen prompt user wait for entry user pressed [Esc] cancel operation
; ; ; ; ;
blank screen prompt user wait for entry user pressed [Esc] cancel operation
blank table ; update Qe catkiehe smovecie prene Ieyoyo)
12 X-ST.MON+1
; trick
month
DateVal(StrVal(X)+"/01/"+StrVal (YR) )
{End
Date]
=
DateVal
(StrVal (X+1)+"/01/"+StrVal(YR))
{End
Date]
=
DateVal("12/31/"+StrvVal (YR) )
-
1
Else EndIf [Month
Name]
= MOY([Start
Date] )
Down
EndFor
YR = YR BOL xs Loon (Month]
ie tO)
SL evONo
; increment year ; second part of
loop
= X
195
PARADOX
QUERIES:
[Report [Start IE
Month]
=
Date]
Pp css
A DEVELOPER’S
=
REFERENCE
@
@
@
@
X+13-ST.MON DateVal(StrVal(X)+"/01/"+StrvVal (YR) )
[End
MLA ilaverat Date] =
DateVal (StrVal (X+1)+"/01/"+StrVal(YR))
[End
Date]
DateVal("12/31/"+StrVal (YR) )
-
1
Else =
EndIf (Month
Name]
=
MOY([(Start
Date])
Down
EndFor DOmUsen
ClearImage
Assume that April, 1990, must be the first month. The user will specify 4, and Paradox will enter 1 through 9 in 1990 for the Report Month field for April through December, then 10 through 12 in 1991 for this field for January through March. If you now Check the Report Month field instead of the Month field in the query from Figure 10-2, Paradox will sort the ANSWER table with records from April, 1990, through March, 1991, in that order.
There is one final problem, and that’s the report headings for each of the month columns. Because the month placed in each column will vary each time, the way to solve this problem is to define each heading in the report as a PAL variable. You can scan the MONTH table to read the Month Name field into these variables so that the appropriate name is placed at the top of each field.
When You Cannot Use Crosstabs There may be times when you cannot organize your data to use a crosstab and neatly feed one operation into the next. If you need to perform complex calculations as part of organizing the data, you may not be able to use the crosstab solution. When this is the case, the technique for transferring the data from ANSWER to STOCKREP requires that you use twelve queries inside a For loop, one query for each month. Listing 10-6 shows how this is done in Paradox for DOS. Listing 10-6. Processing twelve queries in a tight loop ; set
up
query
"Shell"
for
the
12
queries
Query
Answer
| Month
| Stock _stock
196
No
Sum
_gty
of
Qty
'Y)
@
@
@ CHAPTER
StockRep
10:
NORMALIZING
AND
DENORMALIZING
; Stock No [ estock
Endquery ForixsErom
Message MoveTo
item
; for
"Processing [ANSWER
->
Month
",
month
Month]
CtrlBackSpace
Typein MoveTo
StrVal (X) "STOCKREP(Q) "
MoveTo
Field
Typein
"ChangeTo
blank old month type new month
Strval (X) "
Example
"qty"
next
month
lini
co
process
Donen MoveTo
each
X
field
Sum
iors Oy;
X'th
query
"STOCKREP(Q)"
MoveTo Field Strval CtrlBackspace
(X)
field blank
just done "ChangeTo"
EndFor
Changed
ClearImage MoveTo "ANSWER (Q) "
ClearImage
MoveTo
ClearImage
"STOCKREP(Q)"
table
This script loops through two query images, updating the selection criteria and query operation for each loop. It starts by loading the query images onto the Workspace with the example elements that do not change for the duration of the script. The next step is to place the correct month number into the ANSWER table’s Month field. The script then jumps to the STOCKREP table, and then to the appropriate month field. (This technique works very nicely if the fields are named by their number—that’s why STOCKREP uses month numbers instead of month names!) It then enters the appropriate ChangeTo operation and processes the query. When the query has finished, the ChangeTo is removed, so that the next loop does not have to deal with the operators from the previous loop. Two issues are critical for this technique to work: ¢ If your script enters a selection criterion or an operator into a field that will change each time you traverse the loop, it must be removed before you actually increment the counter. e When you move between query images and fields inside a loop like this, you must be very explicit about the table and field you are moving to for each operation, so that each iteration of the loop behaves in exactly the same way. 197
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
G@
Normalizing Existing Data The companion volume explored the various theoretical steps required to convert a mass of data in one large table into a number of smaller tables. This section demonstrates the basic queries and operations you may need to perform when you meet this situation in real life. Assume that you are starting with an unnormalized table like the one following. (Tables and code for this section can be found in the CHAP10\ subdirectory created when you installed the diskette that accompanies this book.) Here is the structure of the UNNORMAL table: Field #
Name
Type
1
Order No
N*
2
Order Date
D
3
Ship Via
A7
4
Payment Method
A7
5
Customer Name
A30
6
Customer Address
ASO
7
Customer City
A15
8
Customer State
A2
9
Customer Zip
A10
10
Customer Phone
A15
11
Stock Item 1
A30
12
Stock Vendor 1
A30
13
Stock Price 1
$
14
Stock Qty 1
S
15
Stock Item 2
A30
16
Stock Vendor 2
A30
i,
Stock Price 2
$
18
Stock Qty 2
S
19
Stock Item 3
A30
20
Stock Vendor 3
A30
aM
Stock Price 3
22
Stock Qty 3
198
S
@
@
@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
This table contains orders for the past months or years. You have just completed a new Paradox application to manage this data, and your tables are normalized to third normal form. The tables in your database schema are shown in the following tables. Here is the structure of the CUSTOMER table: Field #
Name Customer No
Type N®
Customer Name
A30
Customer Address
ASO
Customer City
A15
Customer State
A2
Customer Zip
A10
NH FW HH NN Customer
Phone
A15
Here is the structure of the ORDERS table:
Field # 1
Name
Type
Order No
N*
Customer No
Order Date
Ship Via nA LK WwW &
Payment Method
Following is the structure of the LINEITEM table. (Note that this table starts out with additional fields for Stock Item and Stock Vendor, but these are deleted as part of the normalization process.) Field # 1 2 3 4
Name
Type
Order No
Né N*
Stock No
Stock Price Stock Qty
$ S
199
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Here is the structure of the STOCK table: Field #
Name
Type
1
Stock No
N*
2
Stock Item
A30
3
Stock Vendor
A30
4
Stock Price
$
The problem now is to convert all the data from the UNNORMAL table and merge it into the four tables listed above, with no loss of data! Loading the CUSTOMER table The first step is to extract all the customer information and place it into the CUSTOMER table. The Check query for all customers is shown in Figure 10-8. This query includes a “Calc Blank as Customer No” operator to generate a new field in the ANSWER table. Because the Calc Blank is placed in an N field, the resulting Customer No field will also be of type N. Query Unnormal calc
blank
as
“Customer
==
No”
sis Rr Ss a
Figure 10-8. Extracting customer information to an ANSWER table
Because the query uses Check operators, the ANSWER table is sorted and all duplicate values removed. The next step is to add the Customer No numbers, using the script shown in Listing 10-7. Listing 10-7. Inserting a customer ID number CoEdit
"ANSWER"
Scan (Customer EndScan
200
No]
=
[#]
; for ; use
each record the record no.
Do_It!
; save
ClearAll
; clean
up
@
@
B@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
This query uses the record number for the Customer No, but it could easily be modified to use any valid numbering scheme. It operates directly on the ANSWER table from the above query. In Paradox for Windows, you would use a TCursor, but the operation is fundamentally the same. The final step is to add this information to the CUSTOMER table by using the Insert query in Figure 10-9.
Figure 10-9. Adding ANSWER to CUSTOMER by using an Insert query
Loading the ORDERS table The next step is to load the records into the ORDERS table. This operation is simplified by the fact that each record in the UNNORMAL table translates directly into a record in the ORDERS table. UNNORMAL is fundamentally an orders table
with a badly designed structure and a number of fields that don’t belong there. This query is shown in Figure 10-10. It requires a link to the just-completed CUSTOMER table, since only the Customer No will be stored in ORDERS.
Figure 10-10. Adding orders data into ORDERS
Loading the LINEITEM table Loading the LINEITEM table is a little more complex because it requires multi-
ple rows and unusual linking between tables. It is shown in Figure 10-11. This query 201
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
converts an unnormalized table to first normal form, removing the repeating groups to separate tables. Each of the repeating groups requires a separate row in the query, and separate example elements to keep the order records apart from each other. The query assumes that every order has at least one Stock item, and that it may have more than one. So the Not Blank operators only insert records into LINEITEM where line items were actually found in the UNNORMAL table.
Figure 10-11. Adding line items into LINEITEMS
Loading the STOCK table
At this point, the LINEITEM table contains a considerable amount of redundant information, violating second normal form. The Stock Item and Stock Vendor fields are not attributes of the complete multi-field, Primary key of LINEITEM. To remove them, start out by querying for all unique descriptions and suppliers, as shown in Figure 10-12. This query also generates a blank Stock No field, which is filled with IDs using code similar to that shown in Listing 10-7 for the CUSTOMER table. When this step is done, the query in Figure 10-13 moves Stock data to the STOCK table.
Figure 10-13. Loading the STOCK table with unique stock items
202
@
@
@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
Cleaning up line items The LINEITEM table still contains redundant Stock item and vendor information. It should be replaced by a simple reference to the Stock No in the STOCK table. To remove this data, restructure the LINEITEM table to add a field called Stock No, if it is not already present. Then run a query like the one shown in Figure 10-14 to link LINEITEM to STOCK and change the matching field.
Figure 10-14. Updating the Stock No field in LINEITEM
Finally, restructure LINEITEM again to remove the Stock Item and Stock Vendor fields. LINEITEM is now in second normal form. Normalizing the STOCK table If you want to normalize the STOCK table as far as it can go, you should create a VENDOR table and move the vendor fields to this one. Each Stock item should reference a Vendor No, which becomes the ID and Primary key field in the VENDOR table. The process to move this data over is virtually identical to the process described above for customer and stock data. At this point, in a typical application, you are about half done! Unfortunately, the remaining steps cannot be accomplished easily with queries. Instead, they require painful manual labor to clean up the results. In a typical flat-file database, your data will be a mess, with many inconsistencies between data in different records. For example, you will probably find customer records with similar but not identical names that are actually the same customer. The same may be true for stock items and for vendors. Cleaning up this data is simplified on the one hand because you have far fewer customer, stock, and vendor records to wade through. All duplicate entries have already been weeded out, and the obvious, glaring errors should clearly stand out.
203
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
&@
But the task is also made more difficult because you cannot simply delete the incorrect record from CUSTOMER. There are likely to be orders in the ORDERS table that link to this record. Here is one solution to this problem: 1. Create a new field in the CUSTOMER table called New Customer No,
with the same field type as the Customer No field.
2. As you scan through the table and find a customer record that should be subsumed into another one, place the Customer No for the replacement record into the New Customer No field. 3. Perform a query like the one in Figure 10-15, which changes the appropriate records in ORDERS to the new Customer No.
4. Issue a Delete query to remove all records from the CUSTOMER table where the New Customer No field is not blank. These are the records whose orders were reassigned!
Figure 10-15. Updating corrected Customer No values in the ORDERS table
This final step can be very painstaking, but it is vital if you want to be confident that your data is both internally consistent and representative of the universe your client sees.
Summary This chapter explored the important topics of denormalizing and normalizing. When you need to denormalize your data, the simplest technique is to perform a query that assembles the data you need into one table with a multi-field Primary key. Then use Paradox’s crosstab facility to generate the denormalized result. 204
@
@
@ CHAPTER
10:
NORMALIZING
AND
DENORMALIZING
In Paradox for DOS, the crosstab is placed in a separate table around which you can build a report. In Paradox for Windows, the crosstab is generated as a form-based object that you can view on-screen or send to the printer. Normalizing is commonly required when a client asks you to create a new application around existing tables. Your goal is to transform the data with a series of queries from one table into many tables with fundamentally different structures. A fundamental objective must be the preservation of your client’s existing data, warts and all. Once these transformations have been performed, you can review the data and resolve the inevitable inconsistencies.
205
~
4
we04oy
ashe
cot; Aa th tie benyeds r eu
abu)
Aine
|
ei TS @ ale
cock grees Go)
ar
8
sen
ién
eS
datmenadeniialh
Seen
A
La=apoah
stets « cre lena oor
70
then
=
substr(CONFIRM.TXT,1,70)
CONFIRM.TXT
endif DLG.WIDTH
252
dialog
=
len(CONFIRM.TXT)
+ 7
initial
column column column
value
query"
@
Beeee
Bw
CHAPTER
13:
AD
DLG.WIDTH = iif( DLG.WIDTH < 28, DLG.COL = int ((79-DLG.WIDTH) /2)
; set BUT1 BUT2
button = =
positions
based
int(DLG.WIDTH/2 int (DLG.WIDTH/2)
CONFIRM.TXT
=
showdialog
28,
QUERY
DLG.WIDTH
on dialog
TECHNIQUES
)
size
12)
format ("w"+strval (DLG.WIDTH)+",ac",CONFIRM.TXT)
"Confirm"
@ 5,DLG.COL
@ 1,0
HOC
height
7 width
DLG.WIDTH
?? CONFIRM.TXT
pushbutton "_O-K"
@ 3,BUT1
width
10
width
10
ok value True GagenOk” to
DLG.VAL
pushbutton @ 3,BUT2 "Cancel" cancel default value False tag "Cancel" to
DLG.VAL
enddialog Return
IIF(
DLG.VAL,
2,
1)
EndProc
proc
DISPLAY_PROMPT() ; custom
prompt
based
on
If NImages() > 1 Then Prompt "Ad Hoc Query
Else Prompt EndIf
"Ad Hoc
Query
the number
of query
system.
[F10]
Menu
system.
[F10]
Menu"
images
[F3]
UpImage
[F4]
DownImage"
EndProc
253
A DEVELOPER’S
QUERIES:
PARADOX
proc
REFERENCE
ZBeesae
return
user
RUN_QUERY() ; blank
top
line
menu
ShowPul1Down EndMenu
; check
for
'
query
processing
errors,
and
them
to the
Do_It!
If Window() "" Then Message Window() Else VIEW_ANSWER_TABLE ()
; modal
view
proc
EndIf
; redisplay
the
top
line
menu
DISPLAY_MENU ()
Return
1
EndProc
proc ADD_TABLE () private
FILE.NAME, FILE.LIST, DOS. PATH, DLG.VAL, X
; Initialize =
PIES
Tee
""
DOS.PATH
= Directory()
DLG. VAL
=
ShowDialog Proc
"Pick
a Table"
"ADD_TABLE
@5,8 height @1,1
DIALOG_PROC"
"Select",
254
"Depart",
13 width
FILE.NAME
48
"Accept"
64
"~T~able/Dir:
Accept @1,13 width tag "DirAccept" to
; Start
0
Trigger
Label
directory or file name entry from picklist path for picklist dialog control attributes dynarray
Variables
FILE.NAME
GCS
; ; ; ; ;
" for
"A60"
"DirAccept"
with
current
dir
See
ee
CHAPTER
PickTable
we.
; note
59 columns
call
PushButton @9,10 "~R~etrieve"
To
QUERY
TECHNIQUES
4 change
dynamically
List
proc
tag
HOC
; Can
to
; this proc issues ; as necessary.
value
AD
@3,1
height 5 width DOS. PATH tag "FileList" COmi
13:
RETRIEVE_DAVED_QUERY()
the AcceptDialog
width
and
from
sets
the
dialog
DLG.VAL
12
RETRIEVE_SAVED_QUERY(
"Dialog"
)
"Retrieve" DLG.VAL
PushButton
@9,25
"~O~K"
OK
value
width
12
DEFAULT
1
tag "OK" to
DLG.VAL
PushButton @9,40 width "~C~ancel" CANCEL value 0 tag "Cancel" to
12
DLG.VAL
EndDialog ; process
different
returned
; DLG.VAL
=
from
; only
2 comes
return
Switch Case
codes
DLG.VAL
Return
Case
=
of
values
RETRIEVE_SAVED_QUERY()
1 or
0 are
allowed
0 :
DLG.VAL
DLG.VAL
=
1
Echo Off Menu {Ask} Select FILE.NAME ; load query Window GetAttributes GetWindow() to X X{"CanMaximize"] = False
Window
SetAttributes
Echo Normal DISPLAY_PROMPT () Return Case
from X
; update
prompt
1
DLG.VAL
Return
GetWindow()
image
= 2
1
EndSwitch EndProc
255
PARADOX
Proc
QUERIES:
A DEVELOPER’S
ADD _TABLE_DIALOG_PROC(EventType,
REFERENCE
TagValue,
EventVal,
@
@
ElemValue)
Switch Case
EventType
=
"DEPART"
If TagValue = "DirAccept" Then ResyncControl "DirAccept" DOS.PATH = FILE.NAME RefreshControl "FileList" Return
; assign new value ; new DOS path ; scan new directory
True
Endlf
Case
EventType
FILE.NAME DLG.VAL
= =
=
"SELECT"
:
; double-click
in pickist
FILE.LIST
1
AcceptDialog Return
True
Case EventType = If DirExists( FILE.NAME
=
EndIf If IsTable( DLG.VAL
"ACCEPT" FILE.NAME
FILE.NAME =
) = 1 Then
FILE.NAME
+ FILE.LIST
) Then
; table
exists
+ " could
not
1
AcceptDialog Return
True
Else
Message "Table " + FILE.NAME SelectControl "DirAccept" Return False
be
found"
EndIf EndSwitch EndProc
proc
CLEAR_TABLE(
; HOW.MANY 6 If HOW.MANY
HOW.MANY
is ong
"One" NII
=
"One"
ClearImage, ClearAll
image?")
LPEner vole —=2e then
ClearImage EndIf
256
WinClose,
Close
icon
Then
CONFIRM_DIALOG("Are
query
)
you
sure
you
want
to clear
the
"+Table()+"
@
@
Bees
ew
CHAPTER
Else CONFIRM_DIALOG("Are If retval = 2 Then ClearAll EndIf EndIf
13:
you
AD
sure
you
HOC
QUERY
want
to clear
TECHNIQUES
all
query
images?")
Switch Case
= 0! 2p
NImages()
; no
images
left
ADD_TABLE()
If Retval = 0 Then If NImages() = 0 Then Quit "Exited Ad Hoc EndIf Endlif Case
NImages()
DISPLAY
=
; user
query
1 :
canceled
system"
; 1 image
left
PROMPT ()
Return
1
OtherWise
:
DISPLAY
; > 1 image
left
PROMPT ()
Return
1
EndSwitch EndProc
proc SAVE_CURRENT_QUERY () Private DLG.VAL,
; dialog control ; specified script ; overwrite confirm
SC.NAME, MENU . CHOICE DLEy VAlee=
set
SC.NAMEM=
2"
ShowDialog
@7,18
"Save
height
Current
7 width
Query"
44
Label @1,1 "~S~cript: Accept @1,10 width 30 tag "FileName" to SC.NAME PushButton "~O~K"
value tag to
@3,10
width
" for "A60"
"FileName"
; script
name
10
DEFAULT
PROCESS_SAVE_QUERY()
R Geli,
jonaete
“OK! DLG.VAL
257
PARADOX
QUERIES:
A DEVELOPER’S
PushButton @3,22 width "~C~ancel" CANCEL value False tag "Cancel" to
REFERENCE
8
@
@
@
10
DLG.VAL
EndDialog Return
DLG.VAL
EndProc
proc
PROCESS_SAVE_QUERY()
; don't Echo
show
menu
choices
to
the
Off
lif “Search
(ust)
SCANAME,
)f S50
‘Then
Message "You cannot specify SelectControl "FileName" Echo Normal Return 1 EndIf Menu
user
{Scripts}
; specified
{QuerySave}
name
If MenuChoice() ShowPopup
US
a
already =
"Cancel"
a file
Select
extension"
SC.NAME
exists Then
al
"Cancel"
: "Script
exists;
cancel
"Replace"
: "Script
exists;
replace
EndMenu To MENU.CHOICE If MENU.CHOICE ; walk Menu
the
=
"REPLACE"
menus
{Scripts}
again,
and
since
{QuerySave}
the
Select
ShowPopup SC.NAME
Else
SelectControl Echo Normal Return
EndIf
258
1
to specify "FileName"
another"
a new
file name
: "CANCEL",
: "REPLACE"
Then
AcceptDialog
; jump back
specify
it"
cleared {Replace}
them
S@EeBeeBe
Bw
CHAPTER
13:
AD
HOC
QUERY
TECHNIQUES
Else
AcceptDialog Endlf Echo
Normal
Return
1
EndProc
proc
RETRIEVE_SAVED_QUERY(
from where
SOURCE.PROC
was
proc
called
) private
FILE.NAME, FILE.LOST, DOS. PATH, DLG. VAL
; Initialize
directory or script selected script selected dir dialog control
;
Start
Variables
FILE.NAME
=
""
amine
=e
EY)
ES
; ; ; ;
DOS.PATH
= Directory()+"*.SC"
DLG.VAL
=n0
with
current
dir
ShowDialog "Pick a Query Script" Proc "RETRIEVE_QUERY_DIALOG_PROC" TRIGGER
"Select",
@5,8
height
Label
@1,1
Accept tag to
"Depart",
13 width
"~S~cript/Dir:
@1,14 width "DirAccept"
"Accept"
64
47
" for
"DirAccept"
"A60"
FILE.NAME
PickFile
@3,1
height
5 width
59 columns
4 ; Can
DOS. PATH
change
dynamically
NOEXT
tag
"FileList"
EOE DUE iS L
PushButton
@9,20
"~O~K"
value ec) to
OK
width
10
DEFAULT
1 One:
DLG.VAL
PushButton @9,32 width "~C~ancel" CANCEL
10
259
PARADOX
QUERIES:
value tag to
A DEVELOPER’S
REFERENCE
@
@
@
0 "Cancel"
DLG.VAL
EndDialog If DLG.VAL If
= 0 Then
SOURCE.PROC
=
"Dialog"
SelectControl EndIf Return 0 EndIf ; don't Echo
show
menu
"DirAccept"
tree
to
the
; user
canceled
; from
ADD_TABLE()
; control
proc
in that
dialog
user
Off
Menu
{Scripts}
Echo
Normal
{Play}
DISPLAY_PROMPT MoveTo 1 If
Then
Select
FILE.NAME
()
SOURCE.PROC
=
; update custom prompt ; start on first image "Dialog"
Then
; from
AcceptDialog Return Else Return EndIf
ADD_TABLE()
; dialog
in that
proc
proc
2
; from menu 1
EndProc
proc
RETRIEVE_QUERY_DIALOG_PROC(EventType,
TagValue,
EventVal,
Switch Case
EventType
=
"DEPART"
If TagValue = "DirAccept" Then ResyncControl "DirAccept" DOS.PATH
=
FILE.NAME+"*.SC"
RefreshControl EndIf Case
EventType
=
"FileList"
; Assign ; New
; re-scan
disk
"SELECT"
AcceptDialog
Case
True
EventType
If DirExists(
260
=
"ACCEPT"
FILE.NAME
value
Path
FILE.NAME = FILE.LIST DLG.VAL = 1 Return
new
DOS
) = 1 Then
ElemValue)
@
SEeeBae
Bw
CHAPTER
FILE.NAME
=
13:
AD
FILE.NAME
+
HOC
QUERY
TECHNIQUES
FILE.LIST
EndIf
; script If
exists
IsFile(
FILE.NAME+".SC"
DLG.VAL
=
) Then
1
AcceptDialog Return
True
Else
Message "Script " + FILE.NAME SelectControl "DirAccept" Return False EndIf
+
" could
not
be
found"
EndSwitch EndProc
; View proc
the
ANSWER
table
VIEW_ANSWER_TABLE()
Prompt "Viewing Wait Table Proc
ANSWER
table.
[Esc]
or
[Ctrl-F8]
to return"
"WAIT_ANSWER"
Key
"DOS",
Message
"DOSBig",
"Next",
"Rotate",
"Close",
"F8",
"Esc"
"MenuSelect"
EndWait ClearImage DISPLAY _PROMPT ( )
EndProc
; Wait
proc
Proc
for
the
WAIT_ANSWER(
ANSWER
TR.TYPE,
table
EV.DYN,
CN
)
Switch Case
TR.TYPE
=
"EVENT"
If EV.DYN["KeyCode"] or EV.DYN["KeyCode"] Return
and
EV.DYN["Type"]
= asc("F8") = asc("Esc")
=
"KEY"
=
"MESSAGE"
Then
2
Else Return
1
Endlf Case TR.TYPE = "EVENT" and EV.DYN["Type"] If EV.DYN["Message"] = "CLOSE" Then
261
PARADOX
QUERIES:
A DEVELOPER’S
Return Else
2
Return EndIf
1
REFERENCE
@
@
@
@
EndSwitch EndProc
QUERY_ANY_TABLE()
The problem with Query-By-Image and Paradox Runtime In the past, there was one fundamental problem with Query-By-Image: it could
not be used in Paradox Runtime. Runtime is a special version of Paradox that is used to run canned applications but that has no interactive capabilities. Developers buy it once, and can then make as many copies as they need to distribute with finished applications. Paradox Runtime in versions prior to 4.0 did not allow you to use the Wait command on a query image. If you needed this capability, you had to buy a full license of Paradox. You might think that you could simulate this capability by using this command sequence: Echo Echo
Normal Off
SyncCursor
However, the Echo Normal command was also unavailable in Runtime. Instead, Borland provided a special procedure (without source code) called REFRESH_CANVAS(), which performed an echo function, worked in Runtime, but which inserted an artificial one second delay. In Paradox 3.X, this procedure can be found in the Data Entry ToolKit library.
Borland recently documented the TKEcho command, which provides the same functionality as Echo, but which is fully operational in Runtime. (TKEcho was added for the Data Entry ToolKit and is the command inside the REFRESH_CANVAS() procedure.) 262
BEeee
ew
CHAPTER
13:
AD
HOC
QUERY
TECHNIQUES
If you want to use Query-By-Image in Paradox Runtime versions prior to 4.0,
you have to use code similar to Listing 13-4. This example uses the GetChar() function to read a character from the keyboard and passes that character to Paradox using the KeyPress command, after which the PAL Canvas is refreshed using TKEcho. Listing 13-4. Simulating the Wait command in Paradox 3.X While (True) X = GetChar() tte X= 27 or x = QuitLoop Endif KeyPress X TKEcho Normal Echo Off
—60
Then
SyncCursor EndWhile
; read char 7 Ke Cre ie
; pass show ; hide
to Paradox workspace workspace
; move
canvas
cursor
In Paradox 4.0, both restrictions are removed, so the issue is moot. You can now
use the Echo command in Paradox Runtime. But there is no need to do this since you can also use the Wait command on a query image in Runtime. These are welcome moves that demonstrate how Borland listens to customer concerns! Note that the concept of the canvas and the Echo command doesn’t exist in Paradox for Windows.
Saving Queries in Memo Fields The code in Listing 13-3 demonstrates how to save user-defined queries with Paradox’s QuerySave facility. This approach is simple, but it does leave many discrete files sitting in a subdirectory, one for each query saved by the user. Another approach in Paradox 4.0 is to save queries into a memo field in a table. Recall that a memo can hold anything, even binary, unformatted data. This technique involves writing the saved query image into a memo field in a table, where the other fields in the table track a title and description of that query. An example of this technique can be found in the Query Script Manager routine in Chapter 17. The code to save an image is also shown in Listing 13-5. This code uses a temporary script name for the QuerySave operation, and then the FileRead command to read this file into a variable. The Binary option must be used to keep Paradox from stripping out CR/LF characters and reformatting the lines.
263
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Listing 13-5. Saving a query into a table’s memo field Message
"Saving
; Save
the
current
query
to
a
query
definition"
temporary
Menu {Scripts} {QuerySave} If MenuChoice() = "Cancel" {Replace} Endlf
script
Select Then
name
PrivDir()+"XXZZZXxX"
; Create a new query record in the table ; which is already on the workspace MoveTo
"QRYSAVED"
CoEditKey Ins
(Query
Script
Name]
= QueryName
[Description]
= QueryDesc
[Directory]
= directory()
; add
saved
FileRead (Query
query
Binary
into
memo
field
PrivDir()+"XXZZZXX.SC"
Itself]
=
to TEMP.VAR
TEMP.VAR
Do_It!
Menu
{Tools}
{Delete}
{Script}
Select
PrivDir()+"XXZZZXX"
{Ok}
To play a saved query out of the table, you reverse the operation, as shown in Listing 13-6. This time, the FileWrite Binary command writes a variable to a script file that can then be played to re-create the query on your Workspace. Listing 13-6. Reading a query stored in a memo field Message "Playing query script" QueryDir = [Directory]
; read memo
to a variable
TEMP.VAR
=
[Query
FileWrite
Binary
; change SetDir
264
script
Itself]
to the original
directory
from TEMP.VAR
and play
the
script
QueryDir
PrivDir()+"XXZZZXx"
Menu
{Tools}
; run
it to a holding
PrivDir()+"XXZZZXX.SC"
Play
Do_It!
and write
the
{Delete}
query
{Script}
Select
PrivDir()+"XXZZZXX"
{Ok}
Sees
ee
CHAPTER
13:
AD
HOC
QUERY
TECHNIQUES
A similar technique is also used in the Paradox 4.0 Application Workshop for the queries you define.
Query-By-Table: Using a Table as an Analog for a Query Image A Paradox query image displays all fields, even the ones that do not contribute to the query. Users must understand Paradox’s syntax in order to use native images successfully. But in a large table where complex conditions must be entered, users may have neither the requisite knowledge nor the comfort necessary to set up the query they need. One way to hide QBE from an unsophisticated user is to use a different kind of query image. The structure of QRYTABLE is shown below. This table serves as an analog to the real data table. It allows the user to specify a field, condition, and value for each criterion. Field #
Field Name
Field Type
1 2 3 4
Query Field Operation Value 1 Value 2
A25* A9 A20 A20
Each record in the table holds a different selection criterion. Users can specify the field they want, the query comparison to perform, and the selection values. A list of available operations is shown in Table 13-1. For Or and Between criteria, two value fields are provided so that users can specify two values or a range. Table 13-1. Query Comparison Operations for Query-By-Table
Operation
Meaning
> =
greater than greater than or equal to
0: ?? "For \"BLANK\" or \"NOT BLANK\", no value
Otherwise : Ths = "Value. 2" ( Search( ">", Search( " 0 or [Operation] ) > 0 is required"
is required"
PARADOX
QUERIES:
??
A DEVELOPER’S
"Enter
a selection
REFERENCE
@& @
criterion"
EndIf
EndSwitch @4,2 ?? @5,2 ?? Return 0
"Press
[F2]
"Press
[Esc]
to process to
cancel
the
query
and
return
you to
have
defined"
Paradox"
EndProc
Proc
DEPART_FIELD_PROC() ;
for
a
BLANK
; anything
or
into
NOT
the
BLANK,
"Value"
users
cannot
enter
fields.
If Search( "Value", Field() ) > 0 Then If Search( "BLANK", [Operation] ) > 0 Then LE )e 0 then
a \"Value
2\"
for
this
operation"
@
@
B@OeeBee
ew
CHAPTER
13:
AD
HOC
X = Field()
QUERY
; save
TECHNIQUES
current
Switch Case EV.DYN["KeyCode"] = asc("Help") If Not (X = "Query Field" or X = "Operation") Message "Popup help not available for this Return 0 EndIf Echo
field
Then field"
Off
If Not IsValid() CtrlBackSpace EndIf CopyToArray Del
Then
; field ; clear
HOLD.REC
contents it out
not
valid
; grab record ; delete it
Doz ine!
; call
proc
If X =
"Query
Return
for
custom
Field"
table
lookup
Then
SELECT_FROM_TABLE(
"STRUCT",
"a
field"
SELECT_FROM_TABLE(
"QRYOPS",
"an
)
Else Return
operator"
)
EndIf Case
EV.DYN["KeyCode"]
=
asc("Do_It!")
POST_RECORD_PROC()
If Retval = 1 Then Return 1 EndIf PROCESS_QUERY ( ) return 1 Case
EV.DYN["KeyCode"]
Return
; define
you
sure
Case EV.DYN["KeyCode"] = asc("Del") If CONFIRM_DIALOG("Are you sure you = 2etnen Del Endlf Return 1
:
return
1
run
the
query
= asc("Esc")
CONFIRM_DIALOG("Are
Otherwise
and
you
want
want
to
quit?")
to delete
this
entry?")
EndSwitch EndProc
271
PARADOX
Proc
QUERIES:
A DEVELOPER’S
SELECT_FROM_TABLE(
Private
REFERENCE
@
; display table ; assigned in Wait
Proc
G@
ENTER. VAL
Prompt "Select Wait Table Proc
"
+ PROMPT.TEXT
+
" and press
[F2];
or press
“SELECT_WAIT_PROC"
Key
sDOSs ss DOSBIO”,
"MiniEdit", Message "Next",
Rotate)
"“OrderTable", "Close"
SRS
Sthcolee
Doe littl,
"ToQPro"
EndWait
ClearImage
; remove
table
CoEditKey
; restore
mode
; restore
record
End
Down CopyFromArray
MoveTo Field If ENTER.VAL
HOLD.REC
X
; restore field ; 1f value defined
"" Then CtrlBackSpace Typein ENTER. VAL Right
; new
ARRIVE_FIELD_PROC ()
; next
value
field
EndIf
Echo Normal PROMpEm a? Return 1 EndProc
SELECT_WAIT_PROC(
TR.TYPE,
EV.DYN,
CN
)
Switch
Case TR.TYPE
= "EVENT"
and EV.DYN["Type"]
Switch Case
EV.DYN["KeyCode"] Return 2
Case EV.DYN["KeyCode"] Return 2
272
@
POPUP.TABLE, PROMPT. TEXT
View POPUP.TABLE ENTER.VAL = ""
Proc
@
= asc("F8")
= asc("Esc")
= "KEY"
[Esc]
to cancel"
BSESBee
ew
CHAPTER
Case
13:
AD
EV.DYN["KeyCode"]
HOC
QUERY
TECHNIQUES
= asc("Do_It!")
CtrlHome
Right ENTER.VAL Otherwise
:
Return
1
=
{]
; grab
new
value
EndSwitch Case
TR.TYPE
=
"EVENT"
and
If EV.DYN["Message"] Return
=
EV.DYN["Type"]
"CLOSE"
=
"MESSAGE"
Then
2
Else Return
1
Endif
EndSwitch EndProc
Proc CHOOSE_TABLE() private FILE.LIST, DOS. PATH, DLG. VAL ; Initialize FILE.NAME PIE
=
iSite,
; entry from picklist MAtHS LOG palckiast ; dialog control
Variables "" 4
DOS.PATH
= Directory()
DLG. VAL
=n0
; Start
with
current
dir
ShowDialog "Pick a Table" Proc "CHOOSE_TABLE_DIALOG_PROC" Trigger
@5,8
"Select",
height
13 width
"Depart",
"Accept"
64
Label @1,1 "~T~able/Dir: " for Accept @1,13 width 48 "A60" tag to
"DirAccept" FILE.NAME
PickTable height
@3,1 5 width
DOS. PATH tag "FileList" to
"DirAccept"
59 columns
4 ; Can
change
dynamically
FILE.LIST
273
PARADOX
QUERIES:
PushButton
@9,20
"~O~K"
OK
value tag to
A DEVELOPER’S
width
REFERENCE
@
@
@
10
DEFAULT
1 "OK"
DLG.VAL
PushButton
@9,32
"~C~ancel"
width
10
CANCEL
value 0 tag "Cancel" to
DLG.VAL
EndDialog ; generate
; until If
a STRUCT
needed
DLG.VAL
=
then
clear
it away
user
1 Then
Menu {Tools} ClearImage Endlf Return
table,
by the
{Info}
{Structure}
Select
FILE.NAME
DLG.VAL
EndProc
Proc
CHOOSE_TABLE_DIALOG_PROC(EventType,
TagValue,
EventVal,
ElemValue)
Switch Case
EventType
=
"DEPART"
If TagValue = "DirAccept" Then ResyncControl "DirAccept" DOS.PATH
=
FILE.NAME
RefreshControl Return
; assign ; new
"FileList"
new
DOS
; scan
new
value
path
directory
True
EndIf
Case
EventType
FILE.NAME
DLG.VAL
= =
=
"SELECT"
:
; double-click
FILE.LIST
1
AcceptDialog Return Case
True
EventType
=
"ACCEPT"
If DirExists( FILE.NAME
:
FILE.NAME =
) = 1 Then
FILE.NAME
+ FILE.LIST
EndIf
If IsTable( DLG.VAL
FILE.NAME =
1
AcceptDialog
274
) Then
; table
exists
in pickist
@
Beee
eB
CHAPTER
Return
13:
AD
HOC
QUERY
TECHNIQUES
True
Else Message "Table " + FILE.NAME SelectControl "“DirAccept" Return False Endlif
+ " could
not
be
found"
EndSwitch EndProc
Proc
CONFIRM_DIALOG( private DLG.VAL,
DLG.VAL ; set if
CONFIRM.TXT
)
; dialog
control
DLG.WIDTH,
; dialog
width
DLG.COL, BUnIs BUT2
; dialog ; button ; button
start column 1 start column 2 start column
= False
max/min
; set
width
and
center
the
dialog
value
box
len(CONFIRM.TXT)
>
70
then
=
substr(CONFIRM.TXT,1,70)
CONFIRM.TXT
initial
endif DLG.WIDTH
=
len(CONFIRM.TXT)
DLG.WIDTH
=|
i11f(
DLG.COL
; set
=
DLG.WIDTH
+
=
"
+ StrVal(BEGVAL)
"LessThan"
= ENTER_VALUE(
If retval
low value
+ StrVal(BEGVAL)
[ ELEMENT
MENU.CHOICE
retval
; new
"",
= False
"maximum
BEGVAL
or
"
+ Lower (WHICH.TYPE),
)
IsBlank(retval)
Then
Loop Else BEGVAL = retval EndIf CtrlBackspace TypeIn
"
—_-
|
4
SOU} geaeg He dolore abet Wha 24 an prints yi)
coed
|
»J_abee
—
=
on)
aaa
ATG
* Piene eo hee wera euor-oey Sp nin ale oa belies a Enupinn sssadrgniat) Oey ph ee hy eeully ree eifT 21> popes pilideqes veup soe be cee aprioty of legen wht te ce : ae srdineys wad eats instiza ion!
che Ge Ott
re.
mao
So AeMielmeetiesaalllnoyg em
tomes
coca) Wel
ban cot wat
LE
toler: saints oi
a .
preitin e wht widest pl not eters vino ni sowed 200% Weel Py tiem ty aout eerie i 2eew heomscoqes ood vol ben Shade 406 Amen 59 coin) 169 hee or -peul) aq) stab thse jit :
|
aus syjanmrn o¢haw: cory TNF hath
compenh anit Ivtiwwog siltsue oF denf ewOHey
9.2 gubewull pe Fibindas Yor Ca HE *-cowt) siti slid lace
sob eosin
bog aetlis Sea
roapioden Laced-rind baegrsalb dale aqedo att.7
‘reewohr
nets
¥ ety ot tte
adagi
Mites
a
yp Sea
ewog- hoe mitoicues boanl-rdd Sit Sth Thy DOE Aoeinpoe ae
Ae esis
) mary
>
Seace FAvi 7
iaeh Ge
&
Pree
By
1
ig
“fax
a
+ fh A
Joa cartes bow. litte
(ps 06 hopin bbe @
wil
a
ee
ine Onery- Oe born
o samerae et Ghia chewrtts
et
Sean
| qlites Gi UO
208 sol wohaset nt al fis
bes aS = Gait veka nat
wah,
ewobai sot sebair'l oi stra ass! Mid
7
motinsiee tolrar
loved pevath y read Sen
arm
uh ae
> = cle
pices
i
BA
ed
Sox fit Wee oF:
eo
cay hag
we Re es cant oodebis smechi @
‘aleatk ieliaview bystrand) bile nei Ga \ aout erbe wpe
"sh
rm
ty peetoreee
ny alm a
on
fe
a
wai
CHAPTER
FIFTEEN
A Stepwise Query System When lawyers need to locate precedents for a specific legal case nowadays, they don’t pull dusty, heavy volumes from the shelves of a law library. Instead, they fire up the computer, log into the Lexis or Westlaw electronic systems, and perform an electronic search based on keywords. When scientists are doing research, they ask a librarian to track related topics through the popular Dialog network, which can find articles and references published in the last ten years on any topic using virtually any keyword combination. These researchers are using ad hoc query systems, just like the ones you explored in the previous two chapters. But the systems used by these lawyers and scientists differ in one major respect: they provide for iterative searching. This capability works as follows: 1. You should begin your search with relatively simple criteria to see how many matching records are found. 2. If the answer set is too large, you can apply successively more restrictive selections to whittle the list down to a manageable size.
3. If you perform a search and the result set is too small, you can back up at least one level and try again to find a more appropriate result set. 4. At any time, you can print the current list of results—either references to printed documents, abstracts, citations, or complete articles. 5. At any time, you can also print the selection criteria that you used to arrive at your list. This is one of the most important capabilities because it provides an explanation of what your list actually represents. 6. Finally, you can reset to the beginning or exit at any time. 323
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
Modelling a Stepwise System In Paradox, you can model a system like this in a number of different ways. One approach is to use a technique known as stepwise refinement, where the same block of code is applied against a list of records that becomes successively smaller. (This is similar to a technique called recursive programming but is easier to implement for this application.) Stepwise refinement uses a global variable to track the /evel you are at. When this variable equals 1, you have just started, have not performed any queries yet, and the source table is the original. When this variable equals 2, you have performed the first query, are dealing with a smaller list, and the source table is the ANSWER from this query. Whenever you perform another query to further narrow the list, you increment the variable. When you allow the user to back up one or more levels to a previous result, you decrement the variable. Another issue to consider is the number of prior result sets you will save and allow the user to restore. Users must have the ability to restore at least one level, in case a query does not generate enough records. This is accomplished by renaming the ANSWER table to HOLDING or a similar name before the next query is performed. If you want to give users the ability to restore any previous level, you must save each result set to a table. This table is usually linked to the level counter, like so: Level
Table Name
1
ORIGINAL
2
RESULT2
3
RESULT3
If you also save the selection criteria to a reference table, you can display this list and allow the user to pick any level to restore.
However, there is a problem with this approach: it may use a lot of disk space, especially if you are saving every field in the table. For example, what if the first query on a 50,000-record list of documents generates a result set of 10,000 records. This table is potentially very large.
324
Bee
ew
ew
CHAPTER
15:
A STEPWISE
QUERY
SYSTEM
But why save every field in the table? With some careful attention to design, you can query for just the Primary key field or fields and save those to a table. When you query for a subsequent level you have to link this result table in as well, and Paradox does the work for you! Figure 15-1 demonstrates how a join limits subsequent queries to records that exist in the last result set.
COUNTRY ———
Country
alias
RESULTxx
Country
|
~| VPREW
| eRe
Continent
aliens
. Other fields
Figure 15-1. Limiting selected records in a stepwise query system by joining to the previous result table
You can even extend the model to provide for multi-table queries. If the underlying data exists in a One:Many relationship and selection criteria can be placed in either or both tables, you may need to link four tables for each run around the loop. This approach is shown in Figure 15-2, and is the basis of the stepwise query system described in the remainder of this chapter.
COUNTRY ———
Country spe pene sli ...other fields
|/PReW]
COUNTEXP
——— Country
|¢[PREVI
QRYKEYWD ———
Exports =
Set RESULTxx ———
~Tever
Country
5
Figure 15-2. A stepwise query with two data tables—a criterion table and the previous result table
325
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
An Example Stepwise Query System The remainder of this chapter describes a stepwise query system based around information on countries and their primary exports. The tables and scripts for this system can be found in the STEPWISE\ subdirectory on the diskette accompanying this book. This example was developed for Paradox 4.0 and will only run on that version. Following is the structure of the master data table, called COUNTRY: Field #
Name
Type
Country
A20*
2
Continent
A10
3
Population
N
4
Literacy Rate
S
>)
Urbanization
N
1
6
Primary Language
A1S
vs
Secondary Language
A15
8
Ethnic Groups
A60
9
Religion
A1S
10
Life Expectancy
N
11
Capital
A20
12
Largest City
A20
13
Largest City Pop.
N
14
Democracy
Al
ile)
Currency
A10
16
Area
N
17
Highest Point
A20
18
High Elevation
N
Here is the structure of the detail COUNTEXP table: Field #
Name
Type
1
Country
Als.
Z
Keyword
AZS*
326
BeBe
ww
CHAPTER
15:
A STEPWISE
QUERY
SYSTEM
The diskette also contains ValCheck-TableLookup tables for continents and primary religions in each country. The main screen is shown in Figure 15-3. Notice the menu choices that allow you to initiate a search, view the current result set, back up to a previous level, prepare a report, and quit the system. In the middle of the screen is a status window showing the current level and the number of records available to be queried. This window is automatically updated after each query.
Figure 15-3. The Stepwise Query System main screen
System tables The following additional tables are used by the stepwise query system itself. These are standard tables that can be applied to any use of this application. QRYCRITS
This table needs a similar structure to the primary data table, with a field for each data field that might be used as a selection criterion. Number fields are changed to alpha fields, as described in Chapter 14, to allow ranges to be entered.
QRYKEYWD
This table is used to enter selection criteria for the application’s detail table, in this case COUNTEXP. It has just one field, “Keyword.”
327
PARADOX
QUERIES:
LEVEL
A DEVELOPER’S
REFERENCE
@
@
@
@
This table is used to track the current level within the stepwise system. Another field lists the number of matching records found at each level. Form #1 is a multi-table form with LVL_CRIT embedded as a linked detail.
bVEECRID
This table stores the selection criteria used for each level. It is linked to the LEVEL table in a One:Many relationship. This table also holds the criteria report, showing how a result set was obtained.
REP_HOLD
This table holds the brief and detail reports for selected data
records. Its structure is specific to your data and represents a join between the two primary data tables. The script
Source code for the stepwise query engine is listed at the end of this chapter. The operation begins by calling STEPWISE_QUERY_ENGINE(), the driver procedure from which all other procedures are called. It uses a GetMenuSelection command inside a loop to process menu selections from a registered ShowPullDown menu. The first procedure called is SETUP_ENGINE(), which initializes the system, emp-
ties tables, loads images and windows, and generally arranges the screen. One important task is to set up the LEVEL table so that its multi-table form is current, and data for the first evel is present in the table, as shown in Listing 15-1. Listing 15-1. Initializing the LEVEL table View
Window
"LEVEL"
Handle
Window Move CoeditKey
Image
LEVEL.TBL
ImageNo()
to
-100,
to
LEVEL.TBL
0
FormKey Window Handle Form to LEVEL.FRM Window Move LEVEL.FRM to -100, 0 [Level] = | [Quantity] = NRecords (MASTER. TABLE) Dome!
328
; shift ; default
off-screen multi-table
; shift off-screen ; load level 1
form
BEeBee
we Bw
CHAPTER
15:
A
STEPWISE
QUERY
SYSTEM
An important task is to initialize and display a status window to show users the current level and the number of records available for querying. This is done with a nonfloating canvas window that allows table images to be displayed above it. The next step is to display a pulldown menu along the top line of the script. Because this menu is periodically cleared and redisplayed, the command is placed in a separate procedure for easy calling. Before allowing the user to make a selection from the menu, the status window in the middle of the screen is always updated to reflect the most current information. The message is specific for the current level. The SEARCH_AND_GO() procedure controls the sequence of steps for entering selection criteria, defining the query, running it, and interpreting the results. It first invokes the ENTER_CRITERIA() procedure, which displays a custom form and allows the user to enter information. The STAT.FLAG variable tells the program if the user pressed [F2] to accept and continue, or [Esc] to cancel. From this screen the user can also invoke another form to enter a list of keywords that will be applied as selection criteria to the linked detail table, in this example exports for each country. When the user presses [F2], the program checks to see if criteria were entered into the main table. If they were, it defines and loads a dynamic array where each array index is a field name and the element values are the selection criteria, as shown in
Listing 15-2. This is a highly efficient method for tracking which criteria belong in which field when the query is actually performed. Listing 15-2. Loading a dynarray with selection criteria Message "Querying for matching Dynarray CRIT.FIELDS[] If
IsEmpty(
records" ; define
MASTER.CRITERIA.TABLE
IsEmpty ( DETAIL.CRITERIA.TABLE
Message Loop
"No
criteria
array
) and ) Then
specified"
Else If
Not SIZE
IsEmpty( =
MASTER.CRITERIA.TABLE)
Then
NFields(MASTER.CRITERIA.TABLE)
CtrlHome FLAG = False Rom x sion ito) SIZE we Pi es BNO amas FLAG = True CREE IEDC Ie ELelal()\ Endlif Enter
; how
many
fields
; still in form view ; check for data ; for each field
=|)
; value found! load. dynarrcay:
; next
field
329
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
EndFor
If
not FLAG Then Message "No criteria
specified"
Loop Else QuitLoop Endlif Else QuitLoop EndIf EndIf
The program then calls the CUSTOM_ADJUST_CRITERIA() procedure. This provides an opportunity for you to customize the selection criteria to a specific application. This sample application is designed to be as generic as possible. Within the constraints of a One:Many relationship, all custom code is concentrated in this procedure. In the sample tables, Ethnic Groups is the only field where an entry may appear in a portion of the field. So the code surrounds the user’s entry with wildcards so that the string can be found anywhere in the field, and so that embedded punctuation marks and reserved words do not adversely affect the query, as shown in Listing 15-3. Listing 15-3. Performing special processing on a criterion field If
IsAssigned(
CRIT.FIELDS["Ethnic
CRLF ELE DS ub ehmticm
GaoupS
ils
Groups"]
) Then
et art
+ CRIT.FIELDS["Ethnic
Groups"]
ce maktataoe Endlif
The next step is to load the required query images, link them, and fill them in. These operations are performed by the LOAD_QUERY() procedure. Because the dynarray contains one element for each field that contributes to the query, you can scan the elements and fill them in directly, as shown in Listing 15-4. The criteria are actually transferred into a query image for the master table. Listing 15-4. Filling the query image from the dynarray ForEach X in CRIT.FIELDS MoveTo Field X TypeiIn CRIT.FIELDS[X] EndForEach
330
; each dynarray element ; corresponding field ; transfer criterion
Bee
Bw Bw
CHAPTER
15:
A STEPWISE
QUERY
SYSTEM
RESULTxx tables only contain the Primary key field, so this is the only one checked in the master table. Once you have moved beyond the first level, the current RESULTxx table is always linked via example elements. Note that the procedure has to test if a criterion was placed on this field, so that the example element is correctly formatted. If keywords were specified, the detail table and keywords table are also linked in, as shown in Listing 15-5. Listing 15-5. Optionally linking the KEYWORDS table If
LEVEL.COUNT CtrlHome
Right If not ae
{Ask}
Right Endlif
IS.KEYWORDS
Then ; beginning
>
query
Then
; Primary
image
there
key
1 Then
Select
Example
of
; key field ; criterion already ; insert a comma
"pklink"
LEVEL.COUNT Menu
1 Or
IsBlank([])
EndIf Example EndIf If
>
"RESULT"
+
StrVal
"pklink"
(LEVEL.COUNT)
; Primary
key
After the query is run—and if records were returned—the procedure indexes the field in the ANSWER table, which does not have a Primary key. This index will speed up subsequent queries on this table. Finally, the level variable is incremented and the ANSWER table renamed to the next RESULTxx. So that you can see where you came from, the script next records your selection criteria in the LEVEL table, as shown in Listing 15-6. Keyword selection criteria are concatenated into one string, which ultimately becomes a single entry in the LVL_CRIT table. Listing 15-6. Recording selection criteria in the LEVEL table Window Select LEVEL.TBL CoEditKey FormKey Ins [Level] = LEVEL.COUNT [Quantity] = NRecords("RESULT"+StrVal MoveTo "LVL_CRIT" ForEach X in CRIT.FIELDS Ins {Field Name] = X (Value] = CRIT.FIELDS[X] EndForEach
; level
; switch
tracking
to multi-table
; current
; new
form
level
(LEVEL.COUNT)
; detail
table
)
table
record
; criterion
field
; criterion
value 331
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
G@
When the menu returns, the user can narrow the list further, view the list of records,
back up to any level, print a report, or quit. Narrowing the list further traverses the same code described above, but this time with a new level variable.
When the user elects to back up a level, the program deletes the appropriate RESULTxx table, decrements the level counter, and removes matching entries in the criterion table. This final step is performed in the ADJUST_LEVEL_TABLE() procedure, which has to remove linked detail records before it can remove the master record
for each level backed out (Listing 15-7). Listing 15-7. Removing selection criteria from the LEVEL table to restore a previous result End While [Level] LEVEL.COUNT Moveto "LVL_CRIT" While NImageRecords() > 1 Del EndWhile Del MoveTo
; last
level
; jump
to
in
table
detail
; delete
to
last
; delete
last
; delete
master
record
record
"LEVEL"
Del EndWhile
If the user chooses to print a brief or a detail report, the program generates a join table containing master and linked detail fields for each record in the result set. The reports themselves are attached to a holding table with the correct structure. The criterion report is a simple task, since everything you need can be found in the LEVEL and LVL_CRIT tables, and they are already perfectly linked for a Paradox report. The remainder of this chapter, except for the “Summary” at the end, consists of Listing 15-8, a complete listing of the stepwise demo script. Listing 15-8. Source code for the stepwise query system
STEPWISE_QUERY_ENGINE()
; Ad Hoc Proc
query
subsystem
with
"recursive"
capabilities
STEPWISE_QUERY_ENGINE (
MASTER. TABLE,
; master
table
to
query
DETAIL.TABLE, MASTER.CRITERIA. TABLI
detail master
table to query criteria table
DETAIL.CRITERIA. TABLI GC
detail
criteria
table
) Private
332
LEVEL.COUNT,
; which
loop
are
we
on
See
we
CHAPTER
15:
A STEPWISE
LEVEL.TBL, LEVEL. FRM,
; ; ; ; ; ; ; ;
KEY. FIELD, STAT.WIN, MAST.CRIT.WIN, MAST.CRIT.FRM, DETL.CRIT.WIN, DETL.CRIT.FRM, MENU . CHOICE
= False
SYSTEM
level table window level form window of master table status window master criteria table master criteria form detail criteria table detail criteria form
initialization
SETUP_ENGINE( )
If Retval
handle for handle for key field handle for handle for handle for handle for handle for
QUERY
proc
Then
Return EndIf ; need
REDISPLAY_MENU()
While
a menu
to
start
(true)
UPDATE_PROMPT_WINDOW ()
; update
Echo Normal GetMenuSelection Echo Off
; modal
to MENU.CHOICE
; process
Switch Case
MENU.CHOICE
=
level/record
select
the
from
count
menu
selection
"SEARCH"
SEARCH_AND_GO() Case MENU.CHOICE = VIEW_RESULTS()
"VIEW"
Case MENU.CHOICE = "PREVIOUS" PREVIOUS_LEVEL() Case MENU.CHOICE = "SELECT" SELECT_RESET_LEVEL()
Case MENU.CHOICE = "LEVEL1" BACK_TO_FIRST_LEVEL( ) Case MENU.CHOICE = "BRIEF" PRINT_REPORT ("B") Case MENU.CHOICE = "DETAIL" PRINT_REPORT ("D") Case MENU.CHOICE = "CRITERIA" PRINT_REPORT ("C") Case MENU.CHOICE OU CONFIRM_DIALOG("Are you Main menu?")
sure
you
want
to quit
and
return
to
the
333
PARADOX
QUERIES:
If Retval
A DEVELOPER’S
= True
REFERENCE
@
Then
QuitLoop EndIf EndSwitch Endwhile ClearPul1lDown ClearAll window select window
; clean up menu ; tables and ; status window
STAT.WIN
close
EndProc
Proc SETUP_ENGINE() private STAT, QUERY .CRITS,
window dynarray for criteria table
QUERY .KEYWD,
; handle
for
COLR
; custom
window
; system
variable
LEVEL.COUNT
= 1
Empty
"LVL_CRIT"
Empty
"LEVEL"
If
; status ; handle
; empty
IsTable("ANSWER") Delete
keyword
criteria
table
colors
tables
Then
"ANSWER"
EndIf
; load ; move View
the first level into the level tracking table the window to the side and grab a handle for it
"LEVEL"
Window
Handle
Image
ImageNo()
to LEVEL.TBL
Window Move LEVEL.TBL to -100, 0 CoeditKey FormKey Window Handle Form to LEVEL.FRM Window Move LEVEL.FRM to -100, 0
; shift
[Level]
til
; load
[Quantity]
= NRecords (MASTER.TABLE)
Omit!
View
; default ; shift
multi-table
off-screen level
1
; save
MASTER. TABLE
; key
Right KEY.FIELD
=
Field()
ClearImage
334
off-screen
; view
and
; grab
a handle
reposition
for
criteria
each
table
tables
field
of master
form
@
@
@&
Beee
ew
View
w@
15:
A STEPWISE
QUERY
SYSTEM
MASTER.CRITERIA. TABLE
Window
Handle
Window
Move
View
CHAPTER
Image
ImageNo()
MAST.CRIT.WIN
to MAST.CRIT.WIN
to -100,
0
DETAIL.CRITERIA. TABLE
Window Window
Handle Image ImageNo() to DETL.CRIT.WIN Move DETL.CRIT.WIN to -100, 0
; define
the
status
window
in the middle
STAT(] STAT["CanClose" ] STAT["CanMaximize"] STAT ["CanMove" ] STAT["CanResize"]
= = = =
False False False False
STAT ["Canvas" ]
=
True
STAT(["CanvasHeight"] STAT("CanvasWidth"] STAT(["Floating"]
= 5 = 48 = False
STAT
["HasFrame" ]
=
STAT
["HasShadow" ]
= True
STAT
["Height" ]
=i
of the
screen
dynarray
True
STAT["Margin" ]
=
STAT ["Maximized" ]
= False
STAT ["OriginCol"]
ees
STAT
["OriginRow" }
VO)fiii®
= 8
STAT["Style"]
=
STAM
ante evil
=
"Stepwise
STAT
["Width" ]
=6
5)
Window
Create
DynArray
Attributes
To
System"
STAT.WIN
COLR[]
CORRO} esta? COLRMal= asl Window SetColors DO
STAT
Query
STAT.WIN
From
COLR
BP seni (Gey Aus)
Message
""
EndProc
Proc
REDISPLAY_MENU()
; define
pulldown
ShowPul1Down "Search"
"View"
menu
on
top
line
: "Specify criteria : "View the current
and run a query" result table"
7 ORARCH Sy ~ VIEW,
.
335
PARADOX
QUERIES:
"Backup"
A DEVELOPER’S
REFERENCE
"Back
level"
up to a previous
@& 8
8
@
"BACKUP"
SubMenu
"Previous" "Select" "Levell" EndSubMenu,
"Back up to the previous level" "Select the level to which you will "Reset to Level 1 and start over"
"Report"
SubMenu "Brief" "Detail"
on
query
"PREVIOUS", "SELECT",
reset"
"LEVELL"
"Output
reports
results"
"Output "Output
a summary report of results" a detailed report of results"
"BRIEF", "DETAIL",
Separator,
"Criteria"
"Show
the
criteria
used
to produce
this
query"
"CRITERIA"
EndSubMenu, "Oumte”
"Leave
the
query
subsystem"
ROUMie
EndMenu
; disable
"Search"
if only
one
record
If LEVEL.COUNT
> 1 and NRecords("RESULT"+StrVal(LEVEL.COUNT)) MenuDisable "SEARCH" Else MenuEnable "SEARCH" EndIf
=
1 Then
EndProc
Proc UPDATE_PROMPT_WINDOW( ) Private STAT.TEXT SetCanvas
; text
for prompt
; Gestination ; commands
STAT.WIN
for
in window canvas
Switch Case
1
LEVEL.COUNT
STAT.TEXT
=
StrVal(NRecords(
+
" records
Case LEVEL.COUNT = 2 STAT. TEXT it} StrVal (NRecords "
fi
StrVal(NRecords(
STAT.
("RESULT"+StrVal
(LEVEL.COUNT) ) )
MASTER.TABLE ))
in ANSWER"
: TEXT
StrVal "
/
(NRecords
("RESULT"+StrVal
(LEVEL.COUNT)
) )
(NRecords
("RESULT"+StrVal
(LEVEL.COUNT-1)
"
StrVal +
336
to query"
"
++"records
Otherwise
MASTER.TABLE ))
available
records
in ANSWER"
) )
BEBE
e
ew
CHAPTER
15:
A STEPWISE
QUERY
SYSTEM
EndSwitch @ 1,0
??
Format ("w48,ac",
@n3;,0)
22
Format
; adjust If
"Query
Menu
-
Level
"
+ strval(LEVEL.COUNT)
)
(“w48, acl, STAT eTEXT)
the menu
before
LEVEL.COUNT
=
MenuDisable
"BACKUP"
allowing
the
user
to
select
from
it
1 Then
Else MenuEnable
"BACKUP"
EndIf EndProc
; View Proc
the
current
results
table
VIEW_RESULTS()
If LEVEL.COUNT View
=
1 Then
; which
table
to view
MASTER.TABLE
Else View
"RESULT"+Strval
(LEVEL.COUNT)
EndIf
DynArray RSLT.WIN[] RSLT.WIN["CanClose"] RSLT.WIN(["CanMaximize"] RSLT.WIN["CanMove" ]
= True = False
RSLT.WIN["CanResize"
= False
RSLT.WIN["HasFrame" ]
= PEUe
RSLT.WIN["HasShadow"
Sue
RSLT.WIN["Height"]
=
RSLT.WIN["OriginCol"
=a
RSLT.WIN[
a2
"OriginRow"
RSLT.WIN["Title"] StrVal
the
view
= False
=
2)
"Query
Results
Window
for
Level
" +
(LEVEL.COUNT)
RSLT.WIN[ Window
; customize
"Width" ] SetAttributes
= I) GetWindow()
From
ShowPul1Down
RSLT.WIN
; blank
menu
EndMenu
Prompt "Viewing Wait Table Proc
current
Results
table.
[F2]
to return
to the menu"
"WAIT_RESULT"
Keyes
DOSW,
Message
asDOSBiGuy atROwawel,
"Next",
"Close",
mnt
"MenuSelect"
EndWait ClearImage [eneroniyone,
337
PARADOX
QUERIES:
REDISPLAY_MENU
A DEVELOPER’S
()
REFERENCE
; restore
@
8
@
menu
EndProc
; Wait Proc
Proc
for
the
WAIT_RESULT(
ANSWER
table
TR.TYPE,
EV.DYN,
CN
)
Switch Case
TR.TYPE
=
"EVENT"
and
If EV.DYN["KeyCode"] Return 2 Else Return 1 EndIf Case
TR.TYPE
If
=
"EVENT"
and
EV.DYN["Message"]
Return Else Return Endlf
EV.DYN["Type"]
= asc("F2")
=
"KEY"
cancelled
; deny
other
EV.DYN["Type"]
"CLOSE"
=
Then ; user
=
keys
"MESSAGE"
Then
2
; click
on
close
button
1
; deny
other
messages
up
the
EndSwitch EndProc
Proc
PREVIOUS_LEVEL()
CONFIRM_DIALOG("Are If Not Retval Then
you
sure
you
want
to back
to
previous
Return
Endlf ; delete result ; decrement the
table level
for this counter
level
Message "Deleting result for level ", LEVEL.COUNT Delete "RESULT"+StrVal (LEVEL.COUNT) LEVEL.COUNT
; adjust
=
LEVEL.COUNT
the criterion
ADJUST_LEVEL_TABLE() EndProc
338
-
1
tracking
records
in the LEVEL
table
level?")
@&
SEO
eee
Proc
CHAPTER
15:
A STEPWISE
QUERY
SYSTEM
SELECT_RESET_LEVEL()
Window Window
Select LEVEL. FRM LEVEL.FRM to
Move
Prompt "Press Wait Table Proc
[F2]
to
1,0
select
a level
or
[Esc]
to
cancel"
"BROWSE_LEVEL_WAIT_ PROC"
Key
“Donit! ) “hse™)
Message
"DOS",
“DOSBig™,
muRoEates
"Close"
EndWait
Prompt
""
; LEVEL.COUNT was assigned inside the Wait Proc ; adjust the criterion tracking records in the LEVEL
table
ADJUST_LEVEL_TABLE( )
Window
Move
LEVEL.FRM
to
-100,0
EndProc
; Wait Proc
Proc
for
the
ANSWER
table
BROWSE_LEVEL_WAIT_PROC(
TR.TYPE,
EV.DYN,
CN
)
Switch Case
TR.TYPE
=
"EVENT"
and
EV.DYN["Type"]
=
Switch Case EV.DYN("KeyCode"] = asc("F2") LEVEL.COUNT = [Level] ; down Return
to
this
level
2
Case EV.DYN["KeyCode"] Return 2
Otherwise Return
"KEY"
= asc("Esc") ; user
: 1
cancelled
; deny
the
event
EndSwitch Case
TR.TYPE
=
"EVENT"
If EV.DYN["Message"] Return 2 Else Return 1 EndIf
and
=
EV.DYN["Type"]
"CLOSE"
Then ; user ; deny
=
"MESSAGE"
cancelled the
event
339
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
EndSwitch EndProc
; Reset
to Level
One
Proc BACK_TO_FIRST_LEVEL() private X If LEVEL.COUNT = 1 Then Message "Still at first
; can't
go back
further
level"
Return
Endlif
CONFIRM_DIALOG("Are If Not Retval Then
you
sure
you
want
to
reset
to
the
first
level?")
Return
EndIf ; delete
result
tables
Message "Resetting engine For X from LEVEL.COUNT to Delete
"RESULT"
+
to 2
first
level"
StrVal(LEVEL.COUNT)
EndFor LEVEL.COUNT
; adjust
the
=
1
criterion
tracking
records
in the
LEVEL
table
ADJUST_LEVEL_TABLE() Message
""
EndProc
Proc
ADJUST_LEVEL_TABLE()
MoveTo "LEVEL" CoEditKey If Not IsFormView() Then FormKey Endlf End While [Level] LEVEL.COUNT Moveto "LVL_CRIT" While NImageRecords() > 1 Del
340
; tracking
table
; multi-table
form
; last
level
; Jump
to detail
; delete
to
in table
last
record
@
@
@
BABB
BB
CHAPTER
15:
A STEPWISE
EndWhile Del MoveTo
QUERY
; delete
last
; delete
master
; ; ; ; ;
control width start colum 1 start column 2 start column
SYSTEM
record
"LEVEL"
Del EndWhile Dow ite! FormKey Home
MenuEnable
"SEARCH"
EndProc
Proc CONFIRM_DIALOG( CONFIRM.TXT private DLG.VAL, DLG.WIDTH, DLG.COL, BUR BUT2
DLG.VAL
; set if
)
= False
max/min
dialog dialog dialog button button
; set
width
and
center
the dialog
value
box
len(CONFIRM.TXT)
>
70
then
=
substr(CONFIRM.TXT,1,70)
CONFIRM.TXT
initial
endif DLG.WIDTH
=
len(CONFIRM.TXT)
DLG.WIDTH
=
iif(
DLG.COL
; set BUT1
BUT2
=
+
no criteria
Message "Querying for matching Dynarray CRIT.FIELDS[]
records" ; define
IsEmpty( IsEmpty(
Message Loop
342
table
; note that it is possible for 7 record, but that at 1s blank
If
MASTER.CRITERIA.TABLE
) and
DETAIL.CRITERIA.TABLE
) Then
"No
criteria
variable
specified"
one
array
values
@
@
@&
BEBE
ew
CHAPTER
15:
A
STEPWISE
QUERY
SYSTEM
Else If
Not
IsEmpty(
SIZE
=
MASTER.CRITERIA.TABLE)
Then
NFields(MASTER.CRITERIA.TABLE)
; how
fields
form
view
; still
FLAG = False ioe XC seco il wer SiwvAg
; check for data ; for each field
Ee
Sl
ee
FLAG
in
many
CtrlHome
ne:
= True
CRIT.FIELDS[ EndIf
Field()
J=[]
Enter
; value
found!
-; load
dynarray
; next
field
EndFor
If not
FLAG
Message Loop
Then
"No
criteria
specified"
Else QuitLoop EndIf Else QuitLoop Endlf Endlf Endwhile
; adjust selection ; this is a custom
criteria Proc for
as necessary each application
CUSTOM_ADJUST_CRITERIA ()
; define
query
on Workspace,
linking
the
correct
tables
LOAD_QUERY ()
; run
the
query
and
clean
up the
ANSWER
table
and
query
images
Doule!
ClearImage MoveTo
1
While ImageType() ClearImage EndWhile ; did
the
query
=
run?
"Query"
if not,
If not istable("ANSWER") Then Message “Invalid selection Return False
; clear
must
be
criterion
query
images
invalid
- perhaps
a reserved
word"
Else HOWMANY
=
NRecords
=
0 Then
("ANSWER")
Endlf Tf
HOWMANY
343
PARADOX
QUERIES:
Message "No records Return False EndIf
; cannot If
search
HOWMANY
=
A DEVELOPER’S
satisfy
if there
your
is only
REFERENCE
@
criteria"
one
record
left
1 then
MenuDisable "SEARCH" Else MenuEnable "SEARCH" EndIf
; create ; result ; aS you
a nonmaintained secondary index on the table's only field, to speed up operations move to the next level
Message Index
"Indexing "ANSWER"
; adjust
level
on
and
and
recording
results"
KEY.FIELD
rename
table
LEVEL.COUNT = LEVEL.COUNT + 1 Rename "ANSWER" "RESULT" + StrVal(LEVEL.COUNT) ; record
criteria
in level
table
RECORD_CRITERIA ()
Message Return
"" True
EndProc
Proc
ENTER_CRITERIA()
private
STAT.FLAG, WIN.ATT
ShowPul1Down EndMenu
; blank
top
line
menu
CoEditKey Window Select DETL.CRIT.WIN While NImageRecords() > 1 Del EndWhile Del
; select the master criteria ; position and appearance
344
; select ; remove
table
and specify
keywords previous
its
table keywords
@
8
@
BeBe
BB
Window
CHAPTER
Select
MAS1 [.CRIT.WIN
Handle
Form
15:
A STEPWISE
QUERY
SYSTEM
Fo rmKkey
Window
to MAST.CRIT.FRM
DynArray WIN.ATT[ Wl N.ATT "CanClose" Wl N.ATT "CanMaximize" ] Wl ATT "CanMove"
WI WI Wl Wl ia Wl Wl WI
N.ATT N.ATT ATT N.ATT N.ATT
wave
False False
"CanResize"] "HasFrame" |
Tewe
"HasShadow" ]
True
"NOrrgancoL” | "OriginRow" ]
0 1 18 80 "Query
"Height"] "Width" ] aaa Wea Ixey || Wi ndow SetAttributes
False
Criteria Form" MAST. CRIT.FRM From WIN.ATT
Del
; blank
record
CtrlHome
Echo
Normal
Prompt
"Enter
Enter
Wait
criteria.
[F2]
Process
[Esc]
Cancel
[F4]
Record
Proc Key
"CRITERIA WAIT PROC" Sony a ESClmDO SU
Message EndWait Prompe
Echo
query
Exports"
OOS
RIG te
tran
"Close"
a"
Off
Domi!
Window
Move
MAST.CRIT.FRM
to
-100,0
; hide
window
REDISPLAY_MENU() '
STAT.FLAG
’
it
tells
Return
is set us
inside
whether
the
the Wait user
Proc
cancelled
or
said
"OK"
STAT.FLAG
EndProc
; Process DuOGMC
events
from
Wait
RIT ERIA_WAIT_PROC(
; valcheck
in|
TR.TYPE,
EV.DYN,
CN
)
violation
345
If not isvalid() Then Message "Not one of the Return
possible
values
@
REFERENCE
A DEVELOPER’S
QUERIES:
PARADOX
for
this
@
@
field"
1
EndIf ; all
of
these
are
events,
where
TR.TYPE
Switch Case EV.DYN["Type"] = "MESSAGE" CONFIRM_DIALOG("Are you sure If Retval Then STAT.FLAG
Return Else Return EndIf Case
=
you
=
"EVENT"
want
to
cancel
and
return?")
False
2
; cancel
1
; cancelled
EV.DYN["Type"]
=
the
cancel
"KEY"
Switch Case
EV.DYN["KeyCode"]
STAT.FLAG
Return
=
asc("Do_It!")
= True
2
; query
Case EV.DYN["KeyCode"] CONFIRM_DIALOG("Are If Retval Then STAT.FLAG
=
= asc("Esc") you sure you
using
want
2
; cancel
Return EndIf
1
; cancelled
; Jump
subform
Case
EV.DYN["KeyCode"]
IS.KEYWORDS
Return
=
to cancel
and
False
Return Else
to
specified
to
enter
the
cancel
keywords
= asc("F4")
ENTER_KEYWORDS()
1
EndSwitch EndSwitch EndProc
Proc
ENTER_KEYWORDS ()
private
WIN.ATT, STAT. FLAG
Window
346
Select
DETL.CRIT.WIN
; select
keywords
table
values
return?")
@
Bee
Be BB
CHAPTER
15:
A
STEPWISE
QUERY
SYSTEM
FormKey
Window Handle Form to DETL.CRIT.FRM DynArray WIN.ATT([] WIN.ATT["CanClose"] = True WIN.ATT["CanMaximize"] = False WIN. ATT["CanMove" ] = False WIN.ATT["CanResize" ] = False WIN.ATT["HasFrame" ] = True WIN.ATT ["HasShadow" ] = True WIN.ATT["OriginCol"] = Il WIN.ATT["OriginRow" ] =e) WIN.ATT["Title"] = "Exports Criteria Form" Window SetAttributes DETL.CRIT.FRM From WIN.ATT Echo
Normal
Prompt Wait
"Enter
primary
exports.
[F2]
Accept
[Esc]
Cancel"
[Esc]
Cancel
Table
Proc
"CRITERIA_WAIT_PROC"
Keva DOME) a mcce Message EndWait Prompt
"Enter
Enter
DOSt
aDOSBargi
"Close"
query
criteria.
[F2]
Process
[F4]
Exports"
Echo Off Window Close Window
Select
MAST.CRIT.FRM
; STAT.FLAG is set inside ; it tells us whether the Return
the Wait Proc user cancelled
or
said
"OK"
STAT.FLAG
EndProc
; Adjust criteria for special search situations ; This 1s a custom proc for each application Proc
CUSTOM_ADJUST_CRITERIA()
; ethnic groups is the only field where an entry may appear ; in a portion of the field. So we surround the user's entry ; with wildcards so that the string can be found anywhere If IsAssigned( CRIT.
CRIT.FIELDS["Ethnic
ELELDS | UEehnac
Groups|
= + ut
Groups"]
) Then
way
CRIT.FIELDS["Ethnic MON Mea
Groups"]
alt
Endlf EndProc
347
PARADOX
Proc
QUERIES:
A DEVELOPER’S
REFERENCE
RECORD_CRITERIA()
Private
X, KEY .CRIT
; if keywords were specified, parse them ; variable for later posting into table If
IS.KEYWORDS Fae AGT?
Window Scan
into
a single
Then
=)
Ns
Select
KEY.CRIT
DETL.CRIT.WIN =
KEY.CRIT
+
[KeyWord]
+
","
EndScan KEY.CRIT
=
SubStr(KEY.CRIT,1,Len(KEY.CRIT) -1)
Endlif
Window Select CoEditKey FormKey
LEVEL.TBL
; level ; switch
Ins
tracking
to multi-table
; current
[Level]
=
LEVEL.COUNT
[Quantity]
=
NRecords("RESULT"+StrVal
MoveTo ForEach
X
in
level
(LEVEL.COUNT)
"LVL_CRIT"
; detail
table
)
table
CRIT.FIELDS
Ins [Field Name] {[Value]
= X = CRIT.FIELDS[X]
; new record ; criterion field ; criterion value
EndForEach
; insert If
an
extra
IS.KEYWORDS
record
for
the Keyword
criteria
Then
Ins
Field Name] Value] EndIf Do_It! FormKey
EndProc
348
=
"Primary KEY.CRIT
Exports" ; concatenated
values
form
Bee
ew
CHAPTER
15:
A STEPWISE
QUERY
SYSTEM
; LOAD_QUERY()
; Load
query
private
with
check
operators
and
selection
X
; dynarray
; load master query table and Check the ; this is the only one which propogates ; result table. Menu
Right
criteria
{Ask}
Select
counter
key field only through each
MASTER.TABLE
Check
CtrlHome
; fill
in selection
criteria
from
the
ForEach X in CRIT.FIELDS MoveTo Field X
TypeIn
dynarray ; each dynarray ; corresponding
CRIT.FIELDS[X]
; transfer
element field
criterion
EndForEach ; 1£ you have moved beyond the first level, ; link in the appropriate RESULTxx table to restrict ; the query to the list from the previous result set If LEVEL.COUNT > 1 Or IS.KEYWORDS CtrlHome Right If not IsBlank([{]) Then ve EndIf Example "pklink" EndIf If
LEVEL.COUNT
>
If
in keyword
IS.KEYWORDS
" set
Right
{ask}
beginning of query image key field criterion already there insert a comma
; Primary
key
table
+ StrVal(LEVEL.COUNT) ; Primary key
and keyword
criteria
table
Then
Menu {ask} Select DETAIL.TABLE Right GroupBy Example "pklink" Right "every " Example "keyw" Menu
; ; ; ;
1 Then
Menu {Ask} Select "RESULT" Right Example "pklink" EndIf
; link
Then
Select
; Primary ; keyword
key link
DETAIL.CRITERIA. TABLE
"
Example
"keyw"
; keyword
link
EndIf EndProc
349
PARADOX
QUERIES:
; Print one ; Demo code Proc
of three to print
PRINT_REPORT(
A DEVELOPER’S
available reports to the screen only
REP.TYPE
)
ShowPul1Down
; clear
EndMenu Prompt "Press
Message GE
[F2]
to
"Preparing
REPSPR =
Menu
REFERENCE
Wer
{Report}
return
selected
to
the
menu
menu"
report"
Then
{Output}
{LVL_CRIT}
{1}
{Screen}
Else
; query Menu
Select
Example {ask}
Right Right
reporting
table
MASTER.TABLE
"pk"
Select
DETAIL.TABLE
Example "pk" Check "as Exports"
; only If
assemble
{ask}
Check Right Menu
to
link
results
LEVEL.COUNT
Menu
Right EndIf
{ask}
>
table
has
been
performed
1 Then
Select
Example
if a query
"RESULT"+StrVal
(LEVEL.COUNT)
"pk"
Dos!
ClearImage MoveTo 1 While ImageType() ClearImage EndWhile
"B"rief eRe
or
PS ty Pre =
CopyReport
Else CopyReport Endlf Menu {Report} EndIf Prompe
350
=
"Query"
"D"etail
report
- copy
from holding
Benen
"REP_HOLD"
1 "ANSWER"
1
"REP_HOLD"
2 "ANSWER"
1
{Output}
{ANSWER}
{1}
{Screen}
table
8
@
@
@
Bee
eee
CHAPTER
15:
A STEPWISE
call
the
QUERY
SYSTEM
REDISPLAY_MENU() EndProc
; define
table
names
for
demo
and
main
proc
; STEPWISE_QUERY_ENGINE () MASTER.TABLE
=
"COUNTRY"
DETAIL.TABLE
=
"COUNTEXP"
MASTER.CRITERIA.TABLE
=
"QRYCRITS"
DETAIL.CRITERIA.TABLE
=
"QRYKEYWD"
STEPWISE_QUERY_ENGINE(
; temporary
cleanup
MASTER.TABLE, DETAIL. TABLE, MASTER.CRITERIA. TABLE, DETAIL.CRITERIA. TABLE
code
for
demo
only
Message "Cleaning up RESULTxx tables" Moe Kagan SEP exo) ty Siege Sil If IsTable("RESULT"+StrVal(X)) Then Delete "RESULT"+StrvVal (X) EndIf EndFor Release Vars MASTER.TABLE, DETAIL. TABLE, MASTER.CRITERIA. TABLE, DETAIL.CRITERIA.TABLE, Xx
po
,
oo o--------------=----------=--------
end
of script
Summary Stepwise query systems are important tools when you are dealing with large volumes of data that have to be winnowed down into smaller and more manageable sets. They allow users to extract just the salient records, based on complex selection criteria that the users themselves define. The techniques explained in this chapter will help you to implement these systems in your own applications.
351
‘a2.°os ;
es
ae, taser |
« °
‘
=
;
sf =
7
LTT
Rar
coe
«LATE
aTak
dee Uy LR
— a gar .
oy
=a)
.iem weep
-@
SURET
Pi
=,
Mi
8009
a
“ealO
0
rans
+ »
*
(fac
Tar cE
Gy wes iieelD ' fom
1+ gat Tod 00on§ sad hi fei *PeeRe ietietes 99
(hr Tetra G*, witnietl 2
“i
@
a
e.
eT as
——
:
TBA ACR
e
stan te re
=~ ?-as biett Tee ce
Ava
eR: MIRTLE?
ea STATI id
-
ey 20h: coded
i?
open eener tilt
|
|
re
;
lov agra! Ashegules 2 i out ahve tment 93sstrays sap ne spat
step akaecoithen stan bceallie elder
ca
tite sorbalie estqmes atrbsutd absicr qh elt
S
Dat
6s
ci
SS
oy me
pate) ee
i
pane?
ss
page TeQaes? Bases‘ant : e
eet =
the
CPTR)
next
CURRENT.CHAR
if
save
CURRENT.CHAR
the
;Skip
character,
(WORD)
CURRENT.CHAR LAST.CHAR
upper case, extract the first put it in the Soundex string.
=
CPTR
(SPTR
Sap
Gp
er,
1 -0@et omy, weds 4s at)
Stoeepieee
Pino fa prey testaaaa f
PVike oe
°
ae
7
ui =.
ee
=
ae
hs
:
i, -
a,
.
|
i)
alr
.
an Seeeueniiies D>| p Mai died, oh) TAK.
1G Ye et ocnty: matt Seucetore-Saaral quserich tocap canal an t ‘ons 26 evel; sow aris, Oe-enn) to edexm,
tte
Lin Gams a . wae uve
:
Phierkaprieme
tenes
t¢ shies theethadlt ty sare rash ~ af sees thar, nec cunowear: tollsing she Gog pip Se 3 ide aoe en
pat
i. q
ithe
CHAPTER
SEVENTEEN
Paradox for DOS
Query Tools
This chapter documents a number of utilities that provide increased functionality for queries in Paradox 4.0 for DOS. Most of these utilities are extracted from the query module of Kallista’s DesktopPlus for Paradox 4.0, an extensive collection of pop-up tools for interactive users and developers. The following functions are provided: QueryOrder, which dynamically changes query processing order from TableOrder to ImageOrder.
Workspace, which changes the order of query images on the Paradox Workspace. CopyChecks, which copies Check operators from one line of a query image to the next line.
LinkExamples, which matches example elements in fields with the same name from other query images. FindNext, which implements a way to “find” the next matching record after a Find query.
ScriptManager, which allows you to name and save multi-table queries and restore them to the Workspace at any time.
373
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
@
In addition, this chapter documents a utility called ImageToPAL that converts saved query images to the equivalent PAL code. This utility was developed by Micah Bleecher, a Paradox developer with DataStar International, and is reproduced here with permission. Note that this routine, which is targeted exclusively towards Paradox developers, was not included in the version of the Query Tools provided with the companion volume to this book, Paradox Queries: The Basics. These tools require Paradox 4.0. They use commands and capabilities unavailable in prior versions. For example, advanced dialog boxes and mouse support were added to Paradox in Version 4.0 and are used extensively in these tools.
Accessing the Query Tools These scripts are linked together with a custom'menu. To access this menu, play the script QTOOLS.SC from the diskette enclosed with this book, like so: 1. Make sure that you are in the Paradox 4.0 Standard User Interface. With this UI, a single line menu appears on the top line, with a speedbar and prompt line along the bottom of the screen. To change to the Standard UI if you are not already there, press [Alt-Spacebar] and select Interface / Yes.
2. Make sure you are in Main mode by checking the mode indicator in the lower-right corner of the screen. 3. From the Main menu, either press [F10] and select Scripts or click the mouse on this choice.
4. From the drop-down menu, select Play. 5. In the dialog box, type QTOOLS and press [Enter]. If the query tools were installed in a different subdirectory, preface the script name with the subdirectory name. You will see a pop-up menu similar to the one shown in Figure 17-1. Following you will find a detailed description of how to use each item on the menu.
374
S88
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
Figure 17-1. The Query Tools Main menu
Source code for these utilities can be found in the script QTOOLLIB.SC. The purpose of this script is to generate a series of PAL procedures in a library, QTOOL.LIB. Each procedure is fully commented and should be self-explanatory for experienced PAL programmers.
QueryOrder: Changing the Processing Order When you pick the QueryOrder option, Paradox displays a submenu showing the currently selected order for processing queries, either TableOrder or ImageOrder. Select an option and press [Enter] to change this order. Source code is shown in Listing 17-1. Listing 17-1. Source code for the QueryOrder option
; QUERY_ORDER()
; Change proc
Paradox's
default
processing
for queries
QUERY_ORDER ()
QORDER = QueryOrder() ShowMenu "ImageOrder" : "Process "TableOrder" : "Process Default QORDER To
order
; current
queries queries
order
by recognizing rotated fields", by ignoring rotated fields"
QORDER
Switch Case
QORDER = "ImageOrder" SetQueryOrder ImageOrder
Case
QORDER
=
"TableOrder"
375
PARADOX
QUERIES:
SetQueryOrder EndSwitch
A DEVELOPER’S
REFERENCE
@
@
@
@
TableOrder
endproc
This script reads the current query order to a variable, then displays a menu with the current order highlighted as the default. When you select an option, Paradox’s order is set accordingly. A Switch-Case is used because the user can also press [Esc] to exit the menu. In this instance, QORDER = “Esc” and the script does nothing. To make the QORDER.SC script easier to access, place the following line in an INIT.SC, which is played automatically by Paradox when the program first loads: SetKey
-16
Play
"C:\\PDOX40\\QORDER"
This command creates a keyboard macro and assigns it to the [Alt-Q] key. When you press [Alt-Q], Paradox plays the QORDER script, which you have copied to the indicated directory, allowing you to change default query order without programming.
Workspace: Changing the Order of Query Images Paradox for DOS generates the ANSWER table based on the order of query images on the Workspace. Fields from the first table in the query are placed first in the ANSWER table, followed by fields from the second table, and so on. (Note that in Paradox 4.0, the top-most query window is not necessarily the first image in the query.) If you have placed and filled out a number of query images and you want to change their order so that the fields in the ANSWER table are differently ordered, use this routine. (Don’t forget to bring all query images to the foreground using [Alt-Space] / Desktop / SurfaceQueries, so that you can see every query image on the desktop before you start.) Workspace starts out by displaying a dialog box showing the current query images in the order that Paradox recognizes them. The dialog box, shown in Figure 17-2, allows you to select each image in turn to define a new order in a second window. When you press the OK button, the utility removes all query images from the Workspace and replaces them in the newly specified order.
376
@@@B
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
Figure 17-2. The Workspace dialog box
Behind the scenes, the utility uses an interesting trick. When you tell it to reorder the query images as you have specified, it performs a QuerySave operation, and then imports the saved query into a single field table as if it were a text file. Next, the code rearranges the records in the file so that the various query images are listed in their new order, after which it exports the table back to a text file, which still looks like a saved query! Finally, this script is played to restore the query to your Workspace. Source code is shown in Listing 17-2. Listing 17-2. Source code for the Workspace option
; QUERY _Workspace()
Let proc
the
user
change
the
order
of
images
in the
current
query
QUERY_Workspace()
private
CurrentImage, QueryTables, Querylmages,
QueryBlock,
QElement,
OldQueryList, NewQueryList,
FinalList, OldQueryElement, ewQueryElement,
ExitControl ; Check
for
if nimages()
images =
0 then
377
PARADOX
QUERIES:
ExitMessage return
A DEVELOPER’S
=
"There
are
no
query
REFERENCE
images
on
the
8
8
@
@
desktop"
true
endif ; Save
the
current
CurrentImage ; Since ; image moveto
window
to
return
when
done
= imageno()
Query images are pushed to the top of the desktop, the first window should be of type "Query". If not, then there's no query. 1
if imagetype() ExitMessage
"Query" then = "There are no query
moveto
CurrentImage
return
true
images
on
the
desktop"
endif ; Create message
ordered
"Saving
dynarray dynarray
dynarray dynarray
while
an
list
current
of the query
query
images
desktop
QueryImages[ QueryTables[] OldQueryList[] NewQueryList
imagetype()
=
"Query"
table()
] =
imageno()
QueryTables[{
table()
]
table()
"
OldQueryList[ table() ] = strval(imageno()) if imageno() = nimages() then quitloop endif downimage endwhile
; Use
the
information"
QueryImages[
message
on
+")
" + table()
""
a while
loop
in case
the user
does
not
CONFIRM
ExitControl = false showdialog "Query Re-Order" proc
"QUERY_IMAGE_ DIALOG"
TRIGGER
@3,5
"SELECT"
height
12 width
70
label @1,0 "~E~xisting pickdynarray @2,2
height
5 width
image
order:"
30
OldQueryList tag "OldQueryList"
378
query
to OldQueryElement
for
"OldQueryList"
@@ew
CHAPTER
17:
PARADOX
label @1,34 "~N~ew query pickdynarray @2,36 height 5 width 30
image
FOR
order:"
DOS
for
QUERY
TOOLS
"NewQueryList"
NewQueryList
tag
"NewQueryhist"
pushbutton "~O~K"
@8,20 OK
width
to NewQueryElement 10
DEFAULT
value true tag "Accept"
to ExitControl
pushbutton @8,36 width 10 "~C~ancel" CANCEL value false tag "Cancel" to ExitControl
enddialog ; If user
if not
presses
ExitControl
Esc
or
chooses
"Cancel",
then
quit
then
moveto
CurrentImage
KeruUEns
Exue
endif ; ; ; ;
Rebuild the QueryTables dynarray based on the value of New and Old QueryList arrays. The user can select the first N query images from OldQueryList to place in NewQueryList. After that, the order defaults to the existing order of the remaining images in OldQueryList.
array FinalList [dynarraysize (NewQueryList) +dynarraysize(OldQueryList) ] foreach X in NewQueryList FinalList[ numval(substr(NewQueryList[ X ],1,1)) ] = QueryTables[ X ] endforeach for X from dynarraysize(NewQueryList) + 1 to arraysize(FinalList) ; Find the next non-blank element of OldQuery List to append to ; QueryTables[]. MinImage = 1000 MinIndex = "" foreach Y in OldQueryList if QueryImages[Y] < MinImage then MinImage = QueryImages[Y] MinIndex = Y endif endforeach FinalList[ X ] = QueryTables[ MinIndex release vars OldQueryList[ MinIndex ]
]
endfor
379
PARADOX
QUERIES:
; Save
the
message menu
A DEVELOPER’S
query
and
"Reordering
{Scripts}
import the
the
query
{QuerySave}
text
=
"Cancel"
for x from
= 3
to modify
the
locate
pattern
; Reset
the
dynarray
first
table
; First
record ".."
bag that
{ASCII}
the
to modify
row
for
the
query
query.
Use
with
name
holds
the
query
lines
for
+ ".." this
table
Counter
= 1
counter
while true if isblank([]) then QueryBlock[Counter] = [] Counter = Counter + 1 del if search(
QueryTables[FinalList[X]],[])
quitloop endif endif QueryBlock[Counter] = [] Counter = Counter + 1 del endwhile record
NextQueryStatement
for y from 1 to dynarraysize(QueryBlock) ins {] = QueryBlock|[y] down endfor
380
the
statements
+ QueryTables[FinalList[X]]
line/table
@
query
{Text}
QueryBlock([]
; reset
saved
@
Workspace"
1 to dynarraysize(FinalList)
; find
moveto
the
8
then
; rearrange the rows in the ; of the bag QueryTables[] coedit "QT" right NextQueryStatement
to a table
@
{QT}
if menuchoice() = "Cancel" then {Replace} endif menu {Tools} {ExportImport} {Import}
{OT@SC} {OT} if menuchoice() {Replace} endif
REFERENCE
=
0 then
new
order
Bee
CHAPTER
17:
NextQueryStatement endfor
PARADOX
FOR
DOS
QUERY
TOOLS
= recno()
do_it! clearimage menu
{Tools}
{ExportImport}
(OG) (ORISC} if menuchoice()
=
"Cancel"
{Export}
{ASCII}
{Text}
then
{Replace} endif
; Clear
the
query
moveto 1 while imagetype()
images
=
from
the desktop
"Query"
clearimage endwhile ; Play play
the
resaved
query
script
to put
the
new
query
on
the
desktop
" QT "
; Remove delete
menu
temporary
and
scripts;
move
back
to the
original
window
"QT"
{Tools}
moveto
{Delete}
{Script}
{QT}
{Ok}
CurrentImage
message return
tables
"" true
endproc
,
QUERY_IMAGE_DIALOG()
,
Dialog
proc
for
selecting
the
order
of the query
images
tf
proc QUERY_IMAGE_DIALOG( private RemoveNumber,
Trigger,
EventTag,
EventValue,
Label
)
Xx
switch ; Selected
items
from
the
old
order
are
added
to
the
new
order
case Trigger = "SELECT" and EventTag = "OldQueryList" X = strval (dynarraysize(NewQueryList) + 1) NewQueryList[ OldQueryElement ] = X +")
release -
)
Selected
" + QueryTables[{
vars items
OldQueryList[ from
the
new
OldQueryElement
]
OldQueryElement
]
order
are
removed
381
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
case
Trigger = "SELECT" and EventTag = "NewQueryList" : OldQueryList[ NewQueryElement ] = strval(QueryImages[ NewQueryElement ]) + ")
"
+4 QueryTables[
NewQueryElement
]
RemoveNumber = numval (substr (NewQueryList [NewQueryElement],1,1) ) release vars NewQueryList[ NewQueryElement ] foreach X in NewQueryList if numval (substr (NewQueryList[ X ],1,1)) > RemoveNumber then NewQueryList[X] = substr (numval (substr (NewQueryList [X],1,1))-1,1,1)
substr (NewQueryList[X]
+
,2,len(NewQueryList [X]) )
endif endforeach endswitch
refreshdialog return
endproc
CopyChecks: Copying Check Operators to the Next Line In a wide table, you might have some fields with Check operators, some fields with CheckDescending operators, and other fields that are not checked. If you need to specify multiple Or conditions in different fields, you will need to place each condition on a different row of the query image and have the same fields checked in the same way on each row. Instead of manually stepping through each field in the image to check or uncheck it, use this utility to do it all for you! Behind the scenes, this simple routine saves the current field position and then walks through the table, moving up to grab the CheckMarkStatus(), then down to place the appropriate Check operator. Source code is shown in Listing 17-3. Listing 17-3. Source code for the CopyChecks option
; QUERY_COPY_CHECKS
; Copy proc
check
()
operators
to the next
line
of a query
QUERY_COPY_CHECKS()
private
CurrentField, LastField, PreviousStatus
if imagetype() ExitMessage
382
"Query" then = "Not in a query
image"
image
Bee
CHAPTER
return
17:
PARADOX
FOR
DOS
QUERY
TOOLS
true
endif if rowno() = 1 then ExitMessage = "You return
are
already
in the
first
row"
true
endif
message
"Duplicating
checkmarks"
CurrentField = field() ctrlend LastField = field() ctrlhome while field() LastField right up PreviousStatus = checkmarkstatus() down
if not isblank(PreviousStatus) then if not isblank(checkmarkstatus()) check endif keypress
endif endwhile moveto field message
""
return
true
PreviousStatus
CurrentField
; save
starting
field
; save ; move
last field to # field
in image
; move to previous row ; grab previous status ; move back then
; if check exists ; clear previous mark ; enter
; move ; clear
new
mark
to start message
field window
endproc
LinkExamples: Matching Example Elements A common source of problems in multi-table queries is example elements in the wrong fields. Another problem is not linking a query image correctly, so that Paradox returns the error message One or more query images do not contribute to the query Good database design suggests that fields that contain the same information should have the same name in every table where they appear. Using this recommendation, this utility grabs an example element you have typed into the current field, then searches
383
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
through every other query image on the Workspace and places this example element in each field with the same name as the current field. Source code is shown in Listing 17-4. Listing 17-4. Source code for the LinkExamples option
; QUERY_MATCH_EXAMPLE( ) ‘
Match
proc
example
elements
in
fields
with
the
same
name
QUERY_MATCH_EXAMPLE()
private
ExampleElement, QueryImage,
ExampleField, EndField Find all fields in all query images on currently on the Workspace with the same name as the current field, and place the same example element in them as exists in the current field.
; Test
for
query
if imagetype() ExitMessage return
image
"Query" then = "Not in a query
image"
true
endif
; Need
multiple
images
for multiple
if nimages() = 1 then ExitMessage = "Only recurs
one
query
instances
image
of a field
name
exists"
true
endif ; check
for
instance
if “subsitr (i) jill)
ExitMessage
"No
return
=
example “S"
Sand search (te
example
element
1)e =
0M then
defined"
true
endif ; extract masa
the =
text
of the
example
element
eyeeheel( 7, ip) sal
ExampleElement
=
""
while search(substr([],FirstEE,1),"=,/*+-@*") = 0 and FirstEE ExampleElement = ExampleElement + substr([],FirstEE,1) Facstke = Firstep 4+)
endwhile ExampleElement
= ExampleElement
QueryImage = imageno() ExampleField = field() moveto
384
1
+ substr([],FirstEE,1)
< len([])
@@@B
CHAPTER
if QueryImage downimage endif if
17:
PARADOX
DOS
QUERY
TOOLS
= 1 then
imagetype()
"Query" then one query
ExitMessage = "Only moveto QueryImage return
FOR
image
present"
true
endif ; cycle
through
cursor
while if
and
find
all
matching
field
names
off
imagetype() imageno() TempField home
ctrlend EndField ctrlhome right
=
"Query"
QueryImage = field()
then ; save
current
= field()
while true ; loop through if field() = ExampleField then if search("_"+ExampleElement,[]) = 0 then
if not
field
ebLeESte LeCOnd ; last field ; save last field po ieiashe: Siyeull
isblank([])
fields
then
endif example
typein
endif endif if field() = EndField quitloop endif right
ExampleElement
then
7 at
last
; next
field?
one
endwhile
endif ; return
to original
ctrlhome moveto field if imageno() quitloop endif downimage endwhile
; surface
AltSpace
TempField = nimages()
{Desktop}
QueryImage
return
true
for
this
image
; starting
and
field
then
; next
the queries
moveto
field
restore
image
position
{SurfaceQueries}
endproc
385
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
FindNext: Finding the Next Matching Record As I described in Chapter 11 of the companion volume, when you perform a Find query, Paradox displays the table being queried with the cursor in the first matching record. At this point, there is no way to perform a FindNext operation to move to the next matching record. However, as you also saw, Paradox creates an ANSWER table behind the scenes that contains all matching records. The FindNext utility uses this ANSWER table to implement a FindNext function. When you select this menu choice, it determines how many key fields the data table has, and reads these fields for the current record. It then jumps to the ANSWER table (loading it if necessary), moves down to the next record, reads its key fields, moves
back to the data table, and locates the record it just read. This function uses the powerful Execute command, which is relatively slow. But it allows you to find a match in any table, no matter how many key fields are found. Source code is shown in Listing 17-5. Listing 17-5. Source code for the FindNext option
; QUERY_FIND_NEXT()
; Find
the
next
matching
record
after
a FIND
"Display" then "Not in a display
image"
query
proc QUERY_FIND_NEXT () private
CurrentDisplay, CurrentTableName, CurrentTable, IsQuery Image, NKeyValues, KeyValues, LocateString, N
if
imagetype() ExitMessage return true endif
=
CurrentDisplay = getwindow() CurrentTableName = table()
; If the table is not keyed, we have no way of mapping ; records in ANSWER back to the source table. if nkeyfields(CurrentTableName) = 0 then ExitMessage = "Original table must be keyed" return
386
true
the
@@e@
eB
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
endif ; If ANSWER
doesn't
exist,
then
a find
if not istable("Answer") then ExitMessage = "No Answer table return
query
exists
hasn't
- run
FIND
been
run.
query
first"
true
endif
message ; ; ; ; ;
"Locating
next
value"
The array holds the key values used to locate the next record in the source table. The values are grabbed from ANSWER. If ANSWER is on the Workspace, then skip to the next ANSWER record before saving the values. If ANSWER is not on the Workspace, then view it first. Skip to the second record before grabbing the values.
NKeyValues = nkeyfields( CurrentTableName ) array KeyValues[ NKeyValues ] moveto 1 while table() "Answer" and imageno() < nimages() downimage endwhile if table() "Answer" then view "Answer" endif if recno() = nimagerecords() then ExitMessage = "Match not found" window select CurrentDisplay
return
true
else down endif
ctrlhome
; moveto
; LocateString
is used
because
there
could
be any
number
to
# field
of
key values.
Locatestring = ”™ for N from 1 to NKeyValues
right
KeyValues[
N ] =
LocateString
endfor LocateString
+
"KeyValues["
+ strval(N)
+
"],"
= substr(LocateString,1,len(LocateString) -1)
window select if NKeyValues ctrlhome
[]
= LocateString
CurrentDisplay = 1 then
right
endif ; LocateString ; EXECUTE
is
holds slow,
the
but
expression
there
is no
for other
the
Locate
command
way.
387
PARADOX
QUERIES:
execute
"locate
A
"
DEVELOPER’S
@
@
@
@
+ LocateString
if not retval then ExitMessage = "Match endif return
REFERENCE
from
ANSWER
not
found"
true
endproc
Query Script Manager: Managing Saved Queries As you saw in Chapter 1, Paradox provides a facility to save queries to a script,
so that you can play them back at a later date. When you activate this feature, Paradox prompts you for the name of a script but does not provide an opportunity to describe the query. If you forget the name or lose track of one script among many others in a subdirectory, you may have to re-create the query. The ScriptManager utility provides a table called QRYSAVED that tracks saved queries for you. For each saved query, you can specify a name and description. The utility also tracks the directory you were in when you saved the query, so that this directory can be restored before a script is played; this ensures that relative directory references point to the correct table. When you select the ScriptManager menu choice, the Query Script Manager dialog box comes up, as shown in Figure 17-3. It gives you options for saving the current query, playing a saved query, deleting a saved query, and editing the name and description of a saved query. The list of saved queries is automatically maintained in sorted order. Figure 17-4 shows the Add/Edit dialog box, which allows you to specify a name and description for the query to be saved.
Figure 17-3. The main Query Script Manager dialog box
388
@@@B
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
Figure 17-4. The Add/Edit dialog box
Source code for the ScriptManager option is shown in Listing 17-6. Behind the scenes, the real work in saving a query is done in the QUERY_SAVE() procedure. This
procedure creates a new record in the table to hold the saved query, writing the name and user-specified description into the appropriate fields. It next saves the current query to a temporary name, reads this file into a memo variable, and writes this variable to the Query Itself field. When you choose to play a saved query, the QUERY_PLAY() procedure reads this memo back to a variable, writes the variable to a text file with an .SC extension, and then plays this script. Listing 17-6. Source code for the ScriptManager option
; QUERY_MANAGER ()
; Name, proc
save
and restore
query
images
QUERY_MANAGER( )
private
DirectoryReset, QueryTable, ExitControl, QueryList, ListElement, QueryNames, QTABLE
QTABLE
=
sdir()
+
"QRYSAVED"
if not istable(QTABLE) then ExitMessage = "Query Manager return true endif DirectoryReset
=
; Position
table
view
the
table
"+QTABLE+"
not
found"
false on
the Workspace
and hide
it
QTABLE
389
PARADOX
QUERIES:
window window
A DEVELOPER’S
handle image imageno() to QueryTable move QueryTable to -1000, -1000
ExitControl
=
false
UPDATE_QUERY_LIST ()
; refresh
showpulldown
; clear
top
line
menu
without
a dialog
proc
endmenu
; Main dialog box "Value" procedures
showdialog
"Query
@ 3,5 height
call
Script
12 width
each
routine
Manager"
70
@0,1 ?? " Name Description" pickdynarray @1,1 height 6 width 65 QueryList tag "List" to ListElement pushbutton
@8,2
width
10
"VE~dit"
value
tag
QUERY_EDIT()
"Edit"
to ExitControl
pushbutton @8,13 "~D~elete" value
tag
width
value
tag
to ExitControl
pushbutton " ~P~lay
tag
width
to ExitControl
@8,35
width
QUERY_PLAY()
"Cancel"
"~Q~uit"
@8,46
to ExitControl width
to ExitControl
enddialog up and
exit
DirectoryReset
moveto
QTABLE
clearimage
10
CANCEL
value false tag "Cancel"
if not
10
"
pushbutton
; clean
10
QUERY_SAVE()
"Create"
value
10
QUERY_DELETE()
"Delete"
pushbutton @8,24 "~S~ave"
390
REFERENCE
then
the
list
@@@
Be
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
endif return
true
endproc
; UPDATE_QUERY_LIST()
; Handle proc
the
editing
of a query
description
UPDATE_QUERY_LIST() ; regenerate
dynarray dynarray
the
two
dynarrays
used
to hold
the
list
QueryList[] QueryNames []
scan QueryList[[Query QueryNames[[Query endscan
Script
Name]]
Script
= format ("w1l0,al", [Query Script Name] ) + format ("w60,al", [Description] ) Name]] = [Query Script Name]
endproc
7) QUERY
EDIT ())
; Handle proc
the
editing
of a query
description
QUERY_EDIT()
private
OldName, QueryName, QueryDesc,
QueryDir
; position moveto locate
table
cursor
to the
same
record
as
in the dialog
box
[Query Script Name] controlvalue("List")
if retval
‘
the
then
initialize
OldName QueryName QueryDesc ; display
vars
= = =
with
record
[Query Script [Query Script [Description]
values
Name] Name]
dialog
QUERY_RECORD(
if retval then coeditkey
"Edit"
)
; update
the
record
391
PARADOX
QUERIES:
A DEVELOPER’S
(Query Script [Description] do_it!
; Update
Name]
query
if QueryName
@
= QueryName = QueryDesc
file
REFERENCE
on disk
OldName
then
menu {Tools} {Rename} {Script} select SDir()+0ldName select SDir()+QueryName if menuchoice() = "Cancel" {Replace} endif endif
then
UPDATE_QUERY_LIST()
; refresh
the
list
refreshdialog selectcontrol
; refresh
the
display
"List"
endif
else message endif return
"Query
record
not
found"
false
; stay
in the
dialog
box
endproc
; QUERY_DELETE()
; Handle proc
the
deletion
of a query
spec
QUERY_DELETE ()
private
QueryName
; position moveto
the table
[Query
Script
cursor
to the same
record
as
in the dialog
Name]
locate QueryNames[controlvalue("List") ] if retval then if CONFIRM_DIALOG("Delete
current
script
message "Deleting query script" QueryName = [Query Script Name] ; Gelete
the
record
the
file
coeditkey del
do_it!
; delete
392
on disk
as well
file")
then
@
@
@
@@e@
eB
CHAPTER
17:
PARADOX
FOR
if isfile(sdir()+QueryName+".SC") menu {Tools} {Delete} {Script} select sdir()+QueryName {0k} endif message
DOS
QUERY
TOOLS
then
""
endif
; clean
up and update
release
vars
QueryList [QueryName] , QueryNames [QueryName]
refreshdialog selectcontrol "List" else message "Query record endif return
the display
not
found"
false
endproc
QUERY_SAVE()
Handle ry
This
proc
the
proc
saving
uses
of
a memo
a query
field
spec
to hold
the actual
query
image
QUERY_SAVE()
private
QueryName, QueryDesc,
TEMP.VAR, ERROR. FLAG
To test whether a query is on the Desktop, for errorcode=35 and window()="There is no
we need to query..."
the errorproc is called if there is no query it simply resets ERROR.FLAG and returns
test
to save
ERROR.FLAG = False menu {Scripts} {QuerySave} If ERROR.FLAG message
Then
"No
query
images
to
save"
menu esc return false endif menu esc QueryName
=
""
QueryDesc
=
nn
; add
a new
entry
to
the
list
393
PARADOX
QUERIES:
QUERY_RECORD(
A DEVELOPER’S
"Add"
REFERENCE
8
@
)
if not retval then message "Save canceled" return false endif message
; Save
"Saving
the
current
query
query
to a temporary
menu {Scripts} {QuerySave} if menuchoice() =)/"Cancel" {Replace} endif ; Create
a new
moveto [Query coeditkey ins [Query Script [Description] [Directory]
; add
saved
FileRead [Query
query
Script
Name]
query
Binary
script
select then
record
in the
sdir()+
""
return
false
"XXZZZXxX"
table
Name]
into
memo
field
"XXZZZXX.SC"
to TEMP.VAR
= TEMP.VAR
{Delete}
{Script}
{XXZZZXX}
UPDATE_QUERY_LIST() refreshdialog selectcontrol "List"
message
name
= QueryName = QueryDesc = directory()
Itself]
Glosnie! Menu {Tools}
definition"
{ok} ; vefresh the list ; refresh the display ; position cursor
endproc
; QUERY_RECORD()
; Display
a dialog
box with
proc QUERY_RECORD( Action private ExitControl ExitControl
showdialog
394
=
preset
values
)
PACELON)
false
Action+"
Query
and allow
Record"
the
=
user
Edict
to edit
OnE Adds
@
@
@@@B
CHAPTER
@5,13
height
17:
PARADOX
9 width
FOR
to
TOOLS
aan
"QueryName" QueryName
label @3,1 "~D~esc:" accept @3,8 width 42 "R60
"
tag
"QueryDesc"
to
QUERY
53
label @1,1 "~N~ame:" for "QueryName" accept @1,8 width 10 AS" prcture! “(SAP A(S dries op neta tag
DOS
for
"QueryDesc"
QueryDesc
pushbutton "~O~K" value
tag
@5,13
width
10
DEFAULT CHECK_QUERY_RECORD(
"OK"
pushbutton
Action
)
to ExitControl
@5,28
"~C~ancel"
width
10
CANCEL
value false tag "Cancel"
to ExitControl
enddialog return ExitControl endproc
; CHECK_QUERY_RECORD()
; Dialog
proc
to verify
save
query
name
before
Accepting
[Query
Script
the dialog
,
proc
CHECK_QUERY_RECORD(
Action
)
if isblank(QueryName) then message "Query must have a name" selectcontrol "QueryName" return false endif if
(Action = "Edit" and QueryName (Action = "Add") then moveto [Query Script Name] locate
Name])
or
QueryName
if retval then message "Query with this name selectcontrol "QueryName" return false endif endif
exists"
395
REFERENCE
A DEVELOPER’S
QUERIES:
PARADOX
@
@
@
acceptdialog return
true
endproc
; QUERY_PLAY()
; If the script file is found, play the query ; this proc reads the saved query from a memo proc
script and exit field and plays
it directly
QUERY_PLAY()
private
QueryDir, QueryName, TEMP. VAR
; position table cursor to match the dialog moveto [Query Script Name] locate QueryNames[ controlvalue("List") ] if retval then message "Playing query script" QueryDir = [Directory] QueryName = [Query Script Name]
; read
memo
TEMP.VAR FileWrite ; reset
to a variable
= [Query Itself] Binary "XXZZZXX.SC"
directory
to
the
setdir QueryDir ExitMessage = "Working
play sdir() Menu
{Delete}
DirectoryReset acceptdialog else message "Query endif
endproc
396
same
from one
directory
it to a holding
script
TEMP.VAR as
now
when
the
query
was
saved
" + QueryDir
+ "XXZZZXX"
{Tools}
selectcontrol return false
and write
box
{Script}
= true
record
"List"
not
found"
Select
sdir()+"XXZZZXX"
{ok}
&@
@@e@B
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
Converting Saved Query Images to the Equivalent PAL Code ImageToPAL is a valuable utility that allows you to convert a saved query image to the equivalent PAL code. As I explained in Chapter 2, many developers prefer the code format because it is smaller, is processed a little bit faster by Paradox, and because they feel that it is easier to understand. Converting a saved query image is not an easy task. This routine has to allow for the following standard occurrences:
¢ ¢ ¢ ¢ ¢
Blank lines between images and keywords A table appearing more than once Multi-row images Keywords in the leftmost column under the table name Images with no fields attached to them Column bars that don’t line up Check operators, keywords, selection criteria, and example elements
This routine will not detect a query that is badly formatted or one in which a field has been deleted from a table or the table itself is missing. It is still up to Paradox to detect such errors when you run the script itself. The source code for this routine uses PBE notation, which was invented by Alan Zenreich and James Kocis and was first described in their popular PAL reference, The Paradox Programmer’s Guide: PAL By Example (Scott Foresman, 1991). PBE (PAL By Example) notation delimits variable and procedure names. It suffixes each variable and procedure with a single letter indicating the type of the variable, or the type of the returned value from the called procedure. For example TOP1.n is a variable that holds a number, while RETVAL.v holds a value of varying type. The source code is shown in Listing 17-7. Listing 17-7. Source code for the ImageToPAL option
; SCRIPT ; APPDEV ;
NAME: TEAM:
dsQ2PAL.sc
Copyright
(c)
1992,
1993
DataStar
International
Micah Bleecher, Dan Paolini DataStar International
3111 Route 38, #11 Mount Laurel, NJ 08054
(609)
265-9500
397
; DESCRIPTION:
Evaluates
i
PAL
Workspace
query
images
@
REFERENCE
A DEVELOPER’S
QUERIES:
PARADOX
and
converts
them
@
to
code.
Contains
the
following
inPathDouble.a
Information
procedures:
: p . 5
dsQueryToPalDialogDB.1 dsQueryToPalDialog.u AllChecks.1
;Doubles backslashes for printing ;Dialog event proc ;Primary interface dialog ;Determines if every field is checked
'
Get ProcName.v
;Gets
‘| '
dsQueryToPal.1 PadQuotes.a
;Primary conversion procedure ;Pads quoted strings with backslashes
procedure
name
from
; Permission is granted to use all or a part of these procedures ; charge, provided the copyright notices remain intact. ; dsQuery2PAL
user
free
of
Instructions
; Interactively place desired query images on the Paradox 4.0 Workspace. ; Play the QTOOLS script and select the ImageToPAL option. The ; result will be a conversion of the Workspace query images to PAL code.
; You cam ; command D xir
; The
also call this script by itself in your init.sc script:
SETKEY
default
-16
PLAY
output
by attaching
"C:\\PDOX40\\DSQ2PAL"
file
name
is INSTANT.SC
it to a SetKey
dsQueryToPalDialog.u()
but
you
may
change
that
; needed.
¢
TITLE:
4 ; ;
AUTHOR: CREATED: RETURNS:
; DESCRIPTION: : PROC
dsQueryToPALDialog.u() (c) 1992 Micah J. Bleecher - DataStar International 10-12-92 : 02:34:44am Nothing Dialog box user interface to present utility options
and control
dsQueryToPalDialog.u()
Private
topl.n, botl.n, LOp2eny
bot2.n, bot3.n, (EKOJOS) Alay butvar.1, just.n, outfile.a, proc.n GopLenie=
398
execution.
al2
;top frame color group 1 ;bottom frame color group 1 ;top frame color group 2 ;bottom frame color group 2 ;bottom frame color group 3 ;top frame color group 3 ;button variable ;Output justification type ;output file name ;proceduralize flag
; Alt
as
Q
@
@
@@@B
CHAPTER
botlenr=" eeevAcig, = bot2Z.m = Eos)ia = bot3.n butvar.1
WHILE
PARADOX
FOR
DOS
QUERY
TOOLS
127) 2 a2 Ly TY = FALSE
outfile.a
PUES
17:
=
"INSTANT"
=
(True)
IF NImages() BEEP Message
= 0 THEN "There
Are
No
Images
Present"
ENDIF
ShowDialog PROC
""
"dsQueryToPalDialogDB.1"
TRIGGER
@4,14
"ARRIVE"
Height
15 Width
53
; PaintPAL_Frame_Begin Frame Single From 2,1 PaintCanvas Attribute PaintCanvas Attribute PaintCanvas Attribute PaintCanvas Attribute ; PaintPAL Frame_End
To 4,49 topl.n topl.n botl.n boti.n
; PaintPAL Frame_Begin Frame Single From 5,1 PaintCanvas Attribute PaintCanvas Attribute PaintCanvas Attribute PaintCanvas Attribute ; PaintPAL Frame_End
To 8,49 top2.n top2.n bot2.n bot2.n
; PaintPAL_Frame_Begin Single From 9,1 To
Frame
2,1,2,49 2,1,4,1 4,2,4,49 2,49,4,49
5,1,5,49 5,1,8,1 8,2,8,49
5,49,8,49
12,49
PaintCanvas PaintCanvas
Attribute Attribute
top3.n top3.n
9,1,9,49 9,1,12,1
PaintCanvas
Attribute
bot3.n
12,2,12,49
PaintCanvas Attribute ; PaintPAL_Frame_End
bot3.n
9,49,12,49
; PaintPAL_Static_Text_Begin PaineCanvas Hull YMA ttrubute
112)
3)3.39
@3,3 2?
"Output
File
Name:"
399
PARADOX
QUERIES:
PaintCanvas
A DEVELOPER’S
Attribute
112
3,3,3,19
; PaintPAL_Static_Text_End
; PaintPAL
Static_Text_Begin
PaintCanvas Fill " " Attribute @6,3 22 “dustacticattons"
112
6,3,6,17
PaintCanvas Attribute 112 6,3,6,17 ; PaintPAL_Static_Text_End
; PaintPAL_Static_Text_Begin PaintCanvas Fill " " Attribute 112 @7,3 ?? "Proceduralize:" PaintCanvas Attribute 112 7,3,7,16
7,3,7,16
; PaintPAL_Static_Text_End ; PaintPAL_ Static_Text_Begin PaintCanvas Fill " " Attribute
94
0,8,0,41
@0,8 ??
"|
DataStar
PaintCanvas ; PaintPAL
Query-To-PAL
Attribute
94
Converter"
0,8,0,41
Static_Text_End
; PaintPAL Static_Text_Begin PaintCanvas Fill " " Attribute
112
1,9,1,43
@1,9 2?
ESR
eae
ee
ES),
PaintCanvas Attribute 112 1,9,1,43 ; PaintPAL_Static_Text_End ; PaintPAL Static_Text_Begin PaintCanvas Fill " " Attribute
112
0,42,0,43
@0,42
22 "_" PaintCanvas Attribute 112 0,42,0,43 ; PaintPAL_Static_Text_End
Accept " A8
@3,20
Width
28
"
eles dieo)
EEA
WANE}LY
To outfile.a RadioButtons Slush, Heel ahel Sy
@6,17
Height
1 Width
32
Height
1 Width
18
"Left"
Tag.
"dUSie2"
To just.n RadioButtons "No
400
"
@7,17
REFERENCE
@
8
@
@
@@@B
CHAPTER
‘Ves
Mage
17:
PARADOX
FOR
DOS
QUERY
TOOLS
"
“PRO
Rom oracan
PushButton
@10,8
Width
15
"~D~o_ It!"
Default Value dsQueryToPal.l(outfile.a)
;query
conversion
procedure
fag “OKr3" To butvar.1
PushButton @10,28 "~C~ancel" Cancel Value False Tag
To
Width
15
"CANCEL.3"
butvar.1
EndDialog QUITLOOP
ENDWHILE RETURN ENDPROC
; ; ,
:
TITLE: dsQueryToPALDialogDB.1() AUTHOR: (c) 1992 Micah J. Bleecher CREATED el) 192 -deeowocen RETURNS: Logical
; DESCRIPTION: ; PROC
Dialog event proc. through the dialog
Controls box.
-
DataStar
GUI
International
animation
as
the user
tabs
dsQueryToPalDialogDB. 1(
;GLOBAL 7 , g ; i
type.a,
; EVENT or TRIGGER
tag.a, event.v, element .a)
; Control element tag or null ; DynArray of GetEvent, or control ; Checkbox label or null
topl.n, bot1.n, top2.n, boeZ en, bot3.n, POpSsian,,
;top frame color group 1 ;bottom frame color group ;top frame color group 2 ;bottom frame color group ;bottom frame color group ;top frame color group 3
value
1 2 3
SWITCH CASE
type.a
=
"ARRIVE"
SWITCH
CASE
Search(".1",tag.a)
> 0
401
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
eroyeie yal, =e Lae
bowl ne=sl27 EODZ sie), Hooyer = ALY tOp3 area wT bots ne = 2 CASE
Search(".2",tag.a)
topisne=
> 0:
127
tofoyclliat =
slay
CODA te =
LZ
borZe nea 27 COPS ne el 7 OWS rie alee CASE,
Search ("23 "stadwa)
ELODIE) lofayedl jal =: CODZitie= loxoyeyy suey = tops. n=) lefoyes! wil =
> 0)
27)
ball, 27 lal, 112 ALA
ENDSWITCH
ENDSWITCH
RepaintDialog Return
TRUE
ENDPROC
;
TITLE: AUTHOR: CREATED: RETURNS:
(c) 1992 10-10-92 Nothing
Micah J. Bleecher : 10:35:36pm
- DataStar
; DESCRIPTION:
Converts
interactive
images
; ; ;
dsQueryToPal.u()
query
International
to PAL code
Private (efouil cialy starttable.a, blankrow.1, row.n, fieldvalues.y, checkstatus.y, fieldorder.r, allchecks.1,
402
jnumber of columns in query image jquery image 1 ;flag, true if entire row is blank ;image row numbers ;contents of fields ;check mark status of fields ;sequential order of fields ;true if checkmark status is same in all
fields
@
@
@@@
MB
CHAPTER
maxlen.n, retval.1,
WHILE
17:
PARADOX
;maximum length of ;return variable procedure
FOR
field
procname.v,
;Output
queryimage.1, opal piel?
;query image present ;numeric throw aways
DOS
for output
QUERY
TOOLS
format
name
flag
(True)
IF NImages()
=
0 THEN
;check
for
no
images
Beep Message "There Are retval.1 = FALSE
No
Images
Present"
QUITLOOP ENDIF
IF
Search(".",outfile.a) = FALSE
> 0 THEN
;check
for
file
extensions
retval.1 Beep
Message
"Filename
SelectControl QUITLOOP ENDIF MoveTo
Cannot
Have
An
Extension"
"FILE.1"
1
; First
Image
Startitabillesa queryimage.1
=— 1" = FALSE WHILE Table() starttable.a starttable.a = Table() IF ImageType() = "Query" THEN queryimage.1l = TRUE
;make
sure
;query
there image
is at
least
1
QUITLOOP ENDIF
DownImage ENDWHILE
IF NOT
queryimage.1
BEEP retval.]
=
Message
THEN
FALSE
"There
Are
No
Query
Images
Present"
QUITLOOP ENDIF outtilieva
= outtale,a
+". Sc"
If IsFile(outfile.a)
THEN
;check
for
file
name
Beep ShowPopup Upper (outfile.a)+" Already Exists" CENTERED "~A~ppend" : “Append Current File Name" : "APPEND",
"_O~verwrite"
: "Overwrite
"~R~ename"
:
"Rename
Current
Current
File
File
Name"
Name"
: "OVER", :
"RENAME"
EndMenu TO menuchoice.a
403
IF NOT
retval
retval.l1
REFERENCE
A DEVELOPER’S
QUERIES:
PARADOX
@
@
then =
FALSE
QUITLOOP ENDIF SWITCH
CASE
menuchoice.a
=
"OVER"
:
;overwrite
Menu {Tools} {Delete} {Script} SELECT SubStr(outfile.a,1,Search(".",outfile.a)-1) IF MenuChoice() = "Cancel" THEN {Ok} ENDIF
CASE
menuchoice.a
=
"RENAME":
;rename
SelectControl "FILE.1" retval.l = False QUITLOOP
ENDSWITCH EndIF
IF proc.n
= 2 THEN
procname.v
;proceduralize
output
= GetProcName.v()
IF procname.v
retval.l
=
FALSE
THEN
= FALSE
QUITLOOP ENDIF
PRINT
FILE
outfile.a
"PROC
PRINT
FILE
outfile.a
"\n"
"+procname.v+"\n"
PRINT
FILE
outfile.a
";Private\n"
PRINT
FILE
outfile.a
"\n"
PRINT
FILE
outfile.a
"\n"
;query
header
ENDIF
PRINT PRINT
FILE outfile.a "\n" FILE outfile.a "; dsQueryToPAL: Begin PRINT FILE outfile.a "; Generated: "+ Format ("d2",Today())+" - "+Time()+"\n" PRINT FILE outfile.a "; Description: \n" PRINT FILE outfile.a ";\n" MoveTo
1
FOR
FROM
n2
IF
; First
\n"
Image
1 TO Nimages()
ImageType()
=
"Query"
THEN
starttable.a = Table() col.n = Nfields(Table())+1 HOME
CTRLHOME PRINT FILE
404
Query
outfile.a
"\n"
;process
query
images
only
information
|
@@@
Be
CHAPTER
17:
PARADOX
PRINT FILE outfile.a blankrow.1 = TRUE row.n = 1 WHILE (True)
"
{Ask}
SELECT
FOR
DOS
QUERY
TOOLS
\""+inPathDouble.a(Table())+"\"\n"
CTRLHOME
DynArray fieldvalues.y[] DynArray checkstatus.y[] Array fieldorder.r[col.n] maxlen.n = 0 FOR n FROM 1 To col.n ;read row Message "Reading - Row: "+StrVal(row.n)+", Column: fieldvalues.y[Field()] = [] checkstatus.y[Field()] = CheckMarkStatus() fieldorder.r[{n] = Field() IF NOT IsBlank([] + CheckMarkStatus()) THEN maxlen.n = Max(Len(Field()),maxlen.n) blankrow.1 = FALSE
"+Strval (n)
ENDIF RIGHT ENDFOR
IF blankrow.1
THEN
QUITLOOP ELSE
IF
row.n
> 1 THEN
PRINT
FILE
outfile.a
"
DOWN\n"
ENDIF ENDIF
allchecks.1
= AllChecks.1()
IF allchecks.1 PRINT
FILE
;determine
if all
fields
are
checked
THEN outfile.a
"
CTRLHOME
"
+
Upper (checkstatus.y[fieldorder.r{2]])
+"\n"
ENDIF
IF NOT IsBlank(fieldvalues.y[fieldorder.r{1]]) THEN PRINT FILE outfile.a " \""+ PadQuotes.a(Upper(fieldvalues.y[fieldorder.r[1]]))+"\"\n" ENDIF FOR
nl
FROM
Message
2 To
col.n
"Writing
;write
Row:
row
to disk
"+StrVal(row.n)+",
file Column:
gc NOT allchecks.1 AND NOT IsBlank(checkstatus.y[fieldorder.r[n1l]])
"+Strval(n)
THEN
SWITCH
CACHE
IUStei= a
PRINT
FILE
El
outfile.a
StENCat LOM "
MoveTo"+
Spaces ((maxlen.n) -(len(fieldorder.r[(n1]))+1)+ "("+fieldorder.r{n1}+"] "4 Upper (checkstatus.y[fieldorder.r{n1l]])+"\n"
405
PARADOX
QUERIES:
A DEVELOPER’S
CASE
just.n
PRINT
= 2:
FILE
;Right
REFERENCE
8
8
@
@
justification
outfile.a
Spaces ((maxlen.n)-(len(fieldorder.r[nl]))+3)+ "MoveTo ["+fieldorder.r{n1l]+" "4 Upper (checkstatus.y[fieldorder.r[{nl]])+"\n" CASH
ejusteme=
seis
;left
justification
PRINT FILE outfile.a "MoveTo ["+fieldorder.r[n1]+"] Upper (checkstatus.y[fieldorder.r{n1]])+"\n"
"4
ENDSWITCH ENDIF IF NOT
IsBlank(fieldvalues.y[fieldorder.r[nl]])
THEN
SWITCH
CASH
INSEE
PRINT
=
FILE
se
aru
esteaticaknon
outfile.a
Spaces ( (maxlen.n+7) -(len(fieldorder.r[n1]))+2)+ "("+fieldorder.r(nlJ+"] = \""+ PadQuotes.a(fieldvalues.y[fieldorder.r({n1]])+"\"\n"
CASH)
JUSE-ny=
202)
| righeajustuiiucatron
PRINT FILE outfile.a Spaces ( (maxlen.n+7) -(len(fieldorder.r[{n1]))+3)+ "("+fieldorder.r[(nl]+ "] =
CASE
\""+PadQuotes.a(fieldvalues.y[fieldorder.r[{n1]])+"\"\n"
JUuSst.n = 3) 2)
-Vefteriusti rucatson
PRINT FILE outfile.a "("+fieldorder.r[n1]+"] = \""+ PadQuotes.a(fieldvalues.y[fieldorder.r{n1l]])+"\"\n" ENDSWITCH ENDIF ENDFOR Low.n =
Lowen
blankrow.1
+
= TRUE
ENDWHILE HOME CTRLHOME ENDIF DOWN IMAGE ENDFOR CTRLHOME
PRINT
406
FILE
outfile.a
"\n"
;query
footer
information
BOOB
CHAPTER
17:
PARADOX
FOR
PRINT SS ULE Vout hema PRINT FILE outfile.a
ur DO ut Nn" "; quExecute.1 (True) \n"
PRINT
FILE
outfile.a
";
PRINT
FILE
outfile.a
";
IF
PRINT
FILE
outfile.a
";
FILE FILE
outfile. a outfile. a
" ; ial
= 2 THEN
retval
QUERY
TOOLS
THEN\n"
DEBUG\n"
PRINT PRINT
IF proc.n
NOT
DOS
ENDIF\n"
Le
PCE
OUenys
;proceduralize
=—\n"
output
PRINT FILE outfile.a "\n" PRINT FILE outfile.a "ENDPROC\n" PRINT FILE outfile.a ";??\"\\004\"\n" IF Search("(",procname.v) = 0 THEN PRINT
FILE
outfile.a
";WRITELIB
libname.a
"+procname.v+"\n"
ELSE
PRINT FILE outfile.a ";WRITELIB libname.a "+ SubStr(procname.v,1, (Search("(",procname.v)-1))+"\n" ENDIF ENDIF
SelectControl Message
"CANCEL.3"
"Conversion
retval.1l
Complete"
= true
QUITLOOP ENDWHILE
Return
retval.1
ENDPROC
:
TITLE: AUTHOR:
. °
CREATED: RETURNS:
inPathDouble.a
(c)
1992
07-01-92 No Value
; DESCRIPTION:
Doubles
- Daniel
backslashes
PROC
inPathDouble.a( path.a) Private al, a2, a3 al
J.
Paolini
II
- DataStar
International
03:50:00am
in a Path
String
; Doubles backslashes in a string ; Path to double ; Transient string variables
= path.a
ay) saa WHE
MAGGhiiadl, Were tesa)
a2 = a2 + a3 +"\\\\" ENDWHILE
IF al path.a THEN Return a2 + al ELSE
Return
path.a
ENDIF
407
A DEVELOPER’S
QUERIES:
PARADOX
REFERENCE
@
@
@
@
ENDPROC
¢
TITLE:
; ; '
AUTHOR: CREATED: RETURNS:
; DESCRIPTION:
PadQuotes.a
(c) 1992 - Micah J. 07-01-92 03:50:00am Alphanumeric Adds
escape
characters
PROC PadQuotes.a(string.a) Private len.n, ;original length n IF
;mumeric
counter
Search("\"",string.a)
Bleecher
>
- DataStar
(backslashes)
International
to quoted
strings
of string var
0 THEN
len.n = Len(string.a) FOR n FROM len.n TO 1 STEP -1 ;step backwards to account for IF SubStr(string.a,n,1) = "\"" THEN ;increasing length of string string.a = SubStr(string.a,1,n-1) +"\\"+SubStr(string.a,n,LEN(string.a) ) ENDIF ENDFOR ENDIF
RETURN
string.a
ENDPROC
. ; ;
TITLE: AUTHOR: CREATED:
: RETURNS: ; DESCRIPTION: . PROC
AllChecks.1() (c) 1992 - Micah J. Bleecher 10-10-92 : 11:36:24pm
- DataStar
International
Logical, True if all fields have a positive Checks all query fields to determine if all check mark (check, checkplus or groupby)
check mark status have a positive
AllChecks.1()
Private
firstcheck.a, retval.1, n
;status of the first ;return variable ;numeric throw away
field
;GLOBAL : :
fieldorder.r checkstatus.y col.n
;sequential order of fields ;check mark status of the field ;number of columns in the image
firstcheck.a = checkstatus.y[fieldorder.r[2]] IF NOT IsBlank(firstcheck.a) THEN retval.1l = TRUE FOR
n FROM
IF
2 To
QUITLOOP
408
col.n
firstcheck.a checkstatus.y[fieldorder.r[n]] retval.1l = FALSE
THEN
@@8@B
CHAPTER
17:
PARADOX
FOR
DOS
QUERY
TOOLS
ENDIF ENDFOR ELSE
retval.1
= FALSE
ENDIF
RETURN
retval.1
ENDPROC
;
TITLE:
GetProcName.v()
F AUTHOR: 5 CREATED: ; RETURNS: ; DESCRIPTION: PROC
Micah J. Bleecher - DataStar International 10-12-92 - 09:58:04am String of input procedure name Dialog box requesting user input for proc name
GetProcName.v()
Private
procname.v,
;holds
butvar.1, retval.v
;button ;return
procname.v
butvar.1
=
procedure
name
variable variable
""
= FALSE
ShowDialog "Enter Procedure @3,15 Height 7 Width 49
Name"
; PaintPAL_Frame_Begin
Frame Single PaintCanvas PaintCanvas PaintCanvas PaintCanvas
From 0,1 Attribute Attribute Attribute Attribute ; PaintPAL_Frame_End
Accept
@1,2
Width
To 2,45 112 0,1,0,45 112 0,1,2,1 127 2,2,2,45 127 0,45,2,45
43
"A440"
itetop MO’ To procname.v PushButton " ~O~k
@3,4
Width
14
"
Ok Default Value True Tag "OK" To butvar.1
PushButton
@3,28
Width
14
"~C~ancel" Cancel
409
QUERIES:
PARADOX
Value Tag
To
A
DEVELOPER’S
REFERENCE
@
@
@
@
FALSE "CANCEL"
butvar.1
EndDialog IF
NOT
butvar.1
retval.v
THEN
= butvar.1
ELSE
retval.v
= procname.v
ENDIF
RETURN
retval.v
ENDPROC
The ImageToPAL source code begins by displaying a dialog box with various options. After the user has specified options and clicked OK, the routine reads each saved query image in turn, until a completely blank row is found. For each row, it defines a dynarray to hold the field contents, a dynarray to hold the checkmark status of that field, and an array to list the order in which fields have been rotated in the saved image. This last item is essential because field order controls the structure of the ANSWER table when queries are set to be processed in ImageOrder. Once a row has been loaded into PAL variables, it is written to a new file as a
series of PAL commands. The script provides options to write output in a procedure format and to control justification, and these settings are used as the various PAL commands are written to the new script. Specialized procedures are also used to determine if a table is in a different subdirectory in order both to double the backslash characters as Paradox requires and to determine if a quoted string was specified in the saved image so that the quotation marks themselves can be properly delimited.
Summary This chapter introduced and explained the workings of a number of query utilities that provide added functionality in Paradox 4.0. These tools make tedious operations quicker and give you capabilities that Paradox does not provide. They are also valuable to study for the programming techniques used. In their source code, you will find many unusual techniques and tricks for managing and manipulating query images.
410
CHAPTER
EIGHTEEN
Paradox for Windows
Query Tools
Paradox for Windows does not provide ObjectPAL control over the query user interface. Therefore, many of the tools described in the previous chapter for Paradox for DOS are not possible, and don’t make sense, in Paradox for Windows.
However, the very lack of ObjectPAL control over queries presents a unique opportunity: a generic query engine that formats and processes queries for you! This engine would allow you to define query tables, fields, and operations to perform in each field. It would read this information and design the query as a QueryString object. Such an engine is described in this chapter.
Creating Query Strings under ObjectPAL Saved queries follow the format shown in Figure 18-1. As you saw in Chapter 7, any part of a query string can be defined using string variables, including the table name, field names, query operators, selection criteria, and example elements. But cre-
ating these query strings is a rather tedious operation, with all the \n newline characters and quotation marks that have to be inserted.
(urea
aonaniee ancy
hae Trae)
j
be
i
r
Pele Le
;
TABLENAME ‘| Field |
| |
| Operation
Cuey ye! aueoel | Field | Operation
eqagueey
Figure 18-1. The format of saved queries in Paradox for Windows
411
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
This lack of ObjectPAL control over the query definition process also creates problems for generic applications in which a specific field might be involved in the query for one loop but not for the next loop. Program-controlled queries give you, the developer, freedom to define any fields and operations in those fields at any time. This chapter describes two custom ObjectPAL methods that you can use to create query strings under ObjectPAL control: ¢ BuildQBE_1Row(), which creates query strings for a table where the image has only one row. ¢ BuildQBE_MultiRow(), which creates query strings for a table where the image has more than one row. Each method can be called multiple times within the same operation to define a multi-table query. Each method can also be used in the same multi-table query where one table has only one row and another has more than one row. These methods handle the following query elements for you: e ¢ ¢ ¢ e
Variable table names Table-based query operations in the leftmost column Check operators to select fields Selection criteria, including ranges Example elements
Calling custom methods Calling a custom method from a library requires you to set up a number of components. First, you must open the library. These custom methods are stored in the QRYPROC.LSL library, so you will need the following code in your form or script: var QBELib endVar
Library
QBELib.open("QRYPROC")
QBELIib is a variable of the type Library, defined in the Var window of the form or script. 412
a
CHAPTER
18:
PARADOX
FOR
WINDOWS
QUERY
TOOLS
You also need to tell the script or form which custom methods are called from that library, and the format for the arguments passed to that custom method. This is done in the Uses window, like so: Uses
Object PAL
BuildQBE_1Row(
const const const const const var
TblName String, TblOp String, QBECriteria QBEDyn, StartQBE Logical, EndQBE Logical, BuiltQBE String)
; ; ; ; ; ;
table name table operation array of values new or continuation stop or continuation query string itself
endUses
In the preceding example, the custom method BuildQBE_1Row/() requires six arguments, five of which are passed as constants and are not modified by the procedure. This results in more efficient code. The sixth argument is passed by reference; BuiltQBE is the actual query string built by the method. The third argument in the above method call is QBECriteria, which is a dynamic array. ObjectPAL requires that you pass dynarrays using custom types. QBEDyn is used in the preceding example. In order for ObjectPAL to recognize this custom type, you have to define it in the Types window of both the form or script, and in the library where the custom method is stored, like so: Type QBEDyn
= DynArray[]
String
endType
BuildQBE_1Row/(): Creating Query Strings for a One-Row Table Image The simplest way to explain the first custom method is with an example. Suppose you need to perform the query shown in Figure 18-2, which sums and counts the invoices for each order in the ORDERS table after 1989, grouping the results by shipping method.
413
REFERENCE
A DEVELOPER’S
QUERIES:
PARADOX
@
@
@
@
Query : Ree
Figure 18-2. Aggregating orders after 1989 by total invoice
Following is the QBEString for this query. The query is built and executed by the QTOOLS1.SSL script, which is shown in Listing 18-1. "Query\n\n"
"Orders
+
| Sale
ele
Date
; Ship
let
VIA
; Total
Ome Oneek:
} Gale
Invoice
Sum,
Cale
NT
Count
All
|\n\n"
"Endquery"
Listing 18-1. QTOOL1.SSL, demonstrating the BuildQBE_1Row() custom method method
run(var
eventInfo
Event)
var TblName Tb1l0Op QBELib
String String Library
; table name ; table operation ; library with define
QBEDy
Dynarray[]
QryImage Result
String TableView
String
; query
method
fields/values
; the string being built ; viewing the results
endvar
; open
library
QBELib. open ("QRYPROC" )
message("Defining
; define TblName
query =
Query")
table,
fields
and
elements
"ORDERS"
Do kyes meee QBEDy["Sale Date"]
=1/1/90*%Calc
Average"
¢ For a two-row query where the second row for this field is blank you have to allow for a minor bug with the breakApart() method. Paradox does not correctly parse the string in this situation, so you have to add a space between the last caret and the close quote: "DHL
A
Wn
(The custom method could be modified to add this space automatically where necessary, but this would require that query operators be passed by reference instead of as constants, resulting in slower performance.)
¢ For a four-row query with nothing in this field except a Check in the last row: " SAGh
eGo
At the core of this custom method, the program loops for the number of defined rows specified by the RowCount parameter. If the TableOp string is not blank, it must also be broken apart to allow for left-column operators on different rows. Then the field values are placed using the array element that matches the current row.
This code is a little inefficient because it has to break the same string apart for each row in the query in order to read a specific element within the string. Unfortunately, there is no truly generic way to perform this operation just once for each field, 422
a
CHAPTER
18:
PARADOX
FOR
WINDOWS
QUERY
TOOLS
saving the values until they are needed. Keep in mind that each table in a query object has to be defined from left to right, starting at the first row of the table and finishing at the last. Figure 18-6 shows a six-row query to find the top five orders by Total Invoice amount. Listing 18-7 shows an extract from the QTOOLS.SSL script that defines this query and calls BuiltQBE_MultiRow() to create the query string. Query:
PRIV:ANSWER.DB
Figure 18-6. A six-row Set query
Listing 18-7. Defining a six-row Set query ; define
query
table,
fields
and
TblName
=|
"ORDERS"
TblOp
=
SeriSerssecu“Sels Sets
OBEDY ["Order
Now
w=.
elements
i
'o°oe “Cheer
QBEDy["Total Invoice"]= "“_A* =Maxaalh!
_A,_B”%
(a
22m
roe) ov
=.
ae
a
_—
~
~~
nH ~
—
f*
aa
=
°
-
7
7h
| ey
i?
=
"aa
wabye
Ss)
P
i Lhe
teh ¢
Cams ule Cally om Sul
art
-_
e ) Sy Sraves
SOP wl OG uid 66 @e G24 eis |) 0 -
ope mh
wae. ;
¥
mete «47! . rate saison9 oie ‘e Ei ate seed
a
o>quraehg rains
dei
Se reo
7
iad Ge Game attr * eaxygbllas 21
OT Gata o% mS
i vr
a
aS
-i wr
beh!
JTS IAew
~
4hty
vas
er avid
eat
oi | a) m°) = =o) —
N\\ PARTIVHR
BBRHBHBHEHEHEHEHBEHEBHRHEHBEHEHBHEHE EB
Querying on a Network Multi-user access to data involves two fundamental and diametrically opposed objectives: ¢ On one side, it is important to provide as much concurrent access as possible to your data. Your programs and Paradox work together so that more than one user can be querying or modifying the same tables at the same time.
¢ On the other side, you and Paradox must do everything possible to protect the integrity of your data, to ensure that the information in your tables is internally consistent and that nothing happens that could damage your data.
To accomplish these objectives, Paradox provides for locks that establish varying levels of control over a table or record. Each lock is associated with a user, so that Paradox can tell you who is using the table you want to access. Chapter 19 deals with locking, the different types of table locks associated with
each type of query, and various query techniques on networks. Chapter 20 deals with query restart, Paradox’s ability to cleanly query a table that is also being modified. When you use Paradox as described in these two chapters, you are working in a multi-user model called the file server model. Under this model, the network is used
427
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
@
only to share data. But Paradox also supports another model for multi-user access called the client server model. Client-server computing requires a separate relational database server attached to the network, and an add-in to Paradox called SQL Link. This model and Paradox’s implementation are described in Chapters 21 and 22.
428
CHAPTER
NINETEEN
Locking and Multi-User Access When you start to perform queries on a network, you have to face a whole set of new and challenging problems. While a query is extracting information from a table, other users may be adding or modifying the very records that are being queried. For this reason, Paradox places table and record locks on all tables currently being accessed on a network. If there is a chance that more than one user may be accessing a table at the same time, Paradox will attempt to place the appropriate lock on that table to ensure that an operation has a sufficient level of access. Paradox places the least restrictive lock that is compatible with the operation being performed. If an operation requires exclusive access to a table, Paradox will ensure that it has this level of access before proceeding. But if an operation can be performed while other users are modifying the table, Paradox will place a lock with minimal restrictions. For every user on a network, Paradox always checks what locks have been placed on an object before it grants you access to that object. If prior locks are sufficiently restrictive, you will not be able to access the table. If prior locks allow for concurrent access, Paradox will place a new lock alongside the existing ones with your network user name attached to it. Access by any user must be compatible with all existing locks on a table. If the lock is successfully placed, then you are guaranteed to finish the operation. If the lock cannot be placed, Paradox will tell you that other users are accessing the table using conflicting locks. In addition to the locks that Paradox places automatically, developers can also place locks preemptively, either by way of the Paradox menus or under script control.
429
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
Table Locks in Paradox for DOS The DOS versions of Paradox place four different types of table locks (they are listed here in order from most to least restrictive): Lock
Description
Full Lock (FL)
You have exclusive access to, and control of, the table. No
one else can even view it. Creating or restructuring a table requires a Full Lock. Write Lock (WL)
Other users can view the table, but you are the only person
who can modify it. Other users can also place a Write Lock,
so that each stops the other from modifying the table. Copying a table requires a Write Lock. Prevent Write Lock (PWL)
This is not a lock in the traditional sense of the word. Instead, this lock prevents other users from placing a Write Lock on the table. In other words, it stops someone else from denying you the right to edit the table.
Prevent Full Lock (PFL)
This lowest level of locking is also not a traditional lock. It
prevents other users from obtaining exclusive control over a table. Viewing a table requires a Prevent Full Lock.
To ensure maximum concurrency, you should issue the lowest level of locking required by an operation. For example, multi-user viewing requires nothing more than a Prevent Full Lock, whereas editing requires a Prevent Write Lock to ensure that you can post records. Lock compatibility Locks can coexist with each other. This way, more than one user can access a table at the same time. Table 19-1 shows the locks that can coexist on a table.
430
Bee
CHAPTER
19:
LOCKING
AND
MULTI-USER
ACCESS
Table 19-1. Paradox for DOS Table Locks and their Compatibility Prevent
Prevent
Full
Write
Write
Full
Lock
Lock
Lock
Lock
Full Lock
Write Lock
J
Prevent Write Lock Prevent Full Lock
As the one person at the same vent Write
WA
J WA
J
J
J
table shows, Prevent Full Locks can coexist. This fact allows more than to view a table at the same time, and even to be in CoEdit mode on a table time, since both of these operations require just a Prevent Full Lock. PreLocks can also coexist on a table at the same time. Because updating a
record in CoEdit mode requires a Prevent Write Lock, more than one person can actually change records in a table at the same time. Two or more people can also have Write Locks on a table at the same time. In this situation, each lock stops the other user from making changes, so the table cannot be modified. If you need exclusive write access to a table so anyone can view it but you are guaranteed the ability to update it, place both a Write Lock and a Prevent Write Lock on the table: ¢ The Write Lock gives you the exclusive ability to modify the table. e The Prevent Write Lock prevents others from placing their own Write Locks. If you want concurrent write access to the table, place a Prevent Write Lock only. Placing locks on a table Paradox for DOS attempts to place the appropriate locks automatically when
you start an operation, and you can set and clear table locks manually with the menu sequence Tools / Net / Lock or Tools / Net / PreventLock.
431
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
8
@
@
Under PAL control, the Lock and UnLock commands are used to place and release explicit locks. Lock has the following syntax: (Un) Lock
"Tablel" "Table2"
LockType, LockType,
"TableN"
LockType
where LockType is the abbreviation for a Full Lock, Write Lock, Prevent Write Lock,
or Prevent Full Lock. (For UnLock, you can also specify the keyword ALL to release all locks held by the current user for the specified table.) Each command allows you to lock or unlock multiple tables at a time. Lock only places locks if every table in the list can be locked, setting Retval to logical True. If one or more locks could not be placed, none are placed, and Retval is set to logical False. This feature provides built-in protection against deadly embrace situations. In a deadly embrace, User 1 locks table A at the same time that User 2 locks table B. The two users then attempt to get a conflicting lock on the other table, and neither one can proceed. By attempting to lock both tables in the same operation, one of the users will be allowed to proceed. Performing multi-step operations Before you perform a multi-step operation, you need to ensure sufficient control over every table used in the operation. The reason for this is simple: if you get
half way through an operation and Paradox finds a conflict with another user on a critical table, you will not be able to proceed and your data will be left in an indeterminate state.
So the recommended approach is to lock all tables you need in advance, with all of the locks that you need. Once this is done, you have guaranteed that you can finish the operation you are about to start. As each stage is completed, you can also release the locks you have finished with to provide better concurrency with other users accessing the tables. Listing 19-1 shows the same multi-step operation described in Listing 1-8. In this example, you are deleting orders prior to a specified date from the ORDERS table, then updating a running total in the CUST_SP table before you discard or archive these deleted orders. The code demonstrates how the different lock commands are placed in the code. 432
|
CHAPTER
19:
LOCKING
AND
MULTI-USER
ACCESS
Listing 19-1. Applying and releasing locks in a multi-step operation in Paradox for DOS Lock If
"ORDERS"
FL,
REGU RSD SY OI
Ny
; control
Retval = False Then Message "Unable to run
the
tables
; 1 or more locks query; tables in use
purge
failed by " + ErrorUser()
Return EndIf
; remove
customers
prior
to
1/1/91
Query
Orders
Sale
delete
eel
Date
/A
Endquery Do_It4
ClearAll UnLock "ORDERS" ; summarize
FL
order
; finished volume
for
these
with
this
table
customers
Query
Deleted
| Customer | Check
No
| Total Invoice | cale sum
Endquery Do_It! ClearAll
; update
|
; no locks necessary ; private table
the
order
volume
field
in the
customer
on
a
table
Query
Cust_sp
Answer
| Customer ieGeust | Customer
mest
No
No
| | _todate, | Sum
of
Archived Orders changeto _todate+_ordervol
Total
| _ordervol
Invoice
|
|
;
Endquery Domi!
ClearAll UnLock "CUSTSP"
FL
; finished
with
this
table
433
PARADOX
A
QUERIES:
@
REFERENCE
DEVELOPER’S
@
@
@
Setting the retry period for table locks Paradox normally tries only once to place a table lock. However, if you expect
the table to not be available immediately but to be available within a finite period of time, you can have Paradox keep trying before it gives up. The SetRetryPeriod command allows you to designate up to 30,000 seconds for Paradox to keep trying. The RetryPeriod() function returns the currently set retry period. There are two reasons why you might not want to rely exclusively on these built in operations: ¢ Paradox tries continuously for the specified time. These tests put a heavy burden on your network and can slow down other operations. ¢ Using a retry period does not excuse you from the responsibility of testing for lock failure. You will always need code to provide for the possibility that a lock cannot be placed. Instead of using the built-in retry period, many developers resort to code like that shown in Listing 19-2. This code inserts a one second delay between each lock test. If the lock is not obtained after ten tries, a menu is displayed that gives the user the option of continuing to try or returning to a previous menu. This approach minimizes the significant overhead imposed by continuous retries. Listing 19-2. Simulating lock retry without the network overhead in Paradox for DOS SetRetryPeriod 0 While True COUNTER = 0 While True Lock "CUSTOMER" If
Retval = QuitLoop EndIf
True
; do
not
retry
; initialize FL Then
counter
; attempt lock ; success! jump
COUNTER = COUNTER + 1 If COUNTER = 10 Then
out
; increment counter ; tried 10 times
ShowMenu
"TryAgain"
:
"Unable
to
lock
Customer
table
after
10
lock Customer menu"
table
after
10
EXLeS;Ptryeagaimet, "Return" tries; To
434
MENU.CHOICE
: "Unable to return to the
|
CHAPTER
19:
LOCKING
If MENU.CHOICE
AND
"TryAgain"
MULTI-USER
ACCESS
Then
Return
Else Retval = QuitLoop EndIf
False
; try again ; jump out
for
another
10
Else
Sleep
1000
; artificial
1 second
pause
Endlif EndWhile
If
Retval = QuitLoop EndIf
True
Then
; lock
succeeded
EndWhile ; continue
here
with
query
or
operation
on
the
CUSTOMER
table
Query lock requirements in Paradox for DOS In order to display a query image on the Workspace, Paradox has to read the structure of the table, an operation that alone requires a Prevent Full Lock. (You cannot query if someone is restructuring a table because this operation requires a Full Lock!) If you play a saved query script to load predefined query images onto the network and Paradox finds a Full Lock on one of the tables, it will not be able to com-
plete the saved query. Paradox will stop at the table that it cannot open and present the error message Tablename table has been locked by Username (Because Paradox is maintaining the lock files itself, and because each user has a
predefined name within Paradox, the program knows who owns the lock you have bumped into, and will present this name to you as part of the error message.) For Insert, Delete, and ChangeTo queries, Paradox places a Full Lock on the table, and no one else can access the table even to view it. (Theoretically, Paradox might get by with a combination of a Write Lock and Prevent Write Lock, which would allow other users to view the table during the update but not make changes.) For all other query types, including Check, Calc, Set, and Find queries, Paradox’s default locks depend on the version you are using and the Paradox concurrency set435
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@
@
@
@
tings you have defined. In Versions SE and 3.5, Paradox places a Prevent Full Lock on the tables being queried, and will restart the query if another user modifies a record. In Version 4.0, Paradox places either a Write Lock or a Prevent Full Lock, depending on whether query restart is enabled or not. For more information on query restart, see the next chapter. Other types of locks Paradox for DOS also supports other types of locks, including locks on family members when a table or the family is being copied, record locks when you are editing a record, and group locks when you are modifying the master record in a multitable One:One or One:Many form. Of these locks, only a record lock can be placed by the user. The others are automatically placed by Paradox. As you learned, the three queries that modify a table (Insert, Delete, and ChangeTo) all require that a Full Lock be placed on the table being modified. This restriction is
necessary to ensure that all affected records can be updated. Record locks cannot be placed unless the table can be co-edited, which requires a Prevent Full Lock. Because a Prevent Full Lock is incompatible with a Full Lock, record locks are not placed, nor are they necessary, during Insert, Delete, and ChangeTo queries.
Table Locks in Paradox for Windows Paradox for Windows places the following types of table locks (they are listed in order from most to least restrictive): Lock
Description
Exclusive Lock (FL) You have exclusive access to, and control of, the table. No one else
can even view it. Creating or restructuring a table requires an Exclu-
sive Lock. An Exclusive Lock in Paradox for Windows is the same as a Full Lock in Paradox for DOS. Write Lock (WL)
— Other users can view the table, but you are the only person who can
modify it. Copying a table requires a Write Lock. A Write Lock in Paradox for Windows is the same as the combination of a Write Lock and a Prevent Write Lock in Paradox for DOS.
436
|
CHAPTER
Read Lock (RL)
19:
LOCKING
AND
MULTI-USER
ACCESS
You have the ability to read the table and write to it if no other user
also has a Read Lock. Other users can also read the table at all times. A Read Lock in Paradox for Windows is the same as a Write Lock in Paradox for DOS.
Open Lock (OL)
This lowest level of locking allows you to open the table and read its contents; other users can do the same. Of all the users viewing a table,
one person can write to it at a time. An Open Lock in Paradox for Windows is the same as a Prevent Full Lock in Paradox for DOS.
Lock compatibility Table 19-2 shows the Paradox for Windows locks that can coexist with each other. Table 19-2. Paradox for Windows Table Locks and their Compatibility Exclusive
Write
Read
Open
Lock
Lock
Lock
Lock
Exclusive Lock
Write Lock
J
Read Lock
Open Lock
J
"A
J
J
J
Open Locks can coexist, allowing more than one person to view and edit a table at the same time. Two or more people can also have Read Locks on a table at the same time. In this situation, each stops the other from writing to the table. If you need exclusive write access to a table, place a Paradox for Windows Write Lock. Placing locks on a table Paradox for Windows attempts to place the appropriate locks automatically when you start an operation. You can also set and clear table locks manually with the menu
sequence File / MultiUser / SetLocks.... Paradox will display the dialog box shown in Figure 19-1, from which you can select a table and specify a lock for that table. When you use this dialog box, only one lock for a table can be in effect at a time.
437
PARADOX
=
DEVELOPER’S
A
QUERIES:
REFERENCE
@
@
a
8
Table Locks ft
Re
Table Name-
ee
CUST1314._DB
|
CUSTOMER_DB
> Exclusive Lock
VENDORS.DB -PRIV-ANSWER_DB -PRIV:DEFAULT.DB
4 OK
L:PRIV:LIST.DB
Path Type[ Cannot perform operation on CHANGED table together with a ChangeTo query p Cannot perform operation on DELETED table together with a Delete query p> Cannot perform operation on INSERTED table together with an Insert query Paradox generates these safety-net tables so you can recover from a query that did not produce the intended results. A query that modifies one of these tables and also generates a new one is not possible because Paradox has to erase the table being modified in order to create the new one. p> Cannot use patterns with LIKE or range operators
The Like operator uses its own internal algorithms to perform an inexact match, so it is not compatible with the pattern operators. Paradox is also unable to use range operators and patterns together because there may not be a consistent selection criterion.
p ChangeTo can be used in only one query form at a time The ChangeTo operator generates a CHANGED table. If you used it in more than one query image at the same time, Paradox would have to create two CHANGED tables with different structures, which is impossible. (Interestingly, the Paradox 4.0 Fast operator does not remove this limitation.)
p ChangeTo cannot be used with Insert, Delete or Find p> ChangeTo cannot be used with Insert or Delete If you have specified Insert, Delete, or Find in the leftmost column of the
query image, you cannot also specify ChangeTo in the body of the query. (Paradox for Windows does not support the Find operator and will display the second form of this message.) 504
BEBEBHEHEeBee
eB
APPENDIX
A:
ERROR
HANDLING
p> ChangeTo must be followed by the new value for the field When you use the ChangeTo operator, you have to specify a new value! p Checkmark or Calc expressions cannot be used in Find queries In Paradox for DOS, if you specify Find in the leftmost column of the query image, you cannot also place Check operators or the Calc operator in the body of the query. p Checkmark [F6] not relevant in current context You will see this message if you press [F6] or one of its Shift variants outside a query image. p Delete can be used in only one query form at a time You cannot delete records from more than one table in a single query. You will see this message if you press [F2] on a multi-table query and the Delete keyword is listed in more than one query image.
p Directory name not expected One reason for this message is that Paradox for DOS does not recognize tables with the same name as a subdirectory of the current directory. Paradox will always find the subdirectory first for any operation. You may also see this message if you have edited a saved query image and a blank line doesn’t appear between the Query command and the first query image.
p Example element XXX can’t be used more than twice with a ! query If you use an example element more than twice in a multi-table join, then you cannot use the inclusion operator with that example element. Paradox cannot include all records from one table while still linking to two other tables for matching records. One workaround is to use separate example elements to link to each of the other tables. p Example element XXX is used in two fields with incompatible types Example elements are supposed to link fields with the same data. If you join two A15 fields, for example, Paradox does not know if one contains a Cus505
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
tomer City and the other a Stock Vendor Name, or the data truly is related. But it is axiomatic that you cannot join fields with fundamentally different data types.
p Example element not allowed in a memo or binary (BLOB) field Paradox does not allow you to link on a memo or BLOB field due to the large or amorphous nature of the field’s contents. This is especially unfortunate if you want to transfer data from one field to another using an Insert query. In this case, the memo or data cannot be moved. (Not in Paradox 3.X.)
p Example element not appropriate in this field You cannot place example elements in extended field types, including Paradox 4.0 Memo and BLOB fields; and Paradox for Windows Memo, For-
matted Memo, Graphic, OLE, and Binary fields. p> Example element with ! makes no sense in expression When you use an example element as part of a Calc or ChangeTo expression, the inclusion operator is not appropriate.
p Example element XXX has no defining occurrence An example element is designed to reference a field in another part of the same query. Therefore, each one has to appear at least twice in a query. p Excess OR When you use the Or operator, you have to specify selection criteria on either side of it! Also, check to be sure that queries for Oregon use “Or” (with dou-
ble quotes) instead of Or. p Expecting one of TABLEORDER or IMAGEORDER The PAL SetQueryOrder command requires one of these two keywords. They
control how Paradox processes query images with rotated fields.
506
BESBEHEHEHEeee
BB
APPENDIX
A:
ERROR
HANDLING
p Expression makes no sense This error might result from any number of formatting or syntax errors in your query expression. Be sure that you have matching parentheses and that the calculation is mathematically correct. p> Field must contain an expression to insert (or be blank) Only literal values, example elements, expressions that result in literals,
and the Blank and Today operators can be used in the same row as an Insert operation.
p FIND can be used in only one query form at a time The Find operator locates records in one table and places them in an ANSWER table. Paradox cannot find records in more than one table at a time with this operator. (Paradox for Windows does not support the Find operator.) p FIND can’t be used with the ANSWER table In Paradox for DOS, you cannot specify a Find query on the ANSWER table.
This query places you on the first matching record in the current table. It also creates an ANSWER table behind the scenes. But this ANSWER table would have to overwrite the data table for the query. p Image qualifier must be either O or a number This error message is generated by PAL when you use the Field Specifier Notation. You can reference any table on the Workspace by using a number inside parentheses for a display image. For example, you could use MoveTo CUSTOMER(2) or the letter O for the single query image from each table that Paradox allows on the Workspace. p Incomplete query statement: query only contains a set definition Set queries require a definition of the records that comprise the set and a grouping/selection operation that is performed against the set. Both must be specified.
907
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
@
p Insert can be used in only one query form at a time You cannot insert records into more than one table in a single query. You will see this message if you press [F2] on a multi-table query and the Insert keyword is listed in more than one query image.
p Insert, Delete, ChangeTo and Set rows may not be checkmarked You cannot specify Check operators in the same query image as any of these four operators. p Insert, Delete, Find, and Set can be used only in the leftmost column These are all record-based operations, so the operators can only be placed in the leftmost column.
Insufficient password rights to insert a record Insufficient password rights to insert or delete records Insufficient password rights to modify field Insufficient password rights to perform operation Insufficient rights to read XXX field of XXX table vvvvv One or more of the tables involved in this query have been encrypted with a master and auxiliary passwords. The password you used to display the query image in the first place does not grant you sufficient rights to perform the operation you have specified. Invalid expression Invalid expression Invalid expression vvv Paradox does criterion, as a that make up
in INSERT row in SET definition not understand the expression you have specified as a selection value to be inserted in a field, or as a means to define the records your set.
p Invalid OR expression When you use the Or operator, each of the choices must evaluate to a unique selection criterion.
508
BEEBE
eEeEee
eB
APPENDIX
A:
ERROR
HANDLING
p Invalid query form You may see this message if you save a query to an SC file in Paradox for DOS or a QBE file in Paradox for Windows, and that file is later modified so that it no longer represents a saved query that Paradox can interpret. p Invalid use of example element in summary expression Summary expressions combine or aggregate records, whereas example elements represent single records in other parts of the query. Therefore, these two operations are not compatible with each other. p Keyword ChangeTo not allowed in a memo or binary (BLOB) field p Keyword INSERT not allowed with a memo or binary (BLOB) field p Keyword LIKE not allowed in a memo or binary (BLOB) field Memo fields may contain up to 256 megabytes of data, which is far too large to specify an expression to insert, a value to ChangeTo, or a Like expression to match. (Not in Paradox 3.X.) p Keyword XXX not expected You have used a reserved query operator in a context that is inappropriate. For example, you will see this message if you specify one of the summary operators outside the context of a summary operation. p Missing comma p> Missing XXX
Paradox often generates these messages when the query is incorrectly formatted, or when the expression you have defined is too complex for Paradox’s internal parser. Try simplifying the query or breaking it down into two or more separate queries.
p Missing right quote (expression longer than 175 chars) You will see this message if you try to save a query with an especially long selection criterion or query expression in one field. In Paradox for DOS scripts, the maximum line length is 175 characters, and Paradox cannot save a script if the line would be longer than this.
509
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
@&
p> Missing | This message appears if the EndQuery keyword in a saved query is spelled incorrectly, or if a blank line separating query images is inadvertently removed.
pe More than 255 fields in answer table If you join two or more very wide tables and check a significant percentage of fields in each table, you may exceed Paradox’s internal limit of 255 fields per table. p> Must be in Main mode to query
Paradox for DOS is a modal program. Queries can only be performed in Main mode, if “Ask” is displayed on the menu when you press [F10]. > No grouping is defined for set operation Set queries require selection criteria to define the set and a grouping operation to determine how records will be compared against the set. This is usually a Check or GroupBy operator on the same line as the set comparison. & No query images present
In Paradox 4.0, you pressed [Alt-Spacebar] and selected Desktop / SurfaceQueries, but there were no queries to surface.
p> No SpeedUp possible | This is not an error message, but rather an indication from Paradox for DOS that the program determined it was not worthwhile to generate Secondary indexes to speed up the current query. You may see this message after selecting Tools / QuerySpeed from the Main menu. p> Not enough disk space to complete operation p Not enough disk space to regenerate secondary indices Many Paradox operations, especially queries, require a considerable amount of free disk space for temporary tables. You may see these messages if Paradox does not have enough disk space to work with. Note that if you are running on a network or if you have a Paradox Private directory defined,
510
BEEBEEHEHEeEee
eB
APPENDIX
A:
ERROR
HANDLING
Paradox may be referring to the disk containing this directory rather than the data drive. You may also see the first message if one of the tables involved in the query is damaged. When this happens, try restructuring the table or exporting it to an ASCII file and then re-importing it. As a last resort, use TUtility to rebuild the file. pe Nothing to process now Paradox for DOS will generate this message if you press [F2] in Main mode and the query image is empty. p Number is out of range You have tried to perform an operation in a Short Number field that would exceed the allowable range of values in this field (-32,767 to +32,767).
p Number of fields exceeds 255 field maximum Paradox tables are limited to 255 fields. If you perform a query involving two or more very wide tables and you have checked or are calculating all or nearly all the fields in each table, Paradox may not be able to create the ANSWER table because you have checked too many fields. p One or more query rows do not contribute to the ANSWER This message usually appears where the rows in a multi-row query do not generate ANSWER tables with the same structure. Look at the Check operators to see that they match on each row. p Only numeric and date fields may be averaged pe Only numeric fields may be summed Aggregate operations using the Average operator can only be performed in Number, Short Number, Currency, and Date fields. In addition, the Sum oper-
ator also cannot be used in Date fields. p Operation cancelled This is not an error message, but rather an indication from Paradox for DOS that you pressed [Ctrl-Break] while a query was running to cancel it. 511
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
@
p> Processing Query...
This is not an error message, but rather an indication from Paradox for DOS that you pressed [F2] to process the current query on the Workspace.
& Ouantifier expression not allowed in a memo or blob (BLOB) You cannot perform an arithmetic calculation or operation in a memo or BLOB field. (Not in Paradox 3.X.)
& Overy appears to ask two unrelated questions You will see this message if Paradox cannot determine a consistent question from the query you have defined. This often happens on multi-row queries in which the questions being asked are different on each row.
pe Ouery has no checked fields If you don’t specify one of the Insert, Delete, ChangeTo, Find, or Calc operators, you have to check fields so that Paradox can generate an ANSWER table. > Ouery may take a long time to process This is not an error message, but rather an indication in Paradox for DOS that the query has crossed a particular threshold of complexity. It does not mean that the query actually will take a long time. p SET can only be used in the leftmost column The Set keyword defines a collection of records against which comparisons will be performed. Therefore, like the Insert, Delete, and Find operators, this keyword belongs in the leftmost column under the table name. p SET keyword expected When you perform a comparison against a group of records rather than individual fields, you must place the Set operator in the leftmost column. Paradox has determined that you are trying to perform this operation, but it does not find the keyword in your query definition.
512
BEeBEBee
eB
APPENDIX
A:
ERROR
HANDLING
: Can't link rows in ChangeTo and DELETE queries : Can't mix local and remote tables in a query : Can’t mix tables from two remote locations : Check mark can’t be used in INSERT or DELETE queries : FIND query is not supported : INSERT, DELETE, and ChangeTo can’t be combined in one query : INSERT of a table into itself is not supported : Multiple unlinked CALC expressions are not supported : Only single line ChangeTo queries are supported : Operator is not supported for Date type : Only single line INSERT queries are supported : Patterns are supported only for Alphanumeric columns These errors will only appear if you have installed the Paradox SQL Link add-in and you have attempted a query operation that is not supported by this tool. p Summary operator not allowed in a memo or binary (BLOB) The Average, Count, Sum, Min, and Max operators have no meaning in memo
or BLOB fields. (Not in Paradox 3.X.)
p Table was changed by another user - retrying operation This is not an error message, but rather an indication that the query is being restarted because one of the tables was modified. You can tell Paradox 4.0 and Paradox for Windows to disable this feature. For more information, see
Chapter 20. If you get this message on a single-user system, it usually indicates a damaged lock file. Exit Paradox, delete all *.LCK files in your data directories, then reload Paradox and try again. p There is no query statement on the Workspace to save You will get this message in Paradox for DOS if you select Scripts / QuerySave from the menu without a query image on the Workspace.
513
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
p Unused SET row
You have specified an extraneous Set definition that is not required or is not being used in your query. Try removing the Set keyword from the nongrouping table. p Use only Insert, Delete, Set or Find in leftmost column This message appears in Paradox for DOS if you place anything but these four keywords in the leftmost column of the query image. In Paradox 4.0, you can also specify Fast Insert and Fast Delete. This message will never appear in Paradox for Windows because it controls what you can place in the leftmost column with a special pop-up menu of options.
p Use only one Insert, Delete, Set or Find per line This message appears if you specify Insert Fast instead of Fast Insert in the leftmost column of a Paradox 4.0 query. p> Variable must be followed by an example element defined in a Set When you specify one of the Every, Only, No, or Exactly keywords, you have to follow it with an example element that links this operation to a set definition.
Paradox for Windows Query Error Constants Paradox for Windows uses constants to handle many fundamental operations because this allows the compiler to create efficient code. When you need to perform a test of some kind, you will often be testing a constant. There are over 1300 documented constants in Paradox for Windows. For example, information about events that occur is returned in an event object in which one element is the event ID describing what happened. To test for a specific event, such as the deletion of a record, you compare this ID against a predefined constant, as shown in the following example: if
eventInfo.id() = DataDeleteRecord then msgStop("Denied", "You cannot delete records return
endif
514
in this
context")
BEEBE
eEeEee
eB
APPENDIX
A:
ERROR
HANDLING
To handle errors alone, Paradox for Windows provides over 700 separate constants in various categories. ObjectPAL constants are documented in the following three ways: ¢ On-line. When you are editing a script, form method, or library method, select Language / Constants from the menu to see the ObjectPAL Constants dialog box. It is shown in Figure A-2. In the documentation. Appendix G of the ObjectPAL Language Reference lists all the constants except for those pertaining to errors. In a table. You can create a short script like the one shown in Listing A-1. This script uses the ObjectPAL procedure enumRTLConstants() to enumerate a list of constants to a table that you designate. If you are interested in the internals of Paradox, you should take a few moments to peruse this list. It offers many insights into how Paradox handles common operations.
Constants,
Figure A-2. The ObjectPAL Constants dialog box
915
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
8
@
@
@
Listing A-1. Writing constants to a Paradox table method
run(var
eventInfo
enumRTLConstants
Event)
(":priv:RTL.DB")
How Paradox uses query error constants
If you interactively create a badly formatted query and press [F8] to run the query or attempt to save it, Paradox will display an error dialog such as the one shown in Figure A-1 at the beginning of this appendix. Internally, Paradox goes through a parsing and analysis process to evaluate the query you have specified. If Paradox determines that it cannot process the query, the reason is converted to a query error constant, and that constant passed to Paradox’s error handler. Paradox translates this constant into one or more error messages and displays these messages in the dialog box. Triggering a query error
You will not normally see one of these errors with a running application, because they represent a query that simply will not work. Paradox includes these constants primarily for its own internal use. But you may see them during the design phase of your application. In this case they indicate a problem that has to be corrected. However, if you deliberately create a badly formatted query, you can force Paradox to create one of these errors and watch how Paradox uses the error constants and converts them into more detailed error messages. Listing A-2 shows a custom script that forces a query error. It attempts to per-
form a ChangeTo operation on the CHANGED table, which Paradox does not allow. (You may need to interactively perform a ChangeTo query in order to create a CHANGED table in your Private directory before you run this script.)
516
BEEBEEBHEEeEe eB
APPENDIX
A:
ERROR
HANDLING
Listing A-2. Generating a query error to demonstrate error codes and messages method var
run(var
gg x
Event)
query LongInt
Wl endvar qq
eventInfo
String
=
; define
Query
ANSWER:
query
:PRIV:ANSWER.DB
: PRIV: CHANGED. DB
State/Prov
AZ,
changeto
I
CA
|
EndQuery if
errorcode
X.view()
to fi read first ; display it
Y =
F read
errormessage
not
qq.executeQBE()
X = errorCode() errorMessage()
Y.view() errors are loaded ; one off the stack ,
while
F attempt
first ; display it
run
into a stack, and you have to pop the first before you can read subsequent entries
errorPop()
X = errorCode() X.view() Y
then
=
errorMessage()
Y.view()
i read next errorcode ; display it ; read next errormessage ; display it
endwhile endif endmethod
When you run this script, Paradox displays the following sequence of messages, as a result of the view() methods specified in the code:
1. -30459 This error code is defined by the constant peQryErr and indicates a general query error.
917
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
2. An error was triggered on the ‘executeQBE()’ method on an object of Ouery type
This is the text associated with the general error constant. 3. 11¥91
This error code is defined by the constant peQryChgToChg. 4. Cannot modify CHANGED table This is the actual error message associated with this error constant. In one circumstance you might see these error constants within an application. It
occurs if your program actually builds query strings under ObjectPAL control and then attempts to execute them. For example, if you use the generic query engine described in Chapter 18, it is possible to pass parameters to the custom method so that an invalid query is created. Under these circumstances, you cannot test every possible error that might occur. But you can implement error-checking code to trap situations in which the executeQBEString() method returns logical False, indicating that the query is invalid in some way.
A table of error constants Table A-1 lists all of the Paradox query error constants, their small integer value, and, where possible, an educated guess at the error’s meaning. The actual meaning of these constants is not documented by Borland, but many of them are obvious, especially if you are familiar with the Paradox for DOS error messages listed earlier in this appendix. Note that you may also receive other errors as a result of your queries. For example, if the CHANGED table in the Listing A-2 sample script does not yet exist, Paradox will display a code of 10024, which is translated into the constant peNoSuchTable and the following error message: Table
doesn't
exist
Table:
CHANGED.DB
File:
C:\PDOXWIN\PRIVATE\CHANGED. DB
Table A-1 lists a number of these constants as well.
518
BEBE
EHReEe
eee
APPENDIX
A:
ERROR
HANDLING
Table A-1. Table of Error Constants
Constant
Value
Possible Meaning
peAliasInUse
-30391
peAliasNotDefined peAllFieldsReadOnly
-30393 -31184
Specified alias is the Private directory of another user Specified alias was not recognized Cannot perform an Insert, Delete, or ChangeTo query
peBadAlias
-31731
Specified alias is not formatted correctly
peBadFileFormat
-31722
Specified file format for the result table is invalid
peBadTable peCannotLock
-31700 -31740
Specified table name is not formatted correctly Unable to obtain the necessary table locks
peCannotMakeQuery
_-31666
Cannot build the query object in memory
peCannotOpenTable peDetailRecordsExist
-31206 9734
Cannot open a table referenced in the query Cannot delete a master record because of a defined RI relationship
peDirLocked
10246
Directory is locked by another user
peDirNoAccess
9219
The user has no access rights in this directory
peFileCorrupt |
8962
A specified table is physically damaged
peFileLocked
10245
peFileNoAccess
9221
pelnvalidQuery
-30439
peNoSuchTable
10024
pePathNonExistant (sic)
-30392
peQBEbadFileName peQryAmbOutPr
-30416 11780
A specified table is locked by another user The user has no access rights to this file The query definition could not be interpreted for
some reason Table referenced in the query could not be found Invalid path specified Invalid name for the ANSWER table
peQryAmbSymAs
11781
peQryAmbigJoAsy
11777
Ambiguous asymmetric join
peQryAmbigJoSym
11778
Ambiguous symmetric join
peQryAmbigOutEx
12779
peQryAseToPer
11782
peQryAveNumDa
11783
The Average operator is only available in number and date fields
peQryBadExpr1
11784
Invalid calculation
peQryBadFieldOr
11785
Invalid use of OR in a field
peQryBadFormat
11885
Invalid format for a date
919
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
Table A-1. Continued
Constant
Value
Possible Meaning
peQryBadVName
11786
Invalid tilde variable in query
peQryBitmapErr
11787
Invalid operation on a graphic field
peQryBlobErr
11896
Invalid operation on a BLOB field
peQryBlobTerm
11895
peQryBuffTooSmall
11888
peQryCalcBadR
11788
peQryCalcType
11789
Invalid calculation for the field type
peQryCancExcept
11880
Query in progress was canceled
peQryChNamBig
11797
Field name in As expression is too long
peQryChgTolti
11790
ChangeTo can be used in only one table at a time
peQryChgToChg
11791
Cannot perform a ChangeTo on the CHANGED
Not enough table buffer to perform the query
table peQryChgToExp
11792
Invalid expression in a ChangeTo
peQryChgTolns
11793
Cannot perform a ChangeTo on the INSERTED
peQryChgToNew
11794
peQryChgToVal
11795
ChangeTo violates the field’s ValChecks
peQryChkmrkFi peQryChunkErr
11796 11798
Check mark cannot be used in a Find query
peQryColum255
11799
Too many fields?
peQryConAftAs
11800
peQryDBExcept
11881
peQryDelitime
11801
Delete can be used in only one table at a time
peQryDelAmbig
11802
Ambiguous Delete operation
peQryDelFrDel
11803
Cannot perform a Delete on the DELETED table
peQryEgFieldTyp peQryEmpty
11804 11886
Example element is used in fields of a different type No operation defined between the Query ... EndQuery
peQryExaminOr
11805
peQryExprTyps
11806
Expression type mismatch
peQryExtraCom
11807
Extra comma
peQryExtraOro
11808
Extra Or operator
peQryExtraQro
11809
Extra query row
peQryFatalExcept
11883
table
520
BEB
SEHEeEBeee
eB
APPENDIX
A:
ERROR
HANDLING
Table A-1. Continued
Constant
Value
Possible Meaning
peQryFind1Att
11810
Find queries ??? (not supported in Paradox for
peQryFindAnsT
11811
Windows)
Find queries ??? (not supported in Paradox for Windows)
peQryGrpNoSet
11812
No grouping defined for Set operation
peQryGrpStRow
11813
A set cannot be included in its own grouping
peQryldfPerli
11815
peQryldfinlco
11814
peQryInAnExpr
11816
peQryIns1Time
is iZ
Insert can be used in only one table at a time
peQryInsAmbig
11818
Ambiguous Insert operation
peQrylInsDelCh
11819
Cannot Insert in the DELETED or CHANGED table
peQryInsExprR
11820
Invalid expression to be inserted
Invalid And expression
peQrylnsToIns
11821
Cannot perform an Insert on the INSERTED table
peQrylsArray
11822
Query variable is an array and not a single variable
peQryLabelErr
11823
peQryLinkCalc
11824
peQryLngvName
11825
Variable name is too long
peQryLongExpr
11878
Expression is too long
peQryLongQury
11826
Query is too long
peQryMemExcept
11882
peQryMemVProc
11827
peQryMisSrtQu
11830
Missing sort definition
peQryMisngCom
11828
Missing comma
peQryMisngRpa
11829
Missing right parenthesis
peQryNIY
11884
peQryNamTwice
11831
peQryNoAnswer
11856
peQryNoChkmar
11832
Query has no checked fields
peQryNoDefOcc
11833
Example element has no defining occurrence
peQryNoGroups
11834
No grouping defined for Set operation
peQryNoPatter
11836
Pattern wildcards cannot be used for this operation
peQryNoQryToPrep
11887
No query to prepare
Field name appears twice in query definition
521
PARADOX
QUERIES:
A DEVELOPER’S
REFERENCE
@& @
@
Table A-1. Continued
Constant
Value
Possible Meaning
peQryNoSuchDa
11837
No such date
peQryNoValue
11838
No value to Insert or ChangeTo
peQry Nonsense
11835
peQryNotHandle
11890
Object name does not define a query
peQryNotParse
11889
Unable to parse query definition
peQryNotPrep
11857
peQryOnlyCons
11839
peQryOnlySetR
11840
peQryOutSens1
11841
peQryOutTwicl
11842
Example elements with outer join can appear only twice in a query
peQryPaRowCnt
11843
peQryPersePar
11844
peQryProcPlsw
11845
peQryPwInsrts
11846
Insufficient password rights to insert records
peQryPwModrts
11847
Insufficient password rights to modify records Field not found in the referenced table
peQryQbeFieldFound
11848
peQryQbeNoFence
11849
ee! peQryQbeNoHeaderT
pee 11851
peQryQbeNoTab
11852
peQryQbeNumCols
11853
Too many fields in QBE definition
peQryQbeOpentab
11854
Error opening table referenced in query
peQryQbeTwice
11855
peQryQualInDel
11858
peQryQuaInIns
11859
peQryQxFieldCount
11892
peQryQxFieldSymNotFound peQryQxTableSymNotFound
11893 11894
peQryRagInIns
11860
peQryRagInSet
11861
peQryRefresh
11879
peQryRegister
11877
922
Invalid QBE definition
Query index errors (?)
@
BEEBE
eEeEee
Table A-1. Continued Constant
Value
peQryRestartQry
11897
peQryRowUsErr
11862
peQrySQLg_Alpho
11902
peQrySQLg_Avera
11915
peQrySQLg _BadPt
11917
peQrySQLg_Chini
11907
peQrySQLg_Cntln
11906
peQrySQLg_Count
11914
peQrySQLg_ DateA
11916
peQrySQLg_Dateo
11903
peQrySQLg_FndSu
11920
peQrySQLg_IDcco
11922
peQrySQLg_IfDcs peQrySQLg_Liken peQrySQLg_MDist peQrySQLg_NoAri
11921
peQrySQLg_NoQuery
11925
peQrySQLg_ OTJvr
11910
peQrySQLg_Onlyc
11905
|B
APPENDIX
A:
ERROR
HANDLING
Possible Meaning
11901 11899 11900
These error codes will support Paradox’s SQL connectivity when it is activated in a future version
peQrySQLg_Onlyi
11923
peQrySQLg_Patrn
11919
peQrySQLg_ Quant
11912
peQrySQLg_RegSo
11913
peQrySQLg_RelPa
11918
peQrySQLg_Relop
11904
peQrySQLg_SQLDialect
11924
peQrySQL g_SlfIn
11909
peQrySQLg_StRow
iaIG
peQrySQLg_ Union
11908
peQrySetExpec
11863
Set operator expected in the leftmost column
peQrySetVAmb1
11864
Ambiguous Set operation
peQrySetVBad1
11865
peQrySetVDef1
11866
923
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
@
@
Table A-1. Continued Constant
Value
Possible Meaning
peQrySumNumbe
11867
Sum can only be used in number fields
peQrySyntErr
11891
Syntax Error
peQryTablelsWP3
11868
Table is write-protected
peQryTokenNot
11869
peQryTwoOutr1l
11870
Example element with inclusion operator can only appear twice in an outer join query
peQry TypeMIsM
11871
Type mismatch (perhaps a date operation in a Number field)
peQryUnknownAnsType
11898
The TYPE: expression currently supports Paradox
and dBASE only peQryUnrelQ1
11872
peQryUnusedSt
11873
Unused Set row
peQryUselnsDe
11874
Insert or Delete operator can only be used in the leftmost column
peQryUseOfChg peQryVarMustF peQueryERR
11875 11876 -30459
peReadOnlyDB
10501
peReadOnlyDir
10500
peTableProtected
-30395
peTableReadOnly
10763
924
Invalid use of the ChangeTo operator
General query error Insert, Delete, and ChangeTo cannot formed in a read-only database Insert, Delete, and ChangeTo cannot formed in a read-only directory Insert, Delete, and ChangeTo cannot formed in a write-protected table Insert, Delete, and ChangeTo cannot formed in a read-only table
be perbe per-
be perbe per-
ae
APPENDIX
B
gal ZL, o
Additional Resources
~
a
Periodicals that Often Print Articles about Queries The following periodicals specialize in Paradox, and frequently publish articles on queries. The Paradox Informant Informant Communications Group 10519 E. Stockton Blvd., Suite 142 Elk Grove, CA 95624-9743
(916) 686-6610 Subscription rate: $44.95 per year (published monthly) The Paradox Developer’s Journal The Paradox User’s Journal The Cobb Group 9420 Bunsen Parkway, Suite 300 Louisville, KY 40220
(800) 223-8720 Subscription rate: $79.00 each per year (both published monthly) Instant Scripts Southern California Paradox User’s Group (LAPALS) 4009 Montaigne Way Palos Verdes, CA 90276
(310) 541-3461 Individual membership in LAPALS: $35.00 per year Subscription rate: $25.00 per year (published monthly) 929
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@
@
@
@
Electronic Support A number of electronic communications networks maintain forums for Paradox support. By far the largest are the BORLAND forums on CompuServe, which are staffed by an energetic crew of Borland tech support engineers and volunteers (including the author of this book, who can be reached at 70007,4644). The Paradox forums
generate over 600 messages per day on various topics, and are a wonderful fountain of information and discussion. To reach the main Borland section, type GO BORLAND. Paradox for DOS is supported in its own forum, which you can reach by typing GO PDOXDOS. This forum is organized into the sections and libraries shown in Table B-1. Table B-1. Forum Sections and Libraries in PDOXDOS Section
Library New Users/Misc
Pdx Vers/1.0-3.5
Install/CFG/Hrd Ware Forms/Handling Data Queries/Reports
App Workshop PAL—Learning PAL—Using KR LK WO FP HH HN ON ©
Ul/Wait/Dialogs Networks/MultiUser SQL-Link
Addins/Training eS pe SOF oS ex a
User’s Groups
Paradox for Windows also has its own forum, which you can reach by typing GO PDOXWIN. This forum is organized into the sections and libraries shown in Table B-2.
526
S88
eB
eB
APPENDIX
B:
ADDITIONAL
RESOURCES
Table B-2. Forum Sections and Libraries in PDOXWIN
Section
Library
1
New Users/Misc
2 3 4 5 6 7, 8
Install/CFG/HrdWare Tables/Table View Forms Reports Queries Utilities/Browser Multi-User/Interop
9
Learning ObjectPAL
10
ObjectPAL Environ.
dd
Using ObjectPAL
12
SQL-Link (future)
13
Addins/Training
14
User Groups
NOTE: Borland sometimes realigns these sections as the supported products change, and as customer needs evolve. You should check the section names before posting messages or accessing the libraries.
If you have query-related questions, or any other Paradox-related questions for that matter, you should leave a public message in the appropriate section and you will be sure to receive a number of replies. Many valuable tools are also available in each section’s library, most of them free except for the download time. In addition to CompuServe, Borland also maintains a presence on GEnie and BIX. To visit the Borland section on BIX, type JOIN BORLAND at any system prompt. To visit the Borland RoundTable on GEnie, type BORLAND at any system prompt. Borland tech support maintains a download BBS with technical bulletins, product news, and utilities. Tech support is not available via this BBS. Dial (408) 439-9096, using any speed up to 9600 baud (and settings of 8 data bits, No Parity, 1 Stop Bit). Finally, the Paradox Informant (described above) maintains a popular three-line bulletin board with utilities, conferences, and news. To reach PIBB, dial (916) 6864740. Settings are 8 data bits, No Parity, 1 Stop Bit.
927
cooqenaya
/
ea
=
=
= ae
7
grewak
—
ie
eee Li tp tid aatigd
; -
pve
x
o
;
eng 13D
Pirae4 sn byi eyiliy al owes ee
=
poregeaT
©
fous
=e
le Sf) Wau
“Ol
nye
Ni wx pe?
S bavi Se
R08
es
oolewll
TPE let
Peel he
WET
Ce
ee
iii
Ove, yd
hyd
ee”. i
alting soit
40) heey
er bed gon
alee
cual » ine He4 6
Li
“eo aoete Gil?
pene
oS
SE seers
ee
eyo
of)4 kU
o.-0
serin cre % Ue
GN
Pett
FSegp avg
irre ds
ait
veel ht a
. bun
le.
online
ty vy
CTT
noel Sony teat
vi a
v4
ay eee Oesmth
rsa cia? oearelge CONVO mg Ea talsRell ge fytiont® nrsmive
Ue
viteta (TAR
we trai a
Ul
Tie)
Of Gilad | Biwi allot Tale, arty
eeUws,.)
at
ts apee
~
7H4
hung
tbe
mer ered
mab
34 re airtiji bouall ould wih ae oF
bjsdew vee
S eats a eet
Vc
h Sey Oe F igoty ESE
Jina! Soil Re =i pata, fhrit tape!
iT OF yew aii (aly engage H
eons
verve? avis’
Wight ping
geodUn staleetnesin
wi whlweb &
oe 2
Index \
A absolute value 359
Accept command 67-68, 207 access mechanisms 469
access rights 475 active database 141 addAlias() method 174 ad hoc queries 6, 76, 79, 151, 168, 181
Answer Table / Sort... 193 application development 91 applications developing 171-180 multi-user 4 Application Workshop 247, 265 arithmatic Calc operation 479 array 496
capability 4
As operator 480, 502-503
Paradox for Windows 311
AtFirst() 85
system 136, 242, 303, 323
AtLast() 85
techniques 241-283, 246, 285-321
attach() 133
ageregate operations 511 alias 116, 173, 176, 447-448, 481
[Alt-F2] 475 [Alt-F3] 33-34 [Alt-F8] 16, 25 alternate syntaxes 131-132 analog table 241, 295 ANSWER: 116 ANSWER table 26, 77, 86, 89, 104, 129, 480, 507, $11 properties 115 removing 27
supressing with SQL NoFetch 495 viewing 122 Answer Table / Options... 192
ATTRIB 449 AUTOEXEC.BAT 449 AUTOLIB system variable 93-94
B
;
backslash character 102 backslash notation 46, 424
binary field 506 BIX 526
blank as zero 89, 178 lines 18, 38 query rows 23 tilde variables 149 blankAsZero() method 178
PARADOX
QUERIES:
A
DEVELOPER’S
Blank operator 151, 479, S507 BLOB 512
BLOB field 506 Borland Interbase Server 470 braces 35 branching commands 91 breakApart() method 421-422
bubbling 158 bug, parsing 143 BuildQBE_1Row() 412-413, 415 BuildQBE_MultiRow() 412, 419, 423 bulk change, making 207
REFERENCE
@& @
@
@&
CheckBoxes 310 CheckDescending 19, 114, 382, 477 CheckMarkStatus() function 83-84, 382 CheckPlus 19, 114, 476, 498 CheckSyntax 120, 143 child directories 100 clean read 453-454, 462 ClearAll 25 Clearlmage 25 client server model 428, 465, 467 close() method 123 Codd, Edgar 468
code, defining queries 127
C C language 46 Calc 502-503, 512-513 Calc queries 435, 442 calculated field 118, 358 canvas 75, 263, 302
catalog 468 CD ROM drive 442 CHANGED table 104, 503-504 ChangeTo 89, 197, 266, 355, 474,
concatenated string 137 concurrent access 427, 431 Constants 121, 463, 514
502-516 multi-user 445
containership 158
operator 482 queries 27591 90} 2079221 7359;
Convert() 481
435-436, 442, 473 characters, formatting 66 check 19, 114, 502, 508, 513 all fields 476 operators 19, 47, 114, 365, 373,
382, 412, 484, 510-511 queries 220-221, 435, 442, 445, 476 mark speedbar icon 120 930
CoEdit mode 431 command line switches 86 commas 65 comment 26 commit and rollback 497 commit operation 498 CompuServe 353, 526
control elements 302
CopyChecks 373, 382 CopyToArray 496 correlated subqueries 490 COUNTEXP 326 COUNTRY table 326 crosstabulation 183, 187-188, 192, 194 when not to use 196 [Ctrl-Break] 81 [Ctrl-F] 77
INDEX
[Ctrl-Y] 24 Currency field 511 cursor keys 35, 77 CUSTOM 449 Custom Configuration Program 86, 89,
105, 455 custom dialog box 157 menu 67, 165
methods 109, 171, 412-413, 415
D data
consolidating 3 control language (DCL) 470 definition language (DDL) 469 formatting 3 integrity 427, 466-467 manipulation language (DML) 470
sublanguage 468 database
date, continued operations 358 range 60, 295
dBASE 115-116 deadly embrace 432, 439 Debug 78 Debugger 81 decomposing queries 460
Dec Rdb/VMS 470 Delete 27, 89, 246, 266, 415, 418, 474, 503-505, 508, 512-514 keyword 491 operator 483 queries 362, 435-436, 442, 473, 483 DELETED table 104, 125, 220, 504 Deliver command 173 denormalizing 6, 183-205 data 190 Design Layout dialog box 193 DesktopPlus for Paradox 373 desktop properties 451
active 141
DesqView 448-449
intelligence 465
developing applications 171-180
normalization 181, 366
dialog boxes 158, 164, 208, 302 custom 157 Design Layout 193 Print File 231, 234 Sort Answer 118
object type 140-141 schema 199 server 467-468, 470
variables 142 views 490 Data Entry Toolkit 262
Data Model 229, 231-232, 238, 315, 318-319 date 494 fields 37, 511 functions 481
Sort Table 117 Types and Methods 131 dictionary 468 direct field assignment 50, 64 directories child 100 home 100-101 931
PARADOX
QUERIES:
A
DEVELOPER’S
directories, continued managing 173 Private 104-105, 173, 176, 238, 446-448, 510, 516
REFERENCE
end-of-string delimiter 102 EndProc 91 EndQuery 25, 113 enumAliasNames() 176
Directory() function 106
error
enumRTLConstants() 515
Directory Lock 443
checking 96, 211
dirty read 232, 455, 462
constants
disk space 510
detection 77 handler, temporary 282 handling 501-524 message 242, 501
dmAddTable() 315 dmRemoveTable() 315 Do_It! command 25, 81, 96, 119,
514-516, 518
procedure 81, 243, 279
129, 489 DOS 5 448 DOS Path command 94 dot notation 130
syntax 138 system 138 triggering 516 ErrorCode() 243, 456
double quotes 37
ErrorInfo 475
DownImage 26 dummy table 295 dynamic array 329, 413, 415 help 279 link library (DLL) 146 dynarray 330, 357, 368, 417
errorMessage() 138
E Echo 81 Fast 82 Normal 262 Slow 82 electronic search 323 support 526
532
@
embedded variables 31, 172
working 173 directory addressing 100
Distinct 476
@& @
ErrorProc 243 event model 320 oriented 108 trapping 279 Example command 49 example elements 19, 46-47, 77, 114, 354, 358, 373, 383, 412, 502-509 entering 46 matching 383 Exclusive Lock 436-437, 442-443 Write access 431-437, 442
Write Lock 442
@
BEERBBHEHHBEHEHEHEHBEHBEBHEHEHRHEHBBBBBB
Execute 386, 448
INDEX
fields, continued
executeQBE() 129, 131
independent 365
executeQBEFile() 121, 124
memo 263, 506
executeQBEString() 138, 140, 144, 415, 518
moving between 39 Number 37, 511
partial 355
F
querying 68
[F2] 17 [F3] 26 [F4] 26 [F519 333 [F6] 19 [F8] 25 family locks 436 Fast keyword 474, 503 field properties 115 rotations 88 sequence 116
Specifier Notation 40, 507 specifiers 103 title properties 115 types, advanced 214 values breaking apart 421 changing 215 Field() 85 fields 114 binary 506 BLOB 506 calculated 118, 228, 358 check all 52, 476 Currency 511 Dates 7,511 Foreign key 212
semaphore 220 separating 19 Short Number 511
FieldType() 85 FieldView 77 file allocation table 104 Browser 180 formats 469
sample 8 server model 427, 465-466
temporary 105 fileBrowser() 180 FileBrowserInfo 180 File / Insert File 29 FileRead 263 FileWrite 264 Find 266, 504-505, 512-514 operator 507 queries 386, 435, 473
FindNext 373, 386
Foreign key 212-213 For loop 196 Format() 359
formatting characters 66 formfeed 102 forms 157-159, 171, 192 FOR UPDATE 476
933
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
@& @
free-form text 294 frequency distribution 367 Full Lock 430-431, 435-436, 442-443 function key presses 36
IIF() function 495
G procedure 74, 81, 221, 285
ImageToPAL 374, 397, 410 ImageType() function 82 inclusion operator 186, 502-506 independent fields 365
queries 98, 155
indexes 469, 490
generic
applications 412
GEnie 526 GetChar() 263 getQueryRestartOptions() 462
grid 115 GroupBy 19, 114, 510 group locks 436
H handle 130-131, 146
heading 115 heuristic query optimizer 485 hierarchy of objects 158 holding tables 225
HOLDLOCK 476 home directories 100-101
|
IBI EDA/SQL 470 IBM DB2 471 DDCS 471
OS/2 Extended Edition 471 icons, margin 8 IDAPI 470
If, EndIf commands 67 534
@
G@
ImageOrder 21, 51, 89, 375, 506 queries 86, 88
image qualifier 507 ImageRights 218-219
secondary 58 InfoLib command 94 initialization routine 106 Insert 89, 246, 266, 415, 474, 503-504, 508-509, 512-514
expression 502 keyword 508 operation 507 operator 482 queries 435-436, 442-443,
473-474, 482, 506 INSERTED table 104, 504 INSTANT.SC 34 Instant Script 33 Int() function 359 integrity 468 Interbase 473 intermediate results 104 tables 445-447 interpreting saved queries 21 invalid expression 508 query 509 selection criterion 80
INDEX
invisible user 13, 35, 107
local area network 465
Is Null 479
Locate 85, 444
IsAssigned() 178, 357 IsBlankZero() 89, 178 isEmpty() 125 iterative searching 323
lock 432 failure 441 file 513
J
locking and multi-user access 429-451 locks 427 compatibility 437
joining tables 484 joins supported by SQL 474 justification 410
Directory 443 Exclusive 437, 442
K
group 436
KeyPress 263 keys cursor 35 Foreign 212-213 function 36 special 36-37 unavailable by default 77 keywords 47, 108, 120, 329
placing 431, 437
searching 323
L Language menu 120
LEVEL table 328 level variable 324 Library 412 contents 94
custom methods 109 lightning bolt speedbar icon 120 Like operator 367, 473, 504, 509
linked tables 211 LinkExamples 373
family 436 Full 430-431
query requirements 442 record 436, 442 requirements for queries 435
simulating retry 434 table 429, 431, 436, 442 when not to place 442 Write 430-431, 437 logical relationships 469 lookup table 294 looping through query images 29 loops 91 Lower() 359
LVL_CRIT 328
M main mode 510-511 margin icons 8 Match () function 66, 357 memo field 263, 473, 506, 512 menuAction() 171
939
PARADOX
QUERIES:
menus
A
DEVELOPER’S
REFERENCE
multi-user, continued
custom 165
Change To 445
Language 120
conflicts 220 considerations 5 environment 104
popup 165
Script 120 selections 35, 103 message() 124
message window 80, 242 method 121, 158
reserved word 143
@& @
locking 429-451 model 427 queries 6 viewing 430 multiple conditions 210
MicroDecision Ware 471
Microsoft SQL Server 471 missing values 361 mission-critical systems 487
Mod() 364 MONTHS table 185 MoveTo command 40, 49
options 41-43 Record 85 msgInfo() 121 msgStop() 124 multi-line queries 83, 478 strings 139 multi-row queries 419 multi-step operation 432, 439 multi-table
insert/delete 490 queries 95, 325 query image 71 multi-user access 427, 429-451
applications 4
936
N network traffic 466 user name 429 querying 427 networks local area 465
simulating in Paradox for DOS 448 for Windows 450 new line 137, 411
NImageRecords() 85 NoFetch 491, 496 normalization 6, 356, 366 normalizing 183-205 data 198 databases 366 Novell NetWare SQL 471 NRows() 85
Null values 494 Number field 37, 511 NUPDATE program 448-449
@
@
INDEX
0
Oracle 476
object methods 108 properties 107, 318 types 107
database 140-141 string 140
Server 471, 485
Order By 476, 478 Order By ... Desc 477 order of tables 186
Order / Range 225 OS/2 448-449
object-based 107, 134
outer join 186, 473, 490
programming 128 programming language 146 ObjectPAL 107-108, 128, 146,
Pp
158-159, 411-412 building programs 108 event-oriented 108 printing reports 230
Runtime Library 128 variables 139 objects delivering 173 methods 128 properties 128 open() 131, 230
Open Lock 437, 442 Open method 160 OpenScript icon 119 operations, Subtract 443 operators check 19, 47 inclusion 186 query 19, 72, 152 range 72
Or expression 503 conditions 30, 210, 295
PAL Add command 474 Canvas 295
code 48 commands 6, 11, 25, 66 Debugger 78 Echo command 81 functions 228, 358 incorporating queries 11
scripts 38 variable 228, 492 Paradox 3.X for DOS 7
avoiding query restart 456 query restart 453 4.0 7, 374 performance 455 query restart 454 and SQL 470 error messages 502 for DOS 58, 112 error messages S()1
query tools 373-410 simulating a network 448
operator 478, 506, 508
937
PARADOX
QUERIES:
A
DEVELOPER’S
error messages S01
performance benefits 93 period character 58 phone numbers 355 Picture ValChecks 295-296
managing tables 447 programming queries 107 query lock requirements 442
popup menu 165 posting 459 Prevent
Paradox, continued
for Windows 7, 58, 190
ad hoc queries 311
query restart 462 query tools 411-425 reports 228
simulating a network 450 table locks 436 Informant 526 model 12
@& @
@
Full Lock 430-431, 436, 453, 459 Write Lock 430-431, 435-436, 442, 445, 455
Primary key 213, 357 print() 231
pausing 67
printer, changing 234 Print File dialog box 231, 234 :PRIV: 173, 176
performance 127
Private directory 104-106, 173, 176,
Runtime 77, 262-263, 448
saving queries 17 security 127
SQL Link 465-486 programming with 487-499 PARADOX.CEG 448 PARADOX.NET 449 PARADOX.SOM 448 parsing bug 143 partial fields 355 passing by reference 416 password rights 508 passwords 468 PBE notation 397 PDOXUSRS.NET 449 peQryChgToChg 518 peQryErr 517 percent (%) operator 480
performance 51, 127, 422, 461, 471, 474
538
REFERENCE
446-448, 510, 516 defining 105 on single-user systems 105 PrivDir() function 106
PrivTables 447 PrivIables command 177, 446 Proc 91 procedures 91, 108, 134, 121
Do_It 96 executing 92 names 397 notation 134, 136, 140 reading 93 size 94 swapping 94
writing to libraries 93 processing queries 17, 25 stepwise 6
@&
BHEEBEBHBEEHEHEHEEBEHEHEHEHBREHREHBEBEHRBEeBeeB
program-controlled queries 412 prompting 155, 294
proper form 359 properties 160
Properties / Desktop... 451 prototyping 172 pushButton method 165 pushButton() 123, 208
INDEX
queries, continued defining within code 127 Delete 473 editing control 181 embedding variable information 31 errors 6, 77-79, 242
Find 386, 473 for tildes 62 generic 98, 155
Q QBE file 112, 135, 239, 468 QBEString 414 QRYCRITS table 327
QRYKEYWD table 327
quantifier expression 512 queries
access rights 475 ad hoc 5-6, 76, 79, 151, 168, 181 capability 4 in Paradox for Windows 311 techniques 241-283, 246 affected by PAL commands and functions 6 attaching to a Form Button 123 badly formatted 211 blank lines 18 blank rows 23 Ghangello.27,,190,217;221, 359; 473 changing 55 Check 220-221, 476 combining in sequences 27 comparing sizes 51 date range 60 decomposing 460
heuristic optimizer 485 ImageOrder 86, 88 incorporating in PAL applications 11
Insert 443, 473, 482 invalid 81 linking sometime tables 70 lock requirements 435 modifying 5 multi-line 83 multi-row 419 multi-table 95, 325
multi-user 6 network 429 object type 130-131 optimizing 468 performance, comparing 51
problems tilde variables 57 underscores and periods 58 processing 17, 25, 51, 474
program-controlled 412 programming
in Paradox for Windows 107 variables 145 prototyping 172
reading 134 939
PARADOX
QUERIES:
A
DEVELOPER’S
queries, continued recursive 181
related commands / functions 75-90 related PAL commands 25 running 119, 129 saved 21, 388 saving 15, 111 to PAL script 488
with Paradox 17
@& @
operations, table-based 412 operators 19, 72, 114, 152
entering 45
stepwise 4
restart 427, 436, 453-464, 474
using direct assignments 47
using PAL code 33-54 variable 5, 55, 128-129
viewing control 181 query SOS definition 127 engine 411 error constants 514-516 errors, triggering 516
image order 376 images 424 looping through 29 manipulating 280 multi-table 71 problems 30 removing 27 rotation 478 saved 6 using the Wait command 76
@
object 129, 132, 147, 153, 171-172
Set 473, 510
storing and running 171 supported by SQL 473 TableOrder 86 Update 482
@
query, continued keywords 18, 113 lock requirements 442 manager 247 methods, comparing 48
optimization 485 order 86-87, 376
sequencing 123-124
540
REFERENCE
avoiding in Paradox 3.X 456 constants 463 minimizing 461
Paradox Paradox Paradox results 115 result tables,
3.X 453 4.0 454 for Windows 462 renaming 447
Script Manager 17, 263, 388
scripts, combining 53 statement 126 string 136, 138, 144-145, 167,
172, 235, 413, 415 systems ad hoc 242, 303, 323 stepwise 181, 323-351 techniques 427 ad hoc 285-321 networks 443 tools 87, 181, 374 Paradox for DOS 373-410 Paradox for Windows 411-425 Write Lock 454-455
INDEX
Query-By-Dialog 242-243, 302, 320 Query-By-Form 242-243, 296, 302, 3125-320 Query-By-Image 241, 243-263 Query-By-Menu 241, 243, 285, 296,
303 Query-By-Table 79, 241, 243, 265 QueryDefault 463 Query ... End Query 127, 496 Query.execute...() 424
querying
for matching records 186 for ranges 72 for reports 225 on a network 427 system tables 490 sometime conditions 69 varying fields 68 QueryLock 463 QueryNoLock 463 QueryOrder 21, 373, 375 QueryOrder() function 86 query-related commands, performing 35 error message 501 QueryRestart 463 QuerySave 15, 112, 263, 377, 513
QueryString 411 quotation marks 411
R RadioButtons 310 RadioButtons... 159 Rand() 360 random 364
random records 360 range table 358 of dates 185 operators 72, 504 querying for 72 Read 29 reading a query 134 ReadLib command 93 Read Lock 437 read-only subdirectory 442 RecNo() 85
record number 357 next matching 386 records deleting 3 editing a subset 218 extracting a subset 3 locks 436, 442
random 360 selecting subsets 207 working with 207-224 recovery issues 467 recursive programming 324 queries 181
referencing 102 in saved query images 103
with field specifiers 103 referential integrity 3 relational 354 algebra 468 calculus 468 relative 100 541
PARADOX
QUERIES:
A
DEVELOPER’S
REFERENCE
REP_HOLD 328
S
repeating groups 202
safety-net tables 504 sample files 8
replica table 472 ReportInfo record 232 ReportOpenInfo 233 ReportPrintInfo 233, 235-236 report restart options 232 reports 6, 181 copying 225
Paradox for Windows 228 printing with ObjectPAL 230 querying 225
variables 239 reserved words 64, 66
handling 64 method 143 Reset command 446 resources 525 retry period 434, 440 RetryPeriod() function 434, 440 RETVAL 208, 432, 491, 496 right quote 509 rights 508 rollback operation 498 rolling months 195 Round() function 359 RowNo() 85
Run 120 run error 243 run() method 119 RunQuery 119 Runtime 448
542
@& @
@
saved queries, interpreting 21
saved query image 48, 374, 397 embedding variables 56 referencing 103 storing 52 saving queries 15 Scan 364 loop 358, 444 script 119 definition 12 menu 120 ScriptManager 373 scripts cleaning up 38 saved keystrokes 38 Scripts / Editor 18, 25 Scripts / Play 16 Scripts / QuerySave 16 SDir() 101 Secondary indexes 58, 166, 207, 221, 225, 370, 461, 474, 510 security 127, 468, 490 Select 72 Distinct operation 476 selection criteria 4, 19, 47, 64, 114, 2135295323, DATE customizing 330
invalid 80 variable 150 self join 484 semaphore field 220 semaphores 456-459
@
INDEX
sequence of fields 115 sequencing queries 123-124
server-independent format 488 Session type 438, 440 set 415, 503-504, 508, 512, 514 queries 435, 442, 473, 507, 510
operation 502 setAlias() method 176
SetLocks... 437 SetPrivDir command 106 SetQueryOrder 86-87, 506 setQueryRestartOptions() 462, 464
SetRecordPosition 85 SetRestartCount 455-456 SetRetryPeriod command 434 setRetryPeriod() 440
SetSwap 95-96 SHARE 449
Short Number 511 ShowDialog 302 ShowMenu 67-68, 207, 213 ShowPlay 20, 43, 82 ShowPopup 67, 213 ShowPullDown 67 ShowSQL 487 single quote 66 single-user systems, private directories 105 SINGLE.CFG 448 SINGLE.SOM 448 Skip 85
snapshot 453, 474 sometime conditions 69
tables, linking to queries 70
sort Answer dialog box 118 order 115, 117
Table dialog box 117 SORT: expression 118 Soundex code 368, 370
searching 367-368 source code 375 special characters 46 keys 36-37 speedbar 120 SpeedUp 510 spreadsheet 183, 187 SQL 468 and Paradox 470 commands, sending to database server 490
data types 473 displaying 487 Link 428, 470, 472-473, 480, 496 add-in 6 NoFetch 495 Server 476, 481
SQLAutoCommit Yes 498 SQLCommit 498 SQL ... EndSQL 490-493, 496 SQLErrorCode() 475 SQLFetch 496-497 SQLRelease 496 SQLRollBack 498 SQLSave 492 SQLSetup program 473 SQLStartTrans 498 543
PARADOX
QUERIES:
A
DEVELOPER’S
SQLVal() 493-494 statistical applications 359-360 status window 279, 327, 329 stepwise
demo script 332 processing 6 queries 4 query system 181, 323-351 stored procedures 490 string concatenation 72
string object type 140 strings
concatenated 137 multi-line 139
query 136, 138, 144-145, 167, 235
REFERENCE
system error 138
T table object types 438 family 99 locks 427, 429, 431
Paradox for DOS 430 name 19, 114
string 103 type 115-116 Table() 85
table-based query operations 412 tableframe 314 TableOrder 375, 506
StrVal() 68
queries 86 tables 104
544
@
syntaxes, alternate 131-132
structured query language 468
subdirectory 99, 116 path 100 read-only 442 subsets of records, extracting 3 selecting 207 Subtract operations 443 Summary operator 474, 513 Calc operation 479 expressions 509 Sum operator 511 SurfaceQueries 376, 510 survey results 365 Switch-Case 364 Switch / Case / EndSwitch 67 Sybase SQL Server 471 syntax error 138
@
analog 241, 295 ANSWER 104, 480, 495 CHANGED 104 copying 436
COUNTRY 326 creating 436
DELETED 104, 220, 504 dummy 295 file allocation 104 filling 361 holding 225 INSERTED 104, 504 intermediate 445, 447 joining 484 linked 211 linking to queries 70 locks 436, 442
@
@
BEBSBSEBEHEHEHEHEHEHBEHEHREHBEHBHBBBHRe Bs
tables, continued
lookup 212, 294 managing 447 MONTHS 185 querying 490 Range 358 record 429 referencing 102
testing commands 91 text
entering 44
handling 37 tilde
renaming a query result 447
blank 149 character 492, 494 field and table names 154 problems 57
replica 472
querying for 62
restructuring 436
using 59 variables 5, 56-58, 62, 64,
safety-net 504 splitting 364 structure 39 temporary 104, 177, 446, 510
ANSWER 29 CHANGED 29 DELETED 29 ValCheck-TableLookup 327 variable 168 names 72 viewing 122
virtual 132 TableView 122-123 object 131 Tandem SQL SErver Gateway 471 TCursor 166, 176, 190, 201, 363 object 132, 438 temporary files 105 tables 104, 177, 446, 510 . ANSWER 29 CHANGED 29 DELETED 29
INDEX
145-153, 167, 235, 239, 488 TKEcho 262-263 Today operator 507 Tools / Query Save 488 transaction-oriented 470 transactions 468 triggers 490 TUtility 511 TV file 116 Typeln 45, 63, 72 Types... 120 Types and Methods dialog box 131
U underscore 19, 58 UnLock command 432
Update query 482 UpImage 26 Upper() 359 user-interface objects 158
Uses window 413 utilities 373
945
PARADOX
QUERIES:
A
DEVELOPER’S
V
W
ValCheck Pictures 311
Wait
ValCheck-TableLookup tables 327
@& @
@
command 67, 75-76, 207, 244,
wildcard characters 58, 152, 474, 480
using 59, 62
Window Style... 161 window properties 158 Window() 79, 81, 242 Windows 128, 234, 311, 448-449 Control Panel 234
Notepad 112
defining 55-56
:WORK: 173
embedded 56, 172, 235
working directory 173
global 324
Workspace 301, 373, 376
in queries 55 in reports 239 ObjectPAL 139 PAL 228, 492 programming in queries 145 query 128-129
WriteLib 93 Write Lock 430-431, 435-437, 442, 460 WriteQBE() 135-136, 239
tilde 5, 56-64, 145-153, 167,
Z
235, 239, 488
using PAL 63 vertical bar 19, 30, 114 view() 156, 517 viewing SQL 475 virtual memory 94 table 132
@
262, 310 Record 79 wait() method 123 Where clause 477 white spaces 38
var-endVar 128 variable 146 information 6 names 397 parameters 98 scope 146 selection criteria 150 table names 72, 412 tables 168 variables 136, 172, 228 AUTOLIB 93-94 blank tilde 149 database 142
546
REFERENCE
writing QBE files 135
zero for blank 89
Zloof, Moshe 20
Heo hf
U N IVE RS |Tey, SAN DIEGO
M&T BOOKS ES
OTTER VNC
RIAU
RON
A Library of Technical References from M&T Books
Paradox QUl
Paradox Queries: The Basics
by Dan Ehrmann
The companion volume to Paradox Queries: A Developers Reference, this comprehensive guide will teach you query techniques in every version of Paradox, including Paradox for Windows and Paradox SE, 3.5, and 4.0 for
DOS. Written for both users and developers, Paradox Queries: The Basics provides detailed information on using queries in day-to-day applications. Readers will learn new techniques for extracting information from tables, methods for inserting, deleting, or changing large amounts of data, and tips on pitfalls to avoid. This book includes a3 1/2” disk that includes sample tables and all of the code described in the book. It also contains an extensive collection of query tools to boost productivity. Book/Disk
$39.95
#2535
Level: All
Developing Paradox Databases
Developing Paradox Databases: An Object-Based Approach
by Gary Entsminger
A comprehensive guide to developing modifiable databases with Paradox for Windows, Borland’s object-based and event-driven database system. You'll learn its visual programming style and how to use its easily accessible language, ObjectPAL to develop powerful Paradox for Windows applications. You'll also learn how to organize, code, and test applications, how to use memory efficiently and ways to extend Paradox for Windows using ObjectPAL, DDE, OLE, and other languages. Disk includes source code, numerous illustrations, exercises, and sample applications. Book/Disk Level: Intermediate
1-800-628-9658
$39.95
#2969
M&T BOOKS SE SUS PR SLES 5 SDL L ©
A Library of Technical References from M&T Books
Clippers :
Clipper 5: A Developer's Guide by Joseph D. Booth, Greg Lief, and Craig Yellick At 1,351 pages, this tome is impressive! Better yet, any database programmer developing applications for Clipper 5.0 at any level can benefit from this, the guide for all reasons! First you get a quick introduction to Clipper 5 basics and then on to common programming needs such as designing data files, user interfaces, reports, and more. Advanced topics include networking, debugging, and pop-up programming. Code examples are used throughout the text, providing valuable functions that can be applied immediately. Comes with all source code on disk in MS/PC-DOS format. Covers version 5.1. A huge hit! 1351 pp. Book/Disk
$44.95
#242X
Level: Intermediate-Advanced
ES pene ne entree Som
FoxPro 2
FoxPro 2: A Developer's Guide Expert Guide for Industrial-Strength Programming by Jeff Winchell A team of expert FoxPro developers put this one together—definitely industrial strength! Start with the strategies for developing database applications and implementing relational database design. Advanced FoxPro users get valuable insights into keyboard macros, event-driven programming, and tips for optimizing hardware and software performance. Plus FoxPro's application programming interface as it relates to Watcom C, Assembler, and the Macintosh. 03 pp. Book
$29.95
#0834
Book/Disk
$39.95
#0842
Level: Advanced
1-800-628-9658
M&T BOOKS
ORDER
To Order:
FORM
Return this form with your payment to M&T Books, 115 West 18th Street, New York, NY 10011 or call toll-free 1-800-628-9658.
CARD NO.
Charge my: O
Visa
a
ey ae
SIGNATU.
,
(_] MasterCard
[(_] AmExpress (_] Check enclosed,
NAME ADDRESS
payable to
oe
M&T Books.
STATE
zIP
M&T GUARANTEE: If your are not satisfied with your order for any reason, return it to us within 25 days of receipt for a full refund. Note: Refunds on disks apply only when returned with book within guarantee period. Disks damaged in transit or defective will be promptly replaced, but cannot be exchanged for a disk from a different title.
,
i’
*
:
fa
¥
.
Vine “ 7
>
-
c
7
-
,
7
a
.
+
g vs 72
«@
AT eaRATD “+ @¢ teen
Tell us what you think and we'll send you a free M&T Books catalog It is our goal at M&T Books to produce the best technical books available. But you can help us make our books even better by letting us know what you think about this particular title. Please take a moment to fill out this card and mail it to us. Your opinion is appreciated. Tell us about yourself
What chapters did you find valuable?
Name Company Address
City State/Zip
What did you find least useful? Title of this book?
Where did you purchase this book? C1) C1) CL) LC] L] (1)
What topic(s) would you add to future editions of this book?
Bookstore Catalog Direct Mail Magazine Ad Postcard Pack Other
What other titles would you like to see M&T Books publish?
Why did you choose this book? LC] CL) (1) (J CL] CL) LC)
Recommended Read book review Read ad/catalog copy Responded to a special offer M&T Books’ reputation Price Nice Cover
Which format do you prefer for the optional disk? 5.25" 0 3.5"
c
Any other comments? How would you rate the overall content of this book? Oj L) L) 1
Excellent Good Fair Poor
CL) Check here for M&T Books Catalog
Why? 3116
500 8il
NO POSTAGE NECESSARY IF MAILED IN THE UNITED STATES
BUSINESS
REPLY MAIL
FIRST CLASS MAIL PERMIT
1184 NEW YORK, NY
POSTAGE WILL BE PAID BY ADDRESSEE
M&T BOOKS Att: Special Markets 115 West 18th Street New
York, NY
10011
PLEASE FOLD ALONG LINE AND STAPLE OR TAPE CLOSED Eso
i =~ "eee
i) |LM ri
& PERS
Ai?)
UNIVERSIT
oA
oP
‘DIEGO
Paradox Queries: A Developers Reference Destined to become the standard Paradox reference, this book teaches database developers the techniques for incorporating queries into their programs. It includes step-by-step instructions plus solid code examples you can drop straight into your applications. You'll learn how to integrate queries into your scripts, introduce variable information into queries, manipulate query images in PAL and ObjectPAL, include ad hoc query capabilities in applications, and more.
Paradox Queries: ADeveloper's Reference covers every version of Paradox, including Paradox for Windows and Paradox SE, 3.5, and 4.0 for DOS.
Topics include: © Using saved query images © Programming queries directly in PAL
© Incorporating variable information into queries © Using query-telated commands and functions
© Normalizing and denormalizing data
© Viewing and editing using queries © Ad hoc query techniques using tables, forms, and dialog boxes Software Features: This book comes with a 3/2" disk that includes sample tables and all of the code described in the book. Italso contains an extensive collection of query tools to boost your productivity. These tools include a Query Image to PAL Converter, a
Query Script Manager to organize saved queries, a tool to manage query processing order, and more. Paradox developers should also read the companion volume from M&T Books, Paradox Queries: The Basics, which explores the fundamentals of queries in Paradox. Together, these books form an invaluable Paradox reference library that teaches you how to use queries interactively and how to use them effectively in your applications. Dan Ehrmann is one of the most respected Paradox developers in the United States. He is the author of Paradox Queries: The Basics (M&T Books, 1993). He was the Paradox columnist for DBMS magazine, served as Chairman of the Advisory Board for the first International Paradox
Users Conference, wos a featured speaker on Borland’s first Paradox World Tour, and cofounded the Chicago Paradox Users Group. Dan speaks regularly at Paradox user group meetings and conferences and is the founder and president of Kallista, Inc. based in Chicago.
Why this book is for you—See pagel. a Wedg
oe i_—]
i_—]
| \|
9°781558"51 MST bos = [ aa on
LEVEL © TOPIC SU aa\ta HARDWARE
Intermediate - Advanced Database Design and Programming Paradox for Windows and Paradox for = ie
bsSBN > $39
7a 935
tt)
1-55851-31 La5