Select List of Column Names / Aliases from Custom Sub-Query - oracle

In Oracle, is there a way to select all the columns that are returned from a custom query with aliases? As an example, let's say we have a query as the following:
SELECT FIRST_NAME AS COL1, LAST_NAME AS COL2, ADDRESS AS COL3
FROM PEOPLE
I would like to know if an encapsulating query can be made that would return:
COL1
COL2
COL3

Here is how to do it in PL/SQL. Don't know if it is possible with straight oracle SQL only. You could always encapulate it in some kind of function if needed.
DECLARE
TYPE RefCursor_Type IS REF CURSOR;
D_RefCur RefCursor_Type;
D_DescriptionTable DBMS_SQL.DESC_TAB2;
D_ColumnCount INTEGER;
D_CursorHandle INTEGER;
BEGIN
OPEN D_RefCur
FOR 'SELECT FIRST_NAME AS COL1, LAST_NAME AS COL2, ADDRESS AS COL3 FROM PEOPLE';
D_CursorHandle := DBMS_SQL.to_cursor_number (D_RefCur);
DBMS_SQL.DESCRIBE_COLUMNS2 (D_CursorHandle,
D_ColumnCount,
D_DescriptionTable);
FOR idx IN 1 .. D_ColumnCount
LOOP
DBMS_OUTPUT.put_line (D_DescriptionTable (idx).col_name);
END LOOP;
END;

Related

ORA-00928: missing SELECT keyword

In MYTABLE there are courses and their predecessor courses.
What I am trying to is to find the courses to be taken after the specified course. I am getting missing SELECT keyword error. Why I am getting this error although I have SELECT statement in FOR statement ? Where am I doing wrong ?
DECLARE
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
BEGIN
WITH DATA AS
(select (select course_name
from MYTABLE
WHERE predecessors like ('''%' || coursename||'%''')
) str
from dual
)
FOR cursor1 IN (SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(cursor1);
END LOOP;
end;
Unless I'm wrong, WITH factoring clause can't be used that way; you'll have to use it as an inline view, such as this:
declare
coursename varchar2(200) := 'COURSE_101';
str varchar2(200);
begin
for cursor1 in (select str
from (select (select course_name
from mytable
where predecessors like '''%' || coursename||'%'''
) str
from dual
)
)
loop
dbms_output.put_line(cursor1.str);
end loop;
end;
/
Apart from the fact that it doesn't work (wrong LIKE condition), you OVERcomplicated it. This is how it, actually, does something:
SQL> create table mytable(course_name varchar2(20),
2 predecessors varchar2(20));
Table created.
SQL> insert into mytable values ('COURSE_101', 'COURSE_101');
1 row created.
SQL>
SQL> declare
2 coursename varchar2(20) := 'COURSE_101';
3 begin
4 for cursor1 in (select course_name str
5 from mytable
6 where predecessors like '%' || coursename || '%'
7 )
8 loop
9 dbms_output.put_line(cursor1.str);
10 end loop;
11 end;
12 /
COURSE_101
PL/SQL procedure successfully completed.
SQL>
Also, is that WHERE clause correct? PREDECESSORS LIKE COURSENAME? I'm not saying that it is wrong, just looks somewhat strange.
To extend #Littlefoot's answer a bit: you can use a common table expression (WITH clause) in your cursor, but the WITH must be part of the cursor SELECT statement, not separate from it:
DECLARE
coursename varchar2(200) := 'COURSE_101';
BEGIN
FOR aRow IN (WITH DATA AS (select course_name AS str
from MYTABLE
WHERE predecessors like '''%' || coursename||'%''')
SELECT str FROM DATA)
LOOP
DBMS_OUTPUT.PUT_LINE(aRow.str);
END LOOP;
END;
Also note that the iteration variable in a cursor FOR-loop represents a row returned by the cursor's SELECT statement, so if you want to display whatever was returned by the cursor you must use dotted-variable notation (e.g. aRow.str) to extract fields from the row.
Best of luck.
CREATE TABLE product
(
PRODUCT_ID int Primary key,
NAME VARCHAR (20) not null,
Batchno int not null,
Rate int not null,
Tax int not null,
Expiredate date not null
);
INSERT INTO PRODUCT VALUSES(1 , 'vasocare', 32 , 15 , 2 , 01-JAN-2021);

Get list of columns and alias name by a query string in Oracle

I'm using Pl/SQL with Oracle Database 11g.
I want to get a list of columns with alias by a query string.
There is a way to get all the column names of a query, using dbms_sql.describe_columns2 but only I get alias.
For example:
DECLARE
l_cursor NUMBER := dbms_sql.open_cursor;
l_ignore NUMBER;
l_desc dbms_sql.desc_tab2;
l_cnt NUMBER;
BEGIN
dbms_sql.parse( l_cursor, 'select a.col1 column1, a.col2 column2 from table_test a', dbms_sql.native );
dbms_sql.describe_columns2( l_cursor, l_cnt, l_desc );
FOR i IN 1 .. l_cnt LOOP
dbms_output.put_line(l_desc(i).col_name);
END LOOP;
dbms_sql.close_cursor( l_cursor );
END;
/
Returns:
column1
column2
Is there any way to get the values a.col1, or a.col2 with alias in the query?
The column names used in a SQL statement can be retrieved using Rob van Wijk's custom view DBA_DEPENDENCY_COLUMNS.
Install the view as SYS and grant select on dba_dependency_columns to <your user>;.
The view only works on objects. Create a VIEW on the SELECT statement and then the dependencies will be available. For example:
create table table_test(col1 number, col2 number);
create or replace view test_view as
select a.col1 column1, a.col2 column2 from table_test a;
select referenced_column
from sys.dba_dependency_columns
where owner = user
and name = 'TEST_VIEW'
order by 1;
Results:
REFERENCED_COLUMN
-----------------
COL1
COL2
The above results get the "list of columns". But part of your answer also implies you may want to get the "column expressions". That would be a completely different task, but is possible. Parsing SQL can be ridiculously difficult so it might help to explain exactly what you want and why you want it.

Eliminating duplicates in collection

I have a declared collection of object type:
CREATE TYPE category_type AS OBJECT (
col1 VARCHAR2(6),
col2 VARCHAR2(10),
col3 NUMBER);
/
CREATE TYPE category_tab AS TABLE OF category_type;
/
And in the PL/SQL code:
v_category_data category_Tab := category_tab();
After I populate v_category_data, I find there are duplicates. I need to remove them. So, I have tried to work with MULTISET UNION DISTINCT by this:
v_new_data := v_category_data;
v_new_data := v_new_data MULTISET UNION DISTINCT v_category_data;
However, I get this error:
804/17 PLS-00306: wrong number or types of arguments in call to
'MULTISET_UNION_DISTINCT'
I tried using the MAP functionality, but did not understand how exactly to use it, or if it would help.
Does anyone know of a way to remove the duplicates in an object collection?
multiset union distinct requires the elements of the collection to be comparable. In your case the elements are PL/SQL records that are unfortunately not comparable data structures (i.e. PL/SQL provides no build-in mechanism to compare PL/SQL records).
multiset union works because it doesn't need to compare the elements.
If for some reason you do not want to use distinct when you're populating your v_category_data collection then I advise to do as I have written below. Selecting from the collection and using distinct on the columns.
This is the part that interests you:
SELECT Category_type(col1, col2, col3)
bulk collect INTO v_new_data
FROM (SELECT DISTINCT col1,
col2,
col3
FROM TABLE(v_category_data));
And this is the entire code with which I checked.
DECLARE
v_category_data CATEGORY_TAB := Category_tab();
v_new_data CATEGORY_TAB := Category_tab();
BEGIN
--populate the collection with 2 types of duplicates and print them
FOR i IN 1 .. 10 LOOP
v_category_data.Extend();
IF i < 4 THEN
V_category_data(v_category_data.last) :=
Category_type('Test', 'Test', 1);
ELSE
V_category_data(v_category_data.last) :=
Category_type('Test2', 'Test2', 2);
END IF;
END LOOP;
FOR i IN v_category_data.first..v_category_data.last LOOP
dbms_output.Put_line(V_category_data(i).col1
||' '
||V_category_data(i).col2
||' '
||V_category_data(i).col3);
END LOOP;
dbms_output.Put_line('After processing'
||Chr(10));
-- populate your collection using distinct and print the content
SELECT Category_type(col1, col2, col3)
bulk collect INTO v_new_data
FROM (SELECT DISTINCT col1,
col2,
col3
FROM TABLE(v_category_data));
FOR i IN v_new_data.first..v_new_data.last LOOP
dbms_output.Put_line(V_new_data(i).col1
||' '
||V_new_data(i).col2
||' '
||V_new_data(i).col3);
END LOOP;
END;
See below how we can achieve this :
CREATE TYPE category_type AS OBJECT (
col1 VARCHAR2(6),
col2 VARCHAR2(10),
col3 NUMBER);
/
CREATE OR REPLACE TYPE category_tab AS TABLE OF category_type ;
/
declare
v_category_data category_Tab := category_tab();
v_new_data category_Tab := category_tab();
begin
--Defining collection length
v_category_data.extend(3);
--Populating the collection
v_category_data(1):= category_type('A','B',1);
v_category_data(2):= category_type('A','B',1);
v_category_data(3):= category_type('B','C',2);
dbms_output.put_line('*********First Collection Elements**************');
for i in 1..v_category_data.count
loop
dbms_output.put_line(v_category_data(i).col1 || v_category_data(i).col2 || v_category_data(i).col3 );
end loop;
--Way to remove duplicate
select
v_category_data MULTISET UNION DISTINCT category_Tab(category_type('A','B',1))
into
v_new_data
from
dual;
--Displaying elements of second collection
dbms_output.put_line('*********Second Collection Elements**************');
for i in 1..v_new_data.count
loop
dbms_output.put_line(v_new_data(i).col1 || v_new_data(i).col2 || v_new_data(i).col3 );
end loop;
end;
Output
SQL> /
*********First Collection Elements**************
AB1
AB1
BC2
*********Second Collection Elements**************
AB1
BC2
PL/SQL procedure successfully completed.

If statement inside create table query -Oracle-

I would like to create table which asks user input first. Then based on the input, it select which columns are added.
for example, if the response is 'N', then table is created including columns col1, col2, col3.
If the response is 'Y', table is created including columns col1, col2, col3, col4, col5.
Is this possible?
If yes, please provide me simple and primitive query so that I can apply it to my case.
Thanks,
Using SQL*Plus it's simple:
ACCEPT table_option -
PROMPT 'Create more columns? '
SET TERM OFF
COLUMN extra_columns NEW_VALUE extra_columns
SELECT
CASE '&table_option'
WHEN 'Y' THEN ', C4 NUMBER, C5 VARCHAR2(255), C6 DATE'
END extra_columns FROM DUAL;
CREATE TABLE tmp (
C1 NUMBER,
C2 VARCHAR2(255),
C3 DATE &extra_columns
);
SET TERM ON
You can store the script as a file and invoke it from SQL*Plus using #filename.
CREATE OR REPLACE FUNCTION tmp_custom_DDL( p_input VARCHAR2 IN, p_resp CHAR IN OUT) RETURN CHAR
AS
v_str VARCHAR2(4000);
IF p_resp = 'Y' THEN
v_str := 'col1 varchar2(10), col2 varchar2(10), col3 varchar2(10)';
ELSE v_str := 'col1 varchar2(10), col2 varchar2(10), col3 varchar2(10), col4 varchar2(10), col4 varchar2(10) ' ;
EXECUTE IMMEDIATE v_comm_1 || v_str || v_comm2;
--v_comm_1 is the first half of create table command till the specified cols
--v_comm_2 is the rest of the create table command
RETURN p_resp;
END;
this is only a quick draft, fix the few lexical bug and the missing definitions :) (this is the first step)

ORACLE PL/SQL for each passing tablename to procedure

I need to do a FOR EACH loop in a procedure, but I need to pass the table name dynamically.
This is the declaration
CREATE OR REPLACE PROCEDURE MIGRATE_PRIMITIVES_PROPS
(
FromTable IN VARCHAR2,
ToTable IN VARCHAR2
)
When I try and do this
FOR EachRow IN (SELECT * FROM FromTable) It says the table isn't valid
The table coming into the procedure is dynamic, columns are added and deleted all the time so I can't spell out the columns and use a cursor to populate them.
You have to use dynamic SQL to query a table whose name you don't know at compile time. You can do that with a dynamic cursor:
as
l_cursor sys_refcursor;
begin
open l_cursor for 'select * from ' || fromtable;
loop
fetch l_cursor into ...
... but then it breaks down because you can't define a record type to fetch into based on a weak ref cursor; and you don't know the column names or types you're actually interested in - you're using select * and have specific names to exclude, not include. You mentioned an inner loop that works and gets the column names, but there is no way to refer to a field in that cursor variable dynamically either.
So you have to work a bit harder and use the dbms_sql package instead of native dynamic SQL.
Here's a basic version:
create or replace procedure migrate_primitives_props
(
fromtable in varchar2,
totable in varchar2
)
as
l_cursor pls_integer;
l_desc_tab dbms_sql.desc_tab;
l_columns pls_integer;
l_value varchar2(4000);
l_status pls_integer;
begin
l_cursor := dbms_sql.open_cursor;
-- parse the query using the parameter table name
dbms_sql.parse(l_cursor, 'select * from ' || fromtable, dbms_sql.native);
dbms_sql.describe_columns(l_cursor, l_columns, l_desc_tab);
-- define all of the columns
for i in 1..l_columns loop
dbms_sql.define_column(l_cursor, i, l_value, 4000);
end loop;
-- execute the cursor query
l_status := dbms_sql.execute(l_cursor);
-- loop over the rows in the result set
while (dbms_sql.fetch_rows(l_cursor) > 0) loop
-- loop over the columns in each row
for i in 1..l_columns loop
-- skip the columns you aren't interested in
if l_desc_tab(i).col_name in ('COL_NAME', 'LIB_NAME', 'PARTNAME',
'PRIMITIVE', 'PART_ROW')
then
continue;
end if;
-- get the column value for this row
dbms_sql.column_value(l_cursor, i, l_value);
-- insert the key-value pair for this row
execute immediate 'insert into ' || totable
|| '(key, value) values (:key, :value)'
using l_desc_tab(i).col_name, l_value;
end loop;
end loop;
end;
/
I've assumed you know the column names in your ToTable but still used a dynamic insert statement since that table name is unknown. (Which seems strange, but...)
Creating and populating sample tables, and then calling the procedure with their names:
create table source_table (col_name varchar2(30), lib_name varchar2(30),
partname varchar2(30), primitive number, part_row number,
col1 varchar2(10), col2 number, col3 date);
create table target_table (key varchar2(30), value varchar2(30));
insert into source_table (col_name, lib_name, partname, primitive, part_row,
col1, col2, col3)
values ('A', 'B', 'C', 0, 1, 'Test', 42, sysdate);
exec migrate_primitives_props('source_table', 'target_table');
End up with the target table containing:
select * from target_table;
KEY VALUE
------------------------------ ------------------------------
COL1 Test
COL2 42
COL3 2015-05-22 15:29:31
It's basic because it isn't sanitising the inputs (look up the dbms_assert package), and isn't doing any special handling for different data types. In my example my source table had a date column; the target table gets a string representation of that date value based on the calling session's NLS_DATE_FORMAT setting, which isn't ideal. There's a simple but slightly hacky way to get a consistent date format, and a better but more complicated way; but you may not have date values so this might be good enough as it is.

Resources