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.
Related
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.
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.
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].*/
I have written a program for matching one records to another records in FOR Each loop but i don't know how to give an error message if none of the records are matching. Let me share my codes
DEFINE VARIABLE cPos AS INTEGER NO-UNDO.
DEFINE TEMP-TABLE tt_data NO-UNDO
FIELD cPosition AS CHARACTER FORMAT "X(60)"
FIELD cEndCode AS CHARACTER
FIELD cShotCode AS CHARACTER.
/*so many records are stored in tt_data and below is one of the records for your understanding*/
CREATE tt_data.
ASSIGN
tt_data.cPosition ="S$$$^^^^^^^^^^$$$^^^MC^^^^^^^^^^^^R^^^^^^^^^^^^^^^^^^^^^^^^^"
tt_data.cEndCode = 10
tt_data.cShotCode = "S".
cPos = integer( tt_data.cEndCode / 10 ).
/* Consider 60 records available in tt_data */
FOR EACH tt_data.
FIND FIRST tt_date WHERE tt_data.cShotCode =
SUBSTRING(tt_data.cPosition,cPos,1) NO-LOCK NO-ERROR.
DISPLAY tt_data.cShotCode. /* Displayed Value is S */
IF NOT AVAILABLE tt_date THEN
MESSAGE "NONE OF THE RECORDS MATCHING WITH tt_data.cPosition "
LEAVE.
END.
I can get at least one record matching in tt_data. Here the problem is I don't want to LEAVE if any one record is matching but I want to get an error message with LEAVE statement if none of the records are matching. Could you please help this case?
I think this might be what you are trying to do:
DEFINE VARIABLE cPos AS INTEGER NO-UNDO.
DEFINE TEMP-TABLE tt_data NO-UNDO
FIELD cPosition AS CHARACTER FORMAT "X(60)"
FIELD cEndCode AS CHARACTER
FIELD cShotCode AS CHARACTER.
/*so many records are stored in tt_data and below is one of the records for your understanding*/
CREATE tt_data.
ASSIGN
tt_data.cPosition ="S$$$^^^^^^^^^^$$$^^^MC^^^^^^^^^^^^R^^^^^^^^^^^^^^^^^^^^^^^^^"
tt_data.cEndCode = 10
tt_data.cShotCode = "S".
cPos = integer( tt_data.cEndCode / 10 ).
/* Consider 60 records available in tt_data */
FOR EACH tt_data: /* although it 'works', "." is not appropriate, FOR EACH should end with a ":" */
FIND FIRST tt_date WHERE tt_data.cShotCode = SUBSTRING(tt_data.cPosition,cPos,1) NO-LOCK NO-ERROR.
/* changes start here */
IF AVAILABLE tt_date THEN
do:
DISPLAY tt_data.cShotCode. /* Displayed Value is S */ /* only display this when it is available! */
end.
else
do:
MESSAGE "NONE OF THE RECORDS MATCHING WITH tt_data.cPosition ". /* a "." was missing */
/* LEAVE. */
end.
END.
I would personally try to do the error check before you ever get in the FOR EACH block. Sometimes you can't, but based on your sample code I think you could examine the temp-table first and provide an error message. But I'm not completely sure what you are going for based on the sample.
DEFINE VARIABLE cPos AS INTEGER NO-UNDO.
DEFINE TEMP-TABLE tt_data NO-UNDO
FIELD cPosition AS CHARACTER FORMAT "X(60)"
FIELD cEndCode AS CHARACTER
FIELD cShotCode AS CHARACTER.
/*so many records are stored in tt_data and below is one of the records for your understanding*/
CREATE tt_data.
ASSIGN
tt_data.cPosition ="S$$$^^^^^^^^^^$$$^^^MC^^^^^^^^^^^^R^^^^^^^^^^^^^^^^^^^^^^^^^"
tt_data.cEndCode = 10
tt_data.cShotCode = "S".
cPos = integer( tt_data.cEndCode / 10 ).
/* Add check here */
IF can-find( FIRST tt_date WHERE
tt_data.cShotCode = SUBSTRING(tt_data.cPosition,cPos,1 ) )
THEN
message "ERROR message".
ELSE DO:
/* Consider 60 records available in tt_data */
FOR EACH tt_data.
/* DO thing */
END.
END.
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".