Looping Methods needed here - openedge

I have written a query with the help of one experienced person from progress 4GL but i missed one concept to ask. I have shared the query. Here what i need is to calculate the total orders based on start and end hour for every shift sequence (Total 21 seq) and assign to variable. For example look below
DEFINE VARIABLE StartHour06 AS INTEGER NO-UNDO.
DEFINE VARIABLE StartHour07 AS INTEGER NO-UNDO
FIND FIRST gdmf_shift WHERE gdmf_shift.shft_sequence = 1 NO-LOCK NO-ERROR.
StartHour06 = gdmf_shift.shft_start_hour.
StopHour07 = gdmf_shift.shft_stop_hour.
Like this i need to write the query up to 21 shift sequence..actually its not a good code..i need to make it simple and tried one method but from that i don't know how to do assign to variables. Please look below what i tried
DEFINE VARIABLE sSeq AS INTEGER EXTENT 21 NO-UNDO. /* start hour */
DEFINE VARIABLE eSeq AS INTEGER EXTENT 21 NO-UNDO. /* end hour */
FOR EACH gdmf_shift WHERE gdmf_shift.shft_sequence LE 21 NO-LOCK BY gdmf_shift.shft_sequence:
sSeq[1] = gdmf_shift.shft_start_hour.
eSeq[21] = gdmf_shift.shft_stop_hour.
DISP sSeq[1] eSeq[21].
END.

If you want to keep track of up to 21 individual shift sequences. Perhaps you should do something like the code below.
Right now you just update sSeq[1] and eSeq[21] for each iteration of the loop.
Insert shift sequence as index of the array instad:
DEFINE VARIABLE sSeq AS INTEGER EXTENT 21 NO-UNDO. /* start hour */
DEFINE VARIABLE eSeq AS INTEGER EXTENT 21 NO-UNDO. /* end hour */
FOR EACH gdmf_shift WHERE gdmf_shift.shft_sequence LE 21 NO-LOCK BY gdmf_shift.shft_sequence:
sSeq[gdmf_shift.shft_sequence] = gdmf_shift.shft_start_hour.
eSeq[gdmf_shift.shft_sequence] = gdmf_shift.shft_stop_hour.
DISP sSeq[gdmf_shift.shft_sequence] eSeq[gdmf_shift.shft_sequence].
END.
If you absolutely need individual variables instead on extents its would be a lot more code and a CASE-statement (could be an IF as well) deciding what variable to assign:
DEFINE VARIABLE sSeq1 AS INTEGER NO-UNDO. /* start hour */
DEFINE VARIABLE eSeq1 AS INTEGER NO-UNDO. /* end hour */
DEFINE VARIABLE sSeq2 AS INTEGER NO-UNDO. /* start hour */
DEFINE VARIABLE eSeq2 AS INTEGER NO-UNDO. /* end hour */
/* More variables here... */
DEFINE VARIABLE sSeq21 AS INTEGER NO-UNDO. /* start hour */
DEFINE VARIABLE eSeq21 AS INTEGER NO-UNDO. /* end hour */
FOR EACH gdmf_shift WHERE gdmf_shift.shft_sequence LE 21 NO-LOCK BY gdmf_shift.shft_sequence:
CASE gdmf_shift.shft_sequence:
WHEN 1 THEN DO:
sSeq1 = gdmf_shift.shft_start_hour.
eSeq1 = gdmf_shift.shft_stop_hour.
END.
WHEN 2 THEN DO:
sSeq2 = gdmf_shift.shft_start_hour.
eSeq2 = gdmf_shift.shft_stop_hour.
END.
/* more code here */
WHEN 21 THEN DO:
sSeq21 = gdmf_shift.shft_start_hour.
eSeq21 = gdmf_shift.shft_stop_hour.
END.
END CASE.
END.
But that will be LOTS of code. Consider using something like a temp-table instead if you dont like the array. If you name the fields in the temp-table like the names in the table you can use BUFFER-COPY otherwise you can ASSIGN any field.
DEFINE TEMP-TABLE tt NO-UNDO
FIELD shft_sequence LIKE gdmf_shift.shft_sequence
FIELD shft_start_hour LIKE gdmf_shift.s.shft_start_hour
FIELD shft_stop_hour LIKE gdmf_shift.s.shft_stop_hour.
FOR EACH gdmf_shift WHERE gdmf_shift.shft_sequence LE 21 NO-LOCK BY gdmf_shift.shft_sequence:
CREATE tt.
BUFFER-COPY gdmf_shift TO tt.
END.

Related

How to be sure that only one record is taken into account at a given stage of the query?

I wonder how exactly the "queries" for each and first work.
lets say I have some tables A, B, C and D.
How many records will I find if I use sth like:
first B,
each C,
first D:
<block>
end.
1*|C|*1? [assuming there will be C that has some relation with B, etc]
And then I have sth like this:
for each A,
first B,
/*-----------------------*/
each C,
first D:
<block>
end.
How can I make sure to have the same complexity after this change? (B has now relation with A and there will be some where statements) Will it be |A|*1*|C|*1? And If I want to have the same complexity as before is there anything I can add there?
Example of the problem
define temp-table ttA no-undo
field mainID as character format "x(20)"
field foreignKey as character format "x(20)"
index idx_mainID is unique primary mainID
.
define temp-table ttB no-undo
field mainID as character format "x(20)"
field foreignKey as character format "x(20)"
index idx_mainID is unique primary mainID
.
define temp-table ttC no-undo
field mainID as character format "x(20)"
field foreignKey as character format "x(20)"
index idx_mainID is unique primary mainID
.
define temp-table ttD no-undo
field mainID as character format "x(20)"
field foreignKey as character format "x(20)"
index idx_mainID is unique primary mainID
.
DEFINE VARIABLE viCounterA as integer no-undo.
DEFINE VARIABLE viCounterB as integer no-undo.
DEFINE VARIABLE viCounterC as integer no-undo.
DEFINE VARIABLE viCounterD as integer no-undo.
DEFINE VARIABLE viTotal as integer no-undo.
DEFINE VARIABLE viTotal2 as integer no-undo.
assign
viCounterA = 0
viCounterB = 0
viCounterC = 0
viCounterD = 0
viTotal = 0
viTotal2 = 0
.
DEFINE VARIABLE vcAID as character no-undo.
DO viCounterA = 1 TO 9:
vcAID = "A0" + STRING(viCounterA).
create ttA.
ttA.mainID = vcAID.
ttA.foreignKey = "".
DEFINE VARIABLE vcBID as character no-undo.
DO viCounterB = 1 TO 9:
vcBID = vcAID + "B0" + STRING(viCounterB).
create ttB.
ttB.mainID = vcBID.
ttB.foreignKey = vcAID.
DEFINE VARIABLE vcCID as character no-undo.
DO viCounterC = 1 TO 9:
vcCID = vcBID + "C0" + STRING(viCounterC).
create ttC.
ttC.mainID = vcCID.
ttC.foreignKey = vcBID.
DEFINE VARIABLE vcDID as character no-undo.
DO viCounterD = 1 TO 9:
vcDID = vcCID + "D0" + STRING(viCounterD).
create ttD.
ttD.mainID = vcDID.
ttD.foreignKey = vcCID.
END.
END.
END.
END.
/* DISPLAY V1 */
for first ttB,
each ttC where ttC.foreignKey = ttB.mainID,
first ttD where ttD.foreignKey = ttC.mainID:
viTotal = viTotal + 1.
display
viTotal
ttD.mainID
.
end.
/* DISPLAY V2 */
for each ttA where ttA.mainID = "A01" or ttA.mainID = "A03",
first ttB where ttB.foreignKey = ttA.mainID,
each ttC where ttC.foreignKey = ttB.mainID,
first ttD where ttD.foreignKey = ttC.mainID:
viTotal2 = viTotal2 + 1.
display
viTotal2
ttD.mainID
.
end.
display
viTotal
viTotal2
.
It will be the same as:
define variable i as integer no-undo.
define variable j as integer no-undo.
define variable k as integer no-undo.
for each A no-lock:
i = i + 1.
for first B no-lock:
j = j + 1.
for each C no-lock where C.something = B.something:
k = k + 1.
display i j k.
end.
end.
end.

What is the easiest way to simulate an SQL "IN" statement

What is the easiest way to achieve the "IN" SQL functionality in Progress? Here is my code block, and I want to limit this query to 5 different pin numbers for my test. I'd rather not use a string of "OR"s if I can avoid that.
//using the "IN" way with specific pin numbers
FOR EACH names NO-LOCK WHERE names.m-date GE 1/1/1900: //AND names.pin IN (179,198,200,201,210)
FOR EACH nacminfo NO-LOCK WHERE nacminfo.pin = names.pin:
FIND FIRST nacmtype WHERE nacmtype.contact_type_num EQ nacminfo.contact_type_num
AND nacmtype.descr MATCHES ("~*" + "email") NO-LOCK NO-ERROR.
IF AVAILABLE nacmtype THEN DO:
DISPLAY
nacmtype.type_val
nacmtype.descr.
END.
END.
END.
As Stefan says, you can use LOOKUP but performance may suffer since you will need to compare every record with the list.
Using a series of OR comparisons can be very efficient and if the list is short and static (like your example) not at all hard to do.
If the list is longer or changes frequently or if it is held in a variable then you might consider iterating over the list outside the FOR EACH.
Something like this:
define variable i as integer no-undo.
define variable j as integer no-undo.
define variable n as integer no-undo.
define variable myList as character no-undo.
myList = "179,198,200,201,210".
n = num-entries( myList ).
do j = 1 to n:
FOR EACH names NO-LOCK WHERE names.m-date GE 1/1/1900 AND names.pin = entry( j, myList ):
FOR EACH nacminfo NO-LOCK WHERE nacminfo.pin = names.pin:
FIND FIRST nacmtype NO-LOCK
WHERE nacmtype.contact_type_num EQ nacminfo.contact_type_num
AND nacmtype.descr MATCHES ("~*" + "email") NO-ERROR.
IF AVAILABLE nacmtype THEN DO:
DISPLAY
nacmtype.type_val
nacmtype.descr.
END.
END.
END.
end.
Or, finally, transform the list into a temp-table. Something like this:
define temp-table tt_myList no-undo
  field namePIN as character
  index namePIN-idx is unique primary namePIN.
.
define variable i as integer no-undo.
define variable n as integer no-undo.
define variable myList as character no-undo.
myList = "179,198,200,201,210".
/* build a TT */
n = num-entries( myList ).
do i = 1 to n:
  create tt_myList.
  tt_myList.namePIN = entry( i, myList ).
end.
for each tt_myList:
FOR EACH names NO-LOCK WHERE names.m-date GE 1/1/1900 AND names.pin = tt_myList.repName:
FOR EACH nacminfo NO-LOCK WHERE nacminfo.pin = names.pin:
FIND FIRST nacmtype NO-LOCK
WHERE nacmtype.contact_type_num EQ nacminfo.contact_type_num
AND nacmtype.descr MATCHES ("~*" + "email") NO-ERROR.
IF AVAILABLE nacmtype THEN DO:
DISPLAY
nacmtype.type_val
nacmtype.descr.
END.
END.
END.
end.
You could join the TT in the FOR EACH but it won't really make any difference and, personally, I find the nested FOR EACH syntax more natural.
Do you really need that FIRST? Can there ever be more than one record in the result of that FIND?
Lastly, MATCHES isn't doing you any performance favors. Hopefully the other parts of the WHERE clause are narrowing the result set enough that its impact is minimal.
Beware of the performance, since a function on the left side generally cannot use an index, but you can use the lookup function:
for each names
where names.m-date ge 1/1/1990
and lookup( string( names.pin ), '179,198,200,201,210' ) ) > 0
no-lock:
// do something
end.
I'd rather not use a string of "OR"s if I can avoid that.
As Stefan notes, using a function in the WHERE clause means that you will not use any indexes. That will impact performance, possibly very badly.
Look into using dynamic queries to build a WHERE clause with a a bunch of OR names.pin = 179 phrases.
You will need to tweak the building of the where string to make sure that it uses the best indexes available (and that's a huge topic by itself). You can see what indexes are used via the LOG-MANAGER - see https://docs.progress.com/bundle/openedge-abl-troubleshoot-applications-122/page/Query-information-logged.html for some info on this.
define variable pins as integet extent 5 initial [179,198,200,201,210] no-undo.
define variable loop as integer no-undo.
define variable cnt as integer no-undo.
define variable whereString as character no-undo.
define query q1 for names.
whereString = ' for each names no-lock where names.m-date GE 1/1/1900'.
cnt = extent(pins).
do loop = 1 to cnt:
whereSTring = whereSTring + substitute(' OR names.pin = &1', pins[loop]).
end.
query q1:query-prepare(whereString).
query q1:query-open().
query q1:get-first().
do while available names:
// do something with the names
query q1:get-next().
end.
finally:
query q1:query-close().
end finally.

Display dynamic number of fields

I'm new to progress and I'm trying to learn dynamic queries. As a Task I gave to myself I want to read a csv file and create a table based on the contents of said file. So far everything works but I can't really seem to find a way to display the contents properly.
I have a temp-table created based on a csv input with the first line being the columns or fields of the table and everything after being the records.
Field1,Field2,Field3,Field4,Fieldn...
Value1.1,Value2.1,Value3.1,Value4.1,Valuen.1...
Value1.2,Value2.2,Value3.2,Value4.2,Valuen.1...
Value1.3,Value2.3,Value3.3,Value4.3,Valuen.1...
Value1.4,Value2.4,Value3.4,Value4.4,Valuen.1...
etc...
How can I display a dynamic number of fields and their names properly?
The following things are unknown:
Number of fields
Name of fields
Values of records
The following works and shows the data in the desired format (but it's hard coded):
DO WHILE qMyTable:GET-NEXT():
DISPLAY
bMyTable:BUFFER-FIELD(1):BUFFER-VALUE LABEL 'PK'
bMyTable:BUFFER-FIELD(2):BUFFER-VALUE LABEL 'Field1'
bMyTable:BUFFER-FIELD(3):BUFFER-VALUE LABEL 'Field2'
bMyTable:BUFFER-FIELD(4):BUFFER-VALUE LABEL 'Field3'
bMyTable:BUFFER-FIELD(5):BUFFER-VALUE LABEL 'Field4'
WITH FRAME f DOWN.
DOWN WITH FRAME f.
END.
I'm trying to loop over the buffer fields but I can't find a way to do it without redefining the DISPLAY command every iteration. Also I don't know how to display the labels of the fields in as a header row.
I'm looking for something like this :
/*
This doesn't work
*/
DO WHILE qMyTable:GET-NEXT():
DO i = 1 to iNumFields:
DISPLAY bMyTable:BUFFER-FIELD(i):BUFFER-VALUE LABEL cTitlerow[i].
END.
END.
This would be the full code:
/*
Variables
*/
DEF VAR i AS INTEGER INITIAL 0 NO-UNDO. //Counter
DEF VAR iEntry AS INTEGER INITIAL 0 NO-UNDO. //Counter2
DEF VAR cTitleRow AS CHARACTER NO-UNDO. //Fields csv
DEF VAR cDataRow AS CHARACTER NO-UNDO. //Entries csv
DEF VAR cFieldName AS CHARACTER NO-UNDO. //Field
DEF VAR iNumFields AS INTEGER NO-UNDO. //Amount of Fields
DEF VAR iNumLines AS INTEGER NO-UNDO. //Amount of records
DEF VAR cTitleArray AS CHARACTER EXTENT NO-UNDO. //Fields Array
/*
Handles
*/
DEF VAR ttMyTable AS HANDLE NO-UNDO. //Temp table
DEF VAR bMyTable AS HANDLE NO-UNDO. //Buffer
DEF VAR qMyTable AS HANDLE NO-UNDO. //Query
INPUT FROM 'C:\Path\To\CSV\mycsv.csv'.
/*
Get first row for fields and field names
*/
IMPORT UNFORMATTED cTitleRow.
iNumFields = NUM-ENTRIES(cTitleRow) + 1. //Additional field for PK
EXTENT(cTitleArray) = iNumFields.
/*
Dynamic table creation
*/
CREATE TEMP-TABLE ttMyTable.
ttMyTable:ADD-NEW-FIELD('PK', 'integer').
cTitleArray[1] = 'PK'.
DO i = 2 to iNumFields:
iEntry = i - 1.
cFieldName = ENTRY(iEntry,cTitleRow).
ttMyTable:ADD-NEW-FIELD(cFieldName, 'character').
cTitleArray[i] = cFieldName.
END.
/*
Adding and defining indexes
*/
ttMyTable:ADD-NEW-INDEX('idx', TRUE, TRUE).
ttMyTable:ADD-INDEX-FIELD('idx', 'PK', 'asc').
ttMyTable:TEMP-TABLE-PREPARE('myTable').
/*
Creating buffer
*/
bMyTable = ttMyTable:DEFAULT-BUFFER-HANDLE.
/*
Populating data
*/
REPEAT:
IMPORT UNFORMATTED cDataRow.
bMyTable:BUFFER-CREATE.
bMyTable::pk = iNumLines.
DO i = 2 to iNumFields:
iEntry = i - 1.
bMyTable:BUFFER-FIELD(i):BUFFER-VALUE = ENTRY(iEntry,cDataRow).
bMyTable:BUFFER-FIELD(i):COLUMN-LABEL = cTitleArray[i].
bMyTable:BUFFER-FIELD(i):LABEL = cTitleArray[i].
END.
iNumLines = iNumLines + 1.
END.
/*
Creating query
*/
CREATE QUERY qMyTable.
qMyTable:SET-BUFFERS(bMyTable).
qMyTable:QUERY-PREPARE('for each myTable').
qMyTable:QUERY-OPEN().
/*
/*
This doesn't work
*/
DO WHILE qMyTable:GET-NEXT():
DO i = 1 to iNumFields:
DISPLAY bMyTable:BUFFER-FIELD(i):BUFFER-VALUE.
END.
END.
*/
DO WHILE qMyTable:GET-NEXT():
DISPLAY
bMyTable:BUFFER-FIELD(1):BUFFER-VALUE LABEL 'PK'
bMyTable:BUFFER-FIELD(2):BUFFER-VALUE LABEL 'Field1'
bMyTable:BUFFER-FIELD(3):BUFFER-VALUE LABEL 'Field2'
bMyTable:BUFFER-FIELD(4):BUFFER-VALUE LABEL 'Field3'
bMyTable:BUFFER-FIELD(5):BUFFER-VALUE LABEL 'Field4'
WITH FRAME f DOWN.
DOWN WITH FRAME f.
END.
qMyTable:QUERY-CLOSE().
DELETE OBJECT qMyTable.
I think you only might have some frame issues. Your code should basically work.
This minor change with display your data but for it will display them all in one column.
DO WHILE qMyTable:GET-NEXT():
DO i = 1 to iNumFields:
DISPLAY bMyTable:BUFFER-FIELD(i):BUFFER-VALUE WITH FRAME f2 DOWN TITLE "Dynamic" .
DOWN WITH FRAME f2 .
END.
END.
So output will basically be
row1column1
row1column2
row1column3
...
row1columnN
row2column1
row2column2
row2column3
...
row2columnN
etc
Instead of
row1column1 row1column2 row1column3 ... row1columnN
row2column1 row2column2 row2column3 ... row2columnN
One idea to get the same result is to create a frame widget dynamically as well...
I'd do it this way. It would show the field value next to its name, and one record at a time. I hope this helps:
DO WHILE qMyTable:GET-NEXT():
DO i = 1 to iNumFields:
DISPLAY bMyTable:BUFFER-FIELD(i):NAME
bMyTable:BUFFER-FIELD(i):BUFFER-VALUE LABEL cTitlerow[i] WITH FRAME f DOWN.
DOWN WITH FRAME f.
END.
CLEAR FRAME f ALL.
END.
This should get you started on creating a frame and a "text" widgets dynamically:
define variable f as handle no-undo.
define variable t as handle no-undo.
define variable r as integer no-undo initial 1.
define variable c as integer no-undo initial 1.
create frame f.
assign
f:row = 4
f:column = 1
f:width-chars = 132
f:box = no
f:top-only = false
f:overlay = true
f:name = "something"
no-error.
create text t.
assign
t:frame = f
t:name = "text1"
t:format = substitute( "x(&1)", max( 1, 20, length( t:name )))
t:row = r
t:col = c
t:screen-value = "value1"
f:height-chars = max( r, f:height-chars )
.
f:visible = yes.
In your case you would probably want to create the frame just once at the top and then create 2 text widgets for each field - one for the label and one for the data values.

Code to add x minutes to time in fillin field

I'm a beginner to programming and to Progress, first post on StackOverflow, hope I'm posting in the right place!
I have a fillin field where I enter a time (hh:mm), character format. I also have two arrows, one pointing forward and one backward, and I want them to add / subtract 20 min respectively when pushed.
What would be a good way to write code for this? Turn the current time value to integer and seconds past midnight and then add /subtract 1200 sec? How would I get the result back to a hh:mm format to display in the fillin?
Any help greatly appreciated!
/Ellen
I think this will do what you need. Have the ON CHOOSE triggers on your arrow buttons run the changeMins procedure. Pass in the character time string from your fill-in and either "Add" or "Subtract". The output value will be the new adjusted time string. You can then set the screen value of your fill-in to that output value.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cTime = "12:45".
RUN changeMins (INPUT-OUTPUT cTime, INPUT "Add").
MESSAGE cTime VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
PROCEDURE changeMins:
DEFINE INPUT-OUTPUT PARAMETER pcTime AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcAction AS CHARACTER NO-UNDO.
DEFINE VARIABLE iHr AS INTEGER NO-UNDO.
DEFINE VARIABLE iMn AS INTEGER NO-UNDO.
/* Split the time string into hours and minutes */
ASSIGN
iHr = INTEGER(ENTRY(1, pcTime, ":"))
iMn = INTEGER(ENTRY(2, pcTime, ":"))
NO-ERROR.
IF ERROR-STATUS:ERROR THEN RETURN.
/* Adjust the time */
CASE pcAction:
WHEN "Add" THEN iMn = iMn + 20.
WHEN "Subtract" THEN iMn = iMn - 20.
END CASE.
/* Correct for boundaries */
IF iMn > 59 THEN
ASSIGN
iMn = iMn - 60
iHr = iHr + 1.
IF iMn < 0 THEN
ASSIGN
iMn = iMn + 60
iHr = iHr - 1.
IF iHr > 23 THEN iHr = iHr - 24.
IF iHr < 0 THEN iHr = iHr + 24.
/* Build the new time string */
pcTime = STRING(iHr, "99") + ":" + STRING(iMn, "99").
END PROCEDURE.
In this example, change the cTime string to different times and run it.
The Progress TIME function returns the integer number of seconds past midnight. So your idea of converting to such an integer is consistent with other uses within the 4gl which is a positive.
Personally I would keep the UI and the internal storage independent. So I would probably have a variable for the hour, another for the minute and a 3rd for seconds (if you need that). And I would use integers, rather than characters, for all 3.
I've no idea what version of Progress or what the environment that you are running in is but this quick and dirty little snippet might have some useful tidbits:
define variable hh as integer no-undo format ">9".
define variable mm as integer no-undo format "99".
define variable ss as integer no-undo format "99".
define variable myTime as integer no-undo.
form
hh mm ss
with
frame a
.
on value-changed of hh in frame a do:
if integer( self:screen-value ) > 23 then
do:
hh = 23.
display hh with frame a.
end.
end.
on value-changed of mm in frame a do:
if integer( self:screen-value ) > 59 then
do:
mm = 59.
display mm with frame a.
end.
end.
on value-changed of ss in frame a do:
if integer( self:screen-value ) > 59 then
do:
ss = 59.
display ss with frame a.
end.
end.
update hh mm ss with frame a.
myTime = (( hh * 3600 ) + ( mm * 60 ) + ss ).
display string( myTime, "hh:mm:ss am" ).
Deriving from TheDrooper's Code I would probably write something like the following code to fully utilize the built-in functions.
Shorter code that is still at least as easy to understand is often preferable.
Also consider that Progress does not have an optimizing compiler. If you can replace replace several simple statements with less statements (even if those are more complex and powerful than needed) the code is not only more maintainable but also faster.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cTime = "12:45".
RUN changeMins (INPUT-OUTPUT cTime, INPUT "Add").
MESSAGE cTime VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
PROCEDURE changeMins:
DEFINE INPUT-OUTPUT PARAMETER pcTime AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcAction AS CHARACTER NO-UNDO.
DEFINE VARIABLE iMn AS INTEGER NO-UNDO.
iMn = INTEGER(ENTRY(1, pcTime, ":")) * 60
+ INTEGER(ENTRY(2, pcTime, ":"))
NO-ERROR. /* Calculate minutes since midnight */
IF ERROR-STATUS:ERROR THEN RETURN.
/* Adjust the time */
CASE pcAction:
WHEN "Add" THEN iMn = iMn + 20.
WHEN "Subtract" THEN iMn = iMn - 20.
END CASE.
/* Build the new time string */
pcTime = string(iMn * 60, 'HH:MM'). /* Convert minutes to seconds and convert the result to a string */
END PROCEDURE.
If you need to allow for higher hour values (eg. if the field doesn't represent a time of day but a time interval) you can't just convert the resulting integer with the string function. In that case you could write
pcTime = string(iMn / 60, '99') + ':' + string(iMn mod 60, '99').
(Both TheDrooper and Tom Bascom seem to assume time of day.)

Dynamic Query in OpenEdge

Good day:
Quick question: Can I perform a dynamic query in OpenEdge?
Example:
def temp-table tt-num1
field f1 as int
field f2 as int.
def temp-table tt-num2
field f1 as int
field f2 as int.
def temp-table tt-num3
field f1 as int
field f2 as int.
What I need is something that looks like this:
procedure repeat-query:
for each 'variable that contains table-name' no-lock.
disp f1 f2.
end.
end procedure.
or some other way that can solve my problem.
How do I proceed with this? I tried to check for dynamic query on the Internet but with no luck. Thanks in advance.
If you go directly to https://documentation.progress.com/#page/progdocindex%2Fopenedge.html you can find documentation around everything OpenEdge. For instance dynamic queries.
I don't understand exactly what you try to do but here's an example of a dynamic query.
DEFINE TEMP-TABLE tt-num1 NO-UNDO
FIELD f1 AS INTEGER
FIELD f2 AS INTEGER.
DEFINE TEMP-TABLE tt-num2 NO-UNDO
FIELD f1 AS INTEGER
FIELD f2 AS INTEGER.
DEFINE TEMP-TABLE tt-num3 NO-UNDO
FIELD f1 AS INTEGER
FIELD f2 AS INTEGER.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 1
tt-num1.f2 = 1.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 1
tt-num1.f2 = 2.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 2
tt-num1.f2 = 1.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 2
tt-num1.f2 = 2.
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
DEFINE VARIABLE cBuffer AS CHARACTER NO-UNDO.
DEFINE VARIABLE cField AS CHARACTER NO-UNDO.
DEFINE VARIABLE iValue AS INTEGER NO-UNDO.
ASSIGN
cBuffer = "tt-num1"
cField = "f1"
iValue = 1.
CREATE QUERY hQuery.
hQuery:ADD-BUFFER(cBuffer).
hQuery:QUERY-PREPARE("for each " + cBuffer + " where " + cBuffer + "." + cField + " = " + STRING(iValue)).
hQuery:QUERY-OPEN().
queryLoop:
REPEAT:
hQuery:GET-NEXT().
IF hQuery:QUERY-OFF-END THEN LEAVE queryLoop.
DISPLAY hQuery:GET-BUFFER-HANDLE(1):BUFFER-FIELD(cField):BUFFER-VALUE.
END.
hQuery:QUERY-CLOSE().
DELETE OBJECT hQuery.
As Stefan Drissen mentions in a very valid comment: the loop can be more compact:
DO WHILE hQuery:GET-NEXT():
/* Code goes here */
END.

Resources