Cursor with iteration to grab data from last row PL/SQL - oracle

I have a test script that I'm beggining to play with. I'm getting stuck with something that seems simple.
I want to iterate through rows to fetch data from last row of result set to use only it.
procedure e_test_send
is
cursor get_rec is
select
id,
email_from,
email_to,
email_cc,
email_subject,
email_message
from test_email_tab;
begin
for rec_ in get_rec loop
ifsapp.send_email_api.send_html_email(rec_.email_to,rec_.email_from, rec_.email_subject, rec_.email_message);
end loop;
end e_test_send;
All I'm trying to do is send an email with a message and to a person from the last row only. This is a sample table that will grow in records. At the minute I have 2 rows of data in it, if I execute this procedure it will send 2 emails which is not the desired action.
I hope this makes sense.
Thanks

Do you know which row is the last row? The one with the MAX(ID) value? If so, then you could base cursor on a straightforward
SELECT id,
email_from,
email_to,
email_cc,
email_subject,
email_message
FROM test_email_tab
WHERE id = (SELECT MAX (id) FROM test_email_tab)
As it scans the same table twice, its performance will drop as number of rows gets higher and higher. In that case, consider
WITH
temp
AS
(SELECT id,
email_from,
email_to,
email_cc,
email_subject,
email_message,
ROW_NUMBER () OVER (ORDER BY id DESC) rn
FROM test_email_tab)
SELECT t.id,
t.email_from,
t.email_to,
t.email_cc,
t.email_subject,
t.email_message
FROM temp t
WHERE t.rn = 1
which does it only once; sorts rows by ID in descending order and returns the one that ranks as the "highest" (i.e. the last).

Related

Oracle APEX apex_string.split order by

Does apex_string.split always guarantee that the order of the rows returned is the order of the characters of the string ?
Can I rely on the rownum to always correspond to 1 for the first character of the split string ?
or do I need to add a order by rownum ?
What is the method to get the rows in the same order of the characters of the string ?
My requirement is to insert the rows returned by apex_string.split in the same order as the characters of the string.
I am currently executing the below, will this maintain the character order ?
select t.column_value value, rownum seq
from table(apex_string.split('test','')) t
bulk collect into ins_arr;
for i in ins_arr.first..ins_arr.last
loop
/* execute insert statement */
insert into table (seq, value )
values (ins_arr.seq,ins_arr.value);
end loop
The insert should result in
seq
value
1
t
2
e
3
s
4
t
Thank you in advance,
I don't think it's guaranteed, becuase if it was, it would be in the documentation. But I think you can accomplish what you want by changing your routine. (Note, I have not verified this.)
insert into table (seq, value)
select t.column_value value,
row_number() over (order by t.column_value)
from table(apex_string.split('test','')) t
I think you can do the same with rownum, but I'm never 100% sure what order the rownum and the order by happen in.

HIVE equivalent of FIRST and LAST

I have a table with 3 columns:
table1: ID, CODE, RESULT, RESULT2, RESULT3
I have this SAS code:
data table1
set table1;
BY ID, CODE;
IF FIRST.CODE and RESULT='A' THEN OUTPUT;
ELSE IF LAST.CODE and RESULT NE 'A' THEN OUTPUT;
RUN;
So we are grouping the data by ID and CODE, and then writing to the dataset if certain conditions are met. I want to write a hive query to replicate this. This is what I have:
proc sql;
create table temp as
select *, row_number() over (partition by ID, CODE) as rowNum
from table1;
create table temp2 as
select a.ID, a.CODE, a.RESULT, a.RESULT2, a.RESULT3
from temp a
inner join (select ID, CODE, max(rowNum) as maxRowNum
from temp
group by ID, CODE) b
on a.ID=b.ID and a.CODE=b.CODE
where (a.rowNum=1 and a.RESULT='A') or (a.rowNum=b.maxRowNum and a.RESULT NE 'A');
quit;
There are two issues I see with this.
1) The row that is first or last in each BY group is entirely dependant on the order of rows in table1 in SAS, we aren't ordering by anything. I don't think row order is preserved when translating to a hive query.
2) The SAS code is taking the first row in each BY GROUP or the last, not both. I think that my HIVE query is taking both, resulting in more rows than I want.
Any suggestions or insight on how to improve my query is appreciated. Is it even possible to replicate this SAS code in HIVE?
The SAS code has a by statement (BY ID CODE;), which tells SAS that the set dataset is sorted at those levels. So, not a random selection for first. and last..
That said, we can replicate this in HIVE by using the first_value and last_value window functions.
FIRST.CODE should replicate to
first_value(code) over (partition by Id order by code)fcode
Similarly, LAST.CODE would be
last_value(code) over (partition by Id order by code)lcode
Once you have the fcode and lcode columns, use case when statements for the result column criteria. Like,
case when (code=fcode and result='A') or (code=lcode and result<>'A')
then 1 else 0 end as op_flag
Then the fetch the table with where op_flag = 1
SAMPLE
select id, code, result from (
select *,
first_value(code) over (partition by id order by code)fcode,
last_value(code) over (partition by id order by code)lcode
from footab) f
where (code=fcode and result='A') or (code=lcode and result<>'A')
Regarding point 1) the BY group processing requires the input data to be sorted or indexed on BY variables, so though the code contains no ordering, the source data is processed in order. If the input data was not indexed/sorted, SAS will throw error.
Regarding this, possible differences are on rows with same values of BY variables, especially if the RESULT is different.
In SAS, I would pre-sort data by ID, CODE, RESULT, then use BY ID CODE in order to not be influenced by order of rows.
Regarding 2) FIRST and LAST can be both true in SAS. Since your condition for first and last on RESULT is different, I guess this is not a source of differences.
I guess you could add another field as
row_number() over (partition by ID, CODE desc) as rowNumDesc
to detect last row with rowNumDesc = 1 (so that you skip the join).
EDIT:
I think the two programs above both include random selection of rows for groups with same values of ID and CODE variables, especially with same values of RESULT. But you should get same number of rows from both. If not, just debug it.
However the random aspect in SAS code/storage is based on physical order of rows, while the ROW_NUMBERs randomness within a group will be influenced by the implementation of the function in the engine.

Getting value from cursor

I recently started studying PL/SQL and I found myself with this problem. I have a cursor that has the data for a specific person selected via code from another table. I'm fetching all the months with the salary of that person and outputting them. Now I need to make an avg() of all of the salaries. How do I do that ? Can I get only that column from the cursor. I could have counted the rows and divided the total sum of the salaries on that count and I can make another select but is there any faster way ?
You don't need cursor to get average salary as single value. You need to use aggregate functions. And even if you needed a cursor to, lets say, return data to calling code, you still use aggregate function to calculate averages and return cursor of that.
This is just an example
Declare
v_avgSal employees.salary%TYPE;
Begin
SELECT AVG(salary) into v_avgSal
FROM employees
WHERE employee_id = 10 And
(payDate >= To_Date('01012015', 'MMDDYYYY') AND payDate < To_Date('01012016', 'MMDDYYYY'));
Dbms_Output.Put_Line(v_avgSal);
End;

previous row oracle in cursor

I'm here because i could not finde anywhere else if there is a way to return the previous value in a loop (Cursor) to compare with the current value, for instance..
Cursor.Value = Cursor-1.Value;
It's bacause i have several contract numbers that i need to send by mail to the Business sector, but, in order to resume all the rows i want to compare if the current contract number are the same as the last contract number and validate it to dont send duplicated contract numbers.
Exemple of Record that i to skip in order to send no duplicate "Order Numbers": (Order_Number is my Key, not a sequencial numeric id):
cCursor.Value = cCursor-1.Value
cCursor.(111) = cCursor-1.(111)
Exemple of Record that i want to save in order to send as a processed "Order Number": (Order_Number is my Key, not a sequencial numeric id):
cCursor.Value = cCursor-1.Value
cCursor.(132) = cCursor-1.(111)
My Regards.
You cant reference backwards. Th easiest alternative is to store the key value (contract_id) in a variable and have logic like:
DECLARE
CURSOR c1 IS .....;
vLastContractID NUMBER := 0;
BEGIN
FOR r1 IN c1 LOOP
IF vLastContractID != r1.CONTRACT_ID THEN
-- do something
vLastContractID := r1.CONTRACT_ID;
END IF;
END LOOP;
END;
It's not entirely clear what you are asking.
A cursor is a forward-only structure. You cannot fetch a prior row, just the next row (or set of rows). Your query, however, can certainly include data from prior rows using the lag function. For example, this will show you the ename for the prior row in your result
SELECT empno, ename, lag(ename) over (order by empno) prior_ename
FROM emp
ORDER BY empno
In a PL/SQL loop, you can also obviously have a local variable that has the data from the previous row that was fetched and use that to compare against the data from the most current row.
Please use ANALYTICAL function to check for prior or post rows. LEAD,LAG functions are best way to do this.

How to? Correct sql syntax for finding the next available identifier

I think I could use some help here from more experienced users...
I have an integer field name in a table, let's call it SO_ID in a table SO, and to each new row I need to calculate a new SO_ID based on the following rules
1) SO_ID consists of 6 letters where first 3 are an area code, and the last three is the sequenced number within this area.
309001
309002
309003
2) so the next new row will have a SO_ID of value
309004
3) if someone deletes the row with SO_ID value = 309002, then the next new row must recycle this value, so the next new row has got to have the SO_ID of value
309002
can anyone please provide me with either a SQL function or PL/SQL (perhaps a trigger straightaway?) function that would return the next available SO_ID I need to use ?
I reckon I could get use of keyword rownum in my sql, but the follwoing just doens't work properly
select max(so_id),max(rownum) from(
select (so_id),rownum,cast(substr(cast(so_id as varchar(6)),4,3) as int) from SO
where length(so_id)=6
and substr(cast(so_id as varchar(6)),1,3)='309'
and cast(substr(cast(so_id as varchar(6)),4,3) as int)=rownum
order by so_id
);
thank you for all your help!
This kind of logic is fraught with peril. What if two sessions calculate the same "next" value, or both try to reuse the same "deleted" value? Since your column is an integer, you'd probably be better off querying "between 309001 and 309999", but that begs the question of what happens when you hit the thousandth item in area 309?
Is it possible to make SO_ID a foreign key to another table as well as a unique key? You could pre-populate the parent table with all valid IDs (or use a function to generate them as needed), and then it would be a simple matter to select the lowest one where a child record doesn't exist.
well, we came up with this... sort of works.. concurrency is 'solved' via unique constraint
select min(lastnumber)
from
(
select so_id,so_id-LAG(so_id, 1, so_id) OVER (ORDER BY so_id) AS diff,LAG(so_id, 1, so_id) OVER (ORDER BY so_id)as lastnumber
from so_miso
where substr(cast(so_id as varchar(6)),1,3)='309'
and length(so_id)=6
order by so_id
)a
where diff>1;
Do you really need to compute & store this value at the time a row is inserted? You would normally be better off storing the area code and a date in a table and computing the SO_ID in a view, i.e.
SELECT area_code ||
LPAD( DENSE_RANK() OVER( PARTITION BY area_code
ORDER BY date_column ),
3,
'0' ) AS so_id,
<<other columns>>
FROM your_table
or having a process that runs periodically (nightly, for example) to assign the SO_ID using similar logic.
If your application is not pure sql, you could do this in application code (ie: Java code). This would be more straightforward.
If you are recycling numbers when rows are deleted, your base table must be consulted when generating the next number. "Legacy" pre-relational schemes that attempt to encode information in numbers are a pain to make airtight when numbers must be recycled after deletes, as you say yours must.
If you want to avoid having to scan your table looking for gaps, an after-delete routine must write the deleted number to a separate table in a "ReuseMe" column. The insert routine does this:
begins trans
selects next-number table for update
uses a reuseme number if available else uses the next number
clears the reuseme number if applicable or increments the next-number in the next-number table
commits trans
Ignoring the issues about concurrency, the following should give a decent start.
If 'traffic' on the table is low enough, go with locking the table in exclusive mode for the duration of the transaction.
create table blah (soc_id number(6));
insert into blah select 309000 + rownum from user_tables;
delete from blah where soc_id = 309003;
commit;
create or replace function get_next (i_soc in number) return number is
v_min number := i_soc* 1000;
v_max number := v_min + 999;
begin
lock table blah in exclusive mode;
select min(rn) into v_min
from
(select rownum rn from dual connect by level <= 999
minus
select to_number(substr(soc_id,4))
from blah
where soc_id between v_min and v_max);
return v_min;
end;

Resources