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 ) ;
Related
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;
I need help on figuring out the problem with the ORA-00905: missing keyword ORA-06512: at line 73
When it says line 73 it actually refers to the sql statement itself at line 56. However, I am using this same script with a different table which working perfectly.
By changing the schema, table and column name I keep getting this error. I have be experimenting with several versions and also using fetch into cursor.
It keeps saying the sql statement has missing keyword but it is working on another script with the same line. I am hoping somebody could help me here. This is my first time posting on this forum and I am hoping someday I could contribute to this great community. Thank you in advance!
DECLARE
--CREATE OR REPLACE PROCEDURE setcomment
--IS
CURSOR cur IS
SELECT COLUMN_NAME, TABLE_NAME, OWNER
FROM DBA_TAB_COLUMNS
WHERE COLUMN_NAME = 'SSAN'
ORDER BY OWNER ASC, TABLE_NAME ASC, COLUMN_NAME ASC;
c_schema_name DBA_TAB_COLUMNS.OWNER%type;
c_table_name DBA_TAB_COLUMNS.TABLE_NAME%type;
c_column_name DBA_TAB_COLUMNS.COLUMN_NAME%type;
--This is a variable name to concatenate column names from <c_schema_name>.<c_table_name>.<c_column_name>
col_name VARCHAR(250) ;
--This is a variable to hold SQL statement and the message to be commented
sql_stmt1 VARCHAR(2000) ;
msg VARCHAR(250) := ' '' Comment going here '' ';
BEGIN
--Looping r cursor through cur cursor. Retrieving a row of record at a time
FOR r in cur LOOP
c_schema_name := r.owner;
c_table_name := r.table_name;
c_column_name := r.column_name;
--Concatenate all the column names into a single column name.
col_name := c_schema_name||'.'||c_table_name||'.'||c_column_name;
sql_stmt1 := 'COMMENT ON COLUMN '|| col_name ||' IS ''Comment going here '' ' ;
-- sql_stmt1 := 'COMMENT ON COLUMN '|| col_name ||' IS '||msg;
EXECUTE IMMEDIATE sql_stmt1;
--EXECUTE IMMEDIATE 'COMMENT ON COLUMN '|| c_schema_name||'.'||c_table_name||'.'||c_column_name || ' IS '' Comment going here '' ' ;
DBMS_OUTPUT.PUT_LINE ('COMMENT ON ' || col_name || ' procedure completed....');
END LOOP;
END;
/
If you still cannot find a source of the error, then create a log table, run the below code, and display (select) all error entries from the table
Then try to manually run the command.
Does you user have an appriopriate privileges to comment on tables in other schemas ? It can have a privilege to SELECT from DBA_TAB_COLS, but that doesn't mean that it can modify other schemas/tables.
CREATE TABLE log_errors( error_msg varchar2(4000));
DECLARE
CURSOR cur IS
SELECT COLUMN_NAME, TABLE_NAME, OWNER
FROM DBA_TAB_COLUMNS
WHERE COLUMN_NAME = 'SSAN'
ORDER BY OWNER ASC, TABLE_NAME ASC, COLUMN_NAME ASC;
col_name VARCHAR(250) ;
sql_stmt1 VARCHAR(2000) ;
msg VARCHAR(250) := 'Comment going here';
BEGIN
FOR r in cur LOOP
col_name := '"'|| r.OWNER ||'"."'||r.TABLE_NAME||'"."'||r.COLUMN_NAME||'"';
sql_stmt1 := 'COMMENT ON COLUMN ' || col_name || ' IS ''' || msg || '''';
BEGIN
EXECUTE IMMEDIATE sql_stmt1;
EXCEPTION
WHEN OTHERS THEN
INSERT INTO log_errors( error_msg ) VALUES ( sql_stmt1 );
END;
END LOOP;
END;
/
SELECT * FROM log_errors;
In addition to Mathguy's answer - your script will fail if any of the tables has been created using quoted identifiers
Database Object Naming Rules
Every database object has a name. In a SQL statement, you represent
the name of an object with a quoted identifier or a nonquoted
identifier.
A quoted identifier begins and ends with double quotation marks (").
If you name a schema object using a quoted identifier, then you must
use the double quotation marks whenever you refer to that object.
A nonquoted identifier is not surrounded by any punctuation.
You can use either quoted or nonquoted identifiers to name any
database object. However, database names, global database names, and
database link names are always case insensitive and are stored as
uppercase. If you specify such names as quoted identifiers, then the
quotation marks are silently ignored.
Simple practical example - a name of the first table is nonquoted identifier, a name of the second table is quoted identifier :
CREATE TABLE table_one (
SSAN int
);
CREATE TABLE "TaBle ##% TWO" (
SSAN int
);
SELECT 'COMMENT ON COLUMN ' || OWNER || '.' || TABLE_NAME || '.' || COLUMN_NAME || ' IS ''My superb comment'''
As my_comment_command
FROM ALL_TAB_COLUMNS
WHERE COLUMN_NAME = 'SSAN' ;
MY_COMMENT_COMMAND
----------------------------------------------------------------
COMMENT ON COLUMN SCOTT.TABLE_ONE.SSAN IS 'My superb comment'
COMMENT ON COLUMN SCOTT.TaBle ##% TWO.SSAN IS 'My superb comment'
It's obvious, that the second command will fail.
But if you use quotes in your script, then everything will work fine:
SELECT 'COMMENT ON COLUMN "' || OWNER || '"."' || TABLE_NAME || '"."' || COLUMN_NAME || '" IS ''My superb comment'''
As my_comment_command
FROM ALL_TAB_COLUMNS
WHERE COLUMN_NAME = 'SSAN' ;
MY_COMMENT_COMMAND
----------------------------------------------------------------------
COMMENT ON COLUMN "SCOTT"."TABLE_ONE"."SSAN" IS 'My superb comment'
COMMENT ON COLUMN "SCOTT"."TaBle ##% TWO"."SSAN" IS 'My superb comment'
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;
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...
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