Reputation: 119
I have one sql table something like this
fist_name|last_name
asd | fgh
qwer | ijkl
yuio | zsef
... | ....
The data is arranged sequentially like the above,
I am assigning the retrieved value to some string (let Str) for each row.
I want to create a function with parameters (String str, mode)
such that ,
if mode = 'GET', It will assign the first row to the string,
and
if mode = 'NEXT', It will read the string then assign the next row to the string
and return the string. I have DB on oracle. Also I can't modify the DB (else I would have add some ID related to each entry)
It could be done using if-else for small table, but for large table it's not feasible. Is there some way by which I could get the exact row_number assigned to each rows.
eg:
f1("qwerijkl","NEXT) => "yuiozssef";
f1("",'GET') -> "asdfgh";
Upvotes: 0
Views: 1610
Reputation: 6751
You may use subquery with offset ... fetch first ...
with additional check of previous value. But if you need to process all the values, then it would be better to open a cursor with order by
, then fetch row by row and process the result.
Below is the code. You may use the query from function definition and replace parameters with bind variables.
select * from t
FIRST_NAME | LAST_NAME :--------- | :-------- 0001 | 1 0002 | 22 0003 | 333 0004 | 4444 0005 | 55555 0006 | 666666 0007 | 7777777 0008 | 88888888 0009 | 999999999
with function f( p_name varchar2 , p_method varchar2 ) return varchar2 as l_res varchar2(1000); begin with a as ( select first_name || last_name as res , lag(first_name || last_name) over(order by first_name || last_name) as prev_ from t where first_name || last_name >= p_name or p_method = 'GET' order by 1 offset decode(p_method, 'GET', 0, 'NEXT', 1) rows fetch next 1 rows only ) select max(res) into l_res from a
. /Additional check of previous value/ where prev_ = p_name or p_method = 'GET' ;
return l_res; end; select f('', 'GET') as get1, f('qwe', 'GET') as get2, f('000222', 'NEXT') as next1, f('12', 'NEXT') as next2 from dual
GET1 | GET2 | NEXT1 | NEXT2 :---- | :---- | :------ | :---- 00011 | 00011 | 0003333 | null
db<>fiddle here
Upvotes: 1
Reputation: 95080
You need an order. first_name, last_name
or last_name, first_name
would be appropriate choices here.
A function could look like this:
CREATE OR REPLACE FUNCTION get_name
(
p_mode in varchar2,
p_firstname in out varchar2,
p_lastname in out varchar2
) RETURN VARCHAR2 AS
BEGIN
SELECT p_firstname, p_lastname
INTO p_firstname, p_lastname
from names
WHERE p_mode = 'GET'
OR firstname > p_firstname
OR (firstname = p_firstname and lastname > p_lastname)
ORDER BY firstname, lastname
FETCH FIRST ROW ONLY;
RETURN 'okay';
EXCEPTION WHEN NO_DATA_FOUND THEN
RETURN 'no more rows';
END get_name;
(It may be faster, though, to create a materialized view instead with ROW_NUMBER
where GET
would simply get the row #1, while NEXT
would retrieve the row number for the name given and select the name for the following number.)
Here is a script to test above function:
DECLARE
v_firstname varchar2(100);
v_lastname varchar2(100);
v_result varchar2(100);
BEGIN
v_result := get_name('GET', v_firstname, v_lastname);
IF v_result = 'okay' then
dbms_output.put_line(v_firstname || ' ' || v_lastname);
END IF;
WHILE v_result = 'okay' LOOP
v_result := get_name('NEXT', v_firstname, v_lastname);
IF v_result = 'okay' then
dbms_output.put_line(v_firstname || ' ' || v_lastname);
END IF;
END LOOP;
END;
Upvotes: 1
Reputation: 565
Sorting using rowid is not good, you'd better set another sort column
select aa into res from (
select t1.first_name || t1.last_name aa
from
(
select *
from tab t1
where 'GET' = P_MODE
order by t1.rowid
) t1
where rownum = 1
union all
select t1.res
from (
select lead(t1.first_name || t1.last_name) over(order by t1.rowid) res,
t1.*
from tab1 t1
where 'NEXT' = P_MODE
) t1
where t1.first_name || t1.last_name = P_STR
) t1
If you use a function, you can put this SQL into the function, or you can query it directly.
Upvotes: 1