How to use BY keyword correctly in FOR EACH - progress-4gl

I want to go through the records in descending order by Field4, but when I put in the code BY mybuffer.Field4 DESCENDING I get the error
After "Field4 DESCENDING" not understandable (247) [translated].
FOR EACH mybuffer
WHERE mybuffer.Field1 = value1
AND mybuffer.Field2 = value2
AND mybuffer.Field3 >= value3
BY mybuffer.Field4 DESCENDING
USE-INDEX myindex
NO-LOCK:
/* do something */
END.
What I am doing wrong?

Although I'm not sure that the combination of USE-INDEX and BY is smart. But your problem is likely to be the order of options.
Use in this order:
USE-INDEX
NO-LOCK
BY

The error is the placement of USE-INDEX "After field 4 descending". Just like the error message says.
The correct syntax is:
for each customer no-lock use-index cust-num
where sales-rep = "BBB"
by city:
display customer.
end.
FWIW it is unlikely to be a good idea to have both USE-INDEX in a FOR EACH loop and even less likely to be a good idea to be combining it with BY. The compiler will probably choose a better index (or indexes plural) if you allow it to.

Related

How to update JSON node that matches criteria based on attribute value (instead of index)?

Postgresql 10+
Example from the documentation...
jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]', false)
results in...
[{"f1":[2,3,4],"f2":null},2,null,3]
Fair enough. But I need to find my target node by attribute value, not index. For the life of me, I cannot figure out how do something like...
jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{(where f1 = 1),f1}','[2,3,4]', false)
Any advice on how to accomplish this?
Thanks!
You can split the steps into two jobs:
Split in elements (jsonb_arral_elements)
Indentify wich elements must change (case when...)
Update that element (jsonb_set)
Join all together (jsonb_agg)
solution
select jsonb_agg(case when element->>'f1'='1' then jsonb_set(element, '{f1}', '[2,3,4]') else element end)
from jsonb_array_elements('[{"f1":1,"f2":null},2,null,3,{"f1":3},{"f1":1,"f2":2}]'::jsonb) element
note
I changed the input adding two more elements with "f1" key

How to convert "like each" into a functional form?

Let's say I have a column of a table whose data type is a character array. I want to pass in a functional select where clause, where the column is in a list of given strings. However, I cannot simply use (in; `col; myList) for reasons. Instead, I need to do the equivalent of:
max col like/: myList
which effectively gives the same result. However, I have tried to put this in functional form
(max; (like/:; `col; myList))
And I am getting a type error. Any ideas on how I could make this work?
A nice trick when dealing with this problem is using parse on a string of the select statement you want to functionalize. For example:
q)parse"select from t where max col like/: myList"
?
`t
,,(max;((/:;like);`col;`myList))
0b
()
Or specifically in your case you want the 3rd element of the result list (the where clause):
q)(parse"select from t where max col like/: myList")2
max ((/:;like);`col;`myList)
I even think using this pattern in your actual code can be a good idea, as functionalized statements like max ((/:;like);`col;`myList) can get pretty unreadable pretty quickly!
Hope that helps!
(any; ((/:;like); `col; enlist,myList))
it should be: (max;((/:;like);`col;`mylist))

progress 4gl :i want to avoid error messages while running the program

DEFINE TEMP-TABLE ttservice NO-UNDO
FIELD ad-num AS CHARACTER
INDEX ttprimary AS UNIQUE ad-num .
ASSIGN ttservice.ad-num = vehicles.ad-num NO-ERROR
In this, how to avoid error messages when i am adding duplicate records,
situation is:
when i try to add the duplicate records in temp table it doesnot accept,it is ok ,but it display error messages while running a program,iwant to suppres that error messages.,and avoid the duplicate adding records
You can test for the existence of a duplicate key before you try to create it.
(Filling in the blanks.)
DEFINE TEMP-TABLE ttservice NO-UNDO
FIELD ad-num AS CHARACTER /* you have a "num" field defined as character? that's misleading */
INDEX ttprimary AS UNIQUE ad-num .
for each vehicle no-lock: /* perhaps ad-num is non-unique in the vehicle table? */
find ttservice where ttservice.ad-num = vehicles.ad-num no-error.
if available ttservice then
do:
message "oops!". /* or whatever it is you want when a duplicate occurs... */
end.
else
do:
create ttservice.
ASSIGN ttservice.ad-num = vehicles.ad-num.
end.
end.
Here's another way to get unique ad-num values from the vehicles table:
DEFINE TEMP-TABLE ttservice NO-UNDO
FIELD ad-num AS CHARACTER
INDEX ttprimary AS UNIQUE ad-num .
FOR EACH vehicles NO-LOCK
BREAK BY vehicles.ad-num:
IF FIRST-OF(vehicles.ad-num) THEN
DO:
CREATE ttservice.
ASSIGN ttservice.ad-num = vehicles.ad-num.
END.
END.
Two valued answers have already been added by great professionals but i would like to add mine with minor changes.
def TEMP-TABLE ttservice NO-UNDO
FIELD iservid AS INT
INDEX tt-primary AS UNIQUE iservid.
VEHICLELOOP:
for each vehicles use-index <index-name>
NO-LOCK:
IF CAN-FIND(first ttservice
where ttservice.iservid = vehicles.iservid)
THEN
NEXT VEHICLELOOP.
ELSE DO:
create ttservice.
ASSIGN ttservice.iservid = vehicles.iservid.
END. /* VEHICLELOOP */
So I read the answers and think they're sufficient to fix your particular issue. But here's the general way of thinking you should assume when coding for Progress OpenEdge:
Adding no-error to statements (when they allow it) will "suppress errors", though sometimes they're inevitable and suppressing does no good to the stability of your application, always think of treating them (and displaying errors is a part of this).
Whether you choose to check for existence of a record prior to creation or just set your query to not iterate for undesired (repeated) records is up to you. I advise you to check for performance with different approaches (especially in doing a for each for a large table) to see which one is more satisfactory.
So here's my personal suggestion:
for each vehicles no-lock:
if can-find(first ttService where ttService.ad-num = vehicles.ad-num) then
next.
create ttService.
assign ttService.ad-num = vehicles.ad-num no-error.
if error-status:error then
message "Something went horribly wrong:" + error-status:get-message(1)
view-as alert-box error.
end.
In my example above, the error will only be shown if the assign actually fails. It is not likely to happen, I just wanted to show how the usage of no-error (and its treatment) works.
Anyway, hope it helps!

how to replace all the email id records with progress 4gl

The problem here is I want to update the email ids, I want to update like user#abc.com to user#xyz.com
I have selected all the email ids like this,
for each table where
table.email matches "*" + "#abc.com" + "*" no-lock :
Display
I can't use replace function, since each email ids will be of different length.
Is it possible to change email ids like this ? Please share with me.
Replacing exactly "abc" with "xyz" is done like this:
/* You need to change NO-LOCK to EXCLUSIVE-LOCK if you want to update or change! */
FOR EACH table WHERE table.email MATHES"*" + "#abc.com" + "*" EXCLUSIVE-LOCK:
ASSIGN
table.email = REPLACE(table.email, "#abc.com", "#xyz.com").
END.
But maybe you need to elaborate your question or is this all you want to do?
About performance
This query wont be very fast. Matches does not utilize any indices so the entire table will be scanned.
On later versions of Progress you can add a TABLE-SCAN option. This will increase speed but not by a lot. If you do this you will have to remove the MATCHES expression in the query and do like this:
FOR EACH table EXCLUSIVE-LOCK TABLE-SCAN:
IF table.email MATCHES etcetera
END.
END.
If this is a one time thing to fix e-mail addresses perhaps it doesn't need to be that fast? If not I suggest you add a logical field to the table (table.fixed) and create an index with the field in it. Then you can very fast go through all not fixed records.
I tried myself, and I wrote like this, and it worked.
def var cmail1 as char.
def var cmail2 as char.
assign
cmail1 = "#abc.com"
cmail2 = "#xyz.com".
for each table where
exclusive-lock :
Assign
table.email = REPLACE(table.email, cmail1, cmail2).
but the performance is low. If you any alternate for this, please post.

Sphinx SetSortMode EXPR

I am trying to sort using Sphinx (PHP) to show in order of price but when I do it will show £10 before £1.75 so I need to use ABS like in mySQL.
I have tried this:
$s->SetSortMode (SPH_SORT_EXPR, "ABS(display_price) ASC" );
It doesnt seem to work though.
Can anybody help?
Check, if display_price attribute treated as a decimal in search index
Probably you have
sql_attr_string = display_price
instead of
sql_attr_float = display_price
or
sql_attr_bigint = display_price
updated
SPH_SORT_EXPR is ALWAYS descending order. the ASC/DESC are for use with EXTENDED mode only.
To 'invert' it to become acsending, can build it into the expression.
$s->SetSortMode (SPH_SORT_EXPR, "1000000-CEIL(ABS(display_price*100.0))" );