Iterate through all rows in table PL/SQL - oracle

From table1 I would like to gather values from certain columns. First of all I have tried to copy one table to another but I stuck when tried to:
for row in row_count
for column in column_count
insert into table2 at (x,y) value from (row,column)
column++
end
row++
end
My first function to count how many rows is:
create or replace FUNCTION func_count_rows(table_name IN varchar2,
debug boolean default false)
RETURN number IS
total number(2) := 0;
BEGIN
IF debug = true THEN
DBMS_OUTPUT.put('Function count rows: ');
DBMS_OUTPUT.PUT_LINE('select count(*) from ' || table_name || ';');
DBMS_OUTPUT.put('Returns: ');
DBMS_OUTPUT.PUT_LINE('');
END IF;
execute immediate 'select count(*) from ' || table_name into total;
RETURN total;
END;
Then my procedure to first print values but I stuck here:
create or replace procedure gather_values (rows_quantity in VARCHAR2,
column_count in VARCHAR2,
debug boolean default false
)
is begin
select
FOR i IN 1..rows_quantity LOOP
DBMS_OUTPUT.PUT_LINE('#### ROW 1 ####');
FOR i IN 1..94 LOOP
END LOOP;
END LOOP;
end;
I don't know how to get column quantity and value from exact (x,y) of table.
Could you kindly help me? Thank you.
I have forget to tell that I'm using oracle SQL enviroment.

First of all, this has nothing in common with PL/SQL:
for row in row_count
for column in column_count
insert into table2 at (x,y) value from (row,column)
column++
end
row++
end
See documentation here.
To copy all rows from one table to another:
insert into table2 (x,y)
select a, b
from table1;
It is a simple SQL query, it can be used as is or inside a PL/SQL procedure.
There is a lot of possibilities to iterate all rows of a table. The most simple:
for i in (select column1, column2, ... from table1) loop
dbms_output.put_line(i.column1);
end loop;
Another ways:
Using cursors
Using collections
Using dynamic SQL and dbms_sql package
To count rows in a table, you can use SQL query:
select count(*)
from table1
or almost the same PL/SQL code (you don't need to use execute immediate):
declare
total number;
begin
select count(*)
into total
from table1;
dbms_output.put_line('count of rows: ' || total);
end;
/
But in any case you don't need to know, how many rows and columns a table contains, to iterate them. You need only to know, how to filter, which of them you want to iterate.

Related

Oracle Loop - declaration

I want to use LOOP to go trough all partitions in a table to change some data per partitions.
I am starting like:
BEGIN
FOR n in (here is the select statement which chooses the partition names)
LOOP
UPDATE table_name
PARTITION (n)
SET
here are columns to change with new values;
COMMIT;
END LOOP;
END;
I get error ORA 02149 and ORA 06512 that partition does not exist.
Is it related to some declaration? How I should solve it?
Why bother with a partition name? What benefit do you expect? Simply
update table_name set
col1 = ...,
col2 = ...
where condition_goes_here --> this condition will "determine" the partition
You can use execute immediate and user_tab_partitions data dictionary view together as
Begin
for c in ( select *
from user_tab_partitions p
where p.table_name = 'TABLE_NAME'
order by p.partition_position )
loop
execute immediate 'update '||c.table_name||' partition('||c.partition_name||')
set col1 = ''xYz'' ';
commit;
end loop;
End;

"ORA-01007: variable not in select list" when no rows are returned by EXECUTE IMMEDIATE

I have a procedure which receives as parameter a where clause (i.e. where col1 = 1). I am using this clause to search in some tables using an EXECUTE IMMEDIATE statement and the result to be inserted into a nested table, and than be displayed.
The procedure works fine if any data is found but in case no data is found, then the above error is thrown.
Can someone explain what cause this error, please?
Here is the procedure:
create or replace procedure prc_checks(pi_where varchar2) as
cursor c_tables is
select object_name,
case object_name
when 'XP_IMPORT_MW' THEN 99999999
when 'XP_IMPORT_MW_ARCH' THEN 99999998
else TO_NUMBER(SUBSTR(object_name, -8, 8))
end to_order
from dba_objects
where object_type = 'TABLE'
and object_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (object_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}') order by 2 desc;
type t_result is table of xp_import_mw%rowtype;
v_result t_result;
v_sql varchar2(300);
BEGIN
for i in c_tables
loop
v_sql := 'select * from ' || i.object_name || ' ' || pi_where;
execute immediate v_sql bulk collect into v_result;
if v_result.count > 0
then
for j in v_result.first .. v_result.last
loop
dbms_output.put_line(v_result(j).art_nr);
end loop;
dbms_output.put_line('... the required information was found on table name ' || upper(i.object_name));
exit;
end if;
end loop;
END prc_checks;
You'll get this is one of the tables being found by the cursor has fewer columns than xp_import_mw. For example:
create table xp_import_mw (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160102 (col1 number, art_nr number, dummy number);
create table xp_import_mw_arch_20160101 (col1 number, art_nr number);
insert into xp_import_mw_arch_20160101 values (1, 42);
So the main xp_import_mw table has three columns but no matching data. One of the old archive tables has one fewer columns.
I added a dbms_output.put_line(v_sql) to the procedure to see which table it fails against, then ran it:
set serveroutput on
exec prc_checks('where col1 = 1');
which got output:
select * from XP_IMPORT_MW where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160102 where col1 = 1
select * from XP_IMPORT_MW_ARCH_20160101 where col1 = 1
Error starting at line : 49 in command -
BEGIN prc_checks('where col1 = 1'); END;
Error report -
ORA-01007: variable not in select list
ORA-06512: at "MY_SCHEMA.PRC_CHECKS", line 25
ORA-06512: at line 1
01007. 00000 - "variable not in select list"
*Cause:
*Action:
So the problem isn't that there is no data found; the problem is that there is matching data in a table which has the wrong structure.
You could construct the select list based on the xp_import_mw table's structure, instead of using *; that won't stop it failing, but would at least give you a slightly more helpful error message - in this case ORA-00904: "DUMMY": invalid identifier instead of ORA-01007.
You could do a quick and crude check for discrepancies with something like:
select table_name, count(column_id) as column_count,
listagg(column_name, ',') within group (order by column_id) as columns
from dba_tab_columns
where table_name IN ('XP_IMPORT_MW', 'XP_IMPORT_MW_ARCH')
or REGEXP_LIKE (table_name, 'XP_IMPORT_MW_ARCH_201(5|6|7)[0-9]{4}')
group by table_name
having count(column_id) != (
select count(column_id) from dba_tab_columns where table_name = 'XP_IMPORT_MW'
);
... although if you're using dba_* or all_* view you should really be including the owner, here and in your procedure.

PL SQL- Using Dynamic SQL to Generate Delete Statements

I want to create a stored procedure using PL SQL that allows me to find all tables that contain a specific column, and then delete records from those tables that have a specific value in that column.
For example, I want to find all tables that have the column "year" and then delete all records from all of those tables that have the year "2012"(this year will be a parameter that will be entered upon execution)
My attempt at this has been to create a stored procedure, use a cursor to get all of the tables that have this column of "year" and then loop through that cursor using Dynamic SQL which will generate my Delete Statements that I can execute.
CREATE OR REPLACE PROCEDURE year_deletion
(
p_year NUMBER --Input of the year for records to be deleted
)
IS
CURSOR c1 --Cursor that will find all tables that have the YEAR column
IS
SELECT owner, table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP';
BEGIN
FOR i IN c1 LOOP --Loop through all of the tables that the cursor found, generate a SQL statement for each table that will delete all of the records that have the year of p_year
EXECUTE IMMEDIATE ('SELECT * FROM' ||i.table_name|| 'WHERE YEAR = '||p_year||';');
END LOOP;
END;
Disclaimer: I am using a Select * From instead of a DELETE * From for testing purposes, I will change this when it this procedure executes correctly.
So far this stored procedure compiles correctly, but gives me an error during execution that a FROM keyword was expected but not found. Is this the best method to use for my purpose?
Is must be like this:
EXECUTE IMMEDIATE 'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' USING p_year;
Note the space after FROM and before WHERE.
You cannot simply replace DELETE by SELECT ... for testing because for SELECT you must have an INTO clause.
Your entire procedure can be like this
CREATE OR REPLACE PROCEDURE year_deletion(p_year IN NUMBER) IS
CURSOR c1 IS
SELECT owner, table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP';
res NUMBER;
BEGIN
FOR i IN c1 LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' INTO res USING p_year;
DBMS_OUTPUT.PUT_LINE (res ||' rows will be deleted from table '||i.table_name );
EXECUTE IMMEDIATE
'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = :theYear' USING p_year;
END LOOP;
END;
Hello you can try the below code. It will surely help you out.
CREATE OR REPLACE PROCEDURE year_deletion(
p_year IN NUMBER --Input of the year for records to be deleted
)
IS
BEGIN
FOR i IN (SELECT owner,
table_name
FROM all_tab_columns
WHERE column_name = 'YEAR'
AND owner = 'GTP')
LOOP --Loop through all of the tables that the cursor found, generate a SQL statement for each table that will delete all of the records that have the year of p_year
EXECUTE IMMEDIATE 'DELETE FROM ' ||i.table_name|| ' WHERE YEAR = '||p_year;
END LOOP;
END;

Generating primary key values after new column has been added to Oracle table

I have a table with 2 varchar2 columns. I have added new number column to existing table to make this column primary key. This table now includes 3 columns. I gave a try to use anonymous block as following:
declare
cnt number;
begin
select nvl(count(*),0) into cnt from sometable;
for i in 1..cnt
loop
update sometable set id=i where i=rownum;
end loop;
end;
Using this anonymous block updates this table unexpectedly.
My solution was to use the following statement:
create table sometablecopy as select row_number() over(order by sometable.col1) as id, sometable.* from sometable;
Nevertheless I am curios why doesn't anonymous block produce expected primary key values with the help of rownum pseudocolumn? It must be rownum related issue.
Rownum is a pseudocolumn. Its assigned to rows as they are returned from the select. So you can't say "select * from my_table where rownum = 42" since the row with rownum=42 hasn't been defined yet, it will vary depending on your select and predicate (and "select * from my_table where rownum = 1" will return a single row, not the "first" row, whatever that would be). You could do something like (untested):
declare
cursor sel_cur is
select rowid as row_id
from my_table
order by orderby_col;
v_ctr pls_integer := 0;
begin
for rec in sel_cur
loop
v_ctr := v_ctr + 1;
update my_table set pk_col = v_ctr where rowid = rec.row_id;
end loop;
commit;
exception
when others then
rollback;
raise;
end;
This assumes you have sufficient rollback to update the entire table.
Hope that helps.
You cannot use ROWNUM like that (see ROWNUM in SQL).
What you could have done is this:
UPDATE sometable SET id = ROWNUM;

SELECT DISTINCT CLOB_COLUMN FROM TABLE;

I would like to find the distinct CLOB values that can assume the column called CLOB_COLUMN (of type CLOB) contained in the table called COPIA.
I have selected a PROCEDURAL WAY to solve this problem, but I would prefer to give a simple SELECT as the following: SELECT DISTINCT CLOB_COLUMN FROM TABLE avoiding the error "ORA-00932: inconsistent datatypes: expected - got CLOB"
How can I achieve this?
Thank you in advance for your kind cooperation. This is the procedural way I've thought:
-- Find the distinct CLOB values that can assume the column called CLOB_COLUMN (of type CLOB)
-- contained in the table called COPIA
-- Before the execution of the following PL/SQL script, the CLOB values (including duplicates)
-- are contained in the source table, called S1
-- At the end of the excecution of the PL/SQL script, the distinct values of the column called CLOB_COLUMN
-- can be find in the target table called S2
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE S1 DROP STORAGE';
EXECUTE IMMEDIATE 'DROP TABLE S1 CASCADE CONSTRAINTS PURGE';
EXCEPTION
WHEN OTHERS
THEN
BEGIN
NULL;
END;
END;
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE S2 DROP STORAGE';
EXECUTE IMMEDIATE 'DROP TABLE S2 CASCADE CONSTRAINTS PURGE';
EXCEPTION
WHEN OTHERS
THEN
BEGIN
NULL;
END;
END;
CREATE GLOBAL TEMPORARY TABLE S1
ON COMMIT PRESERVE ROWS
AS
SELECT CLOB_COLUMN FROM COPIA;
CREATE GLOBAL TEMPORARY TABLE S2
ON COMMIT PRESERVE ROWS
AS
SELECT *
FROM S1
WHERE 3 = 9;
BEGIN
DECLARE
CONTEGGIO NUMBER;
CURSOR C1
IS
SELECT CLOB_COLUMN FROM S1;
C1_REC C1%ROWTYPE;
BEGIN
FOR C1_REC IN C1
LOOP
-- How many records, in S2 table, are equal to c1_rec.clob_column?
SELECT COUNT (*)
INTO CONTEGGIO
FROM S2 BETA
WHERE DBMS_LOB.
COMPARE (BETA.CLOB_COLUMN,
C1_REC.CLOB_COLUMN) = 0;
-- If it does not exist, in S2, a record equal to c1_rec.clob_column,
-- insert c1_rec.clob_column in the table called S2
IF CONTEGGIO = 0
THEN
BEGIN
INSERT INTO S2
VALUES (C1_REC.CLOB_COLUMN);
COMMIT;
END;
END IF;
END LOOP;
END;
END;
If it is acceptable to truncate your field to 32767 characters this works:
select distinct dbms_lob.substr(FIELD_CLOB,32767) from Table1
You could compare the hashes of the CLOB to determine if they are different:
SELECT your_clob
FROM your_table
WHERE ROWID IN (SELECT MIN(ROWID)
FROM your_table
GROUP BY dbms_crypto.HASH(your_clob, dbms_crypto.HASH_SH1))
Edit:
The HASH function doesn't guarantee that there will be no collision. By design however, it is really unlikely that you will get any collision. Still, if the collision risk (<2^80?) is not acceptable, you could improve the query by comparing (with dbms_lob.compare) the subset of rows that have the same hashes.
add TO_CHAR after distinct keyword to convert CLOB to CHAR
SELECT DISTINCT TO_CHAR(CLOB_FIELD) from table1; //This will return distinct values in CLOB_FIELD
Use this approach. In table profile column content is NCLOB. I added the where clause to reduce the time it takes to run which is high,
with
r as (select rownum i, content from profile where package = 'intl'),
s as (select distinct (select min(i) from r where dbms_lob.compare(r.content, t.content) = 0) min_i from profile t where t.package = 'intl')
select (select content from r where r.i = s.min_i) content from s
;
It is not about to win any prizes for efficiency but should work.
select distinct DBMS_LOB.substr(column_name, 3000) from table_name;
If truncating the clob to the size of a varchar2 won't work, and you're worried about hash collisions, you can:
Add a row number to every row;
Use DBMS_lob.compare in a not exists subquery. Exclude duplicates (this means: compare = 0) with a higher rownum.
For example:
create table t (
c1 clob
);
insert into t values ( 'xxx' );
insert into t values ( 'xxx' );
insert into t values ( 'yyy' );
commit;
with rws as (
select row_number () over ( order by rowid ) rn,
t.*
from t
)
select c1 from rws r1
where not exists (
select * from rws r2
where dbms_lob.compare ( r1.c1, r2.c1 ) = 0
and r1.rn > r2.rn
);
C1
xxx
yyy
To bypass the oracle error, you have to do something like this :
SELECT CLOB_COLUMN FROM TABLE COPIA C1
WHERE C1.ID IN (SELECT DISTINCT C2.ID FROM COPIA C2 WHERE ....)
I know this is an old question but I believe I've figure out a better way to do what you are asking.
It is kind of like a cheat really...The idea behind it is that You can't do a DISTINCT of a Clob column but you can do a DISTINCT on a Listagg function of a Clob_Column...you just need to play with the partition clause of the Listagg function to make sure it will only return one value.
With that in mind...here is my solution.
SELECT DISTINCT listagg(clob_column,'| ') within GROUP (ORDER BY unique_id) over (PARTITION BY unique_id) clob_column
FROM copia;

Resources