Paradox Queries: A Developer’s Reference 1558513116

147 19 22MB

English Pages 546 [582] Year 1993

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Paradox Queries: A Developer’s Reference
 1558513116

Citation preview

=

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