how to create a temp-table for code_mstr in progress 4gl? - openedge

I want to create a temp-table for generalized code maintenance(code_mstr) such that the input I give in GCM does not hit the database but instead is stored in a temporary table from where I can update or delete the records. which has the fields
code_fldname
code_value
code_cmmt
code_group
I am very new to progress and this task is a little daunting as I am not understanding how to begin, I have been going through examples and syntax of temp-table, the only thing I managed to write so far is this code which Im not sure is correct or not
define temp-table tt_gcm no-undo
field tt_fldname like code_fldname
field tt_value like code_value
field tt_cmmt like code_cmmt
field tt_group like code_group
field tt_domain like global_domain
index tt_idx
tt_domain
tt_fldname
tt_value.
and after this I defined a form for the same
form
code_fldname
code_value
code_cmmt
code_group
with frame a side-labels
now suppose if I enter a particular record in code_mstr, I want only that one particular record to be visible in the temp-table rather than all the records that are in code_mstr, any help on how to proceed with that would be appreciated.

Something along these lines will fill your temp-table:
for each code_mstr no-lock:
create tt_gcm.
assign
tt_gcm.tt_fldname = code_mstr.code_fldname
tt_gcm.tt_value = code_mstr.code_value
tt_gcm.tt_cmmt = code_mstr.code_cmmt
tt_gcm.tt_group = code_mstr.code_group
tt_gcm.tt_domain = code_mstr.global_domain
.
end.
and this will display the data in the form that you created:
for each tt_gcm:
display tt_gcm with frame a.
end.

If you can keep your temp-table field names the same as your database field names, you can use the buffer-copy statement. It will copy all matching fields from one to the other.
define temp-table tt_gcm no-undo
field global_domain like code_mstr.global_domain
field code_fldname like code_mstr.code_fldname
field code_value like code_mstr.code_value
field code_cmmt like code_mstr.code_cmmt
field code_group like code_mstr.code_group
index tt_idx // add is unique?
global_domain
code_fldname
code_value
.
for each code_mstr no-lock:
create tt_gcm.
buffer-copy code_mstr to tt_gcm.
end.

Related

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.

How to implement NOT EXISTS in OPEN QUERY statement in PROGRESS 4GL - OpenEdge 10.2A

I want to create a browse such that it will show all the records from one table if the values of a field do NOT exist in another table.
It is possible to get the records using SQL as:
SELECT myField FROM pub.myTable WHERE
NOT EXISTS (SELECT myField FROM pub.myTable2 WHERE myTable2.myField=myTable.myField)
It is also possible using 4GL as:
FOR EACH myTable WHERE
NOT CAN-FIND(FIRST myTable2 WHERE myTable2.myField=myTable.myField)
The problem is when I put this query in a browse as:
OPEN QUERY myBrowse
FOR EACH myTable WHERE
NOT CAN-FIND(FIRST myTable2 WHERE myTable2.myField=myTable.myField)
it gives an error message
CAN-FIND is invalid within an OPEN QUERY. (3541)
The question is, is it possible to write such an OPEN QUERY statement?
I didn't come up with this, Steve Moore shared it on https://community-archive.progress.com/forums/00026/27143.html
define temp-table ttNoOrder
field field1 as char.
create ttNoOrder.
define query q1 for Customer, Order, ttNoOrder.
open query q1 for each Customer no-lock,
first Order of Customer outer-join no-lock,
first ttNoOrder where not available(Order).
get first q1.
repeat while not query-off-end("q1"):
display Customer.CustNum Customer.Name available(Order).
get next q1.
end.
Works even with dynamic queries:
DEFINE TEMP-TABLE ttNoOrder
FIELD field1 AS CHARACTER .
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
CREATE ttNoOrder.
CREATE QUERY hQuery .
hQuery:SET-BUFFERS (BUFFER Customer:HANDLE,
BUFFER Order:HANDLE,
BUFFER ttNoOrder:HANDLE) .
hQuery:QUERY-PREPARE ("for each Customer no-lock, ~
first Order of Customer outer-join no-lock, ~
first ttNoOrder where not available(Order)") .
hQuery:QUERY-OPEN() .
hQuery:GET-FIRST () .
REPEAT WHILE NOT hQuery:QUERY-OFF-END:
DISPLAY Customer.CustNum FORMAT ">>>>>>>>>9" Customer.Name AVAILABLE(Order).
hQuery:GET-NEXT ().
END.

Adding row from FOR EACH to TEMP-TABLE

I'm trying to create a temp-table which is exactly like an already existing table in the database. How do I add each row to the temp table?
DEFINE TEMP-TABLE o_ttProducts.
FOR EACH Product:
/*Add current row to the o_ttProducts temp table*/
END.
DEFINE TEMP-TABLE o_ttProducts no-undo like Product.
FOR EACH Product
no-lock:
/*Add current row to the o_ttProducts temp table*/
create o_ttProducts.
buffer-copy Product to o_ttProducts.
END.

Resources