Column moving in Oracle Pl/Sql with for loop - sql

For example, I want to move my 2 Column between for example KW_01 (dropdown) and KW_03 (dropdown) to between KW_04 (dropdown) and KW_06 (dropdown). I have 53 column in a table. I wrote code (see below) but unfortunately it does not work properly. He does that when I start a code.
Unexpected results:
Outer Loop counter is kw_04 Inner Loop counter is 1
Outer Loop counter is kw_04 Inner Loop counter is 2
Outer Loop counter is kw_04 Inner Loop counter is 3
Outer Loop counter is kw_05 Inner Loop counter is 1
Outer Loop counter is kw_05 Inner Loop counter is 2
Outer Loop counter is kw_05 Inner Loop counter is 3
Outer Loop counter is kw_06 Inner Loop counter is 1
Outer Loop counter is kw_06 Inner Loop counter is 2
Outer Loop counter is kw_06 Inner Loop counter is 3
DECLARE
plsql VARCHAR2(500);
BEGIN
For i in (SELECT column_id
FROM alsi_bedarfsplanung unpivot(column_value FOR column_id IN("KW_01", "KW_02", "KW_03", "KW_04", "KW_05", "KW_06"))
WHERE column_id BETWEEN :drp1 AND :drp2
and id = 1)
LOOP
FOR o in (SELECT column_value
FROM alsi_bedarfsplanung unpivot(column_value FOR column_id IN("KW_01", "KW_02", "KW_03", "KW_04", "KW_05", "KW_06"))
WHERE column_id BETWEEN :drp3 AND :drp4
and id = 1)
LOOP
plsql := ' UPDATE ALSI_BEDARFSPLANUNG SET ' || i.column_id || ' = ' ||
o.column_value || ' where ID = 1 ';
EXECUTE IMMEDIATE plsql;
END LOOP;
END LOOP;
END;
My DB
What i want,
Outer Loop counter is kw_04 Inner Loop counter is 1//(KW_1 Value)
Outer Loop counter is kw_05 Inner Loop counter is 2//(KW_2 Value)
Outer Loop counter is kw_06 Inner Loop counter is 3//(KW_3 Value)

The problem is the way you're looping within a loop; what you actually need to do is identify the columns to update to and from separately, and then loop over each set of columns to do the updates.
Even better would be to do all the work in a single update, like so:
-- should error with "Invalid columns specified" due to final proc call,
-- but should update the two rows accordingly
DECLARE
TYPE col_array IS TABLE OF VARCHAR2(30) INDEX BY pls_INTEGER;
v_cols_to_update_arry col_array;
v_cols_update_from_arry col_array;
PROCEDURE update_cols (p_id IN INTEGER,
p_drp1 IN VARCHAR2,
p_drp2 IN VARCHAR2,
p_drp3 IN VARCHAR2,
p_drp4 IN VARCHAR2)
IS
v_sql CLOB := 'UPDATE alsi_bedarfsplanung SET '||CHR(10);
BEGIN
SELECT column_id
BULK COLLECT INTO v_cols_to_update_arry
FROM alsi_bedarfsplanung
UNPIVOT (column_val FOR column_id IN (kw_01, kw_02, kw_03, kw_04, kw_05, kw_06, kw_07, kw_08))
WHERE column_id BETWEEN p_drp1 AND p_drp2
AND id = p_id;
SELECT column_id
BULK COLLECT INTO v_cols_update_from_arry
FROM alsi_bedarfsplanung
UNPIVOT (column_val FOR column_id IN (kw_01, kw_02, kw_03, kw_04, kw_05, kw_06, kw_07, kw_08))
WHERE column_id BETWEEN p_drp3 AND p_drp4
AND id = p_id;
IF v_cols_to_update_arry.count > 0
AND v_cols_update_from_arry.count > 0
AND v_cols_to_update_arry.count = v_cols_update_from_arry.count THEN
FOR i IN 1..v_cols_to_update_arry.count
LOOP
if i = 1 then
v_sql := v_sql || ' ' || v_cols_to_update_arry(i) || ' = ' || v_cols_update_from_arry(i);
else
v_sql := v_sql || ',' || CHR(10) || ' ' || v_cols_to_update_arry(i) || ' = ' || v_cols_update_from_arry(i);
end if;
END LOOP;
v_sql := v_sql || chr(10) || 'where id = :p_id';
EXECUTE IMMEDIATE v_sql USING p_id;
ELSE
raise_application_error(-20001, 'Invalid columns specified');
END IF;
END update_cols;
BEGIN
update_cols (p_id => 1,
p_drp1 => 'KW_04',
p_drp2 => 'KW_06',
p_drp3 => 'KW_01',
p_drp4 => 'KW_03');
COMMIT;
update_cols (p_id => 2,
p_drp1 => 'KW_05',
p_drp2 => 'KW_08',
p_drp3 => 'KW_01',
p_drp4 => 'KW_04');
COMMIT;
update_cols (p_id => 1,
p_drp1 => 'KW_01',
p_drp2 => 'KW_02',
p_drp3 => 'KW_05',
p_drp4 => 'KW_05');
END;
/
This works by fetching each list of columns into a separate array, looping over the arrays to build up the list of columns being updated, before concatenating that into the update statement.
Here's a demo of it working

Related

Loop over returned table and drop each table with the same name

I have a function that returns a table containing every child table of a given parent table.
I want to drop all the child tables and for that I'm trying to loop over the result of the function :
Do$$
BEGIN
FOREACH child IN
SELECT partman.show_partitions('public."parent"',p_include_default := TRUE)
LOOP
DROP TABLE child
END LOOP
END$$;
What am I doing wrong?
This is the function I'm using it's from the postgresql extension pg_partman :
CREATE OR REPLACE FUNCTION partman.show_partitions(p_parent_table text, p_order text DEFAULT 'ASC'::text, p_include_default boolean DEFAULT false)
RETURNS TABLE(partition_schemaname text, partition_tablename text)
LANGUAGE plpgsql
STABLE
SET search_path TO 'partman', 'pg_temp'
AS $function$
DECLARE
v_control text;
v_control_type text;
v_datetime_string text;
v_default_sql text;
v_epoch text;
v_parent_schema text;
v_parent_tablename text;
v_partition_interval text;
v_partition_type text;
v_sql text;
BEGIN
/*
* Function to list all child partitions in a set in logical order.
* Default partition is not listed by default since that's the common usage internally
* If p_include_default is set true, default is always listed first.
*/
IF upper(p_order) NOT IN ('ASC', 'DESC') THEN
EXECUTE format('SELECT set_config(%L, %L, %L)', 'search_path', v_old_search_path, 'false');
RAISE EXCEPTION 'p_order paramter must be one of the following values: ASC, DESC';
END IF;
SELECT partition_type
, partition_interval
, datetime_string
, control
, epoch
INTO v_partition_type
, v_partition_interval
, v_datetime_string
, v_control
, v_epoch
FROM partman.part_config
WHERE parent_table = p_parent_table;
IF v_partition_type IS NULL THEN
RAISE EXCEPTION 'Given parent table not managed by pg_partman: %', p_parent_table;
END IF;
SELECT n.nspname, c.relname INTO v_parent_schema, v_parent_tablename
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
WHERE n.nspname = split_part(p_parent_table, '.', 1)::name
AND c.relname = split_part(p_parent_table, '.', 2)::name;
IF v_parent_tablename IS NULL THEN
RAISE EXCEPTION 'Given parent table not found in system catalogs: %', p_parent_table;
END IF;
SELECT general_type INTO v_control_type FROM partman.check_control_type(v_parent_schema, v_parent_tablename, v_control);
RAISE DEBUG 'show_partitions: v_parent_schema: %, v_parent_tablename: %, v_datetime_string: %', v_parent_schema, v_parent_tablename, v_datetime_string;
v_sql := format('SELECT n.nspname::text AS partition_schemaname, c.relname::text AS partition_name FROM
pg_catalog.pg_inherits h
JOIN pg_catalog.pg_class c ON c.oid = h.inhrelid
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
WHERE h.inhparent = ''%I.%I''::regclass'
, v_parent_schema
, v_parent_tablename);
IF v_partition_type = 'native' AND current_setting('server_version_num')::int >= 110000 THEN
IF p_include_default THEN
-- Return the default partition immediately as first item in list
v_default_sql := v_sql || format('
AND pg_get_expr(relpartbound, c.oid) = ''DEFAULT''');
RAISE DEBUG 'show_partitions: v_default_sql: %', v_default_sql;
RETURN QUERY EXECUTE v_default_sql;
END IF;
v_sql := v_sql || format('
AND pg_get_expr(relpartbound, c.oid) != ''DEFAULT''');
END IF;
IF v_control_type = 'time' OR (v_control_type = 'id' AND v_epoch <> 'none') THEN
IF v_partition_interval::interval <> '3 months' OR (v_partition_interval::interval = '3 months' AND v_partition_type = 'time-custom') THEN
v_sql := v_sql || format('
ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) ), %L) %s'
, v_datetime_string
, p_order);
ELSE
-- For quarterly, to_timestamp() doesn't recognize "Q" in datetime string.
-- First order by just the year, then order by the quarter number (should be last character in table name)
v_sql := v_sql || format('
ORDER BY to_timestamp(substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) for 4), ''YYYY'') %s
, substring(reverse(c.relname) from 1 for 1) %s'
, p_order
, p_order);
END IF;
ELSIF v_control_type = 'id' THEN
v_sql := v_sql || format('
ORDER BY substring(c.relname from ((length(c.relname) - position(''p_'' in reverse(c.relname))) + 2) )::bigint %s'
, p_order);
END IF;
RAISE DEBUG 'show_partitions: v_sql: %', v_sql;
RETURN QUERY EXECUTE v_sql;
END
$function$
;
As you can see the function returns a table and I want to loop on each row and drop the the corresponding child table.

Oracle query to gets all COLUMN_NAMES during run-time and then fetch value from each ROW w.r.t. each column

I want an Oracle sql query, which on runtime gets all COLUMN_NAMES from a particular TABLE and then use those COLUMN_NAMES to fetch value from each ROW,considering that I don't know the number and name of COLUMNS within the TABLE.
TABLE1:
COLUMN_1, COLUMN_2, COLUMN_3, COLUMN_4
value11 value12 value13 value14
value21 value22 value23 value24
...
As a result of a sql query I am expecting an Insert statement corresponding to each row as follows:
INSERT INTO TABLE1 VALUES (value11,value12,value13,value14);
INSERT INTO TABLE1 VALUES (value21,value22,value23,value24);
...
My approach so far is as follows:
QUERY 1
SELECT 'INSERT INTO TABLE1 VALUES (' ||COLUMN_1 ||',' ||COLUMN_2 ||',' ||COLUMN_3 ||',' ||COLUMN_4 ||');' AS INSERTSCRIPT FROM TABLE1 ORDER BY COLUMN_1;
Using this I get the desired insert statments, but the problem is "I want to avoid specifying the column names manually for each TABLE (as there are more than hundreds of such TABLES)".
So I have been trying next with:
QUERY 2)
SELECT LISTAGG(COLUMN_NAME, ', ') WITHIN GROUP (ORDER BY COLUMN_ID) FROM USER_TAB_COLS WHERE TABLE_NAME = 'TABLE1';
which lists the columns as COLUMN_1, COLUMN_2, COLUMN_3, COLUMN_4 but the problem is that I am not able to use this result in the QUERY_1
Thanks in Advance!
select 'select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from my_table'
from user_tab_columns
where table_name = 'MY_TABLE';
Such query will produce for you query to select all columns from MY_TABLE.
In PL/SQL you can do:
declare
querysql varchar2;
select 'insert into my_table select ' || LISTAGG(column_name , ',') within group (order by column_id) || ' from my_table' into querysql
from user_tab_columns
where table_name = 'REFERENCE1';
execute immediate querysql;
end;
I guess you need DML generator. Something like that:
declare
v_table_name varchar2(30) := 'YOUR_TABLE';
v_date_mask varchar2(64) := 'dd.mm.yyyy hh24:mi:ss';
v_sql varchar2(4000);
cur int;
n int;
v_cols dbms_sql.desc_tab;
v_varchar2 varchar2(10);
v_number number;
v_date date;
v_column_list varchar2(4000);
v_values_list varchar2(4000);
v_single_value varchar2(4000);
begin
v_sql := 'select * from ' || v_table_name;
cur := dbms_sql.open_cursor;
dbms_sql.parse(cur, v_sql, dbms_sql.native);
dbms_sql.describe_columns(c => cur, col_cnt => n, desc_t => v_cols);
for i in v_cols.first..v_cols.last
loop
case
when v_cols(i).col_type = dbms_sql.Varchar2_Type
then
dbms_sql.define_column_char(c => cur, position => i, column => v_varchar2 , column_size => v_cols(i).col_max_len);
when v_cols(i).col_type = dbms_sql.Number_Type
then
dbms_sql.define_column(c => cur, position => i, column => v_number);
when v_cols(i).col_type = dbms_sql.Date_Type
then
dbms_sql.define_column(c => cur, position => i, column => v_date);
end case;
v_column_list := v_column_list || ',' || v_cols(i).col_name;
end loop;
v_column_list := ltrim(v_column_list,',');
n := dbms_sql.execute(cur);
n := dbms_sql.fetch_rows(cur);
while (n > 0)
loop
for i in v_cols.first..v_cols.last
loop
case
when v_cols(i).col_type = dbms_sql.Varchar2_Type
then
dbms_sql.column_value_char(c => cur, position => i, value => v_varchar2);
v_single_value := case
when v_varchar2 is not null
then '''' || trim(v_varchar2) || ''''
else 'NULL'
end;
when v_cols(i).col_type = dbms_sql.Number_Type
then
dbms_sql.column_value(c => cur, position => i, value => v_number);
v_single_value := nvl(to_char(v_number), 'NULL');
when v_cols(i).col_type = dbms_sql.Date_Type
then
dbms_sql.column_value(c => cur, position => i, value => v_date);
v_single_value := case
when v_date is not null
then 'to_date(''' || to_char(v_date, v_date_mask) || ''',''' || v_date_mask || ''')'
else 'NULL'
end;
end case;
v_values_list := v_values_list || ',' || v_single_value;
end loop;
v_values_list := ltrim(v_values_list,',');
dbms_output.put_line ('INSERT INTO ' || v_table_name || '(' || v_column_list || ')');
dbms_output.put_line (' VALUES (' || v_values_list || ');' || chr(10));
v_values_list := null;
n := dbms_sql.fetch_rows(cur);
end loop;
dbms_sql.close_cursor(cur);
exception
when others then
dbms_sql.close_cursor(cur);
raise;
end;

How to validate and print message when there is no data? PL SQL

Using PL/SQL I'm trying to validate and print a message when there's no data found on a cursor. My main problem is that if I use the %NOTFOUND it gets printed multiple times or along when data is found.
Here is my code:
set SERVEROUTPUT ON
set verify off
DECLARE
rut int;
CURSOR Ejercicio2 (rut int)
IS
SELECT alumno.nombre,alumno.apellidoP,alumno.apellidoM,Ramo.descripcion,profesor.nombre
FROM alumno
INNER JOIN alumnoramo ON alumnoramo.cod_matricula = alumno.cod_matricula
INNER JOIN Ramo ON ramo.cod_ramo = alumnoramo.cod_ramo
INNER JOIN profesor ON profesor.cod_prof = ramo.cod_prof
WHERE alumno.cod_matricula = rut;
alumno_nombre VARCHAR2(45);
alumno_apellido VARCHAR2(45);
alumno_apellidoM VARCHAR2(45);
ramo_nombre VARCHAR2(45);
profesor_nombre VARCHAR2(45);
BEGIN
rut := '&rut';
OPEN Ejercicio2 (rut);
LOOP
FETCH Ejercicio2 INTO alumno_nombre,alumno_apellido,alumno_apellidoM,ramo_nombre,profesor_nombre;
EXIT WHEN Ejercicio2%NOTFOUND;
dbms_output.put_line('Nombre: ' || alumno_nombre);
dbms_output.put_line('Apellido: ' || alumno_apellido);
dbms_output.put_line('Apellido Materno: ' || alumno_apellidoM);
dbms_output.put_line('Ramo: ' || ramo_nombre);
dbms_output.put_line('Profesor: ' || profesor_nombre);
END LOOP;
CLOSE Ejercicio2;
END;
When the user inputs a "rut" it should normally print the found data and end. If the input "rut" and no data is returned, then it should print a message and ending the program. I haven't being able to do so.
use the below code block .let me know if you get some error.I just wrote it on notepad and it should as I expect.
set SERVEROUTPUT ON
set verify off
DECLARE
rut int;
CURSOR Ejercicio2 (rut int)
IS
SELECT alumno.nombre,alumno.apellidoP,alumno.apellidoM,Ramo.descripcion,profesor.nombre
FROM alumno
INNER JOIN alumnoramo ON alumnoramo.cod_matricula = alumno.cod_matricula
INNER JOIN Ramo ON ramo.cod_ramo = alumnoramo.cod_ramo
INNER JOIN profesor ON profesor.cod_prof = ramo.cod_prof
WHERE alumno.cod_matricula = rut;
TYPE Ejercicio2_typ IS TABLE OF Ejercicio2%ROWTYPE;
Ejercicio2_tbl Ejercicio2_typ;
BEGIN
rut := '&rut';
OPEN Ejercicio2 (rut);
FETCH Ejercicio2 BULK COLLECT INTO Ejercicio2_tbl;
IF Ejercicio2_tbl.count >0 THEN
FOR rec IN Ejercicio2_tbl.first..Ejercicio2_tbl.last LOOP
dbms_output.put_line('Nombre: ' || Ejercicio2_tbl(rec).alumno_nombre);
dbms_output.put_line('Apellido: ' || Ejercicio2_tbl(rec).alumno_apellido);
dbms_output.put_line('Apellido Materno: ' || Ejercicio2_tbl(rec).alumno_apellidoM);
dbms_output.put_line('Ramo: ' || Ejercicio2_tbl(rec).ramo_nombre);
dbms_output.put_line('Profesor: ' || Ejercicio2_tbl(rec).profesor_nombre);
END LOOP;
ELSE
dbms_output.put_line('No data found');
END IF;
CLOSE Ejercicio2;
END;
/
Happy coding.
Mark it as an answer if it satisfy your needs.
Just a variation and a clean way of handling the scenario. Hope it helps.
CREATE OR REPLACE TYPE obj IS OBJECT
(
nombre VARCHAR2(100),
apellidoP VARCHAR2(100)
apellidoM VARCHAR2(100)
descripcion VARCHAR2(100)
nombre VARCHAR2(100)
);
CREATE OR REPLACE TYPE tab IS TABLE OF obj;
SET SERVEROUTPUT ON
SET verify OFF
DECLARE
rut INT:=&Enter_rut;
tab1 tab;
BEGIN
SELECT obj(alumno.nombre,alumno.apellidoP,alumno.apellidoM,Ramo.descripcion,profesor.nombre) BULK COLLECT
INTO tab1
FROM alumno
INNER JOIN alumnoramo
ON alumnoramo.cod_matricula = alumno.cod_matricula
INNER JOIN Ramo
ON ramo.cod_ramo = alumnoramo.cod_ramo
INNER JOIN profesor
ON profesor.cod_prof = ramo.cod_prof
WHERE alumno.cod_matricula = rut;
IF tab1.EXISTS(1) THEN
FOR I IN tab1.FIRST..tab1.LAST
LOOP
dbms_output.put_line('Nombre: ' || tab1(i).nombre);
dbms_output.put_line('Apellido: ' || tab1(i).apellidoP);
dbms_output.put_line('Apellido Materno: ' || tab1(i).apellidoM);
dbms_output.put_line('Ramo: ' || tab1(i).descripcion);
dbms_output.put_line('Profesor: ' || tab1(i).nombre);
END LOOP;
ELSE
dbms_output.put_line('no data found for the Input');
END IF;
END;
You could set a Boolean variable in the loop, and then check at the end whether it was true or false:
set define on
accept rut number prompt 'Introduza un identificador de class: '
var rut number
exec :rut := &rut
declare
cursor ejercicio2
( cp_rut int )
is
select alumno.nombre as alumno_nombre
, alumno.apellidop as alumno_apellido
, alumno.apellidom as alumno_apellidom
, ramo.descripcion as ramo_nombre
, profesor.nombre as profesor_nombre
from alumno
join alumnoramo
on alumnoramo.cod_matricula = alumno.cod_matricula
join ramo
on ramo.cod_ramo = alumnoramo.cod_ramo
join profesor
on profesor.cod_prof = ramo.cod_prof
where alumno.cod_matricula = cp_rut;
l_encontro boolean := false;
begin
for r in ejercicio2(:rut)
loop
l_encontro := true;
dbms_output.put_line('Nombre: ' || r.alumno_nombre);
dbms_output.put_line('Apellido: ' || r.alumno_apellido);
dbms_output.put_line('Apellido Materno: ' || r.alumno_apellidom);
dbms_output.put_line('Ramo: ' || r.ramo_nombre);
dbms_output.put_line('Profesor: ' || r.profesor_nombre);
end loop;
if not l_encontro then
dbms_output.put_line('No se encontraron las classes por rut ' || :rut);
end if;
end;
/
(Excuse my Google Spanish.)
Or you could make it a numeric value initialised to 0 and report the number of rows processed.

Finding specific data in Oracle Tables

I needed to find a value for a column in my oracle database but i don't know which
table or column it's stored in
How can I search for a specific or like %% data as I do in
select * from SYS.dba_source
is there a table like that
Column Name ID Data Type Null? Comments
OWNER 1 VARCHAR2 (30 Byte) Y
NAME 2 VARCHAR2 (30 Byte) Y Name of the object
TYPE 3 VARCHAR2 (12 Byte) Y
Type of the object:
"TYPE", "TYPE BODY", "PROCEDURE", "FUNCTION",
"PACKAGE", "PACKAGE BODY" or "JAVA SOURCE"
LINE 4 NUMBER Y Line number of this line of source
TEXT 5 VARCHAR2 (4000 Byte) Y Source text
LINK: pl/sq to find any data in a schema
Imagine, there are a few tables in your schema and you want to find a specific value in all columns within these tables. Ideally, there would be an sql function like
select * from * where any(column) = 'value';
Unfortunately, there is no such function.
However, a PL/SQL function can be written that does that. The following function iterates over all character columns in all tables of the current schema and tries to find val in them.
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;

pl/sql - can collection loop through column names?

The output from the below code is:
|LAT|MISC|SID|NO
MIN_LENGTH|1|2|1|1
MAX_LENGTH|6|6|4|2
The output is as I expect, but is there anyway to loop through the columns using an index (ie. j) instead of doing RESULTS(I).MAX_LENGTH , RESULTS(I).MAX_LENGTH etc ? The concern is that when adding extra columns to the 'R_RESULT_REC' record, another loop is required.
set serveroutput on
DECLARE
TYPE R_RESULT_REC IS RECORD
(COL_NAME VARCHAR2(100),
MIN_LENGTH NUMBER,
MAX_LENGTH NUMBER
);
TYPE tr_RESULT IS TABLE OF R_RESULT_REC;
RESULTS TR_RESULT := TR_RESULT();
v_counter NUMBER := 1;
BEGIN
FOR J IN (SELECT DISTINCT COLUMN_NAME FROM ALL_TAB_COLUMNS
WHERE OWNER = 'SYSTEM'
and TABLE_NAME = 'SPECCHAR')
LOOP
RESULTS.EXTEND;
RESULTS(V_COUNTER).COL_NAME := J.COLUMN_NAME;
EXECUTE IMMEDIATE 'SELECT MIN(LENGTH('||J.COLUMN_NAME||')),
MAX(LENGTH('||J.COLUMN_NAME||'))
FROM '||'SYSTEM'||'.'||'SPECCHAR' INTO
RESULTS(V_COUNTER).MIN_LENGTH,
RESULTS(V_COUNTER).MAX_LENGTH;
V_COUNTER := V_COUNTER + 1;
END LOOP;
FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
IF I = RESULTS.LAST THEN
DBMS_OUTPUT.PUT_LINE(RESULTS(I).COL_NAME);
ELSIF I = RESULTS.FIRST THEN
DBMS_OUTPUT.PUT(' |'||RESULTS(I).COL_NAME||'|');
ELSE
DBMS_OUTPUT.PUT(RESULTS(I).COL_NAME||'|');
END IF ;
END LOOP;
FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
IF I = RESULTS.LAST THEN
DBMS_OUTPUT.PUT_LINE(RESULTS(I).MIN_LENGTH);
ELSIF I = RESULTS.FIRST THEN
DBMS_OUTPUT.PUT('MIN_LENGTH|'||RESULTS(I).MIN_LENGTH||'|');
ELSE
DBMS_OUTPUT.PUT(RESULTS(I).MIN_LENGTH||'|');
END IF ;
END LOOP;
FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
IF I = RESULTS.LAST THEN
DBMS_OUTPUT.PUT_LINE(RESULTS(I).MAX_LENGTH);
ELSIF I = RESULTS.FIRST THEN
DBMS_OUTPUT.PUT('MAX_LENGTH|'||RESULTS(I).MAX_LENGTH||'|');
ELSE
DBMS_OUTPUT.PUT(RESULTS(I).MAX_LENGTH||'|');
END IF ;
END LOOP;
end;
This uses DBMS_SQL, so it's pretty snarly to read. The main reason I saw to use it was that I could get columnar descriptions of a SQL statement and to a buffer-based, not object-based fetch.
Rather than making calls to DBMS_OUTPUT during the processing, it builds a table of records for output, using associative arrays for simplicity.
It could further be refined to have an array or parsable list of functions to apply to each function, but that seems excess to current requirements. The nature of the code would require editing if new aggregation functions are being added.
Call overview (2c + a + s):
3 loops;
2 loops over column list (c),
1 loop over number of analytic functions (a).
1 SQL statement against table data (s).
OP's call overview (c*s + a + 1):
1 loop, executing a sql statement against table data per column (c*s)
a+1 loops, where a is the number of analytic functions
Test data:
1 select min(length(GP_ID)), max(length(GP_ID)),
2 min(length(GGP_ID)), max(length(GGP_ID)),
3 min(length(OBJECT_NAME)), max(length(OBJECT_NAME))
4* from AMUSCH.GP
SQL> /
MIN(LENGTH(GP_ID)) MAX(LENGTH(GP_ID)) MIN(LENGTH(GGP_ID))
MAX(LENGTH(GGP_ID)) MIN(LENGTH(OBJECT_NAME)) MAX(LENGTH(OBJECT_NAME))
1 7 1
4 9 41
Code:
declare
p_owner varchar2(30);
p_table_name varchar2(30);
TYPE OUTPUT_TAB_TYPE IS TABLE OF VARCHAR2(32767) index by binary_integer;
OUTPUT_TAB OUTPUT_TAB_TYPE;
l_columns_tab dbms_sql.desc_tab;
l_columns_cur integer;
l_columns_sql varchar2(32767);
l_columns_cnt number;
l_minmax_sql varchar2(32767);
l_minmax_cur integer;
l_minmax_tab dbms_sql.desc_tab;
l_minmax_cnt number;
l_fetch_ok number;
l_fetch_value number;
begin
p_owner := 'AMUSCH';
p_table_name := 'GP';
output_tab(1) := lpad(' ', 20, ' ');
output_tab(2) := lpad('MIN_LENGTH', 20, ' ');
output_tab(3) := lpad('MAX_LENGTH', 20, ' ');
l_columns_sql := 'select * from ' || p_owner || '.' || p_table_name ||
' where 1 = 0';
l_columns_cur := dbms_sql.open_cursor;
dbms_sql.parse (l_columns_cur, l_columns_sql, dbms_sql.native);
dbms_sql.describe_columns (l_columns_cur, l_columns_cnt, l_columns_tab);
-- build the min/max sql statement
l_minmax_sql := 'select ' ;
for i in 1..l_columns_cnt
loop
l_minmax_sql := l_minmax_sql ||
' min(length(' || l_columns_tab(i).col_name || ')), ';
l_minmax_sql := l_minmax_sql ||
' max(length(' || l_columns_tab(i).col_name || ')), ';
end loop;
l_minmax_sql := substr(l_minmax_sql, 1,
length(l_minmax_sql) - 2); -- trim trailing comma
l_minmax_sql := l_minmax_sql || ' from ' || p_owner || '.' || p_table_name;
l_minmax_cur := dbms_sql.open_cursor;
dbms_sql.parse (l_minmax_cur, l_minmax_sql, dbms_sql.native);
dbms_sql.describe_columns (l_minmax_cur, l_minmax_cnt, l_minmax_tab);
for i in 1..l_minmax_cnt
loop
dbms_sql.define_column(l_minmax_cur, i, l_fetch_value);
end loop;
l_fetch_ok := dbms_sql.execute(l_minmax_cur);
loop
l_fetch_ok := dbms_sql.fetch_rows(l_minmax_cur);
exit when l_fetch_ok = 0;
-- loop over the columns selected over
for i in 1..l_columns_cnt
loop
output_tab(1) := output_tab(1) || '|' || l_columns_tab(i).col_name;
dbms_sql.column_value(l_minmax_cur, (2*i-1), l_fetch_value);
output_tab(2) := output_tab(2) || '|' ||
lpad(l_fetch_value, length(l_columns_tab(i).col_name), ' ');
dbms_sql.column_value(l_minmax_cur, (2*i), l_fetch_value);
output_tab(3) := output_tab(3) || '|' ||
lpad(l_fetch_value, length(l_columns_tab(i).col_name), ' ');
end loop;
end loop;
if dbms_sql.is_open(l_minmax_cur) then
dbms_sql.close_cursor (l_minmax_cur);
end if;
if dbms_sql.is_open (l_columns_cur) then
dbms_sql.close_cursor (l_columns_cur);
end if;
for i in output_tab.first..output_tab.last
loop
dbms_output.put_line(output_tab(i));
end loop;
end;
/
Results:
|GP_ID|GGP_ID|OBJECT_NAME
MIN_LENGTH| 1| 1| 9
MAX_LENGTH| 7| 4| 41
If you want to use the DBMS_SQL package (which is sometimes very complex), then there is a DBMS_SQL.COLUMN_VALUE function that may work for you.
update:
Or even better: DBMS_SQL.DESC_REC
you can refer to:
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#i996963
notice example 8
I haven't tested it
update:
Perhaps what you really want is to loop on an Object type attributes and not a table column, so maybe you should try a different approach:
Make your type R_RESULT_REC an Object type in the DB and then you can loop on the query results:
SELECT attr_name
FROM user_type_attrs
WHERE type_name = 'R_RESULT_REC'
It's not like working with indexes but you still don't need to hard code the column names / type attributes
here is the code (based on yours):
CREATE OR REPLACE TYPE R_RESULT_REC AS OBJECT
(
COL_NAME VARCHAR2(100),
MIN_LENGTH NUMBER,
MAX_LENGTH NUMBER
);
/
and then:
DECLARE
TYPE tr_RESULT IS TABLE OF R_RESULT_REC;
RESULTS TR_RESULT := TR_RESULT();
v_counter NUMBER := 1;
v_max number;
v_min number;
BEGIN
FOR J IN (SELECT DISTINCT COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE OWNER = 'SYSTEM'
and TABLE_NAME = 'SPECCHAR') LOOP
EXECUTE IMMEDIATE 'SELECT MIN(LENGTH(' || J.COLUMN_NAME || ')),
MAX(LENGTH(' || J.COLUMN_NAME || ')) FROM ' ||
'SPECCHAR'
INTO v_min, v_max;
RESULTS.EXTEND;
RESULTS(V_COUNTER) := new R_RESULT_REC(J.COLUMN_NAME, v_min, v_max);
V_COUNTER := V_COUNTER + 1;
END LOOP;
for r in (select attr_name
from all_type_attrs t
where t.owner = 'SYSTEM'
and t.type_name = 'R_RESULT_REC') loop
FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
IF I = RESULTS.LAST THEN
execute immediate 'declare rec R_RESULT_REC := :0; begin' ||
' DBMS_OUTPUT.PUT_LINE(rec.' || r.attr_name || ');' ||
'end;'
using RESULTS(I);
ELSIF I = RESULTS.FIRST THEN
execute immediate 'declare rec R_RESULT_REC := :0; begin' ||
' DBMS_OUTPUT.PUT(''' || r.attr_name ||
'|'' || rec.' || r.attr_name || ' || ''|'');' ||
'end;'
using RESULTS(I);
ELSE
execute immediate 'declare rec R_RESULT_REC := :0; begin' ||
' DBMS_OUTPUT.PUT(rec.' || r.attr_name ||
' || ''|''); ' || 'end;'
using RESULTS(I);
END IF;
END LOOP;
end loop;
end;
If you'll add another attribute to the Record (and initiate it with values) , it will automatic display it.
Take advantage of Oracle's stats for this.
First, fully build stats on table using dbms_stats.gather_table_stats
Then, create the following function to help translate the raw low/high values that Oracle stores in all_tab_columns
create or replace function show_raw(i_raw raw, i_type varchar2)
return varchar2 is
l_varchar2 varchar2(32);
l_number number;
l_date date;
l_nvarchar2 nvarchar2(32);
l_rowid rowid;
l_char char;
begin
if (i_type = 'VARCHAR2') then
DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_varchar2);
return to_char(l_varchar2);
elsif(i_type = 'NUMBER') then
DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_number);
return to_char(l_number);
elsif(i_type = 'DATE') then
DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_date);
return to_char(l_date);
elsif(i_type = 'NVARCHAR2') then
DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_nvarchar2);
return to_char(l_nvarchar2);
elsif(i_type = 'ROWID') then
DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_rowid);
return to_char(l_rowid);
elsif(i_type = 'CHAR') then
DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_char);
return l_char;
else return 'Unknown type value';
end if;
end;
Then, just select the low/high values for each column:
select column_id,
column_name,
data_type,
show_raw(low_value, data_type) as min_val,
show_raw(high_value, data_type) as max_val
from all_tab_columns
where table_name = 'SOME_TABLE'
and owner = 'SOME_OWNER'
;