Pl/SQL- Get column names from a query - sql

I'm using Pl/SQL with Oracle Database 11g.
I'm writing a function that takes in a select statement as a parameter (varchar2). The function uses a for loop to go over the rows and apply formatting to specific columns, and output the whole thing. Basically, I need some way to get the column names so that I can display them at the top. I know there are various ways to do this for tables, but since this query is passed in, all columns may not have been selected, aliases may have been used, etc.
Is there a way I can select out the column names from this query?
Ideally something like:
select column_names from (subquery)

I believe you can use DESCRIBE_COLUMNS to do this. Just pass in the cursor and the other required parameters.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#i1026120
declare
v_sql varchar2(32767) := 'select 1 column1, 2 column2 from dual';
v_cursor_id integer;
v_col_cnt integer;
v_columns dbms_sql.desc_tab;
begin
v_cursor_id := dbms_sql.open_cursor;
dbms_sql.parse(v_cursor_id, v_sql, dbms_sql.native);
dbms_sql.describe_columns(v_cursor_id, v_col_cnt, v_columns);
for i in 1 .. v_columns.count loop
dbms_output.put_line(v_columns(i).col_name);
end loop;
dbms_sql.close_cursor(v_cursor_id);
exception when others then
dbms_sql.close_cursor(v_cursor_id);
raise;
end;
/
Output:
COLUMN1
COLUMN2

Based on dseibert's answer, I created function for usage:
create type cols_name
as table of varchar2(32767)
/
CREATE OR REPLACE FUNCTION GET_COLUMNS_NAME(p_selectQuery IN VARCHAR2) RETURN cols_name PIPELINED IS
v_cursor_id integer;
v_col_cnt integer;
v_columns dbms_sql.desc_tab;
begin
v_cursor_id := dbms_sql.open_cursor;
dbms_sql.parse(v_cursor_id, p_selectQuery, dbms_sql.native);
dbms_sql.describe_columns(v_cursor_id, v_col_cnt, v_columns);
for i in 1 .. v_columns.count loop
pipe row(v_columns(i).col_name);
end loop;
dbms_sql.close_cursor(v_cursor_id);
return;
exception when others then
dbms_sql.close_cursor(v_cursor_id);
raise;
end;
/
Using it:
select * from TABLE(get_columns_name('select 1 column1, 2 column2 from dual'));
Results:
**COLUMN_VALUE**
COLUMN1
COLUMN2

Related

Return SQL Array from string [duplicate]

I'd like to create an in-memory array variable that can be used in my PL/SQL code. I can't find any collections in Oracle PL/SQL that uses pure memory, they all seem to be associated with tables. I'm looking to do something like this in my PL/SQL (C# syntax):
string[] arrayvalues = new string[3] {"Matt", "Joanne", "Robert"};
Edit:
Oracle: 9i
You can use VARRAY for a fixed-size array:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t('Matt', 'Joanne', 'Robert');
begin
for i in 1..array.count loop
dbms_output.put_line(array(i));
end loop;
end;
Or TABLE for an unbounded array:
...
type array_t is table of varchar2(10);
...
The word "table" here has nothing to do with database tables, confusingly. Both methods create in-memory arrays.
With either of these you need to both initialise and extend the collection before adding elements:
declare
type array_t is varray(3) of varchar2(10);
array array_t := array_t(); -- Initialise it
begin
for i in 1..3 loop
array.extend(); -- Extend it
array(i) := 'x';
end loop;
end;
The first index is 1 not 0.
You could just declare a DBMS_SQL.VARCHAR2_TABLE to hold an in-memory variable length array indexed by a BINARY_INTEGER:
DECLARE
name_array dbms_sql.varchar2_table;
BEGIN
name_array(1) := 'Tim';
name_array(2) := 'Daisy';
name_array(3) := 'Mike';
name_array(4) := 'Marsha';
--
FOR i IN name_array.FIRST .. name_array.LAST
LOOP
-- Do something
END LOOP;
END;
You could use an associative array (used to be called PL/SQL tables) as they are an in-memory array.
DECLARE
TYPE employee_arraytype IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
employee_array employee_arraytype;
BEGIN
SELECT *
BULK COLLECT INTO employee_array
FROM employee
WHERE department = 10;
--
FOR i IN employee_array.FIRST .. employee_array.LAST
LOOP
-- Do something
END LOOP;
END;
The associative array can hold any make up of record types.
Hope it helps,
Ollie.
You can also use an oracle defined collection
DECLARE
arrayvalues sys.odcivarchar2list;
BEGIN
arrayvalues := sys.odcivarchar2list('Matt','Joanne','Robert');
FOR x IN ( SELECT m.column_value m_value
FROM table(arrayvalues) m )
LOOP
dbms_output.put_line (x.m_value||' is a good pal');
END LOOP;
END;
I would use in-memory array. But with the .COUNT improvement suggested by uziberia:
DECLARE
TYPE t_people IS TABLE OF varchar2(10) INDEX BY PLS_INTEGER;
arrayvalues t_people;
BEGIN
SELECT *
BULK COLLECT INTO arrayvalues
FROM (select 'Matt' m_value from dual union all
select 'Joanne' from dual union all
select 'Robert' from dual
)
;
--
FOR i IN 1 .. arrayvalues.COUNT
LOOP
dbms_output.put_line(arrayvalues(i)||' is my friend');
END LOOP;
END;
Another solution would be to use a Hashmap like #Jchomel did here.
NB:
With Oracle 12c you can even query arrays directly now!
Another solution is to use an Oracle Collection as a Hashmap:
declare
-- create a type for your "Array" - it can be of any kind, record might be useful
type hash_map is table of varchar2(1000) index by varchar2(30);
my_hmap hash_map ;
-- i will be your iterator: it must be of the index's type
i varchar2(30);
begin
my_hmap('a') := 'apple';
my_hmap('b') := 'box';
my_hmap('c') := 'crow';
-- then how you use it:
dbms_output.put_line (my_hmap('c')) ;
-- or to loop on every element - it's a "collection"
i := my_hmap.FIRST;
while (i is not null) loop
dbms_output.put_line(my_hmap(i));
i := my_hmap.NEXT(i);
end loop;
end;
Sample programs as follows and provided on link also https://oracle-concepts-learning.blogspot.com/
plsql table or associated array.
DECLARE
TYPE salary IS TABLE OF NUMBER INDEX BY VARCHAR2(20);
salary_list salary;
name VARCHAR2(20);
BEGIN
-- adding elements to the table
salary_list('Rajnish') := 62000; salary_list('Minakshi') := 75000;
salary_list('Martin') := 100000; salary_list('James') := 78000;
-- printing the table name := salary_list.FIRST; WHILE name IS NOT null
LOOP
dbms_output.put_line ('Salary of ' || name || ' is ' ||
TO_CHAR(salary_list(name)));
name := salary_list.NEXT(name);
END LOOP;
END;
/
Using varray is about the quickest way to duplicate the C# code that I have found without using a table.
Declare your public array type to be use in script
type t_array is varray(10) of varchar2(60);
This is the function you need to call - simply finds the values in the string passed in using a comma delimiter
function ConvertToArray(p_list IN VARCHAR2)
RETURN t_array
AS
myEmailArray t_array := t_array(); --init empty array
l_string varchar2(1000) := p_list || ','; - (list coming into function adding final comma)
l_comma_idx integer;
l_index integer := 1;
l_arr_idx integer := 1;
l_email varchar2(60);
BEGIN
LOOP
l_comma_idx := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_idx = 0;
l_email:= SUBSTR(l_string, l_index, l_comma_idx - l_index);
dbms_output.put_line(l_arr_idx || ' - ' || l_email);
myEmailArray.extend;
myEmailArray(l_arr_idx) := l_email;
l_index := l_comma_idx + 1;
l_arr_idx := l_arr_idx + 1;
END LOOP;
for i in 1..myEmailArray.count loop
dbms_output.put_line(myEmailArray(i));
end loop;
dbms_output.put_line('return count ' || myEmailArray.count);
RETURN myEmailArray;
--exception
--when others then
--do something
end ConvertToArray;
Finally Declare a local variable, call the function and loop through what is returned
l_array t_array;
l_Array := ConvertToArray('email1#gmail.com,email2#gmail.com,email3#gmail.com');
for idx in 1 .. l_array.count
loop
l_EmailTo := Trim(replace(l_arrayXX(idx),'"',''));
if nvl(l_EmailTo,'#') = '#' then
dbms_output.put_line('Empty: l_EmailTo:' || to_char(idx) || l_EmailTo);
else
dbms_output.put_line
( 'Email ' || to_char(idx) ||
' of array contains: ' ||
l_EmailTo
);
end if;
end loop;

How to make a select from a cursor that returns from a plsql function ORACLE

i have a function that return a cursor my package is like this
FUNCTION SEDIRUNTIME (sede varchar2) return SYS_REFCURSOR
this cursor return x number of row with only one value, for example :
ROW1 - 34
ROW2 - 55
ROW3 - 56 ecc. ecc.
now i have i select like this
.. AND field in (select DBK_ENIN_REPORT.*SEDIRUNTIME*(sede) from dual)
this will simulate a clause IN which we can know the values at run time.
for example, based on the location parameter, the cursor can give me 22 and 34 rather than just 56 or 78 98 09.
written so i do not work from error number 00932 incoherent data types.
solutions?
You cannot use a CURSOR like that.
But you can change the function to return a collection:
CREATE TYPE Numberlist IS TABLE OF NUMBER;
/
CREATE FUNCTION DBK_ENIN_REPORT.SEDIRUNTIME (
sede varchar2
) return NumberList
IS
out_numbers NumberList;
BEGIN
SELECT id
BULK COLLECT INTO out_numbers
FROM your_table;
RETURN out_numbers;
END;
/
Then you can do:
.. AND field MEMBER OF DBK_ENIN_REPORT.SEDIRUNTIME(sede)
or
.. AND field IN ( SELECT COLUMN_VALUE FROM TABLE( DBK_ENIN_REPORT.SEDIRUNTIME(sede) ) )
Well I would say you CAN do it but you need to twist little bit. You can see how i have done it as below. I take employee table as and example.
Create function:
CREATE OR REPLACE FUNCTION SEDIRUNTIME (sede VARCHAR2)
RETURN SYS_REFCURSOR
AS
cur SYS_REFCURSOR;
BEGIN
OPEN cur FOR SELECT employee_id FROM employee;
RETURN cur;
END;
/
Anonymous block which you can implement in your package as Procedure;
DECLARE
x SYS_REFCURSOR;
y NUMBER;
v VARCHAR2 (100);
v_sql VARCHAR2 (200);
TYPE var_emp IS TABLE OF employee%ROWTYPE
INDEX BY PLS_INTEGER;
v_emp var_emp;
BEGIN
x := SEDIRUNTIME ('sede');
LOOP
FETCH x INTO y;
v := v || ',' || y;
EXIT WHEN x%NOTFOUND;
END LOOP;
--Created the IN clause list
v := LTRIM (v, ',');
v_sql := 'Select * from employee where employee_id in (' || v || ')';
EXECUTE IMMEDIATE v_sql BULK COLLECT INTO v_emp;
FOR i IN 1 .. v_emp.COUNT
LOOP
DBMS_OUTPUT.put_line ( v_emp (i).employee_id || '--' || v_emp (i).first_name);
END LOOP;
END;
OUTPUT:
SQL> /
1--XXX
2--YYY
PL/SQL procedure successfully completed.
SQL>
PS: Ofcourse the solution provided by MTO is going to be much faster that this.

Oracle Sql selecting all data from a list of views

I am trying to write a sql to select all data from a list of views for a particular view.
I'm getting all the user view this way:
select view_name from user_views
Say the output is:
emp_v
dept_v
countries_v
jobs_v
Is it possible for me to pass one of these views as parameter in the original sql(which is pulling all the views) so i can get all the data in this view?
Thanks
This might be what you are looking for. This will loop those views and select everything in them. NOTE: This can cause a lot of DBMS_OUTPUT. I suggest hardcoding the cursor for a specific view first to make sure it is what you are looking for.
(1) Create this procedure...
CREATE OR REPLACE procedure print_view(p_query in varchar2) is
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_descTbl dbms_sql.desc_tab;
l_rowCnt number := 0;
l_colCnt number;
begin
dbms_sql.parse(l_theCursor, p_query, dbms_sql.native);
dbms_sql.describe_columns(l_theCursor, l_colCnt, l_descTbl);
dbms_output.put_line(l_colCnt);
for i in 1 .. l_colCnt loop
dbms_sql.define_column(l_theCursor, i, l_columnValue, 4000);
end loop;
l_status := dbms_sql.execute(l_theCursor);
while(dbms_sql.fetch_rows(l_theCursor) > 0) loop
l_rowCnt := l_rowCnt +1;
dbms_output.put_line('========== ROW '||l_rowCnt||' ==========');
for i in 1 .. l_colCnt loop
dbms_sql.column_value(l_theCursor, i, l_columnValue);
dbms_output.put_line(rpad(l_descTbl(i).col_name, 30)||': '||substr(l_columnValue, 1, 200));
end loop;
end loop;
end print_view;
/
(2) Then run this...
declare
/*
NOTE: Edit the where clause. You should try this with just a single view first to make sure this is what you want.
*/
cursor cursor1 is
select view_name from user_views
where view_name in ('emp_v','dept_v','countries_v','jobs_v');
begin
for c1 in cursor1 loop
print_view('select * from '|| c1.view_name); /* Pass the view as a parameter like requested. */
end loop;
end;
/

How do I view a CLOB output parameter in TOAD from an Oracle Stored Procedure?

I have a stored procedure in a package in an Oracle database that has 2 input parameters + 1 output CLOB parameter. How do I view the output in Toad? (Preferably with the user only having execute/select permissions)
Solution:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
DBMS_OUTPUT.PUT_LINE(my_output_parameter);
END;
Don't forget to execute as script, rather than just execute statement, and results appear in the DBMS Output window, not the datagrid.
I guess DBMS_OUTPUT.PUT_LINE has an internal line limit of 255 chars. However it has been removed from 10g Release 2 onwards. You can try inserting the column data in a table and view it later on by querying that table.
Please refer -
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:146412348066
Would you consider printing the CLOB as a result set? You could then use a PIPELINED function (more about them here: PIPELINED functions by Tim Hall) which would return the CLOB line by line, take a look at the example below:
CREATE TABLE my_clob_tab (
id NUMBER,
clob_col CLOB
)
/
INSERT INTO my_clob_tab
VALUES (1,
to_clob('first line' || chr(10) ||
'second line, a longer one' || chr(10) ||
'third'))
/
CREATE OR REPLACE TYPE t_my_line_str AS TABLE OF VARCHAR2(2000)
/
CREATE OR REPLACE FUNCTION print_clob_func(p_id IN NUMBER)
RETURN t_my_line_str PIPELINED
AS
v_buffer VARCHAR2(32767);
v_clob CLOB;
v_len NUMBER;
v_offset NUMBER := 1;
v_line_break_pos NUMBER;
v_amount NUMBER;
BEGIN
SELECT clob_col
INTO v_clob
FROM my_clob_tab
WHERE id = p_id;
IF v_clob IS NOT NULL THEN
v_len := dbms_lob.getlength(v_clob);
WHILE v_offset < v_len
LOOP
v_line_break_pos := instr(v_clob, chr(10), v_offset);
IF v_line_break_pos = 0 THEN
v_amount := v_len - v_offset + 1;
ELSE
v_amount := v_line_break_pos - v_offset;
END IF;
dbms_lob.read(v_clob, v_amount, v_offset, v_buffer);
v_offset := v_offset + v_amount + 1;
PIPE ROW (v_buffer);
END LOOP;
END IF;
END;
/
(the function can be changed so that it takes as a parameter the CLOB you get from your procedure)
The code reads the content of the CLOB line by line (I assumed that the line separator is CHR(10) - if you are on Windows, you can change it to CHR(10) || CHR(13)) and PIPEs each line to the SELECT statement.
The function that reads the clob could also print the output to the standard output via dbms_output.put_line, but it would be trickier, because you'd have to take into account that standard output's maximal line length is limitied to, correct me if I'm wrong, 2000 characters, but it is doable (can't try that solution right now, unfortunately). In the meanwhile, please check above proposal and give me some feedback if that would work for you.
Back to the solution, now we can issue this SELECT statement:
SELECT COLUMN_VALUE AS clob_line_by_line FROM TABLE(print_clob_func(1));
Which will give us the following output:
CLOB_LINE_BY_LINE
-------------------------
first line
second line, a longer one
third
Check it at SQLFiddle: SQLFiddle example
Approach with inserting PL/SQL block and dbms_output:
DECLARE
my_output_parameter CLOB;
BEGIN
my_package.my_stored_proc(1, 2, my_output_parameter);
declare
vClob CLOB := my_output_parameter;
vPos number;
vLen number;
begin
vLen := DBMS_LOB.GetLength(vClob);
vPos := 1;
while vPos < vLen loop
DBMS_OUTPUT.Put(DBMS_LOB.Substr(vCLOB, 200, vPos));
vPos := vPos + 200;
end loop;
DBMS_OUTPUT.new_line;
end;
END;

writing a generic procedure in oracle

i want to write procedure which accents name of 2 tables as arguments and then compare the number or rows of the 2.
Also i want to each field of the 2 columns.The row which has a missmatch shold be
moved to another error table.
Can anyone give a PL/SQL procedure for doing this.
I want to achive this in oracle 9
Pablos example wont work, the idea is right though.
Something like this do it.
create or replace PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_r1 number;
v_r2 number;
v_sql1 varchar2(200);
v_sql2 varchar2(200);
BEGIN
v_sql1 := 'select count(1) from ' || T1;
v_sql2 := 'select count(1) from ' || T2;
EXECUTE IMMEDIATE v_sql1 into v_r1;
EXECUTE IMMEDIATE v_sql2 into v_r2;
dbms_output.put_line(T1 || ' count = ' || v_r1 || ', ' || T2 || ' count = ' || v_r2);
END;
DBMS_SQL is your friend for such operations.
You can use dynamic sql in PL/SQL. EXECUTE IMMEDIATE is your friend.
So, if you take two table names and trying to compare their row counts, you would do something like:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2(200), T2 IN VARCHAR2(200)) AS
v_cursor integer;
v_r1 integer;
v_r2 integer;
v_sql varchar2(200);
BEGIN
v_sql := "select count(1) into :1 from " || T1;
EXECUTE IMMEDIATE v_sql USING v_r1;
v_sql := "select count(1) into :1 from " || T2;
EXECUTE IMMEDIATE v_sql USING v_r2;
-- compare v_r1 and v_r2
END;
Not 100% sure about PL/SQL syntax. It's been a while since the last time I coded in great PL/SQL!
You can achieve same results with similar approach using DBMS_SQL. Syntax is a little bit more complicated though.
I am just posting here to note that all answers gravitate around dynamic SQL, and do not turn the attention to the implied problems using it.
Consider passing the following string as first or second parameter:
dual where rownum = 0 intersect
SELECT 0 FROM dual WHERE exists (select 1 from user_sys_privs where UPPER(privilege) = 'DROP USER')
I'll leave it to that.
To answer your question - Oracle actually stores these values in the data dictionary, so if you have access to it:
CREATE OR REPLACE PROCEDURE COMPARE_ROW_COUNT(T1 IN VARCHAR2, T2 IN VARCHAR2) AS
v_text varchar2(1000);
BEGIN
select listagg(owner || ' ' || table_name || ' count = ' || num_rows, ',')
into v_text
from all_tables --user, all or dba tables depends on requirements
where table_name in (T1, T2);
dbms_output.put_line(v_text);
exception
when others then raise; -- Put anything here, as long as you have an exception block
END COMPARE_ROW_COUNT;