I am trying to get report which query is being dynamically pulled by PL/SQL block.
I got inspiration from
this article
number of columns is dynamic and depend on the loop:
declare
l_qry VARCHAR2(4300);
s_qry VARCHAR2(80) := 0;
v_resort_id NUMBER := 1;
begin
l_qry := 'select a.column1,';
FOR pci in ( select id,name from table_pci where resort_id = v_resort_id) LOOP
s_qry := s_qry||'package.some_function (a.id,''' || pci.id || ''' , ''PC'') Property_type,' ;
END LOOP;
l_qry := l_qry ||s_qry;
l_qry := l_qry||'a.column2 from features a where a.feature_type = ''condition1'' ';
return(l_qry);
end;
Property_type is hardcoded alias on the dynamic column, so everytime loop goes round it will try to generate column with the same name and Apex with flag it as error.If I select
Use Generic Column Names (parse query at runtime only) it return correct number of columns but named Col1,Col2,Col3...
If I try to grab something dynamic from table_pci like name, I tried:
s_qry := s_qry||'package.some_function (a.id,''' || pci.id || ''' , ''PC'') '''|| pci.name || ''',' ;
I get error
failed to parse SQL query:
ORA-00923: FROM keyword not found where expected
Any help with finding a way to dynamicaly name the column greatly appreicated.
Any pointers, any advices and links.
I think you might have a syntax error in your attempt to use a dynamic column alias.
Try:
s_qry := s_qry||'package.some_function (a.id,''' || pci.id || ''' , ''PC'') AS '|| pci.name || ',' ;
If the value in pci.name potentially has spaces in it them enclose it with double quotes:
s_qry := s_qry||'package.some_function (a.id,''' || pci.id || ''' , ''PC'') AS "'|| pci.name || '",' ;
Hope it helps...
Related
Idea is I am trying to query information schema metadata and build columns based on datatypes if date then I create min date max date ,if number then check count of distinct or count rows
I have simplified now I will pass dbname , schema name and table name as parameters,
My output should return with below columns
SCHEMA_NM,TBL_NAME,_COUNT,_DISTINCT_COUNT,_MIN_DATE,_MAX_DATE.
For testing purpose at least if two measure is syntactically correct remaining I will take care
CREATE OR REPLACE PROCEDURE PROFILING( DB_NAME VARCHAR(16777216),TBL_SCHEMA VARCHAR(16777216), TBL_NAME VARCHAR(16777216))
RETURNS VARCHAR(16777216)
LANGUAGE SQL
EXECUTE AS CALLER
AS
$$
DECLARE
BEGIN
create or replace temporary table tbl_name as (select case when data_type=NUMBER then (execute immediate 'select count(col_val) from ' || :1 || '.' || :2 || '.' || :3) else null end as col_value_num,case when data_type=DATE then (execute immediate 'select count(col_val) from ' || :1 || '.' || :2 || '.' || :3) else null end as col_min_date from ':1.information_schema.columns where table_catalog=:1 and table_schema=:2 and table_name=:3 ' )) ;
insert into some_base_table as select * from tbl_name;
truncate tbl_name
RETURN 'SUCCESS';
END;
$$;
With above query I am getting below error
error : SQL compilation error: Invalid expression value (?SqlExecuteImmediateDynamic?) for assignment.
Any help here please
There are multiple errors in your code, but the error you mentioned is about this line:
num := (execute immediate "select count(col_val) from tab_cat.tab_schema.tab_name") ;
NUM is declared as integer, you can't directly assign a value from execute immediate. Execute immediate statement returns a resultset. To access the value you need to define a cursor and fetch the data.
The SQL should be surrounded by single quotes (not double quotes), and you should also build the string correctly. Something like this:
result := (execute immediate 'select count(col_val) from ' || tab_cat || '.' || tab_schema || '.' || tab_name ) ;
The query below is a simplified version of the PL/SQL I am trying to run that just shows the general structure. In short, I am trying to extract information from SPECIAL_TABLE into the variables c, d.
DECLARE
c NUMBER;
d NUMBER;
BEGIN
FOR all_tab IN
(SELECT * FROM all_tables)
LOOP
BEGIN
EXECUTE IMMEDIATE 'SELECT a, d INTO c, d
FROM ' || :name || '.SPECIAL_TABLE WHERE name = '''
:name || ''' AND table_name = ''' || all_tab.table_name || ''';
...
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR OCCURRED');
END;
END;
The above query does not work (by above query I mean the EXECUTE IMMEDIATE), and Oracle states 905 missing keyword. Following the flow here is followed as far as I can tell. Note that the ''' is so that I can escape a single ' for the SELECT statement, and then finish off the whole string all together. What is strange, is that I can follow the below code and run into no errors:
DECLARE
c NUMBER;
d NUMBER;
BEGIN
FOR all_tab IN
(SELECT * FROM all_tables)
LOOP
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(*)
FROM ' || :name || '.SPECIAL_TABLE WHERE name = '''
:name || ''' AND table_name = ''' || all_tab.table_name || ''' INTO c;
...
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR OCCURRED');
END;
END;
The only thing that changes is that I am using MAX(...), and seemingly no longer following the specified syntax in the above hyperlink. I'm not sure about the syntax part and why exactly this works, and MAX(...) would ensure only returning a single row. It seems like this would be the source of the problem, but when I perform the SELECT query by itself it only returns a single row with the two columns specified. The primary key for the table is PRIMARY KEY (a, b) so it shouldn't return more than one row regardless.
What am I missing here and how can I get my initial statement to work to assign the result of a query into multiple variables in Oracle 11g?
The link you provided is for the SELECT INTO statement. When selecting into variables using EXECUTE IMMEDIATE you have to play by EXECUTE IMMEDIATE's rules.
So instead of this:
EXECUTE IMMEDIATE 'SELECT x FROM myTable INTO y';
^ Incorrect: INTO is inside the string
You have to do this:
EXECUTE IMMEDIATE 'SELECT x FROM myTable' INTO y;
^ Correct: INTO is an EXECUTE IMMEDIATE keyword
Your query would go something like this:
EXECUTE IMMEDIATE 'SELECT a, d '
|| 'FROM ' || :name || '.SPECIAL_TABLE '
|| 'WHERE name = ''' || :name || ''' AND table_name = ''' || all_tab.table_name || ''''
INTO c, d;
In Oracle PL/SQL, I have run a query and am trying to read through each column for each row one by one so I can concatenate them together with a delimiter (hard format requirement). The script is used on multiple tables of varying sizes, so the number of columns is not known in advance. I used
SELECT COUNT(column_name) INTO NumColumns FROM all_tabs_cols
WHERE table_name = Table_Array(i);
where Table_Array has already been defined. This is in the middle of a for loop and has successfully gotten me a total number of columns. Table_Cursor is a SELECT * statement. After this I am trying to do something like
FOR j IN 0..NumColumns-1 LOOP
FETCH TABLE_CURSOR.column(j) INTO DataValue;
DBMS_OUTPUT.PUT(DataValue || '/');
END LOOP
The above is pseudo code. It illustrates the concept I am after. I do not know PL/SQL well enough to know how to get a value like this out of a row. I am also worried about accidentally advancing the cursor while doing this. How can I accomplish this task?
You must use some form of dynamic SQL. Here is a quick example:
It builds the SQL statement that will select the '/' separated columns from the table you want. Then it uses dynamic SQL to run that SQL statement.
DECLARE
p_table_name VARCHAR2(30) := 'DBA_OBJECTS';
l_sql VARCHAR2(32000);
TYPE varchar2tab IS TABLE OF VARCHAR2(32000);
l_array varchar2tab;
BEGIN
SELECT 'SELECT ' || listagg(column_name,' ||''/''||') within group ( order by column_id ) || ' FROM ' || owner || '.' || table_name || ' WHERE ROWNUM <= 100'
INTO l_sql
FROM dba_tab_columns
where table_Name = 'DBA_OBJECTS'
group by owner, table_Name;
EXECUTE IMMEDIATE l_sql BULK COLLECT INTO l_array;
FOR i in l_array.first .. l_array.last LOOP
dbms_output.put_line(l_array(i));
END LOOP;
END;
This is how your code should look:
SELECT F1 || ', ' || F2 || ', ' || ... || ', ' || FN
FROM TABLE
NO LOOPS
Here is how you can generate code that does not use loops.
Note, if you want you can take out the where statement and generate the code for the whole database.
Test with just one table first.
SELECT 'SELECT '|| LISTAGG(COLUMN_NAME, ' || '', '' || ') || ' FROM '||TABLE_NAME as sql_stm
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME='tablename'
GROUP BY TABLE_NAME;
The goal is to create a Oracle Function that is capable of query column name off a token provided by the user as to create a function with such capabilities
select cols_like('%e%') from table
This is the point I am currently at
CREATE OR REPLACE Function COLS_LIKE
(v_search in VARCHAR2, v_table in VARCHAR2)
RETURN VARCHAR
IS
TYPE r_cursor IS REF CURSOR;
c_emp r_cursor;
crs_cols VARCHAR(255);
column_list VARCHAR(1000);
BEGIN
OPEN c_emp FOR
'select COLUMN_NAME from cols
where TABLE_NAME = ''' || v_table || '''
and column_name like ''' || v_search || '''';
LOOP
FETCH c_emp INTO crs_cols;
EXIT WHEN c_emp%NOTFOUND;
if column_list IS NULL THEN
column_list := crs_cols;
else
column_list := column_list || ', ' || crs_cols;
end if;
END LOOP;
RETURN column_list;
END;
Where you call the function such as this
Declare
tests VARCHAR(100);
sql_stmt VARCHAR2(200);
begin
tests := COLS_LIKE('%E%', 'table');
DBMS_OUTPUT.PUT_LINE(tests);
-- OR
sql_stmt := 'select ' || COLS_LIKE('%E%', 'table') || ' from table';
DBMS_OUTPUT.PUT_LINE(sql_stmt);
end;
The end goal would be something such as this
select COLS_LIKE('%E%', 'table') from table;
What modifications can I make to my function or how I am calling to so that this function can be applied correctly.
Why you'd want to do such a thing I've no idea but you could return an open cursor to PL/SQL fairly easily:
create or replace function cols_like (
PTable in varchar2
, PColumn in varchar2
) return sys_refcursor
l_cols varchar2(32767);
c_curs sys_refcursor;
begin
select listagg(column_name, ', ') within group (order by column_id)
into l_cols
from user_tab_cols
where table_name = upper(Ptable)
and column_name like '%' || upper(PColumn) || '%'
;
open c_curs for '
select ' || l_cols || '
from ' || Ptable;
return c_curs;
end;
/
Returning this to a standard SQL statement will be a lot more difficult, this is because in selecting this function you're only selecting one column's worth of data. You want to be able to select N columns, which means you need to start returning nested tables that have been dynamically created.
I'm sure it's possible; but, before you get anywhere close to starting to attempt to do this think about why you're doing it. Ask a question where you don't state your end goal but where you state what your actual problem is. Chances are there's a lot simpler solution.
I was also having same problem and found this, and it is working for me. I was just making comparison of mobile numbers from two tables and in one of the tables some numbers have 0 in start and some don't. Finally got solution and just added % in start:
a.number was having numbers, in some of them starting 0 was missing. and b.number was accurate.
b.number like CONCAT('%',a.number)
I want to write a PLSQL stored procedure that accepts a table name as argument. This table is source table. Now inside my procedure i want to manipulate the fields of that table.
EX: I want to insert the records of this source table into another target table whose name is XYZ_<source table name>. The column names for source and target tables are the same. But there may be some extra fields in target table. How do i do it? The order of column names is not same.
You will have to build the INSERT statement dynamically.
create or replace procedure gen_insert
(p_src_table in user_tables.table_name%type
, p_no_of_rows out pls_integer)
is
col_str varchar2(16000);
begin
for rec in ( select column_name
, column_id
from user_tab_columns
where table_name = p_src_table
order by column_id )
loop
if rec.column_id != 1 then
col_str := col_str || ',' || rec.column_name;
else
col_str := rec.column_name;
end if:
end loop;
execute immediate 'insert into xyz_' || p_src_table || '('
|| col_str || ')'
|| ' select ' || col_str
|| ' from ' || p_src_table;
p_no_of_rows := sql%rowcount;
end;
/
Obviously you may want to include some error handling and other improvements.
edit
Having edited your question I see you have a special requirement for naming the target table which was obscured by the SO formatting.
You can do this using Dynamic SQL. Here's a link with basic info on Oracle Dynamic SQL