Select Top 1 From a Table For Each row in another Table - openedge

I am just starting to work with openedge and I need to join information from two tables but I just need the first row from the second one.
Basically I need to do a typical SQL Cross Apply but in progress. I look in the documentation and the Statement FETCH FIRST 10 ROWS ONLY only in OpenEdge 11.
My query is:
SELECT * FROM la_of PUB.la_ofart ON la_of.empr_cod = la_ofart.empr_cod
AND la_of.Cod_Ordf = la_ofart.Cod_Ordf
AND la_of.Num_ordex = la_ofart.Num_ordex AND la_of.Num_partida = la_ofart.Num_partida
CROSS APPLY (
SELECT TOP 1 ofart.Cod_Ordf AS Cod_Ordf_ofart ,
ofart.Num_ordex AS Num_ordex_ofart
FROM la_ofart AS ofart
WHERE ofart.empr_cod = la_ofart.empr_cod
AND ofart.Num_partida = la_ofart.Num_partida
AND la_ofart.doc1_num = ofart.doc1_num
AND la_ofart.doc2_linha = ofart.doc2_linha
ORDER BY ofart.Cod_Ordf DESC) ofart
I am using SSMS to extract data from OE10 using an ODBC connector and querying to OE using OpenQuery.
Thanks for all help.

If I correctly understood your question, maybe you can use something like this. Maybe this isn't the best solution for your problem, but may suit your needs.
DEF BUFFER ofart FOR la_ofart.
DEF TEMP-TABLE tt-ofart NO-UNDO LIKE ofart
FIELD seq AS INT
INDEX ch-seq
seq.
DEF VAR i-count AS INT NO-UNDO.
EMPTY TEMP-TABLE tt-ofart.
blk:
FOR EACH la_ofart NO-LOCK,
EACH la_of NO-LOCK
WHERE la_of.empr_cod = la_ofart.empr_cod
AND la_of.Cod_Ordf = la_ofart.Cod_Ordf
AND la_of.Num_ordex = la_ofart.Num_ordex
AND la_of.Num_partida = la_ofart.Num_partida,
EACH ofart NO-LOCK
WHERE ofart.empr_cod = la_ofart.empr_cod
AND ofart.Num_partida = la_ofart.Num_partida
AND ofart.doc1_num = la_ofart.doc1_num
AND ofart.doc2_linha = la_ofart.doc2_linha
BREAK BY ofart.Cod_Ordf DESCENDING:
ASSIGN i-count = i-count + 1.
CREATE tt-ofart.
BUFFER-COPY ofart TO tt-ofart
ASSIGN ofart.seq = i-count.
IF i-count >= 10 THEN
LEAVE blk.
END.
FOR EACH tt-ofart USE-INDEX seq:
DISP tt-ofart WITH SCROLLABLE 1 COL 1 DOWN NO-ERROR.
END.

Related

Progess 4GL - How to filter multiple records using AND operator in one table field?

I want to check and filter only if the table has value1 = 005 and value1 = 009. But it seems below query is not helping me. I dont know where I am making mistakes. Kindly help to solve this. Note - I cannot use where not as it may have many different value stored in value1 field
DEFINE TEMP-TABLE test NO-UNDO
FIELD value1 AS CHARACTER
.
EMPTY TEMP-TABLE test.
CREATE test.
ASSIGN
value1 = "005".
CREATE test.
ASSIGN
value1 = "009".
CREATE test.
ASSIGN
value1 = "001".
FOR EACH test NO-LOCK
WHERE value1 <> ""
AND (value1 = "005" AND value1 = "009")
:
MESSAGE YES.
END.
You can use can-find
if can-find(first test WHERE value1 = "005")
AND can-find(first test WHERE value1 = "009")
then message yes.
It is safest to always use can-find(first if you're looking for a non-unique value
It looks like you're looking for an OR ooperation, rather than AND.
If you want to check if both records are present you could do :
DEFINE VARIABLE isPresent005 AS LOGICAL NO-UNDO.
DEFINE VARIABLE isPresent009 AS LOGICAL NO-UNDO.
DEFINE VARIABLE bothPresents AS LOGICAL NO-UNDO.
FIND FIRST test WHERE test.value1 = "005" NO-LOCK NO-ERROR.
isPresent005 = AVAIL test.
FIND FIRST test WHERE test.value1 = "009" NO-LOCK NO-ERROR.
isPresent009 = AVAIL test.
bothPresents = isPresent005 AND isPresent009.
But, if you only want to get these 2 records, you should use OR :
FOR EACH test WHERE test.value1 = "005" OR test.value1 = "009" NO-LOCK :
/*do stuff*/
END.
Another option if you are, maybe, looking for some additional fields might look something like this:
define buffer test005 for test.
define buffer test009 for test.
for each test005 no-lock where test005.customer = 1 and test005.value1 = "005",
each test009 no-lock where test009.customer = 1 and test009.value1 = "009":
display test005.customer.
end.
Use OR instead of AND to search the records...
This will return records if value1 = 005 OR value1 = 009.
FOR EACH test NO-LOCK
WHERE value1 <> ""
AND (value1 = "005" OR value1 = "009")
:
MESSAGE YES.
END.
Is not possible to search using your way, because value1 cannot be two values at once, it's always one OR another.

How add only required fields from table to dynamic temp table? - PROGRESS 4GL

I am new to progress 4gl and below is the query used to add all fields from a table to dynamic temp table except few fields but I am not sure how to add only required fields to dynamic temp table. Please help to modify the query I shared.
/* p-ttdyn2.p - a join of 2 tables */
DEFINE VARIABLE tth4 AS HANDLE.
DEFINE VARIABLE btth4 AS HANDLE.
DEFINE VARIABLE qh4 AS HANDLE.
DEFINE VARIABLE bCust AS HANDLE.
DEFINE VARIABLE bOrder AS HANDLE.
DEFINE VARIABLE i AS INTEGER.
DEFINE VARIABLE fldh AS HANDLE EXTENT 15.
bCust = BUFFER customer:HANDLE.
bOrder = BUFFER order:HANDLE.
CREATE TEMP-TABLE tth4.
tth4:ADD-FIELDS-FROM(bCust,"address,address2,phone,city,comments").
tth4:ADD-FIELDS-FROM(bOrder,"cust-num,carrier,instructions,PO,terms").
tth4:TEMP-TABLE-PREPARE("CustOrdJoinTT").
btth4 = tth4:DEFAULT-BUFFER-HANDLE.
FOR EACH customer WHERE cust.cust-num < 6, EACH order OF customer:
btth4:BUFFER-CREATE.
btth4:BUFFER-COPY(bCust).
btth4:BUFFER-COPY(bOrder).
END.
/* Create Query */
CREATE QUERY qh4.
qh4:SET-BUFFERS(btth4).
qh4:QUERY-PREPARE("for each CustOrdJoinTT").
qh4:QUERY-OPEN.
REPEAT WITH FRAME zz DOWN:
qh4:GET-NEXT.
IF qh4:QUERY-OFF-END THEN LEAVE.
REPEAT i = 1 TO 15:
fldh[i] = btth4:BUFFER-FIELD(i).
DISPLAY fldh[i]:NAME FORMAT "x(15)"
fldh[i]:BUFFER-VALUE FORMAT "x(20)".
END.
END.
btth4:BUFFER-RELEASE.
DELETE OBJECT tth4.
DELETE OBJECT qh4.
ADD-FIELDS-FROM only supports excluding fields that are not needed. Instead you can use ADD-LIKE-FIELD multiple times:
CREATE TEMP-TABLE tth4.
tth4:ADD-LIKE-FIELD("address", "customer.address").
tth4:ADD-LIKE-FIELD("address2", "customer.address2").
tth4:ADD-LIKE-FIELD("phone", customer.phone").
...
tth4:ADD-LIKE-FIELD("cust-num", "Order.cust-num").
...
tth4:TEMP-TABLE-PREPARE("CustOrdJoinTT").
btth4 = tth4:DEFAULT-BUFFER-HANDLE.
Depending on your use case, you can also invert the required field list to an except field list:
var handle ht,hb.
var longchar lcjson.
function invertFields returns character (
i_hb as handle,
i_crequired as char
):
var char cexcept,cfield.
var int ic.
do ic = 1 to i_hb:num-fields:
cfield = i_hb:buffer-field( ic ):name.
if lookup( cfield, i_crequired ) = 0 then
cexcept = cexcept + ',' + cfield.
end.
return substring( cexcept, 2 ).
end function.
create temp-table ht.
ht:add-fields-from(
buffer customer:handle,
invertFields( buffer customer:handle, "CustNum,Name" )
).
ht:temp-table-prepare( 'tt' ).
hb = ht:default-buffer-handle.
hb:buffer-create().
assign
hb::CustNum = 1
hb::Name = 'test'
.
hb:write-json( 'longchar', lcjson, true ).
message string( lcjson ).
https://abldojo.services.progress.com/?shareId=624993253fb02369b25437c4

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.

Converting an PLSQL select statement into a update

Guys I have such a problem.
I know how to write a good select statement but I have no idea how to turn it into a corresponding update.
Im still learning plsql
Here is my select
select * --count(*)
from POLISY_OT ot
join polisy p on p.poli_id = ot.ot_poli_id
join sou.rai_skl rs on rs.ot_id = ot.ot_id
where ot_under_promil = 0
and ot_skladka_rok <> ot_skladka_netto_rok
and ot_rodzaj_um = 'OP'
and ot_rodzaj = 'D'
and ot_produkt_id = 17
and p.poli_status in ('AK', 'CZ')
and rs.skl_roczna = ot.ot_skladka_rok;
now I would like to wrap it up with an update and create something like this
update (
select * --count(*)
from POLISY_OT ot
join polisy p on p.poli_id = ot.ot_poli_id
join sou.rai_skl rs on rs.ot_id = ot.ot_id
where ot_under_promil = 0
and ot_skladka_rok <> ot_skladka_netto_rok
and ot_rodzaj_um = 'OP'
and ot_rodzaj = 'D'
and ot_produkt_id = 17
and p.poli_status in ('AK', 'CZ')
and rs.skl_roczna = ot.ot_skladka_rok)
set ot_skladka_rok = ot_skladka_netto_rok;
First, I really hope you aren't a student asking for homework help. That really bugs me.
On the assumption it's not, it's a little hard to tell exactly which columns belong to which tables, given that you didn't include the table aliases throughout.
I read this as that you wanted to update a column based on the value in another table, restricting the table updates to records that match a third table).
So I think you want something like this:
UPDATE polisy_ot ot
SET ot_skladka_rok =
(SELECT ot_skladka_netto_rok
FROM sou.rai_skl rs
WHERE rs.ot_id = ot.ot_id
AND rs.skl_roczna = ot.ot_skladka_rok)
WHERE ot_under_promil = 0
AND ot_skladka_rok <> ot_skladka_netto_rok
AND ot_rodzaj_um = 'OP'
AND ot_rodzaj = 'D'
AND ot_produkt_id = 17
AND EXISTS (SELECT NULL
FROM polisy p
WHERE p.poli_id = ot.ot_poli_id
AND p.poli_status IN ('AK', 'CZ'))
Good luck,
Stew

sqlite - how do I get a one row result back? (luaSQLite3)

How can I get a single row result (e.g. in form of a table/array) back from a sql statement. Using Lua Sqlite (LuaSQLite3). For example this one:
SELECT * FROM sqlite_master WHERE name ='myTable';
So far I note:
using "nrows"/"rows" it gives an iterator back
using "exec" it doesn't seem to give a result back(?)
Specific questions are then:
Q1 - How to get a single row (say first row) result back?
Q2 - How to get row count? (e.g. num_rows_returned = db:XXXX(sql))
In order to get a single row use the db:first_row method. Like so.
row = db:first_row("SELECT `id` FROM `table`")
print(row.id)
In order to get the row count use the SQL COUNT statement. Like so.
row = db:first_row("SELECT COUNT(`id`) AS count FROM `table`")
print(row.count)
EDIT: Ah, sorry for that. Here are some methods that should work.
You can also use db:nrows. Like so.
rows = db:nrows("SELECT `id` FROM `table`")
row = rows[1]
print(row.id)
We can also modify this to get the number of rows.
rows = db:nrows("SELECT COUNT(`id`) AS count FROM `table`")
row = rows[1]
print(row.count)
Here is a demo of getting the returned count:
> require "lsqlite3"
> db = sqlite3.open":memory:"
> db:exec "create table foo (x,y,z);"
> for x in db:urows "select count(*) from foo" do print(x) end
0
> db:exec "insert into foo values (10,11,12);"
> for x in db:urows "select count(*) from foo" do print(x) end
1
>
Just loop over the iterator you get back from the rows or whichever function you use. Except you put a break at the end, so you only iterate once.
Getting the count is all about using SQL. You compute it with the SELECT statement:
SELECT count(*) FROM ...
This will return one row containing a single value: the number of rows in the query.
This is similar to what I'm using in my project and works well for me.
local query = "SELECT content FROM playerData WHERE name = 'myTable' LIMIT 1"
local queryResultTable = {}
local queryFunction = function(userData, numberOfColumns, columnValues, columnTitles)
for i = 1, numberOfColumns do
queryResultTable[columnTitles[i]] = columnValues[i]
end
end
db:exec(query, queryFunction)
for k,v in pairs(queryResultTable) do
print(k,v)
end
You can even concatenate values into the query to place inside a generic method/function.
local query = "SELECT * FROM ZQuestionTable WHERE ConceptNumber = "..conceptNumber.." AND QuestionNumber = "..questionNumber.." LIMIT 1"

Resources