How to place a plsql block inside a sequence - plsql

I am trying to create a user generated sequence. According to usual syntax of oracle sequence we can start with a number and increment a value.
Is there a method to write a plsql block (declare begin end) inside a sequence and generate my own sequnce.
example : ABC001
When i call the next val of sequence , the value should be ABC002

PLEASE CLEAR FIRST WHAT YOU EXACTLY WANT TO ASK.
If you are asking HOW TO DYNAMICALLY CREATE SEQUENCE USING PL/SQL, then check below.
Simplest way.
DECLARE
SQL_S VARCHAR2(100);
BEGIN
SQL_S := 'CREATE SEQUENCE SQN_NAME INCREMENT BY 1 START WITH 1';
EXECUTE IMMEDIATE SQL_S;
END;
/
If you want to dynamically create sequence with some DYNAMIC name as argument passed to procedure, then it will be like
CREATE OR REPLACE PROCEDURE DYNAMIC_SQN (ARG IN VARCHAR2) IS
SQL_S VARCHAR2(100);
PARAM1 VARCHAR2(20);
BEGIN
PARAM1 := 'SQN_NAME_' || ARG;
SQL_S := 'CREATE SEQUENCE ' || PARAM1 || ' INCREMENT BY 1 START WITH 1';
EXECUTE IMMEDIATE SQL_S;
END;
/
And if you simply want to insert in to any column, and create the PK using it in addition to some String, then
INSERT INTO TABLE_T VALUES('ABC'|| SEQUENCE_NAME.nextval, OTHER_VALUES);
It will still give you values like : ABC1, ABC2, .... ABC12, ABC13, .... ABC99, ABC100 and so on...

Considering the sample example you have given i m writing the following code
create your sequence, then
insert into seq values('ABC'||YOURSEQUENCENAME.nextval,YOUR_VALUE);

Related

PL/SQL How to iterate and update from an query inside a loop

I don't know how to iterate and update from a query result inside the loop. Is it possible to loop again from the query inside my first loop? Here is my code:
CREATE OR REPLACE PROCEDURE "myTEST" (sp_type in char)
IS
CURSOR c1 IS
SELECT SP_ID FROM CI_SP
WHERE SP_TYPE_CD = sp_type;
sp_id char(10);
item_id_eq CI_SP_EQ.ITEM_ID_EQ%type;
BEGIN
FOR sp_rec in c1
LOOP
DBMS_OUTPUT.PUT_LINE(sp_rec.sp_id);
SELECT ITEM_ID_EQ INTO item_id_eq FROM CI_SP_EQ
WHERE SP_ID = sp_rec.sp_id;
DBMS_OUTPUT.PUT_LINE('item id eq :' || item_id_eq);
-- iterate here for each item_id_eq
-- execute update for each item_id_eq also
END LOOP;
END myTEST;
Instead of looping twice you could just do a join between CI_SP & CI_SP_EQ and get it done in one shot:
CREATE OR REPLACE PROCEDURE "myTEST"(sp_type IN CHAR) IS
BEGIN
FOR item IN (SELECT item_id_eq
FROM ci_sp_eq JOIN ci_sp USING (sp_id)
WHERE sp_type_cd = sp_type) LOOP
-- do your stuff.
NULL;
END LOOP;
END mytest;
I think you wouldn't even need a PL/SQL block, just a simple UPDATE will do, but I don't exactly know what you're trying to do.
Some other comments:
Don't create objects enclosed in "quotes", the object name is now case sensitive. In your case, the compilation will fail because you've created procedure name as "myTEST" and end it with mytest, which Oracle will treat it as "MYTEST" and you'll get compile error because of syntax check fail
Use VARCHAR2 instead of CHAR, CHAR will pad spaces if the input doesn't match the length specifier and will lead to further problems

Assigning object type in plsql

I need your help to know how to assign the object type through a string in PLSQL
Below is the problem description:
I first created the object types as below:
create or replace type picu_obj is object(Customer_ID varchar2(32767),Customer_Name varchar2(32767),Server_Name varchar2(32767),Time_stamp varchar2(32767));
create or replace type picu_obj_tab is table of picu_obj;
and I have a PLSQL block as below:
declare
l_str1 varchar2(1000);
l_str2 varchar2(10000);
l_newstr1_1 varchar2(10000);
picu_var picu_obj_tab;
cursor c1cudetails
is
select item,current_value
from
(select rownum,
last_value(category ignore nulls) over (order by rownum) category ,
last_value(item ignore nulls) over (order by rownum) item,
current_value
from pi_perfdata_new
order by rownum
)
where upper(category) like '%CUSTOMER%DETAILS%' ;
type cudet is table of c1cudetails%rowtype index by pls_integer;
l_cudet cudet;
begin
/* create dynamic string for items */
open c1cudetails;
fetch c1cudetails bulk collect into l_cudet limit 50;
for i in l_cudet.first..l_cudet.last loop
l_str1:=l_str1||','||''''||l_cudet(i).current_value||'''';
l_str2:=trim(leading ',' from l_str1);
l_newstr1_1:='picu_obj_tab(picu_obj('||l_str2||'))';
end loop;
-- dbms_output.put_line(''||l_newstr1_1||'');
-- picu_var := l_newstr1_1;
close c1cudetails;
end;
For the string "l_newstr1_1" following value is retruned from above PLSQL block
picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
Now I want to assign the above result to variable "picu_var" which I have declared.
Basically I need to convert to the following during runtime.
picu_var := picu_obj_tab(picu_obj('CSCO5','DXRTYE','PI22-pro-333','2015-07-22-22:48:56'))
How to achieve the same?
Please suggest how to initialize the object type variable to the string values.
Use dynamic PL/SQL like this:
execute immediate 'begin :x := ' || l_newstr1_1|| '; end;'
using out picu_var;

nested table element assignment in pl/sql

My requirement in pl/sql nested table is the following:
I have a nested table collection type declared and I populate the elements based on a lookup from a table.
In cases where the lookup yields more than one row(more than one value for code), then add all those multiple values in the nested table and proceed. Here is where i am stuck.
I am not able to increment that parent counter "indx" inside the exception to process those multiple rows. Since i am not, it only stores the latest data in the nested table and not all of them.
declare
TYPE final_coll_typ IS TABLE OF varchar2(100);
l_final_coll final_coll_typ;
MULTI_FETCH EXCEPTION;
PRAGMA EXCEPTION_INIT(MULTI_FETCH, -1422); -- this is an ora error for exact fetch returns more than the required number of rows
begin
for indx in 1..<count> loop
<some processing logic here>
select code into l_final_coll(indx) from lookup_tbl where <some filter>;
exception
when MULTI_FETCH then
for p in (select code from lookup_tbl where <some filter>)
loop
l_final_coll(indx) := p.code;
dbms_output.put_line(l_final_coll(indx));
end loop;
continue; -- this is for further processing after the loop
end loop;
end;
Lets say, the first iteration of the counter indx produced only one row data for code. That gets stored in l_final_coll(indx).
Lets say the next iteration of indx i the main for loop produces 2 rows of values for code. My thought was to catch the exception (ORA-01422) and keep adding these 2 code values in the existing nested table.
So, in effect, my nested table should now have 3 values of code in its element. But, currently, I can only get it to populate 2 of them (the single value from first itreration and the latest value from the next)
Any pointers would be appreciated on how I can accomplish this.
PS: Tried manipulating the counter variables indx and p. But, obviously pl/sql does not allow it for a "for loop".
You don't need to do a single select at all, just use the cursor loop to start with, and append to the collection (which you'd have to initialise):
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
begin
l_final_coll := final_coll_typ();
for indx in 1..<count> loop
<some processing logic here>
for p in (select code from lookup_tbl where <some filter>) loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := p.code;
end loop;
end loop;
dbms_output.put_line('Final size: ' || l_final_coll.count);
end;
/
For each row found but the cursor, the collection is extended by one (which isn't very efficient), and the cursor value is put in the last, empty, row; which is found from the current count.
As a demo, if I create a dummy table with a duplicate value:
create table lookup_tbl(code varchar2(100));
insert into lookup_tbl values ('Code 1');
insert into lookup_tbl values ('Code 2');
insert into lookup_tbl values ('Code 2');
insert into lookup_tbl values ('Code 3');
... then with a specific counter and filter:
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
begin
l_final_coll := final_coll_typ();
for indx in 1..3 loop
for p in (select code from lookup_tbl where code = 'Code ' || indx) loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := p.code;
end loop;
end loop;
dbms_output.put_line('Final size: ' || l_final_coll.count);
end;
/
... I get:
anonymous block completed
Final size: 4
As a slightly more complicated option, you could bulk-collect all the matching data into a temporary collection, then loop over that to append those values into the real collection. Something like:
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
l_tmp_coll sys.dbms_debug_vc2coll;
begin
l_final_coll := final_coll_typ();
for indx in 1..<count> loop
<some processing logic here>
select code bulk collect into l_tmp_coll from lookup_tbl where <some filter>;
for cntr in 1..l_tmp_coll.count loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := l_tmp_coll(cntr);
end loop;
end loop;
end;
/
There may be a quicker way to combine two collections but I'm not aware of one. Bulk-collect has to be into a schema-level collection type, so you can't use your local final_coll_typ. You can create your own schema-level type, and then use it for both the temporary and final collection variables; but I've used a built-in one, sys.dbms_debug_vc2coll, which is defined as table of varchar2(1000).
As a demo, with the same table/data as above, and the same specific count and filter:
declare
type final_coll_typ is table of varchar2(100);
l_final_coll final_coll_typ;
l_tmp_coll sys.dbms_debug_vc2coll;
begin
l_final_coll := final_coll_typ();
for indx in 1..3 loop
select code bulk collect into l_tmp_coll
from lookup_tbl where code = 'Code ' || indx;
for cntr in 1..l_tmp_coll.count loop
l_final_coll.extend(1);
l_final_coll(l_final_coll.count) := l_tmp_coll(cntr);
end loop;
end loop;
dbms_output.put_line('Final size: ' || l_final_coll.count);
end;
/
... I again get:
anonymous block completed
Final size: 4

Unable to dynamically start sequence

Based on this example, I created this code:
DECLARE
new_employee_id INTEGER;
BEGIN
SELECT MAX(EMP_ID) + 1 INTO new_employee_id FROM EMPLOYEE;
EXECUTE IMMEDIATE 'CREATE OR REPLACE SEQUENCE primary_key_seq START WITH ' || new_employee_id || ' INCREMENT BY 1';
END;
/
OBS: perhaps using a sequence to auto increment the primary key of a table doesn't make much sense, but this is a school exercise and that's what we're supposed to do, so please disregard that aspect.
This is the error I have:
ERROR AT LINE 1
ORA-00922: missing or invalid option
ORA-06512: at line 5
What could be wrong?
There is no CREATE OR REPLACE SEQUENCE option, so you must use the CREATE SEQUENCE Statement and the a DROP statement if you need to drop it first.
DECLARE
new_employee_id INTEGER;
BEGIN
SELECT MAX(EMP_ID) + 1 INTO new_employee_id FROM EMPLOYEE;
EXECUTE IMMEDIATE 'CREATE SEQUENCE primary_key_seq START WITH ' || new_employee_id || ' INCREMENT BY 1';
END;
/
there is no REPLACE in create sequence command check documentation (wich is always a good idea ;-)
should be
DECLARE
new_employee_id INTEGER;
BEGIN
SELECT MAX(EMP_ID) + 1 INTO new_employee_id FROM EMPLOYEE;
EXECUTE IMMEDIATE 'CREATE SEQUENCE primary_key_seq START WITH ' || new_employee_id || ' INCREMENT BY 1';
END;
/
This question begs another question - why are you trying to drop and re-add a sequence? You should never need to do this. Create the sequence once:
CREATE SEQUENCE PRIMARY_KEY_SEQ
MINVALUE 1
MAXVALUE 9999999999999999999999999999
START WITH 1
INCREMENT BY 1
NOCACHE;
Then, use it in your code:
DECLARE
new_employee_id INTEGER;
BEGIN
new_employee_id := PRIMARY_KEY_SEQ.NEXTVAL;
INSERT INTO SOME_TABLE_MAYBE_EMPLOYEES
(ID, WHATEVER, WHATEVER_ELSE)
VALUES
(new_employee_id, 'WHATEVER', 'WHATEVER_ELSE');
END;
The whole purpose of the sequence is to provide unique, non-repeating values. Going out to the table, grabbing the MAX(ID), and recreating the sequence to start from there is sort of backwards from the way a sequence should be used. Create a sequence once, then use it many times.
Hopefully this helps.
Share and enjoy.

A generic procedure that can execute any procedure/function

input
Package name (IN)
procedure name (or function name) (IN)
A table indexed by integer, it will contain values that will be used to execute the procedure (IN/OUT).
E.g
let's assume that we want to execute the procedure below
utils.get_emp_num(emp_name IN VARCHAR
emp_last_name IN VARCHAR
emp_num OUT NUMBER
result OUT VARCHAR);
The procedure that we will create will have as inputs:
package_name = utils
procedure_name = get_emp_num
table = T[1] -> name
T[2] -> lastname
T[3] -> 0 (any value)
T[4] -> N (any value)
run_procedure(package_name,
procedure_name,
table)
The main procedure should return the same table that has been set in the input, but with the execution result of the procedure
table = T[1] -> name
T[2] -> lastname
T[3] -> 78734 (new value)
T[4] -> F (new value)
any thought ?
You can achieve it with EXECUTE IMMEDIATE. Basically, you build a SQL statement of the following form:
sql := 'BEGIN utils.get_emp_num(:1, :2, :3, :4); END;';
Then you execute it:
EXECUTE IMMEDIATE sql USING t(1), t(2), OUT t(3), OUT t(4);
Now here comes the tricky part: For each number of parameters and IN/OUT combinations you need a separate EXECUTE IMMEDIATE statement. And to figure out the number of parameters and their direction, you need to query the ALL_ARGUMENTS table first.
You might be able to simplify it by passing the whole table as a bind argument instead of a separate bind argument for each table element. But I haven't quite figured out how you would do that.
And the next thing you should consider: the elements of the table T your using will have a type: VARCHAR, NUMBER etc. So the current mixture where you have both numbers and strings won't work.
BTW: Why do you want such a dynamic call mechanism anyway?
Get from the all_arguments table the argument_name, data_type, in_out, and the position
Build the PLSQL block
DECLARE
loop over argument_name and create the declare section
argument_name data_type if in_out <> OUT then := VALUE OF THE INPUT otherwise NULL
BEGIN
--In the case of function create an additional argument
function_var:= package_name.procedure_name( loop over argument_name);
--use a table of any_data, declare it as global in the package
if function then
package_name.ad_table.EXTEND;
package_name.ad_table(package_name.ad_table.LAST):= function_var;
end if
--loop over argument_name IF IN_OUT <> IN
package_name.ad_table.EXTEND;
package_name.ad_table(package_name.ad_table.LAST):=
if data_type = VARCHAR2 then := ConvertVarchar2(argument_name)
else if NUMBER then ConvertNumber
else if DATE then ConvertDate
...
END;
The result is stored in the table.
To get value use Access* functions

Resources