Implement a sequence that increments by value of a function - oracle

I am looking to create a simple sequence:
CREATE SEQUENCE supplier_seq
MINVALUE 1
START WITH 3
INCREMENT BY 4
CACHE 20000;
However, I want to semi-randomize the incrementing values with a function determined by some factors implemented on the java side.
I thought the first step for testing this might look something like this:
CREATE SEQUENCE supplier_seq
MINVALUE 1
START WITH 3
INCREMENT BY myfunction
CACHE 20000;
I tried this:
CREATE SEQUENCE supplier_seq
MINVALUE 1
START WITH 3
INCREMENT BY (declare
rtrnt number;
begin
rtrnt :=semiRandomize();
end;
)
CACHE 20000;
which I realize is ridiculous.. but there must be some way to do something like this. Any pointers?

How about this? Not really a sequence, but - might suit your needs. It is a function that selects a random number and stores it into a table with a primary key. If the number has already been used, it is skipped. The function checks whether all numbers have been used; if so, it raises an error.
In this example, I'm creating a 5-numbers "sequence" (so that it fails soon enough).
Table & function:
SQL> create table t_seq (supseq number constraint pk_tseq primary key);
Table created.
SQL> create or replace function f_supseq
2 return number
3 as
4 l_range number := 5; -- number of values in a "sequence"
5 l_seq number; -- a new "sequence" number
6 l_cnt number; -- number of used numbers
7 pragma autonomous_transaction;
8 begin
9 select count(*) into l_cnt from t_seq;
10 if l_cnt < l_range then
11 -- there are still some available numbers so - let's get them
12
13 -- don't let anyone mess with the table
14 lock table t_seq in exclusive mode;
15 while l_seq is null loop
16 begin
17 insert into t_seq (supseq) values
18 (round(dbms_random.value(1, l_range)))
19 returning supseq into l_seq;
20 exception
21 when dup_val_on_index then
22 -- that number has already been used; skip it
23 null;
24 end;
25 end loop;
26 commit;
27 else
28 raise_application_error(-20001, 'No more available numbers');
29 end if;
30
31 return l_seq;
32 end;
33 /
Function created.
Fetching random "sequence" values:
SQL> select f_supseq from dual;
F_SUPSEQ
----------
2
SQL> select f_supseq from dual;
F_SUPSEQ
----------
4
SQL> select f_supseq from dual;
F_SUPSEQ
----------
1
SQL> select f_supseq from dual;
F_SUPSEQ
----------
3
SQL> select f_supseq from dual;
F_SUPSEQ
----------
5
SQL> select f_supseq from dual;
select f_supseq from dual
*
ERROR at line 1:
ORA-20001: No more available numbers
ORA-06512: at "SCOTT.F_SUPSEQ", line 28
Table contents:
SQL> select * From t_seq;
SUPSEQ
----------
1
2
3
4
5
SQL>

Related

I want to fetch column values from column name rollnoofstud of tableA using PL/SQL

DECLARE
TYPE norollno IS TABLE OF VARCHAR2(100);
rollno norollno;
BEGIN
BEGIN
SELECT token
BULK COLLECT INTO rollno
FROM tableA
WHERE columname='rollnoofstud';
EXCEPTION
WHEN NO_DATA_FOUND THEN
rollno := norollno();
END ;
IF rollno >0 THEN
FOR i IN rollno.FIRST..norollno.LAST
LOOP
<doSomeThing>
END LOOP;
END IF;
END;
I am trying this but I am not getting output. I doubt if my select statement is correct.
I don't have your table so I created one:
SQL> CREATE TABLE tablea
2 AS
3 SELECT ename AS token, 'rollnoofstud' AS columname
4 FROM emp
5 WHERE deptno = 10;
Table created.
Code you posted isn't that wrong; requires a little bit of fixing (see line #17, the way you check whether collection contains something (count it!); typo in FOR loop):
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 TYPE norollno IS TABLE OF VARCHAR2 (100);
3
4 rollno norollno;
5 BEGIN
6 BEGIN
7 SELECT token
8 BULK COLLECT INTO rollno
9 FROM tableA
10 WHERE columname = 'rollnoofstud';
11 EXCEPTION
12 WHEN NO_DATA_FOUND
13 THEN
14 rollno := norollno ();
15 END;
16
17 IF rollno.COUNT > 0
18 THEN
19 FOR i IN rollno.FIRST .. rollno.LAST
20 LOOP
21 DBMS_OUTPUT.put_line (rollno (i));
22 END LOOP;
23 END IF;
24 END;
25 /
CLARK --> here's the result
KING
MILLER
PL/SQL procedure successfully completed.
SQL>
[EDIT: with your sample table and data:]
(note that there's no text datatype in Oracle!)
SQL> CREATE TABLE students
2 (
3 rollnostud INTEGER PRIMARY KEY,
4 name VARCHAR2 (10) NOT NULL,
5 gender VARCHAR2 (1) NOT NULL
6 );
Table created.
SQL> INSERT INTO students
2 VALUES (1, 'Ryan', 'M');
1 row created.
SQL> INSERT INTO students
2 VALUES (2, 'Joanna', 'F');
1 row created.
SQL> INSERT INTO students
2 VALUES (3, 'John', 'M');
1 row created.
Procedure:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 TYPE norollno IS TABLE OF VARCHAR2 (100);
3
4 rollno norollno;
5 BEGIN
6 BEGIN
7 SELECT name
8 BULK COLLECT INTO rollno
9 FROM students;
10 EXCEPTION
11 WHEN NO_DATA_FOUND
12 THEN
13 rollno := norollno ();
14 END;
15
16 IF rollno.COUNT > 0
17 THEN
18 FOR i IN rollno.FIRST .. rollno.LAST
19 LOOP
20 DBMS_OUTPUT.put_line (rollno (i));
21 END LOOP;
22 END IF;
23 END;
24 /
Ryan
Joanna
John
PL/SQL procedure successfully completed.
SQL>

Checking of current system date in static table

I am new to pl/sql blocks..i have a requirement inside my proc as below:
I have a static data table tb_calender having column extract_date with DATE type. Want to check if the current system date is present in the table tb_calender.extract_date . If not present, exit the proc with logging error.
Please suggest on this.
A function might be more appropriate (so that you'd let the caller know whether today's date exists in the table or not). You could return a Boolean, or e.g. a number (which is simpler as you can use it in SQL, while Boolean works only in PL/SQL).
You probably don't want to check sysdate itself as it contains date AND time components (up to seconds), so ... how probable is it that table contains a value which matches right now? That's why I used TRUNC function.
Setup:
SQL> alter session set nls_date_Format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select * from tb_calender;
EXTRACT_DATE
-------------------
10.01.2022 00:00:00 --> that's today's date
08.01.2022 00:00:00
SQL> select sysdate from dual;
SYSDATE
-------------------
10.01.2022 20:34:42
Function:
SQL> create or replace function f_test_01
2 return number
3 is
4 /* Return 1 if EXTRACT_DATE which is equal to today's date exists.
5 Return 0 otherwise
6 */
7 l_cnt number;
8 begin
9 select count(*)
10 into l_cnt
11 from tb_calender
12 where extract_date = trunc(sysdate);
13
14 return case when l_cnt = 0 then 0
15 else 1
16 end;
17 end f_test_01;
18 /
Function created.
SQL> select f_test_01 from dual;
F_TEST_01
----------
1
Procedure:
SQL> create or replace procedure p_test
2 is
3 l_cnt number;
4 begin
5 select count(*)
6 into l_cnt
7 from tb_calender
8 where extract_date = trunc(sysdate);
9
10 if l_cnt = 0 then
11 dbms_output.put_line('Today''s date does not exist');
12 else
13 dbms_output.put_line('Today''s date exists');
14 end if;
15 end;
16 /
Procedure created.
SQL> set serveroutput on
SQL> exec p_test;
Today's date exists
PL/SQL procedure successfully completed.
SQL>

How to fix ORA-01422:- Fetch isnt working

I'm not understanding why this error is occurring. I have seen examples with him but the people doesn't use the for or the fetch loop. As I have understood, the fetch would loop in all the output of the cursor, in this case, taking row by row of the query and avoiding this error.
See the code:
CREATE OR REPLACE TYPE tab_dias IS TABLE OF INTEGER;
CREATE OR REPLACE FUNCTION uf_dias_internado_paciente
(v_cod_paciente IN internacao.COD_PACIENTE%TYPE,
v_dt_inicio IN internacao.DT_HORA_ENTRADA %TYPE,
v_dt_fim IN internacao.DT_HORA_ALTA %TYPE)
RETURN tab_dias
IS
v_dias tab_dias := tab_dias();
CURSOR c_dias IS
SELECT EXTRACT(DAY FROM (i.DT_HORA_ALTA - i.DT_HORA_ENTRADA)) AS dias
FROM INTERNACAO i
WHERE i.COD_PACIENTE = v_cod_paciente
AND i.DT_HORA_ENTRADA >= v_dt_inicio --13
AND i.DT_HORA_ALTA <= v_dt_fim;
linha_dias c_dias%ROWTYPE;
BEGIN
OPEN c_dias;
LOOP
FETCH c_dias INTO linha_dias;
EXIT WHEN c_dias%NOTFOUND;
v_dias(v_dias.last) := linha_dias.dias;
END LOOP;
CLOSE c_dias;
RETURN v_dias;
END;
The error: ORA-01422: exact fetch returns more than requested number of rows
The code I'm running:
SELECT uf_dias_internado_paciente (5007, CURRENT_TIMESTAMP - 500000, CURRENT_TIMESTAMP ) FROM DUAL;
The desired output:
SELECT (EXTRACT(DAY FROM (i.DT_HORA_ALTA - i.DT_HORA_ENTRADA)))
FROM INTERNACAO i
WHERE i.COD_PACIENTE = 5007
AND i.DT_HORA_ENTRADA >= CURRENT_TIMESTAMP - 500000
AND i.DT_HORA_ALTA <= CURRENT_TIMESTAMP;
(EXTRACT(DAYFROM(I.DT_HORA_ALTA-I.DT_HORA_ENTRADA)))|
----------------------------------------------------|
11|
1|
1|
Oracle documentation:
https://docs.oracle.com/cd/B14117_01/appdev.101/b10807/06_ora.htm#i36655
https://docs.oracle.com/cd/B14117_01/appdev.101/b10807/13_elems020.htm
Code - as you wrote it - will return
ORA-06502: PL/SQL: numeric or value error: NULL index table key value
because you're missing v_dias.EXTEND. It can't fail with ORA-01422 (which is TOO_MANY_ROWS); cursors don't return it (unless you have a subquery - which you don't).
Therefore, there's something wrong in code you posted vs. what you are saying.
As I don't have your tables, here's an example based on Scott's EMP table:
SQL> CREATE OR REPLACE TYPE tab_dias IS TABLE OF INTEGER;
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION f_dias (par_deptno IN NUMBER)
2 RETURN tab_dias
3 IS
4 v_dias tab_dias := tab_dias ();
5
6 CURSOR c_dias IS
7 SELECT empno AS dias
8 FROM emp
9 WHERE deptno = par_deptno;
10
11 linha_dias c_dias%ROWTYPE;
12 BEGIN
13 OPEN c_dias;
14
15 LOOP
16 FETCH c_dias INTO linha_dias;
17
18 EXIT WHEN c_dias%NOTFOUND;
19 v_dias.EXTEND; --> this
20 v_dias (v_dias.LAST) := linha_dias.dias;
21 END LOOP;
22
23 CLOSE c_dias;
24
25 RETURN v_dias;
26 END;
27 /
Function created.
Testing:
SQL> SELECT f_dias (10) FROM DUAL;
F_DIAS(10)
--------------------------------------------------------------------------------
TAB_DIAS(7782, 7839, 7934)
Or:
SQL> SELECT * FROM TABLE(f_dias (10));
COLUMN_VALUE
------------
7782
7839
7934
SQL>
Instead of a loop, perhaps you should consider bulk collect; it is simpler and more efficient:
SQL> CREATE OR REPLACE FUNCTION f_dias (par_deptno IN NUMBER)
2 RETURN tab_dias
3 IS
4 v_dias tab_dias := tab_dias ();
5
6 CURSOR c_dias IS
7 SELECT empno AS dias
8 FROM emp
9 WHERE deptno = par_deptno;
10 BEGIN
11 OPEN c_dias;
12
13 FETCH c_dias BULK COLLECT INTO v_dias;
14
15 CLOSE c_dias;
16
17 RETURN v_dias;
18 END;
19 /
Function created.
SQL> SELECT * FROM TABLE (f_dias (10));
COLUMN_VALUE
------------
7782
7839
7934
SQL>
So, basically, it works. There's something else that produces TOO_MANY_ROWS, not code you posted.

How to execute 2 Store Procedures with conditions

I want to RUN SP2 inside SP1. The SP2 inserts several data into a table, one of those data is actual date.
So, i want that SP2 only execute if in the column date, the date is within the last 10 days.
It´s possible?
Thanks
Here's an example.
First, create a table and insert some sample rows:
SQL> create table my_tbl
2 (id number,
3 my_col date
4 );
Table created.
SQL> insert into my_tbl (id, my_col)
2 -- more than 10 days ago
3 select 1, date '2018-05-25' from dual union all
4 -- less than 10 days ago
5 select 2, date '2018-09-01' from dual;
2 rows created.
Create two procedures; SP_1 is supposed to call SP_2 if condition is met. I'm looping through both rows in the table; one row's date value is OK, another one isn't.
SQL> create or replace procedure sp_2 as
2 begin
3 dbms_output.put_line('...I am in SP_2 now');
4 end;
5 /
Procedure created.
SQL> create or replace procedure sp_1 as
2 begin
3 for cur_r in (select my_col from my_tbl order by id) loop
4 dbms_output.put_line('date value = ' || to_char(cur_r.my_col, 'yyyy-mm-dd'));
5 if cur_r.my_col >= trunc(sysdate) - 10 then
6 dbms_output.put_line('...calling SP_2');
7 sp_2;
8 else
9 dbms_output.put_line('...date condition failed - not calling SP_2');
10 end if;
11 end loop;
12 end;
13 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> begin
2 sp_1;
3 end;
4 /
date value = 2018-05-25
...date condition failed - not calling SP_2
date value = 2018-09-01
...calling SP_2
...I am in SP_2 now
PL/SQL procedure successfully completed.

how to append values to a oracle type

how can I append (insert) 3 or 4 different values to an oracle type and then later open it up for a cursor.
For example (pseudo):
insert into mytype select 1 from dual;
insert into mytype select 3 from dual;
insert into mytype select 5 from dual;
open cursor_1 for select * from table(mytype);
Is this possible to do in pl/sql?
I know this is trivial and can be combined into one query but my real need is to have different queries and keep appending the results to mytype.
Assuming you mean you have a custom SQL type (presumably a nested table type), and a PL/SQL variable of that type: I don't believe you can INSERT into it, and I don't think you can SELECT into it in a way that would append to the collection.
You can select into a scalar variable, then append it to the collection procedurally.
SQL> create type mytype as table of integer;
2 /
Type created.
SQL> set serveroutput on
SQL> l
1 declare
2 mytable mytype := mytype();
3 cursor_1 sys_refcursor;
4 x integer;
5 procedure append_to_table( t IN OUT mytype, y IN INTEGER)
6 is
7 begin
8 t.extend();
9 t(t.COUNT) := y;
10 end append_to_table;
11 begin
12 select 1 into x from dual;
13 append_to_table( mytable, x );
14 select 3 into x from dual;
15 append_to_table( mytable, x );
16 select 5 into x from dual;
17 append_to_table( mytable, x );
18 open cursor_1 for select * from table(cast(mytable as mytype));
19 fetch cursor_1 into x;
20 dbms_output.put_line(x);
21 fetch cursor_1 into x;
22 dbms_output.put_line(x);
23 fetch cursor_1 into x;
24 dbms_output.put_line(x);
25 close cursor_1;
26* end;
SQL> /
1
3
5
PL/SQL procedure successfully completed.
Manipulating PL/SQL collections is a lot easier since 10g, which gave us some SET operators we can use with them.
As you know, to employ the TABLE() function means we have to use a SQL type...
SQL> create or replace type nums_nt as table of number
2 /
Type created.
SQL>
The following block populates a collection with some numbers, which it uses in a FOR loop. Then it executes a another query to populate a second collection. The second collection is added to the first collection using the MULTISET UNION syntax. Unlike the the SQL UNION operator, this implementation does not winnow duplicates (we can use MULTISET UNION DISTINCT for that). The code finishes off by looping through the first collection again, to prove that it contains both sets of numbers.
SQL> set serveroutput on
SQL>
SQL> declare
2 master_nos nums_nt;
3 fresh_nos nums_nt;
4 begin
5
6 dbms_output.put_line ('get some numbers, print some names');
7
8 select id
9 bulk collect into master_nos
10 from t23
11 where name not in ( select upper(name) from t_doctors )
12 and name not in ( select upper(name) from t_kids );
13
14 for r in ( select t23.name
15 from t23
16 join ( select * from table(master_nos)) sq
17 on t23.id = sq.column_value
18 )
19 loop
20 dbms_output.put_line (r.name);
21 end loop;
22
23 dbms_output.put_line ('get more numbers, print all names');
24
25 select id
26 bulk collect into fresh_nos
27 from t23
28 where name in ( select upper(name) from t_doctors );
29
30 master_nos := master_nos
31 MULTISET UNION
32 fresh_nos;
33
34 for r in ( select t23.name
35 from t23
36 join ( select * from table(master_nos)) sq
37 on t23.id = sq.column_value
38 )
39 loop
40 dbms_output.put_line (r.name);
41 end loop;
42
43 end;
44 /
get some numbers, print some names
CAT
PINNER BLINN
LORAX
MR KNOX
FOX IN SOCKS
get more numbers, print all names
CAT
PINNER BLINN
LORAX
MR KNOX
FOX IN SOCKS
DR SINATRA
DR FONZ
PL/SQL procedure successfully completed.
SQL>

Resources