DBMS_OUTPUT not fetching any rows in output using cursor - oracle

DBMS_OUTPUT.PUT_LINE is not returning data while using cursor as in below code
I used boolean to compare amount from the table
SET SERVEROUTPUT ON;
DECLARE
--declaration of variable
x_id test.saa.id%TYPE;
x_acctname test.saa.acctname%TYPE;
x_curbal test.saa.balamt%TYPE;
x_sid test.saa.sid%TYPE;
--setting of the boolean value default to null
b_lowamount BOOLEAN := false;
--declaration of cursor
CURSOR custbal IS
SELECT id,acctname,bal_amt,sid
FROM
test.saa WHERE ROWNUM <= 1000;
BEGIN
--checking cursor is open or not
IF NOT ( custbal%isopen ) THEN
OPEN custbal;
END IF;
LOOP
FETCH custbal INTO
x_id,
x_acctname,
x_curbal,
x_sid;
EXIT WHEN custbal%notfound;
CONTINUE WHEN custbal%found;
--begin another
BEGIN
b_lowamount := ( x_curbal < 10 );
IF b_lowamount THEN
dbms_output.put_line('The customer having '|| x_acctname|| ' with sol_id '|| x_sid|| 'with balance RS. '|| x_curbal);
ELSE
dbms_output.put_line('The customer having '|| x_acctname|| ' with sol_id '|| x_sid|| 'with balance RS. '|| x_curbal);
END IF;
END;
END loop;
end;
didn't returned anything although the procedure completed successfully

The reason why your output is not being shown is because of the CONTINUE - that means go to the start of the next loop.
Instead, your procedure could be written as:
DECLARE
--declaration of variable
x_id test.saa.id%TYPE;
x_acctname test.saa.acctname%TYPE;
x_curbal test.saa.balamt%TYPE;
x_sid test.saa.sid%TYPE;
--setting of the boolean value default to null
b_lowamount BOOLEAN := FALSE;
--declaration of cursor
CURSOR custbal IS
SELECT id,
acctname,
bal_amt,
sid
FROM test.saa
WHERE rownum <= 1000;
BEGIN
OPEN custbal;
LOOP
FETCH custbal
INTO x_id,
x_acctname,
x_curbal,
x_sid;
EXIT WHEN custbal%NOTFOUND;
b_lowamount := (x_curbal < 10);
IF b_lowamount
THEN
dbms_output.put_line('The customer having ' || x_acctname || ' with sol_id ' || x_sid || 'with balance RS. ' || x_curbal);
ELSE
dbms_output.put_line('The customer having ' || x_acctname || ' with sol_id ' || x_sid || 'with balance RS. ' || x_curbal);
END IF;
END LOOP;
CLOSE custbal;
END;
/
N.B. I have removed the extra BEGIN and END that you had inside the loop; there is no need for starting a new block inside the current one!
Also, I have removed your check to see whether the cursor is open or not (it will always be closed at the start of the anonymous block, since the cursor is declared entirely within the scope of the anonymous block, and Oracle closes variables once the scope has ended. I have, however, added an explicit CLOSE statement, to close the cursor. This isn't strictly needed (since the cursor will automatically be closed once the block has finished), but it's good practice to include it if you're manually opening the cursor.
However, your entire procedure could be simplified to just:
BEGIN
FOR rec IN (SELECT id,
acctname,
bal_amt,
sid
FROM test.saa
WHERE rownum <= 1000)
LOOP
IF rec.bal_amt < 10
THEN
dbms_output.put_line('The customer having ' || rec.acctname || ' with sol_id ' || rec.sid || 'with balance RS. ' || rec.curbal);
ELSE
dbms_output.put_line('The customer having ' || rec.acctname || ' with sol_id ' || rec.sid || 'with balance RS. ' || rec.curbal);
END IF;
END LOOP;
END;
/
(I have left the IF statement as-is, even though both branches are outputting the same string - I assume this was a mistake, and you meant different text to be output, depending on the balance being less than 10 or not? If it doesn't matter, you can just get rid of the IF statement, and instead just output the result.)
The nice thing about the cursor for loop is that you don't need to declare variables to return the values into (the record is implicitly created as part of the FOR <record> in (<cursor>) statement), and you don't need to handle opening and closing of the cursor. It also makes your code much simpler and therefore - IMO - easier to understand and maintain.

Related

how to set the AUTO_INCREMENT on Oracle? [duplicate]

In PostgreSQL, I can do something like this:
ALTER SEQUENCE serial RESTART WITH 0;
Is there an Oracle equivalent?
Here is a good procedure for resetting any sequence to 0 from Oracle guru Tom Kyte. Great discussion on the pros and cons in the links below too.
tkyte#TKYTE901.US.ORACLE.COM>
create or replace
procedure reset_seq( p_seq_name in varchar2 )
is
l_val number;
begin
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate
'alter sequence ' || p_seq_name || ' increment by -' || l_val ||
' minvalue 0';
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_val;
execute immediate
'alter sequence ' || p_seq_name || ' increment by 1 minvalue 0';
end;
/
From this page: Dynamic SQL to reset sequence value
Another good discussion is also here: How to reset sequences?
A true restart is not possible AFAIK. (Please correct me if I'm wrong!).
However, if you want to set it to 0, you can just delete and recreate it.
If you want to set it to a specific value, you can set the INCREMENT to a negative value and get the next value.
That is, if your sequence is at 500, you can set it to 100 via
ALTER SEQUENCE serial INCREMENT BY -400;
SELECT serial.NEXTVAL FROM dual;
ALTER SEQUENCE serial INCREMENT BY 1;
This is my approach:
drop the sequence
recreate it
Example:
--Drop sequence
DROP SEQUENCE MY_SEQ;
-- Create sequence
create sequence MY_SEQ
minvalue 1
maxvalue 999999999999999999999
start with 1
increment by 1
cache 20;
My approach is a teensy extension to Dougman's example.
Extensions are...
Pass in the seed value as a parameter. Why? I like to call the thing resetting the sequence back to the max ID used in some table. I end up calling this proc from another script which executes multiple calls for a whole bunch of sequences, resetting nextval back down to some level which is high enough to not cause primary key violations where I'm using the sequence's value for a unique identifier.
It also honors the previous minvalue. It may in fact push the next value ever higher if the desired p_val or existing minvalue are higher than the current or calculated next value.
Best of all, it can be called to reset to a specified value, and just wait until you see the wrapper "fix all my sequences" procedure at the end.
create or replace
procedure Reset_Sequence( p_seq_name in varchar2, p_val in number default 0)
is
l_current number := 0;
l_difference number := 0;
l_minvalue user_sequences.min_value%type := 0;
begin
select min_value
into l_minvalue
from user_sequences
where sequence_name = p_seq_name;
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_current;
if p_Val < l_minvalue then
l_difference := l_minvalue - l_current;
else
l_difference := p_Val - l_current;
end if;
if l_difference = 0 then
return;
end if;
execute immediate
'alter sequence ' || p_seq_name || ' increment by ' || l_difference ||
' minvalue ' || l_minvalue;
execute immediate
'select ' || p_seq_name || '.nextval from dual' INTO l_difference;
execute immediate
'alter sequence ' || p_seq_name || ' increment by 1 minvalue ' || l_minvalue;
end Reset_Sequence;
That procedure is useful all by itself, but now let's add another one which calls it and specifies everything programmatically with a sequence naming convention and looking for the maximum value used in an existing table/field...
create or replace
procedure Reset_Sequence_to_Data(
p_TableName varchar2,
p_FieldName varchar2
)
is
l_MaxUsed NUMBER;
BEGIN
execute immediate
'select coalesce(max(' || p_FieldName || '),0) from '|| p_TableName into l_MaxUsed;
Reset_Sequence( p_TableName || '_' || p_Fieldname || '_SEQ', l_MaxUsed );
END Reset_Sequence_to_Data;
Now we're cooking with gas!
The procedure above will check for a field's max value in a table, builds a sequence name from the table/field pair and invokes "Reset_Sequence" with that sensed max value.
The final piece in this puzzle and the icing on the cake comes next...
create or replace
procedure Reset_All_Sequences
is
BEGIN
Reset_Sequence_to_Data( 'ACTIVITYLOG', 'LOGID' );
Reset_Sequence_to_Data( 'JOBSTATE', 'JOBID' );
Reset_Sequence_to_Data( 'BATCH', 'BATCHID' );
END Reset_All_Sequences;
In my actual database there are around one hundred other sequences being reset through this mechanism, so there are 97 more calls to Reset_Sequence_to_Data in that procedure above.
Love it? Hate it? Indifferent?
alter sequence serial restart start with 0;
This feature is new in Oracle 12c. It is not included in the official documentation.
I found it in scripts generated by the Oracle package DBMS_METADATA_DIFF.
WARNING: I've used this feature several times on production systems and in my opinion it is fine to use this command in ad-hoc scripts. But you might not want to include it in a procedure as part of an application. I created an Oracle Service Request asking about this feature; it's not just a documentation bug, it's an unsupported feature. It's possible the command will disappear some day, like WM_CONCAT. (Although I think that's unlikely - Oracle syntax rarely disappears, and it's a simple feature used internally in at least two places.)
The following script set the sequence to a desired value:
Given a freshly created sequence named PCS_PROJ_KEY_SEQ and table PCS_PROJ:
BEGIN
DECLARE
PROJ_KEY_MAX NUMBER := 0;
PROJ_KEY_CURRVAL NUMBER := 0;
BEGIN
SELECT MAX (PROJ_KEY) INTO PROJ_KEY_MAX FROM PCS_PROJ;
EXECUTE IMMEDIATE 'ALTER SEQUENCE PCS_PROJ_KEY_SEQ INCREMENT BY ' || PROJ_KEY_MAX;
SELECT PCS_PROJ_KEY_SEQ.NEXTVAL INTO PROJ_KEY_CURRVAL FROM DUAL;
EXECUTE IMMEDIATE 'ALTER SEQUENCE PCS_PROJ_KEY_SEQ INCREMENT BY 1';
END;
END;
/
This stored procedure restarts my sequence:
Create or Replace Procedure Reset_Sequence
is
SeqNbr Number;
begin
/* Reset Sequence 'seqXRef_RowID' to 0 */
Execute Immediate 'Select seqXRef.nextval from dual ' Into SeqNbr;
Execute Immediate 'Alter sequence seqXRef increment by - ' || TO_CHAR(SeqNbr) ;
Execute Immediate 'Select seqXRef.nextval from dual ' Into SeqNbr;
Execute Immediate 'Alter sequence seqXRef increment by 1';
END;
/
There is another way to reset a sequence in Oracle: set the maxvalue and cycle properties. When the nextval of the sequence hits the maxvalue, if the cycle property is set then it will begin again from the minvalue of the sequence.
The advantage of this method compared to setting a negative increment by is the sequence can continue to be used while the reset process runs, reducing the chance you need to take some form of outage to do the reset.
The value for maxvalue has to be greater than the current nextval, so the procedure below includes an optional parameter allowing a buffer in case the sequence is accessed again between selecting the nextval in the procedure and setting the cycle property.
create sequence s start with 1 increment by 1;
select s.nextval from dual
connect by level <= 20;
NEXTVAL
----------
1
...
20
create or replace procedure reset_sequence ( i_buffer in pls_integer default 0)
as
maxval pls_integer;
begin
maxval := s.nextval + greatest(i_buffer, 0); --ensure we don't go backwards!
execute immediate 'alter sequence s cycle minvalue 0 maxvalue ' || maxval;
maxval := s.nextval;
execute immediate 'alter sequence s nocycle maxvalue 99999999999999';
end;
/
show errors
exec reset_sequence;
select s.nextval from dual;
NEXTVAL
----------
1
The procedure as stands still allows the possibility that another session will fetch the value 0, which may or may not be an issue for you. If it is, you could always:
Set minvalue 1 in the first alter
Exclude the second nextval fetch
Move the statement to set the nocycle property into another procedure, to be run at a later date (assuming you want to do this).
Jezus, all this programming for just an index restart...
Perhaps I'm an idiot, but for pre-oracle 12 (which has a restart feature), what is wrong with a simpel:
drop sequence blah;
create sequence blah
?
1) Suppose you create a SEQUENCE like shown below:
CREATE SEQUENCE TESTSEQ
INCREMENT BY 1
MINVALUE 1
MAXVALUE 500
NOCACHE
NOCYCLE
NOORDER
2) Now you fetch values from SEQUENCE. Lets say I have fetched four times as shown below.
SELECT TESTSEQ.NEXTVAL FROM dual
SELECT TESTSEQ.NEXTVAL FROM dual
SELECT TESTSEQ.NEXTVAL FROM dual
SELECT TESTSEQ.NEXTVAL FROM dual
3) After executing above four commands the value of the SEQUENCE will be 4. Now suppose I have reset the value of the SEQUENCE to 1 again. The follow the following steps. Follow all the steps in the same order as shown below:
ALTER SEQUENCE TESTSEQ INCREMENT BY -3;
SELECT TESTSEQ.NEXTVAL FROM dual
ALTER SEQUENCE TESTSEQ INCREMENT BY 1;
SELECT TESTSEQ.NEXTVAL FROM dual
Altering the sequence's INCREMENT value, incrementing it, and then altering it back is pretty painless, plus you have the added benefit of not having to re-establish all of the grants as you would had you dropped/recreated the sequence.
I create a block to reset all my sequences:
DECLARE
I_val number;
BEGIN
FOR US IN
(SELECT US.SEQUENCE_NAME FROM USER_SEQUENCES US)
LOOP
execute immediate 'select ' || US.SEQUENCE_NAME || '.nextval from dual' INTO l_val;
execute immediate 'alter sequence ' || US.SEQUENCE_NAME || ' increment by -' || l_val || ' minvalue 0';
execute immediate 'select ' || US.SEQUENCE_NAME || '.nextval from dual' INTO l_val;
execute immediate 'alter sequence ' || US.SEQUENCE_NAME || ' increment by 1 minvalue 0';
END LOOP;
END;
Here's a more robust procedure for altering the next value returned by a sequence, plus a whole lot more.
First off it protects against SQL injection attacks since none of the strings passed in are used to directly create any of the dynamic SQL statements,
Second it prevents the next sequence value from being set outside the bounds of the min or max sequence values. The next_value will be != min_value and between min_value and max_value.
Third it takes the current (or proposed) increment_by setting as well as all the other sequence settings into account when cleaning up.
Fourth all parameters except the first are optional and unless specified take on the current sequence setting as defaults. If no optional parameters are specified no action is taken.
Finally if you try altering a sequence that doesn't exist (or is not owned by the current user) it will raise an ORA-01403: no data found error.
Here's the code:
CREATE OR REPLACE PROCEDURE alter_sequence(
seq_name user_sequences.sequence_name%TYPE
, next_value user_sequences.last_number%TYPE := null
, increment_by user_sequences.increment_by%TYPE := null
, min_value user_sequences.min_value%TYPE := null
, max_value user_sequences.max_value%TYPE := null
, cycle_flag user_sequences.cycle_flag%TYPE := null
, cache_size user_sequences.cache_size%TYPE := null
, order_flag user_sequences.order_flag%TYPE := null)
AUTHID CURRENT_USER
AS
l_seq user_sequences%rowtype;
l_old_cache user_sequences.cache_size%TYPE;
l_next user_sequences.min_value%TYPE;
BEGIN
-- Get current sequence settings as defaults
SELECT * INTO l_seq FROM user_sequences WHERE sequence_name = seq_name;
-- Update target settings
l_old_cache := l_seq.cache_size;
l_seq.increment_by := nvl(increment_by, l_seq.increment_by);
l_seq.min_value := nvl(min_value, l_seq.min_value);
l_seq.max_value := nvl(max_value, l_seq.max_value);
l_seq.cycle_flag := nvl(cycle_flag, l_seq.cycle_flag);
l_seq.cache_size := nvl(cache_size, l_seq.cache_size);
l_seq.order_flag := nvl(order_flag, l_seq.order_flag);
IF next_value is NOT NULL THEN
-- Determine next value without exceeding limits
l_next := LEAST(GREATEST(next_value, l_seq.min_value+1),l_seq.max_value);
-- Grab the actual latest seq number
EXECUTE IMMEDIATE
'ALTER SEQUENCE '||l_seq.sequence_name
|| ' INCREMENT BY 1'
|| ' MINVALUE '||least(l_seq.min_value,l_seq.last_number-l_old_cache)
|| ' MAXVALUE '||greatest(l_seq.max_value,l_seq.last_number)
|| ' NOCACHE'
|| ' ORDER';
EXECUTE IMMEDIATE
'SELECT '||l_seq.sequence_name||'.NEXTVAL FROM DUAL'
INTO l_seq.last_number;
l_next := l_next-l_seq.last_number-1;
-- Reset the sequence number
IF l_next <> 0 THEN
EXECUTE IMMEDIATE
'ALTER SEQUENCE '||l_seq.sequence_name
|| ' INCREMENT BY '||l_next
|| ' MINVALUE '||least(l_seq.min_value,l_seq.last_number)
|| ' MAXVALUE '||greatest(l_seq.max_value,l_seq.last_number)
|| ' NOCACHE'
|| ' ORDER';
EXECUTE IMMEDIATE
'SELECT '||l_seq.sequence_name||'.NEXTVAL FROM DUAL'
INTO l_next;
END IF;
END IF;
-- Prepare Sequence for next use.
IF COALESCE( cycle_flag
, next_value
, increment_by
, min_value
, max_value
, cache_size
, order_flag) IS NOT NULL
THEN
EXECUTE IMMEDIATE
'ALTER SEQUENCE '||l_seq.sequence_name
|| ' INCREMENT BY '||l_seq.increment_by
|| ' MINVALUE '||l_seq.min_value
|| ' MAXVALUE '||l_seq.max_value
|| CASE l_seq.cycle_flag
WHEN 'Y' THEN ' CYCLE' ELSE ' NOCYCLE' END
|| CASE l_seq.cache_size
WHEN 0 THEN ' NOCACHE'
ELSE ' CACHE '||l_seq.cache_size END
|| CASE l_seq.order_flag
WHEN 'Y' THEN ' ORDER' ELSE ' NOORDER' END;
END IF;
END;
In my project, once it happened that someone manually entered the records without using sequence, hence I have to reset sequence value manually, for which I wrote below sql code snippet:
declare
max_db_value number(10,0);
cur_seq_value number(10,0);
counter number(10,0);
difference number(10,0);
dummy_number number(10);
begin
-- enter table name here
select max(id) into max_db_value from persons;
-- enter sequence name here
select last_number into cur_seq_value from user_sequences where sequence_name = 'SEQ_PERSONS';
difference := max_db_value - cur_seq_value;
for counter in 1..difference
loop
-- change sequence name here as well
select SEQ_PERSONS.nextval into dummy_number from dual;
end loop;
end;
Please note, the above code will work if the sequence is lagging.
You can use the CYCLE option, shown below:
CREATE SEQUENCE test_seq
MINVALUE 0
MAXVALUE 100
START WITH 0
INCREMENT BY 1
CYCLE;
In this case, when the sequence reaches MAXVALUE (100), it will recycle to the MINVALUE (0).
In the case of a decremented sequence, the sequence would recycle to the MAXVALUE.
Here's how to make all auto-increment sequences match actual data:
Create a procedure to enforce next value as was already described in this thread:
CREATE OR REPLACE PROCEDURE Reset_Sequence(
P_Seq_Name IN VARCHAR2,
P_Val IN NUMBER DEFAULT 0)
IS
L_Current NUMBER := 0;
L_Difference NUMBER := 0;
L_Minvalue User_Sequences.Min_Value%Type := 0;
BEGIN
SELECT Min_Value
INTO L_Minvalue
FROM User_Sequences
WHERE Sequence_Name = P_Seq_Name;
EXECUTE Immediate 'select ' || P_Seq_Name || '.nextval from dual' INTO L_Current;
IF P_Val < L_Minvalue THEN
L_Difference := L_Minvalue - L_Current;
ELSE
L_Difference := P_Val - L_Current;
END IF;
IF L_Difference = 0 THEN
RETURN;
END IF;
EXECUTE Immediate 'alter sequence ' || P_Seq_Name || ' increment by ' || L_Difference || ' minvalue ' || L_Minvalue;
EXECUTE Immediate 'select ' || P_Seq_Name || '.nextval from dual' INTO L_Difference;
EXECUTE Immediate 'alter sequence ' || P_Seq_Name || ' increment by 1 minvalue ' || L_Minvalue;
END Reset_Sequence;
Create another procedure to reconcile all sequences with actual content:
CREATE OR REPLACE PROCEDURE RESET_USER_SEQUENCES_TO_DATA
IS
STMT CLOB;
BEGIN
SELECT 'select ''BEGIN'' || chr(10) || x || chr(10) || ''END;'' FROM (select listagg(x, chr(10)) within group (order by null) x FROM ('
|| X
|| '))'
INTO STMT
FROM
(SELECT LISTAGG(X, ' union ') WITHIN GROUP (
ORDER BY NULL) X
FROM
(SELECT CHR(10)
|| 'select ''Reset_Sequence('''''
|| SEQ_NAME
|| ''''','' || coalesce(max('
|| COL_NAME
|| '), 0) || '');'' x from '
|| TABLE_NAME X
FROM
(SELECT TABLE_NAME,
REGEXP_SUBSTR(WTEXT, 'NEW\.(\S*) IS NULL',1,1,'i',1) COL_NAME,
REGEXP_SUBSTR(BTEXT, '(\.|\s)([a-z_]*)\.nextval',1,1,'i',2) SEQ_NAME
FROM USER_TRIGGERS
LEFT JOIN
(SELECT NAME BNAME,
TEXT BTEXT
FROM USER_SOURCE
WHERE TYPE = 'TRIGGER'
AND UPPER(TEXT) LIKE '%NEXTVAL%'
)
ON BNAME = TRIGGER_NAME
LEFT JOIN
(SELECT NAME WNAME,
TEXT WTEXT
FROM USER_SOURCE
WHERE TYPE = 'TRIGGER'
AND UPPER(TEXT) LIKE '%IS NULL%'
)
ON WNAME = TRIGGER_NAME
WHERE TRIGGER_TYPE = 'BEFORE EACH ROW'
AND TRIGGERING_EVENT = 'INSERT'
)
)
) ;
EXECUTE IMMEDIATE STMT INTO STMT;
--dbms_output.put_line(stmt);
EXECUTE IMMEDIATE STMT;
END RESET_USER_SEQUENCES_TO_DATA;
NOTES:
Procedure extracts names from trigger code and does not depend on naming conventions
To check generated code before execution, switch comments on last two lines
I make an alternative that the user don’t need to know the values, the system get and use variables to update.
--Atualizando sequence da tabela SIGA_TRANSACAO, pois está desatualizada
DECLARE
actual_sequence_number INTEGER;
max_number_from_table INTEGER;
difference INTEGER;
BEGIN
SELECT [nome_da_sequence].nextval INTO actual_sequence_number FROM DUAL;
SELECT MAX([nome_da_coluna]) INTO max_number_from_table FROM [nome_da_tabela];
SELECT (max_number_from_table-actual_sequence_number) INTO difference FROM DUAL;
IF difference > 0 then
EXECUTE IMMEDIATE CONCAT('alter sequence [nome_da_sequence] increment by ', difference);
--aqui ele puxa o próximo valor usando o incremento necessário
SELECT [nome_da_sequence].nextval INTO actual_sequence_number from dual;
--aqui volta o incremento para 1, para que futuras inserções funcionem normalmente
EXECUTE IMMEDIATE 'ALTER SEQUENCE [nome_da_sequence] INCREMENT by 1';
DBMS_OUTPUT.put_line ('A sequence [nome_da_sequence] foi atualizada.');
ELSE
DBMS_OUTPUT.put_line ('A sequence [nome_da_sequence] NÃO foi atualizada, já estava OK!');
END IF;
END;

Checking if the values found by the cursor meet the new condition

So I'm back with the procedure for searching for keys / identifiers in the schematic.
here is the ready procedure:
CREATE OR REPLACE PROCEDURE SIEROT(i_table_name VARCHAR2) IS
strSelect VARCHAR2(32767);
c SYS_REFCURSOR;
vTYPEPKSTRING PRODUCTS.TYPEPKSTRING%TYPE;
TYPE c_list IS TABLE of PRODUCTS.TYPEPKSTRING%type INDEX BY binary_integer;
TYPEPK_list c_list;
counter integer := 0;
BEGIN
strSelect := 'SELECT DISTINCT i.TYPEPKSTRING ' ||
' FROM ' || i_table_name || ' i ' ||
' LEFT OUTER JOIN COMPOSEDTYPES c ' ||
' ON i.TYPEPKSTRING = c.PK ' ||
' WHERE c.PK IS NULL';
OPEN c FOR strSelect;
FETCH c INTO vTYPEPKSTRING;
WHILE c%FOUND LOOP
counter := counter + 1;
TYPEPK_list(counter) := vTYPEPKSTRING;
dbms_output.put_line('TABLE: '||i_table_name||'('||counter||'):'||TYPEPK_list(counter));
FETCH c INTO vTYPEPKSTRING;
END LOOP;
CLOSE c;
EXCEPTION
WHEN OTHERS THEN
IF c%ISOPEN THEN
CLOSE c;
END IF;
END SIEROT;
And here's the call:
set serveroutput on
DECLARE
ind integer := 0;
BEGIN
FOR ind IN (select table_name from all_tab_columns where column_name='TYPEPKSTRING' AND table_name!='COMPOSEDTYPES')
LOOP
BEGIN
SIEROT(ind.table_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
null;
END;
END LOOP;
END;
This procedure looks for all the values for 'typepkstring' on the schema. These are the values of keys, which can be found in the 'composedtypes' table in the PK column. More specifically, in the framework of this procedure we find identifiers that appear on the schema and are not included in this collective view in the column pk. In my particular bottle, there are three of them. The procedure informs me in which table the key was found.
Everything works great. However, I have to add another one to this functionality.
I have about 132 tables on this schama, which contain columns 'sourcepk' and 'targetpk'. These columns also contain number keys.
Now I want to make sure about one thing:
Both of these columns, i.e. both 'sourcepk' and 'targetpk' should be empty for those of my 'typepkstrings' found.
I know how to find tables with interesting columns:
select distinct table_name from all_tab_columns where column_name='SOURCEPK' OR column_name ='TARGETPK';
Generally, I wanted to convert this part of the procedure:
FETCH c INTO vTYPEPKSTRING;
WHILE c%FOUND LOOP
counter := counter + 1;
TYPEPK_list(counter) := vTYPEPKSTRING;
dbms_output.put_line('TABLE: '||i_table_name||'('||counter||'):'||TYPEPK_list(counter));
FETCH c INTO vTYPEPKSTRING;
END LOOP;
I tried to add here in the for loop the above select to tables containing sourcepk and targetpk and then the if condition on the vTYPEPKSTRING variable. whether after a fetch operation in a given pass, found 'typepkstring' has some values for these columns or not. However, nothing came of it
Generally, I encounter new problems without interruption. And I really need help, at least in targeting, though I would not have despised a code.
Thank you in advance for any hints.:)
I've combined, combined and figured out. I gave up the previous construction of the procedure and decided to do it all over again.
I used 4 nested cursors, of which 2 are ref cursors. Everything works according to the assumptions. Thanks for the advice on the previous post.
CREATE OR REPLACE PROCEDURE SIEROTKA
IS
t_name VARCHAR2(30);
od_typePK PRODUCTS.TYPEPKSTRING%TYPE;
t_nameST VARCHAR(30);
od_Source cat2prodrel.SOURCEPK%TYPE;
od_Target cat2prodrel.TARGETPK%TYPE;
sv_tname VARCHAR2(32767);
Get_typePK SYS_REFCURSOR;
sv_tnameST VARCHAR2(32767);
Get_Sierotki SYS_REFCURSOR;
CURSOR Get_PK_TN
IS
SELECT table_name FROM all_tab_columns
WHERE column_name='TYPEPKSTRING' AND
table_name!='COMPOSEDTYPES';
CURSOR Get_ST_TN
IS
SELECT DISTINCT table_name from all_tab_columns
WHERE column_name='SOURCEPK' OR
column_name='TARGETPK';
BEGIN
OPEN Get_PK_TN;
LOOP
FETCH Get_PK_TN INTO t_name;
EXIT WHEN Get_PK_TN%NOTFOUND;
sv_tname := 'SELECT DISTINCT i.TYPEPKSTRING FROM ' || t_name || ' i LEFT OUTER JOIN COMPOSEDTYPES c ON i.TYPEPKSTRING=c.PK WHERE c.PK IS NULL';
OPEN Get_typePK FOR sv_tname;
LOOP
FETCH Get_typePK INTO od_typePK;
IF Get_typePK%FOUND THEN
dbms_output.put_line('Znalezione Sirotka to: ' || t_name || ' --------- ' || od_typePK);
ELSE
EXIT WHEN Get_typePK%NOTFOUND;
END IF;
OPEN Get_ST_TN;
LOOP
FETCH Get_ST_TN INTO t_nameST;
EXIT WHEN Get_ST_TN%NOTFOUND;
sv_tnameST := 'SELECT SOURCEPK, TARGETPK FROM ' || t_nameST || ' WHERE PK=' || od_typePK || '';
OPEN Get_Sierotki FOR sv_tnameST;
LOOP
FETCH Get_Sierotki INTO od_Source, od_Target;
IF Get_Sierotki%FOUND THEN
dbms_output.put_line('Jednak to nie sierotka, bo posiada wpis: ' || t_name || ' --------- ' || od_typePK || ' ____________ ' || t_namest || ' --------- ' || od_Source || ' --------- ' || od_Target);
ELSE
EXIT WHEN Get_Sierotki%NOTFOUND;
END IF;
END LOOP;
CLOSE Get_Sierotki;
END LOOP;
CLOSE Get_ST_TN;
END LOOP;
CLOSE Get_typePK;
END LOOP;
CLOSE Get_PK_TN;
END;
For this, a simple one-line call in the begin end block:
set serveroutput on
BEGIN
SIEROTKA;
END;
/
Until next, see you soon.

Cursor For Loop how to print message when not found

OK so I have a cursor for loop that I want to print an error message when the select statement doesn't find a course that the user has inputted. But the problem is that cursor for loops automatically exits when the select statement fails, so my else statement never executes. How do I print a message saying that they course they are looking for doesn't exist. Note switching to a cursor fetch is not an option. For example id the user enters a course that exists it prints all relevant information. When the user inputs a course with no prerequisite it prints a proper message, but if the user inputs a course that doesn't exists, nothing gets printed.
DECLARE
course_name VARCHAR2(40) := '&course_input';
TYPE course_r IS RECORD(
course_description course.description%TYPE,
cost course.cost%TYPE,
prerequisite course.prerequisite%TYPE,
prerequisite_cost course.cost%TYPE
);
course_rec course_r;
CURSOR course_cursor IS
SELECT a.description, a.cost, a.prerequisite, b.cost AS preq_cost
FROM COURSE a
LEFT JOIN COURSE b ON a.prerequisite = b.course_no
WHERE UPPER(a.description) LIKE '%'||'&course_input'||'%';
BEGIN
FOR record IN course_cursor
LOOP
course_rec.course_description := record.description;
course_rec.cost := record.cost;
course_rec.prerequisite := record.prerequisite;
course_rec.prerequisite_cost := record.preq_cost;
IF course_rec.prerequisite IS NULL THEN
DBMS_OUTPUT.PUT_LINE('There is NO prerequisite course for any that starts on ' || course_name || '. Try again');
ELSIF course_rec.prerequisite IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE('Course: ' || course_rec.course_description);
DBMS_OUTPUT.PUT_LINE('Cost: ' || course_rec.cost);
DBMS_OUTPUT.PUT_LINE('Prerequisite: ' || course_rec.prerequisite);
DBMS_OUTPUT.PUT_LINE('Prerequisite Cost: ' || course_rec.prerequisite_cost);
DBMS_OUTPUT.PUT_LINE('=================================================');
ELSE
DBMS_OUTPUT.PUT_LINE('There is NO VALID course that starts on '||course_name||'. Try again.');
END IF;
END LOOP;
END;
/
You can do this by having a variable which only gets set inside the loop. Then you can check that variable after the loop has completed to see if it was set, and decide if you need to do additional work.
Something like:
DECLARE
course_name VARCHAR2(40) := '&course_input';
v_rows_present BOOLEAN := FALSE;
BEGIN
FOR course_rec IN (SELECT a.description,
a.cost,
a.prerequisite,
b.cost AS preq_cost
FROM course a
LEFT JOIN course b
ON a.prerequisite = b.course_no
WHERE upper(a.description) LIKE '%' || course_name || '%')
LOOP
v_rows_present := TRUE;
IF course_rec.prerequisite IS NULL
THEN
dbms_output.put_line('There is NO prerequisite course for any that starts on ' || course_name || '. Try again');
ELSE
dbms_output.put_line('Course: ' || course_rec.course_description);
dbms_output.put_line('Cost: ' || course_rec.cost);
dbms_output.put_line('Prerequisite: ' || course_rec.prerequisite);
dbms_output.put_line('Prerequisite Cost: ' || course_rec.prerequisite_cost);
dbms_output.put_line('=================================================');
END IF;
END LOOP;
IF NOT v_rows_present
THEN
dbms_output.put_line('There is NO VALID course that starts on ' || course_name || '. Try again.');
END IF;
END;
/
N.B. I've updated your code as you appear to have misapprehended how to use a cursor for loop.
Cursor-for-loops create their own record variable implicitly, so you don't need to declare one yourself.
You also don't need to declare a cursor explicitly either - that can be done as part of the cursor-for-loop statement.
You don't need to populate a new record with the same values from the cursor-for-loop record in order to use the values (as long as you're using them within the cursor-for-loop, of course!)
You could declare a counter, as say, PLS_INTEGER, initialize it to 0, then increment it inside the loop. After the loop, you can check the value and if it's 0, you know no rows were returned.
THE CURSOR FOR LOOP doesn't execute if there are no such courses in the courses table.So, check if a row exists before entering the loop.
Two other things to note:
LIKE '%'||'&course_input'||'%' is not required in the where clause
as the same variable is already passed from user input and assigned
in the declare section.Simply use LIKE '%' || course_name || '%'
RECORD is a PL/SQL reserved word and shouldn't be used as a loop
index variable, i've changed it to rec.
DECLARE
course_name VARCHAR2(40) := '&course_input';
TYPE course_r IS RECORD ( course_description course.description%TYPE,
cost course.cost%TYPE,
prerequisite course.prerequisite%TYPE,
prerequisite_cost course.cost%TYPE );
course_rec course_r;
cur_count NUMBER;
CURSOR course_cursor IS SELECT a.description,
a.cost,
a.prerequisite,
b.cost AS preq_cost
FROM course a
LEFT JOIN course b ON a.prerequisite = b.course_no
WHERE upper(a.description) LIKE '%' || course_name || '%';
BEGIN
SELECT COUNT(*)
INTO cur_count
FROM course a
WHERE upper(a.description) LIKE '%' || course_name || '%';
IF
cur_count > 0
THEN
FOR rec IN course_cursor LOOP
course_rec.course_description := rec.description;
course_rec.cost := rec.cost;
course_rec.prerequisite := rec.prerequisite;
course_rec.prerequisite_cost := rec.preq_cost;
IF
course_rec.prerequisite IS NULL
THEN
dbms_output.put_line('There is NO prerequisite course for any that starts on ' ||
course_name || '. Try again');
ELSE
dbms_output.put_line('Course: ' || course_rec.course_description);
dbms_output.put_line('Cost: ' || course_rec.cost);
dbms_output.put_line('Prerequisite: ' || course_rec.prerequisite);
dbms_output.put_line('Prerequisite Cost: ' || course_rec.prerequisite_cost);
dbms_output.put_line('=================================================');
END IF;
END LOOP;
ELSE
dbms_output.put_line('There is NO VALID course that starts on ' || course_name || '. Try again.'
);
END IF;
END;
/

%ROWTYPE variable from table name

I've got an Oracle procedure, that I want to be somehow generic. I would like to:
pass a table name as a varchar parameter
use EXECUTE IMMEDIATE to dynamically select data
store the result in the %ROWTYPE variable of passed type
The third point seems to be a problem. I'm not sure if i can create a type dynamically inside the procedure body. It would be great to have something like this:
procedure CHANGE_GENERIC(tableName in VARCHAR2, someOldVal in integer,
someNewVal in integer) is
v_sql varchar2(200);
begin
v_sql := 'select * from ' || tableName || 'where ID = ' || someOldVal;
EXECUTE IMMEDIATE v_sql1 into **myDynamicRowThatIDontHave**;
-- some other code
end;
You probably can't do this (at least not usefully).
You could construct an entire anonymous PL/SQL block
v_plsql := 'DECLARE ' ||
' l_row ' || p_table_name || '%rowtype; ' ||
'BEGIN ' ||
' SELECT * ' ||
' INTO l_row ' ||
' FROM ' || p_table_name ||
' WHERE id = ' || p_some_old_value || ';' ||
...
EXECUTE IMMEDIATE v_plsql;
In general, though, long before you start resorting to dynamic PL/SQL at runtime, you really want to take a step back and assess whether there isn't an easier solution to whatever problem you have. There are any number of frameworks, for example, that dynamically generate CRUD packages for each of your tables. That's using dynamic PL/SQL but it's only doing it once as part of a build rather than doing it every time you want to update data.
Have a look at DBMS_SQL
Here is an example:
DECLARE
YourTable VARCHAR2(30) := 'MY_TABLE';
cur SYS_FERCURSOR;
curid NUMBER;
desctab DBMS_SQL.DESC_TAB;
colcnt NUMBER; -- total number of columns
res NUMBER;
namevar VARCHAR2(4000);
numvar NUMBER;
datevar DATE;
... more if needed
BEGIN
OPEN cur FOR 'SELECT * FROM '||YourTable;
curid := DBMS_SQL.TO_CURSOR_NUMBER (cur);
DBMS_SQL.DESCRIBE_COLUMNS(curid, colcnt, desctab);
FOR i IN 1..colcnt LOOP -- loop over all columns
IF desctab(i).col_type = 1 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar);
ELSIF desctab(i).col_type = 2 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, numvar);
ELSIF desctab(i).col_type = 12 THEN
DBMS_SQL.DEFINE_COLUMN(curid, i, datevar);
.......
ELSE
DBMS_SQL.DEFINE_COLUMN(curid, i, namevar, 25);
END IF;
END LOOP;
-- Fetch Rows
WHILE DBMS_SQL.FETCH_ROWS(curid) > 0 LOOP
FOR i IN 1 .. colcnt LOOP
IF (desctab(i).col_type = 1) THEN
DBMS_SQL.COLUMN_VALUE(curid, i, namevar);
ELSIF (desctab(i).col_type = 2) THEN
DBMS_SQL.COLUMN_VALUE(curid, i, numvar);
ELSIF (desctab(i).col_type = 12) THEN
DBMS_SQL.COLUMN_VALUE(curid, i, datevar);
....
END IF;
--> do here something else with namevar or numvar or datevar or ...
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(curid);
END;
But as the other replies already mentioned, check carefully if you really need this "full" dynamic. You did not tell us what you like to do with retunrned variable values. Perhaps you don't need them explicitly. You can also create complex statements dynamically and run them.
See here a simple example (well, not that simple) I made some time ago. It updates several tables with nested tables of different object type without any variable usage.
CREATE OR REPLACE PROCEDURE UpdateNestedTables IS
sqlstr VARCHAR2(1000);
CURSOR NestedTables IS
SELECT POCL_TABLE_NAME, FTC_NT_TABLE_NAME, PAR_COLUMN_NAME,
FTC_MO_STRUCT_CONSTRUCTOR AS NT_COLUMN_NAME,
FTC_MO_STRUCT_TYPE AS NT_OBJ_TYPE
FROM T_PAR_OBJ_CLASSES
JOIN T_PARAMETERS ON PAR_POCL_ID = POCL_ID
JOIN T_FDN_TABLE_COLUMNS ON FTC_PAR_ID = PAR_ID;
BEGIN
FOR aTab IN NestedTables LOOP
sqlstr := 'UPDATE '
|| '(SELECT /*+ USE_HASH(a b) */ '||aTab.PAR_COLUMN_NAME||', FULL_DISTINCT_NAME '
||' FROM '||SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')||'.'||aTab.POCL_TABLE_NAME||' a '
||' WHERE '||aTab.PAR_COLUMN_NAME||' IS NULL '
||' AND FULL_DISTINCT_NAME =ANY '
||' (SELECT FULL_DISTINCT_NAME FROM '||SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')||'.'||aTab.FTC_NT_TABLE_NAME||' b)) wt '
|| 'SET ('||aTab.PAR_COLUMN_NAME||') = '
|| ' (WITH t AS ('
|| ' SELECT m.FULL_DISTINCT_NAME,'
|| ' CAST(MULTISET('
|| ' SELECT '||aTab.NT_COLUMN_NAME
|| ' FROM '||SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')||'.'||aTab.FTC_NT_TABLE_NAME||' nt '
|| ' WHERE nt.FULL_DISTINCT_NAME = m.FULL_DISTINCT_NAME)'
|| ' AS '||aTab.NT_OBJ_TYPE||') AS MO_REF'
|| ' FROM '||SYS_CONTEXT('USERENV', 'CURRENT_SCHEMA')||'.'||aTab.POCL_TABLE_NAME||' m'
|| ' GROUP BY FULL_DISTINCT_NAME)'
|| ' SELECT MO_REF '
|| ' FROM t '
|| ' WHERE t.FULL_DISTINCT_NAME = wt.FULL_DISTINCT_NAME)';
EXECUTE IMMEDIATE sqlstr;
END LOOP;
END UpdateNestedTables;
/
I can recommend to use object types and object tables/views based on that types. Use can define root type and inherit all subtypes from it. Use can pass variable of root type into/from your generic proc and cast it to you specific type using treat inside dynamic SQL. You can pass SQL object types in/out to execute immediate as well.

PL/SQL Help to check for specific values in specific tables of Particular schema

I am using Oracle DB. I have Toad to execute my queries.
I have two columns
typecd
description
in the schema Product_BIS
I want a know is there is any stored procedure to retrive all tables in schema Product_BIS which has these columns Typecd and description. If so, I want to check whether Typecd ='11'and description='Nokia' is present.
How to find this? Is there any simply stored procedure to check this.Can Anyone help me out
Note:There are around 300 tables in the Schema Product_BIS. So manually checking is difficult
What you want is something which interrogates the data dictionary to find all the tables which have certain columns and then generate queries to find which of those tables have rows which have specific values in those columns. There is no Oracle built-in to do this.
The normal approach to do this is dynamic SQL. You say you don't have the privileges to create objects in the database. Well, okay then, just run an anonymous block.
This code can be run in SQL*Plus or any other IDE. Just make sure you have enabled SERVEROUTPUT.
Note the use of rownum = 1 in the assembled statement. This prevents the block hurling a TOO_MANY_ROWS exception if more than one record matches the criteria. This is acceptable, as the posted question only requires the program to assert the existence of one or more rows. If the actual requirement is different, then obviously you will need to amend the code. For instance if you need to display the number of matched records then select count(*) into a numeric variable and change the DBMS_OUTPUT statement accordingly.
declare
v char(1);
begin
for r in ( select table_name from all_tab_columns
where owner = 'PRODUCT_BIS'
and column_name = 'TYPECD'
intersect
select table_name from all_tab_columns
where owner = 'PRODUCT_BIS'
and column_name = 'DESCRIPTION' )
loop
begin
execute immediate
'select null from '||r.table_name
||' where typecd=''11'' and description = ''nokia'' and rownum = 1'
into v;
dbms_output.put_line ( 'those values exist in '||r.table_name);
exception
when no_data_found then
dbms_output.put_line ( 'no occurrence of those values in '||r.table_name);
end;
end loop;
end;
This solution uses hard-coded values. I presume this is a one-off requirement, so it doesn't matter. However if is a script you'll want to run repeatedly for many different permutations of schema, column names and values then you should re-write this as a script which uses substitution variables.
May Be this Will Help u to Find the Specific value in Specific Schema
CREATE OR REPLACE FUNCTION FIND_IN_SCHEMA (VAL VARCHAR2)
RETURN VARCHAR2
IS
V_OLD_TABLE USER_TAB_COLUMNS.TABLE_NAME%TYPE;
V_WHERE VARCHAR2 (4000);
V_FIRST_COL BOOLEAN := TRUE;
TYPE RC IS REF CURSOR;
C RC;
V_ROWID VARCHAR2 (20);
BEGIN
FOR R IN (SELECT T.*
FROM USER_TAB_COLS T, USER_ALL_TABLES A
WHERE T.TABLE_NAME = A.TABLE_NAME
AND T.DATA_TYPE LIKE '%CHAR%'
ORDER BY T.TABLE_NAME)
LOOP
IF V_OLD_TABLE IS NULL
THEN
V_OLD_TABLE := R.TABLE_NAME;
END IF;
IF V_OLD_TABLE <> R.TABLE_NAME
THEN
V_FIRST_COL := TRUE;
-- DBMS_OUTPUT.PUT_LINE('searching ' || V_OLD_TABLE);
OPEN C
FOR 'select rowid from "' || v_old_table || '" ' || V_WHERE;
FETCH C
INTO V_ROWID;
LOOP
EXIT WHEN C%NOTFOUND;
DBMS_OUTPUT.PUT_LINE (' rowid: ' || V_ROWID || ' in '
|| V_OLD_TABLE
);
FETCH C
INTO V_ROWID;
END LOOP;
V_OLD_TABLE := R.TABLE_NAME;
END IF;
IF V_FIRST_COL
THEN
V_WHERE := ' where ' || R.COLUMN_NAME || ' like ''%' || VAL || '%''';
V_FIRST_COL := FALSE;
ELSE
V_WHERE :=
V_WHERE || ' or ' || R.COLUMN_NAME || ' like ''%' || VAL || '%''';
END IF;
END LOOP;
RETURN 'Success';
END;
/

Resources