Is it good to use LOOKUP in FOR EACH WHERE clause? - Progress 4GL - openedge

Is it good if we use lookup function in for each where clause? Will it cause performance issue? Please help to understand and provide the example how to avoid.
define variable cGroupID as character no-undo.
for each <table> no-lock where lookup(cGroupID,<table.fieldname>) <> 0:
**do something...**
end.
note - table field name can have multiple comma separated group

A lookup function cannot use an index, so yes you can introduce sub-par performance which can be avoided. See the following example using the sports database which will read all records when using lookup and will limit the set to what meets the criteria when breaking the parts out to individual query parts:
def var ii as int no-undo.
def var iread as int64 no-undo.
function reads returns int64:
find _file where _file._file-name = 'customer' no-lock.
find _tablestat where _tablestat._tablestat-id = _file._file-number no-lock.
return _tablestat._tablestat-read.
end function.
iread = reads().
for each customer
where lookup( customer.salesrep, 'dkp,sls' ) > 0
no-lock:
ii = ii + 1.
end.
message 'lookup - records:' ii 'read:' reads() - iread.
ii = 0.
iread = reads().
for each customer
where customer.salesrep = 'dkp'
or customer.salesrep = 'sls'
no-lock:
ii = ii + 1.
end.
message 'or - records:' ii 'read:' reads() - iread.
https://abldojo.services.progress.com/?shareId=6272e1223fb02369b2545bf4
Your example however seems to be performing a reverse lookup, ie the database field contains a comma separated list of values, which seems like not obeying the basic rules of database normalization.
If you want to keep the list in a single field, adding a word index on your comma separated field may help. You can then use contains.

Related

How do I sort by names or postcodes using a drop-down list to determine what to sort by?

Currently, I am trying to create some software on Progress OpenEdge that sorts by customer's names or account codes.
So essentially, a box will open up when the program runs, the user will select "Name" from the drop-down list, and the program will display all the names in the database from alphabetical order.
Or, they will pick "Account" from the drop-down list, and it will display all the account codes in numeric order. I have attached a picture of the program here:
And this is currently the code I am using to print the results:
However, I'm not sure what I need to add for the others. Would I need IF statements, such as:
OR IF [drop down list] = "Account" THEN or something like that?
Any help would be appreciated.
While you can perform a static order by with a convulted set of if statements, it is a lot cleaner with a dynamic query.
To expand on Tom and Stefan's answers, you can use a query. The ABL lets you create a lot of things with static constructs and use them as dynamic. I think that in this case, you want to so something like the below.
Note that you can do build the query string either using OPEN QUERY qry FOR EACH ... or QUERY qry:QUERY-PREPARE('FOR EACH ... ') ; both will work equally well.
What I think you'd want is
(a) having a static definition of the query (ie DEFINE QUERY) since the cost of adding the buffer(s) to the query is done at compile time, not run time, and
(b) accessing the buffer fields statically (ie slmast.name rather than b::name )
define query qry for slmast.
define variable wc as character no-undo.
if condition eq true then
wc = "WHERE kco = s-kco AND warecode = lv-warecode AND pcode = fi-pcode AND name = fi-name BY name".
else
wc = "WHERE TRUE".
/* alternate
if condition then
open query qry for each slmast no-lock WHERE kco = s-kco AND warecode = lv-warecode AND pcode = fi-pcode AND name = fi-name BY name.
else
open query qry for each slmast no-lock.
*/
query qry:query-prepare(wc).
open query qry.
query qry:get-first().
do while available slmast:
/* do stuff with the buffer */
{&OUT} slmast.name.
query qry:get-next().
end.
query qry:query-close().
Using static constructs as far as possible means that you have less cleanup code to write and the code becomes more readable (IMO).
There are multiple ways to loop through the query results: using DO WHILE NOT QUERY qry:QUERY-OFF-END works as well as AVAILABLE slmast or b:AVAILABLE (if using a purely dynamic query).
As Stefan says, a dynamic query is what you want. This might help get you started:
define variable wc as character no-undo.
define variable q as handle no-undo.
define variable b as handle no-undo.
/* run your UI to get selection criteria amd then
* create a WHERE clause as appropriate
*/
wc = "WHERE kco = s-kco AND warecode = lv-warecode AND pcode = fi-pcode AND name = fi-name BY name".
create buffer b for table "slmast".
create query q.
q:set-buffers( b ).
q:query-prepare( substitute( "FOR EACH slmast NO-LOCK &1", wc )).
q:query-open().
do while q:get-next():
display
b:buffer-field( "name" ):buffer-value
b:buffer-field( "acode" ):buffer-value
b:buffer-field( "pcode" ):buffer-value
b:buffer-field( "trunmtd" ):buffer-value
b:buffer-field( "turnytd" ):buffer-value
.
end.

Getting Error - Phrase or option conflicts with previous phrase or option. (277) - Progress 4GL

I am using following dynamic query to fetch the data from a table. But I am getting compilation error "Phrase or option conflicts with previous phrase or option. (277)". Not sure where I am making mistakes and how to fix it. Please help me modifying the below example query.
define variable hbuffer as handle no-undo.
define variable hQuery as handle no-undo.
define variable cQuery as character no-undo.
define temp-table tt_table no-undo
field tt_week1 as character label "Week1"
.
create buffer hbuffer for table "<table>".
cQuery = "for each <table> no-lock ".
create query hQuery.
hQuery:set-buffers(hbuffer).
cQuery = cQuery + ":".
hQuery:query-prepare(cQuery).
hQuery:query-open().
if hQuery:query-open() then
do:
do while hQuery:get-next():
create tt_table.
assign tt_week1 = hbuffer::qty[1] /*field name qty data type is deci-10[52].*/
.
end.
end.
for each tt_table :
disp tt_week1.
end.
As pointed out by Mike, your attempt to reference the extent is throwing the error, a dynamic extent reference uses round parentheses (and works fine with shorthand):
hbuffer::qty(1)
Additionally:
you do not need to terminate your query with a :
get-next() defaults to no-lock
you are opening your query twice
// some demo data
define temp-table db no-undo
field qty as decimal extent 7
.
create db. db.qty[1] = 1.
create db. db.qty[1] = 2.
// the question
define variable hb as handle no-undo.
define variable hq as handle no-undo.
define variable cquery as character no-undo.
define temp-table tt no-undo
field week1 as character label 'Week1'
.
create buffer hb for table 'db'.
cquery = substitute( 'for each &1', hb:name ).
create query hq.
hq:set-buffers( hb ).
if hq:query-prepare( cquery ) and hq:query-open() then do:
do while hq:get-next():
create tt.
tt.week1 = hb::qty(1). // <-- round parentheses
end.
end.
for each tt:
display tt.week1.
end.
https://abldojo.services.progress.com/?shareId=626aff353fb02369b2545434
The compile error should tell you the line the error is coming from.
In the code snippet, you haven't define a tt_week field anywhere.
In general, if you want to assign a (temp)table field, you should use the table.field notation; the AVM can often figure out your intent, but not being specific is error-prone.
The problem is with the shorthand syntax here:
hbuffer::qty[1]
If you replace this with:
hbuffer:buffer-field ("qty"):BUFFER-VALUE (1)
it'll work (until the point, that Peter made with the undefined field tt_week1. I did not find any reference saying if or if not the shorthand syntax should work with EXTENT fields. It may be worth checking that with Progress tech-support.
So this will bring you further:
assign tt_data = hbuffer:buffer-field ("qty"):BUFFER-VALUE (1) /*field name qty data type is deci-10[52].*/

How to fix Dynamic Query error for progress 4gl?

I am new to progress 4GL. I have written logic for buffer by adding multiple tables into a single query and finding the records from another table by using table field names. I am not sure why I am getting errors. Please help to change the logic
DEFINE VARIABLE ix AS INTEGER NO-UNDO.
DEFINE VARIABLE qh AS HANDLE NO-UNDO.
DEFINE VARIABLE bh AS HANDLE NO-UNDO.
DEFINE VARIABLE fh AS HANDLE NO-UNDO EXTENT 10.
DEFINE VARIABLE cQuery AS CHARACTER NO-UNDO.
CREATE BUFFER bh FOR TABLE "Customer, Invoice".
CREATE QUERY qh.
ASSIGN
cQuery = "FOR EACH Customer NO-LOCK, EACH Invoice WHERE Invoice.Cust-Num = Customer.Cust-
Num NO-LOCK: ".
qh:SET-BUFFERS(bh).
qh:QUERY-PREPARE(cQuery).
qh:QUERY-OPEN().
qh:GET-FIRST().
/* Field Invoice.Cust-Num is already defined in cQuery*/
FIND Order WHERE Order.Cust-Num = Invoice.Cust-Num NO-LOCK NO-ERROR.
IF NOT AVAILABLE Order THEN DO:
FIND Ref-Call WHERE Ref-Call.Cust-Num = Invoice.Cust-Num NO-LOCK NO-ERROR.
DISPLAY Ref-Call.Cust-Num.
END.
qh:QUERY-CLOSE().
bh:BUFFER-RELEASE().
DELETE OBJECT bh.
DELETE OBJECT qh.
Looking at the field names, you're using the classic Sports Database for your training. There's a "newer" Sports2000 demo database with a little bit more of data that might be worth playing with.
There are multiple issues in that program.
First, you cannot define a single dynamic buffer for two tables (Customer, Invoice). This would be causing an error at runtime. You need to:
DEFINE VARIABLE bh1 AS HANDLE NO-UNDO.
DEFINE VARIABLE bh2 AS HANDLE NO-UNDO.
CREATE BUFFER bh1 FOR TABLE "Customer".
CREATE BUFFER bh2 FOR TABLE "Invoice".
and then
qh:ADD-BUFFER(bh1).
qh:ADD-BUFFER(bh2).
The second issue (your compile error), is because the compiler does not see that you're accessing the table Invoice already. bh2 will only at runtime be known to be a buffer for the Invoice table. So you need to access the Cust-Num field dynamically:
FIND Order WHERE Order.Cust-Num = bh2::Cust-Num NO-LOCK NO-ERROR.
Note: You're accessing a single Order by the Cust-Num of the invoice here - I assume, you want to do something like accessing the Order by the Invoice's Order-Num field. That would be a logical mistake, not a syntax error.
However, nothing in your program justifies the need for a dynamic query. That just adds in this case unneeded complexity. You program is not yet iterating the records in the dynamic-query qh - but I assume that's the goal. So this simple static FOR EACH block does the same:
FOR EACH Customer NO-LOCK, EACH Invoice WHERE Invoice.Cust-Num = Customer.Cust-
Num NO-LOCK:
FIND Order WHERE Order.Cust-Num = Invoice.Cust-Num NO-LOCK NO-ERROR.
IF NOT AVAILABLE Order THEN DO:
FIND Ref-Call WHERE Ref-Call.Cust-Num = Invoice.Cust-Num NO-LOCK NO-ERROR.
DISPLAY Ref-Call.Cust-Num.
END /* NOT AVAILABLE */.
END. /* FOR EACH */
Lastly, here:
DELETE OBJECT bh.
DELETE OBJECT qh.
DELETE OBJECT statements belong by their nature into a FINALLY
block.
You need to check validity of the handles before deleting
them:
FINALLY:
IF VALID-HANDLE (bh1) THEN DELETE OBJECT bh1.
IF VALID-HANDLE (bh2) THEN DELETE OBJECT bh2.
IF VALID-HANDLE (qh) THEN DELETE OBJECT qh.
END.
Here is a working example - my changes to your code can generally be identified by being in lower case.
DEFINE VARIABLE qh AS HANDLE NO-UNDO.
DEFINE VARIABLE bhc AS HANDLE NO-UNDO.
DEFINE VARIABLE bhi AS HANDLE NO-UNDO.
DEFINE VARIABLE cQuery AS CHARACTER NO-UNDO.
CREATE BUFFER bhc FOR TABLE "Customer".
create buffer bhi for table "Invoice".
CREATE QUERY qh.
cQuery = "FOR EACH Customer NO-LOCK,"
+ "EACH Invoice WHERE Invoice.CustNum = Customer.CustNum no-lock".
qh:SET-BUFFERS(bhc,bhi).
qh:QUERY-PREPARE(cQuery).
qh:QUERY-OPEN().
do while qh:get-next().
message bhc::CustNum bhi::InvoiceNum.
FIND Order WHERE Order.CustNum = bhi::CustNum NO-LOCK NO-ERROR.
IF NOT AVAILABLE Order THEN DO:
FIND RefCall WHERE RefCall.CustNum = bhi::CustNum NO-LOCK NO-ERROR.
if available RefCall then
message RefCall.CustNum.
END.
end.
finally:
DELETE OBJECT bhc no-error.
DELETE OBJECT bhi no-error.
DELETE OBJECT qh no-error.
end finally.
Watch the example run in ABLdojo.

Can I specify the contents of the buffer while creating it in Progress-4GL?

Noob to Progress here and self-taught while working so sorry if I missed some obvious things. I learned about buffers yesterday and I'd like to know if it's possible to delimit the scope of what the buffer will search. Below, an example of what I want to know.
DEFINE BUFFER ex1 FOR emit WHERE emit.id > 50000.
FOR EACH ex1:
DISP ex1.id ex1.name.
end.
I know I could put the WHERE on the FOR EACH part in this example, but I'd like to know if and how can I delimit the buffer so I can do what I intend to in my code here.
Thanks for the help.
EDIT: I'll put the code I wrote here so it's easier to understand what I meant. I really don't see any other solutions besides using temp-tables and it makes everything so much slower. I'll keep my code like this but if anyone knows of a better solution, just give me a shout. Thanks again for your time.
def var contador as int.
def var contador2 as int.
def var contador3 as int.
def temp-table tt-min-oper
field it-codigo like operacao.it-codigo
field num-id-operacao like operacao.num-id-operacao
field op-codigo like operacao.op-codigo
.
def temp-table tt-oper
field it-codigo like operacao.it-codigo
field op-codigo like operacao.op-codigo
.
def temp-table tt-valida-oper
field it-codigo like operacao.it-codigo
.
for each operacao NO-LOCK
break by operacao.it-codigo by operacao.num-id-operacao:
IF FIRST-OF (operacao.it-codigo) THEN DO:
CREATE tt-min-oper.
ASSIGN
tt-min-oper.it-codigo = operacao.it-codigo
tt-min-oper.num-id-operacao = operacao.num-id-operacao
tt-min-oper.op-codigo = operacao.op-codigo
.
END.
END.
FOR EACH tt-min-oper WHERE tt-min-oper.op-codigo <> 10 NO-LOCK:
create tt-oper.
assign tt-oper.it-codigo = tt-min-oper.it-codigo.
END.
FOR EACH operacao NO-LOCK,
EACH tt-oper
WHERE operacao.it-codigo = tt-oper.it-codigo
AND operacao.op-codigo = 10 NO-LOCK:
create tt-valida-oper.
assign
tt-valida-oper.it-codigo = operacao.it-codigo.
END.
Answer to the extended question of the OP.
Answering this without knowing your use-case and details about the table relations is always difficult. But from your code:
In the final FOR EACH, you're only iterating operacao records where first the it-codigo is unique (then you put it into tt-min-oper). Then you filter tt-min-oper for op-codigo <> 10 and add the resulting records into tt-oper.
So at this time tt-oper should contain records with unique it-codigo values and op-codigo <> 10.
So at the minimum you don't need this loop here:
FOR EACH tt-min-oper WHERE tt-min-oper.op-codigo <> 10 NO-LOCK:
create tt-oper.
assign tt-oper.it-codigo = tt-min-oper.it-codigo.
END.
as in the initial FOR EACH you could filter on op-codigo <> 10 as well:
for each operacao WHERE operacao.op-codigo <> 10 NO-LOCK
break by operacao.it-codigo by operacao.num-id-operacao:
How many records are in the able operacao? And is there an index with it-codigo as the first field? The FOR EACH with BREAK-BY will still retrieve all records in the table, but only process the FIRST-OF (it-codigo) ones. That may be a very heavy operation.
In large tables, if may be better to do something like this instead of a FOR EACH with BREAK-BY. My Order table has 700000 records, so this here processes all 700000 records:
FOR EACH Order BREAK BY Order.Salesrep:
IF FIRST-OF (Order.Salesrep) THEN
DO:
DISPLAY Order.Salesrep .
END.
END.
And this here get'S the same result, but only reads 10 records (there are 10 Salesreps in the DB). But this is only possible as there is an index for the Salesrep field.
DEFINE VARIABLE cPrevious-Salesrep AS CHARACTER NO-UNDO .
FIND FIRST Order WHERE Order.Salesrep > cPrevious-Salesrep
NO-LOCK NO-ERROR .
DO WHILE AVAILABLE (Order):
DISPLAY Order.Salesrep WITH DOWN .
DOWN 1 .
ASSIGN cPrevious-Salesrep = Order.Salesrep.
FIND NEXT Order WHERE Order.Salesrep > cPrevious-Salesrep
NO-LOCK NO-ERROR .
END.
So to optimize your code, you need to know your DB schema and actual data.
You limit results in the query, the FOR EACH statement in your case, so
DEFINE BUFFER ex1 FOR emit .
FOR EACH ex1 WHERE ex1.id > 50000:
DISP ex1.id ex1.name.
end.
The code I posted before took several minutes to compile. I managed to reduce it a lot by putting a FOR EACH inside another (now it only takes 25 seconds). Unfortunately, I can't filter op-codigo <> 10 in the first FOR EACH because it changes the results. I didn't use USE-INDEX because I read Tom Bascom saying it wasn't good, but we have indexes in the tables we're using. I don't know a lot about the tables, because I'm fairly new here and I'm still learning a lot.
So my code became like the example below. I don't know if it's good to put a FOR EACH inside another and I always avoided it, but all my colleagues do it.
DEF TEMP-TABLE tt-min-oper
FIELD it-codigo LIKE operacao.it-codigo
FIELD num-id-operacao LIKE operacao.num-id-operacao
FIELD op-codigo LIKE operacao.op-codigo
.
DEF TEMP-TABLE tt-valida-oper
FIELD it-codigo LIKE operacao.it-codigo
FIELD num-id-operacao LIKE operacao.num-id-operacao
.
FOR EACH operacao NO-LOCK
BREAK BY operacao.it-codigo BY operacao.num-id-operacao:
IF FIRST-OF (operacao.it-codigo) THEN DO:
CREATE tt-min-oper.
ASSIGN
tt-min-oper.it-codigo = operacao.it-codigo
tt-min-oper.num-id-operacao = operacao.num-id-operacao
tt-min-oper.op-codigo = operacao.op-codigo
.
END.
END.
FOR EACH tt-min-oper
WHERE tt-min-oper.op-codigo <> 10 NO-LOCK:
FOR EACH operacao
WHERE operacao.it-codigo = tt-min-oper.it-codigo
AND operacao.op-codigo = 10 NO-LOCK:
CREATE tt-valida-oper.
ASSIGN
tt-valida-oper.it-codigo = operacao.it-codigo
tt-valida-oper.num-id-operacao = tt-min-oper.num-id-operacao
.
END.
END.
I couldn't find a solution for buffers like I wanted, but I managed to do it in a way I've never done before, so I see this as a victory. Thanks for your time and suggestions Mike and if there's any other suggestion, I'm all open to it.

progress 4gl query for export count of records in all table available in db

I am trying to export count of all tables in excel or text file.
if any program or any query will help me ?
I am trying to code for export count of data available in each table.
code:
define stream table t1.
output stream t1 to t1.csv.
&scope-define display-fields count(*)
select count(*) from emp.
export starem t1 delimiter ",".
This code create excel with empty value but display result on screen. i out in excel.
Unsure what you want to do. Something like this if you want to count the number of tables in a database:
DEFINE VARIABLE icount AS INTEGER NO-UNDO.
FOR each _file NO-LOCK WHERE _file._owner = "PUB":
/* Skip "hidden" virtual system tables */
IF _file._file-name BEGINS "_" THEN NEXT.
iCount = iCount + 1.
END.
MESSAGE iCount "tables in the database"
VIEW-AS ALERT-BOX INFORMATION.
If you have several db's connected you need to prepend the _file-table with the database name ie database._file.
However: since you say "export to excel" perhaps what you mean is that you want to know the number of records for each table?
To count number of records in a table you can use FOR or SELECT.
SELECT COUNT(*) FROM tablename.
or
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
FOR EACH tablename NO-LOCK TABLE-SCAN:
iCount = iCount + 1.
END.
DISPLAY iCount.
If you don't want to code this for each table you need to combine it with a dynamic query counting all records.
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
DEFINE VARIABLE hBuffer AS HANDLE NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DEFINE VARIABLE cTable AS CHARACTER NO-UNDO.
/* Insert tablename here */
cTable = "TableName".
CREATE QUERY hQuery.
CREATE BUFFER hBuffer FOR TABLE cTable.
hQuery:SET-BUFFERS(hBuffer).
hQuery:QUERY-PREPARE(SUBSTITUTE("FOR EACH &1", cTable)).
hQuery:QUERY-OPEN.
queryLoop:
REPEAT:
hQuery:GET-NEXT().
IF hQUery:QUERY-OFF-END THEN LEAVE queryLoop.
iCount = iCount + 1.
END.
DELETE OBJECT hQuery.
DELETE OBJECT hBuffer.
MESSAGE iCount "records in the table".
Combine those two and you have a solution. It might be slow however since it will count all records of all tables.
A quick and dirty way is to run "tabanalys" on the database instead if you have access to it via the prompt:
proutil DatabaseName -C tabanalys > tabanalys.txt
This can be run online and might have impact on file io etc so run it the first time on off peak hours just to make sure. Then look into that file, you will see record count, sizes etc for all tables: system-tables as well as user-tables.
Proutil ran online might not be 100% correct but most likely "good enough".

Resources