splitting a column value in sql - sql

I have a column in a table which is varray of varchar2. After fetching this column value I want to process each element of this varray column. How to split the retrieved column into individual values.

This might help you:
CREATE TABLE t (id NUMBER, val VARCHAR2(20));
insert into t values (1,'val1');
INSERT INTO t VALUES (2,'val2');
INSERT INTO t VALUES (3, 'val3');
SET serveroutput ON
DECLARE
type my_type IS varray(200) OF VARCHAR2(200);
obj1 my_type := my_type();
BEGIN
FOR i IN 1..3 LOOP
obj1.extend;
SELECT val INTO obj1(i) FROM t WHERE id = i;
end loop;
FOR j IN obj1.first..obj1.last LOOP
dbms_output.put_line(obj1(j));
END LOOP;
END;
results:
val1
val2
val3
or you can use the following folowing for nested table columns:
FOR REC IN
(SELECT * FROM TABLE(c) --or select * from table(cast(c as object_type_tab))
)
LOOP
dbms_output.put_line(rec.id || ', ' || rec.val);
END LOOP;

Related

Errors in PLSQL -

Morning,
I'm trying to write a script that will convert Unload tables (UNLD to HDL files) creating a flat file using PLSQL. I keep getting syntax errors trying to run it and would appreciate some help from an expert out there!
Here are the errors:
Error(53,21): PLS-00330: invalid use of type name or subtype name
Error(57,32): PLS-00222: no function with name 'UNLDTABLE' exists in this scope
Our guess is that the unldTable variable is being treated as a String, rather than a database table object (Not really expereinced in PLSQL)
CREATE OR REPLACE PROCEDURE UNLD_TO_HDL (processComponent IN VARCHAR2)
IS
fHandle UTL_FILE.FILE_TYPE;
concatData VARCHAR2(240);
concatHDLMetaTags VARCHAR2(240);
outputFileName VARCHAR2(240);
TYPE rowArrayType IS TABLE OF VARCHAR2(240);
rowArray rowArrayType;
emptyArray rowArrayType;
valExtractArray rowArrayType;
hdlFileName VARCHAR2(240);
unldTable VARCHAR2(240);
countUNLDRows Number;
dataType VARCHAR2(240);
current_table VARCHAR2(30);
value_to_char VARCHAR2(240);
BEGIN
SELECT HDL_FILE_NAME
INTO hdlFileName
FROM GNC_HDL_CREATION_PARAMS
WHERE PROCESS_COMPONENT = processComponent;
SELECT UNLD_TABLE
INTO unldTable
FROM GNC_HDL_CREATION_PARAMS
WHERE PROCESS_COMPONENT = processComponent
FETCH NEXT 1 ROWS ONLY;
SELECT LISTAGG(HDL_META_TAG,'|')
WITHIN GROUP(ORDER BY HDL_META_TAG)
INTO concatHDLMetaTags
FROM GNC_MIG_CONTROL
WHERE HDL_COMP = processComponent;
SELECT DB_FIELD
BULK COLLECT INTO valExtractArray
FROM GNC_MIG_CONTROL
WHERE HDL_COMP = processComponent
ORDER BY HDL_META_TAG;
fHandle := UTL_FILE.FOPEN('./', hdlFileName, 'W');
UTL_FILE.PUTF(fHandle, concatHDLMetaTags + '\n');
SELECT num_rows INTO countUNLDRows FROM user_tables where table_name = unldTable;
FOR row in 1..countUNLDRows LOOP
rowArray := emptyArrayType;
FOR value in 1..valExtractArray.COUNT LOOP
rowArray.extend();
SELECT data_type INTO dataType FROM all_tab_columns where table_name = unldTable AND column_name = valExtractArray(value);
IF dataType = 'VARCHAR2' THEN (SELECT valExtractArray(value) INTO value_to_char FROM current_table WHERE ROWNUM = row);
ELSIF dataType = 'DATE' THEN (SELECT TO_CHAR(valExtractArray(value),'YYYY/MM/DD') INTO value_to_char FROM current_table WHERE ROWNUM = row);
ELSIF dataType = 'NUMBER' THEN (SELECT TO_CHAR(valExtractArray(value)) INTO value_to_char FROM current_table WHERE ROWNUM = row);
ENDIF;
rowArray(value) := value_to_char;
END LOOP;
concatData := NULL;
FOR item in 1..rowArray.COUNT LOOP
IF item = rowArray.COUNT
THEN concatData := (COALESCE(concatData,'') || rowArray(item));
ELSE concatData := (COALESCE(concatData,'') || rowArray(item) || '|');
END IF;
END LOOP;
UTL_FILE.PUTF(fHandle, concatData + '/n');
END LOOP;
UTL_FILE.FCLOSE(fHandle);
END;
Thanks,
Adam
I believe it is just an overlook in your code. You define unldTable as a varchar, which is used correctly until you try to access it as if it were a varray on line 51
rowArray(value) := unldTable(row).valExtractArray(value);
Given that you have not defined it as a varray, unldTable(row) is making the interpreter believe that you are referring to a function.
EDIT
Now that you have moved on, you should resolve the problem of invoking SELECT statements on tables that are unknown at runtime. To do so you need to make use of Dynamic SQL; you can do it in several way, the most direct being an Execute immediate statement in your case:
mystatement := 'SELECT valExtractArray(value) INTO :value_to_char FROM ' || current_table || ' WHERE ROWNUM = ' || row;
execute immediate mystatement USING OUT value_to_char;
It looks like you need to generate a cursor as
select [list of columns from GNC_MIG_CONTROL.DB_FIELD]
from [table name from GNC_HDL_CREATION_PARAMS.UNLD_TABLE]
Assuming setup like this:
create table my_table (business_date date, id integer, dummy1 varchar2(1), dummy2 varchar2(20));
create table gnc_hdl_creation_params (unld_table varchar2(30), process_component varchar2(30));
create table gnc_mig_control (db_field varchar2(30), hdl_comp varchar2(30), hdl_meta_tag integer);
insert into my_table(business_date, id, dummy1, dummy2) values (date '2018-01-01', 123, 'X','Some more text');
insert into gnc_hdl_creation_params (unld_table, process_component) values ('MY_TABLE', 'XYZ');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('BUSINESS_DATE', 'XYZ', '1');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('ID', 'XYZ', '2');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('DUMMY1', 'XYZ', '3');
insert into gnc_mig_control (db_field, hdl_comp, hdl_meta_tag) values ('DUMMY2', 'XYZ', '4');
You could build a query like this:
select unld_table, listagg(expr, q'[||'|'||]') within group (order by hdl_meta_tag) as expr_list
from ( select t.unld_table
, case tc.data_type
when 'DATE' then 'to_char('||c.db_field||',''YYYY-MM-DD'')'
else c.db_field
end as expr
, c.hdl_meta_tag
from gnc_hdl_creation_params t
join gnc_mig_control c
on c.hdl_comp = t.process_component
left join user_tab_columns tc
on tc.table_name = t.unld_table
and tc.column_name = c.db_field
where t.process_component = 'XYZ'
)
group by unld_table;
Output:
UNLD_TABLE EXPR_LIST
----------- --------------------------------------------------------------------------------
MY_TABLE to_char(BUSINESS_DATE,'YYYY-MM-DD')||'|'||ID||'|'||DUMMY1||'|'||DUMMY2
Now if you plug that logic into a PL/SQL procedure you could have something like this:
declare
processComponent constant gnc_hdl_creation_params.process_component%type := 'XYZ';
unloadSQL long;
unloadCur sys_refcursor;
text long;
begin
select 'select ' || listagg(expr, q'[||'|'||]') within group (order by hdl_meta_tag) || ' as text from ' || unld_table
into unloadSQL
from ( select t.unld_table
, case tc.data_type
when 'DATE' then 'to_char('||c.db_field||',''YYYY/MM/DD'')'
else c.db_field
end as expr
, c.hdl_meta_tag
from gnc_hdl_creation_params t
join gnc_mig_control c
on c.hdl_comp = t.process_component
left join user_tab_columns tc
on tc.table_name = t.unld_table
and tc.column_name = c.db_field
where t.process_component = processComponent
)
group by unld_table;
open unloadCur for unloadSQL;
loop
fetch unloadCur into text;
dbms_output.put_line(text);
exit when unloadCur%notfound;
end loop;
close unloadCur;
end;
Output:
2018/01/01|123|X|Some more text
2018/01/01|123|X|Some more text
Now you just have to make that into a procedure, change dbms_output to utl_file and add your meta tags etc and you're there.
I've assumed there is only one distinct unld_table per process component. If there are more you'll need a loop to work through each one.
For a slightly more generic approach, you could build a cursor-to-csv generator which could encapsulate the datatype handling, and then you'd only need to build the SQL as select [columns] from [table]. You might then write a generic cursor to file processor, where you pass in the filename and a cursor and it does the lot.
Edit: I've updated my cursor-to-csv generator to provide file output, so you just need to pass it a cursor and the file details.

How to return result of many select statements as one custom table

I have a table (let's name it source_tab) where I store list of all database tables that meet some criteria.
tab_name: description:
table1 some_desc1
table2 some_desc2
Now I need to execute a select statement on each of these tables and return a result as a table (I created custom TYPE). However I have a problem - when using bulk collect, only the last select statement is returned. The same issue was with open cursor. Is there any possibility to achieve this goal, another then concatenating all select statements using union all and executing it as one statement? And because I'm the begginer in sql, my second question is, is it ok to use this dynamic sql in terms of sql injection issues? Below is simplified version of my code:
CREATE OR REPLACE FUNCTION my_function RETURN newly_created_table_type IS
ret_tab_type newly_created_table_type;
BEGIN
for r in (select * from source_tab)
loop
execute immediate 'select value1, value2,''' || r.tab_name || ''' from ' || r.tab_name bulk collect into ret_tab_type;
end loop;
return ret_tab_type;
END;
I'm using Oracle 11.
In your case you are trying to populate a collection dynamically and wanted result in a single collection. In your case its not possible to do that in a single loop. Also as mentioned by #OldProgrammer, piperow would be a better solution from performance point. See below demo:
--Tables and Values:
CREATE TABLE SOURCE_TAB(TAB_NAME VARCHAR2(100), DESCRIPTION VARCHAR2(100));
/
SELECT * FROM SOURCE_TAB;
/
INSERT INTO SOURCE_TAB VALUES('table1','some_desc1');
INSERT INTO SOURCE_TAB VALUES('table2','some_desc2');
/
CREATE TABLE TABLE1(COL1 NUMBER, COL2 NUMBER);
/
INSERT INTO TABLE1 VALUES(1,2);
INSERT INTO TABLE1 VALUES(3,4);
INSERT INTO TABLE1 VALUES(5,6);
/
Select * from TABLE1;
/
CREATE TABLE TABLE2(COL1 NUMBER, COL2 NUMBER);
/
INSERT INTO TABLE2 VALUES(7,8);
INSERT INTO TABLE2 VALUES(9,10);
INSERT INTO TABLE2 VALUES(11,12);
/
Select * from TABLE2;
/
--Object Created
--UDT
CREATE OR REPLACE TYPE NEWLY_CREATED_TABLE_TYPE IS OBJECT (
VALUE1 NUMBER,
VALUE2 NUMBER
);
/
--Type of UDT
CREATE OR TYPE NEWLY_CRTD_TYP AS TABLE OF NEWLY_CREATED_TABLE_TYPE;
/
--Function:
--Function
CREATE OR REPLACE FUNCTION MY_FUNCTION
RETURN NEWLY_CRTD_TYP PIPELINED
AS
CURSOR CUR_TAB
IS
SELECT *
FROM SOURCE_TAB;
RET_TAB_TYPE NEWLY_CRTD_TYP;
BEGIN
FOR I IN CUR_TAB
LOOP
--Here i made sure that all the tables have col1 & col2 columns since you are using dynamic sql.
EXECUTE IMMEDIATE 'select NEWLY_CREATED_TABLE_TYPE(COL1, COL2) from '|| I.TAB_NAME
BULK COLLECT INTO RET_TAB_TYPE;
EXIT WHEN CUR_TAB%NOTFOUND;
FOR REC IN 1 .. RET_TAB_TYPE.COUNT
LOOP
PIPE ROW (RET_TAB_TYPE (REC) );
END LOOP;
END LOOP;
RETURN;
END;
/
Output:
SQL> Select * from table(MY_FUNCTION);
VALUE1 VALUE2
---------- ----------
1 2
3 4
5 6
7 8
9 10
11 12
6 rows selected.
May be you can combine all the queries into one using UNION ALL before execution, if the number and type of columns to be retrieved from all the tables are identical.
CREATE OR REPLACE FUNCTION my_function
RETURN newly_created_table_type
IS
ret_tab_type newly_created_table_type;
v_query VARCHAR2 (4000);
BEGIN
SELECT LISTAGG (' select VALUE1,VALUE2 FROM ' || tab_name, ' UNION ALL ')
WITHIN GROUP (ORDER BY tab_name)
INTO v_query
FROM source_tab;
EXECUTE IMMEDIATE v_query BULK COLLECT INTO ret_tab_type;
RETURN ret_tab_type;
END;
You could then use a single select statement to get all the values.
select * FROM TABLE ( my_function );

How to convert varchar2 to numbers in PL / SQL

I have a bash script in which I would like to do such a query in SqlPlus
CREATE TABLE tab1(digits VARCHAR2(100));
INSERT INTO tab1(digits) VALUES (5,6);
COMMIT;
#!/bin/bash
.
.
DECLARE
lv_digits VARCHAR2(100):='';
BEGIN
SELECT digits INTO digits FROM tab1;
FOR i IN (SELECT column1 FROM tab2 WHERE column_id IN (lv_digits) AS text);
LOOP
DBMS_OUTPUT.PUT_LINE(i.text);
END LOOP;
END;
/
I don't know how to properly enter a condition into WHERE to return a value
How to manually introduc WHERE column_id IN (5,6) AS text it works correctly and the value must be loaded from another table with a column that is VARCHAR2.
DECLARE
lv_digits VARCHAR2(100):='';
BEGIN
SELECT digits INTO digits FROM tab1;
FOR i IN (SELECT column1 FROM tab2 WHERE column_id IN (5,6) AS text);
LOOP
DBMS_OUTPUT.PUT_LINE(i.text);
END LOOP;
END;
/
Does anyone have any idea how to convert this?
This can be useful to someone
DECLARE
lv_digits VARCHAR2(100):='';
BEGIN
SELECT digits INTO digits FROM tab1;
FOR i IN (SELECT column1 FROM tab2 WHERE column_id IN (select regexp_substr(digits,'[^,]+', 1, level) from karuzela_loop
connect by regexp_substr(digits, '[^,]+', 1, level) is not NULL) AS text);
LOOP
DBMS_OUTPUT.PUT_LINE(i.text);
END LOOP;
END;
/
#mathguy thanks for the tips

fetch table name from a column for from clause

I have a view t with me which has a column for table name and another column which has where clause condition.
id| name|table_in| where_clause
1 | Sam | t1 | age = 22
2 | John| t2 | age = 23 and sex = 'male'
and so on...
Now, I have put the records in a cursor and I want to run each query.
create or replace procedure create_cursor
is
CURSOR v_records is
select * from t ;
begin
FOR temp IN v_records LOOP
INSERT INTO myTable (id, name)
select temp.id, temp.name
from temp.table where temp.where_clause;
END LOOP;
end;
/
myTable is another table in which I want to put the records for next purpose.
#Akshay,
Please find the code below for your reference.
Create or replace procedure create_cursor is
l_statement varchar2(32767);
cursor v_records is
select * from t;
begin
for temp in v_records
loop
l_statement := 'INSERT INTO myTable (id, name) select '||temp.id||','
||temp.name|| ' from ' || temp.table1
|| ' where ' || temp.where_clause;
execute immediate l_statement;
end loop;
end;
/
You need dynamic sql to do this:
CREATE OR REPLACE PROCEDURE create_cursor
IS
l_statement VARCHAR2(32767);
CURSOR v_records
IS
SELECT * FROM t;
BEGIN
FOR temp IN v_records
LOOP
l_statement := 'INSERT INTO myTable (id, name)
select id, name from ' || temp.table ||
' where ' || temp.where_clause;
EXECUTE immediate l_statement;
END LOOP;
END;
/

select multiple columns of single row as elements of array

I have a table with 100 columns called like col_1, col_2, .. col_100
is there a way to select values of that columns of single into an array of 100 elements?
(Oracle 10.2)
you could just select them like:
SQL> create type foo as table of number; -- or varray, as you wish.
2 /
Type created.
SQL> select foo(l.a, l.b, l.c) foo from your_tab l;
FOO
-----------------
FOO(1, 2, 3)
etc..
Here's a brute force method. There's probably a more elegant way, or at least one that will cut down on typing. The example uses five columns rather than 100.
DECLARE
-- Change VARCHAR2(10) in the next line to your col_1 .. col_100 type
TYPE My100Array IS TABLE OF VARCHAR2(10) INDEX BY PLS_INTEGER;
myVals My100Array;
indx NUMBER;
BEGIN
SELECT 'These', 'are', 'the', 'column', 'values'
INTO myVals(1), myVals(2), myVals(3), myVals(4), myVals(5)
FROM DUAL;
FOR INDX IN 1..5 LOOP
DBMS_OUTPUT.PUT_LINE(indx || ': ' || myVals(indx));
END LOOP;
END;
Here's the output when I run this:
1: These
2: are
3: the
4: column
5: values
Of course, this will be a bit tough with 100 columns, but once you get the query out of the way you'll have the array as you want it.
Another example:
DECLARE
CURSOR c_data IS
SELECT * FROM scott.emp; -- replace emp table with your_table
TYPE t_source_tab IS TABLE OF scott.emp%ROWTYPE;
l_tab t_source_tab;
BEGIN
SELECT * BULK COLLECT INTO l_tab FROM scott.emp;
-- display values in array --
FOR i IN l_tab.FIRST ..l_tab.LAST
LOOP
DBMS_OUTPUT.PUT_LINE (l_tab(i).hiredate ||chr(9)||l_tab(i).empno ||chr(9)||l_tab(i).ename);
END LOOP;
END;
/
sounds like you want to unpivot your data..
unfortunately UNPIVOT was only added in 11g (not 10.2)
you could manually unpivot but one of the other solutions would work better i think.
However, if you were on 11g or later you could try this
create table my_table (col1 number,col2 number, col3 number);
Table MY_TABLE created.
insert into my_table values (4,5,6);
1 row inserted.
select * from my_table;
COL1 COL2 COL3
---------- ---------- ----------
4 5 6
select val from my_table unpivot ( val for col in ( col1,col2,col3));
VAL
----------
4
5
6
from there is trivial to select into an single column array
DECLARE
CURSOR c_data IS
select val from my_table unpivot ( val for col in ( col1,col2,col3));
TYPE t_source_tab IS TABLE OF c_data%ROWTYPE;
l_tab t_source_tab;
BEGIN
open c_data;
fetch c_data bulk collect into l_tab;
close c_data;
-- display values in array --
FOR i IN l_tab.FIRST ..l_tab.LAST
LOOP
DBMS_OUTPUT.PUT_LINE (l_tab(i).val);
END LOOP;
END;
/