Missing Keyword in EXECUTE IMMEDIATE SELECT INTO Statement - Oracle PL/SQL - sql

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;

Related

snowflake procedure syntax error while querying information schema

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 ) ;

How to create a view in a FOR loop in Oracle SQL

What I'm trying to do is create views based off a condition between two tables, and I want it to go through all tables that meet this condition.
I've been doing some research and I found that cursors would be helpful for this sort of thing, but I've been running into a "cursor out of scope" at line 15.
DECLARE
query_str VARCHAR2(32000);
CURSOR all_syn IS
SELECT SYNONYM_NAME, TABLE_NAME
FROM ALL_SYNONYMS
WHERE SYNONYM_NAME LIKE 'S!_AG!_%' ESCAPE '!';
CURSOR our_tables IS
SELECT TABLE_NAME
FROM ALL_TABLES
WHERE TABLE_NAME LIKE 'AG!_%1' ESCAPE '!';
BEGIN
query_str := 'CREATE OR REPLACE VIEW ' || LTRIM(all_syn.SYNONYM_NAME, 'S_') || 'AS
SELECT TO_CHAR(itemnum) itemnum,
TO_CHAR(keywordnum) keywordnum,
TO_CHAR(keysetnum) keysetnum,
MOD_BY_EMPLOYEE,
MOD_BY_PROCESS,
MOD_DATE_EMPLOYEE,
MOD_DATE_PROCESS
FROM all_syn.SYNONYM_NAME,
our_tables.TABLE_NAME
WHERE our_tables.TABLE_NAME = ' || LTRIM(all_syn.SYNONYM_NAME, 'S_');
FOR v_rec IN all_syn LOOP
IF (v_rec.TABLE_NAME LIKE 'KEYXITEM%') THEN
EXECUTE IMMEDIATE query_str;
END IF;
END LOOP;
END;
The reason I am doing this is because my company has tables that aren't directly connected to a certain 3rd party DB link, so they had me change the table names by putting a 1 at the end of the affected tables, creating synonyms for these tables with the DB link, and then make views of these synonyms with the original table name so that they now have the DB link and act as the original table so that we don't have to change any code. I have to join the synonym tables with the changed tables, because we added some attributes that the 3rd party tables don't have.
If anyone has any suggestions or advice, it would be greatly appreciated! I'm new to using dynamic sql and PL/SQL, so bear with me please.
EDIT:
So I've improved my code, and I feel like I'm getting closer to my desired results, however I'm getting this weird error:
line 28, column 52:
PLS-00357: Table,View Or Sequence reference 'ALL_TABLES.TABLE_NAME' not allowed in this context
Which doesn't make sense to me as I'm declaring it in the query.
BEGIN
FOR v_rec IN all_syn LOOP
IF (v_rec.TABLE_NAME LIKE 'KEYXITEM%') THEN
query_str := 'CREATE OR REPLACE VIEW ' || LTRIM(v_rec.SYNONYM_NAME, 'S_') || ' AS
SELECT itemnum AS item_num,
keywordnum AS key_word_num,
keysetnum AS key_set_num,
MOD_BY_EMPLOYEE,
MOD_BY_PROCESS,
MOD_DATE_EMPLOYEE,
MOD_DATE_PROCESS,
FROM ( SELECT TABLE_NAME
FROM ALL_TABLES
WHERE TABLE_NAME LIKE ' || '''AG!_%1''' || ' ESCAPE ' || '''!''' || '
AND ' || RTRIM(ALL_TABLES.TABLE_NAME, '1') ||' = ' || LTRIM(v_rec.SYNONYM_NAME, 'S_') || ') our_tables,
' || v_rec.SYNONYM_NAME;
-- EXECUTE IMMEDIATE query_str;
END IF;
dbms_output.put_line(query_str);
END LOOP;
END;
You cannot reference cursor like that. Move the query_str creation inside the FOR LOOP and reference the record variable.
EDIT: I've tried to fix the FROM/WHERE clause, but you might be missing a join condition there.
DECLARE
query_str VARCHAR2(32000);
CURSOR all_syn IS
SELECT SYNONYM_NAME, TABLE_NAME
FROM ALL_SYNONYMS
WHERE SYNONYM_NAME LIKE 'S!_AG!_%' ESCAPE '!';
CURSOR our_tables IS
SELECT TABLE_NAME
FROM ALL_TABLES
WHERE TABLE_NAME LIKE 'AG!_%1' ESCAPE '!';
BEGIN
FOR v_rec IN all_syn LOOP
IF (v_rec.TABLE_NAME LIKE 'KEYXITEM%') THEN
query_str := 'CREATE OR REPLACE VIEW ' || LTRIM(v_rec.SYNONYM_NAME, 'S_') || 'AS
SELECT TO_CHAR(itemnum) itemnum,
TO_CHAR(keywordnum) keywordnum,
TO_CHAR(keysetnum) keysetnum,
MOD_BY_EMPLOYEE,
MOD_BY_PROCESS,
MOD_DATE_EMPLOYEE,
MOD_DATE_PROCESS
FROM ' || v_rec.SYNONYM_NAME || ',
' || v_rec.TABLE_NAME || '
WHERE ' || v_rec.TABLE_NAME = ' || LTRIM(v_rec.SYNONYM_NAME, 'S_');
EXECUTE IMMEDIATE query_str;
END IF;
END LOOP;
END;

Dynamic SQL with table name as a parameter

I am trying to execute a procedure into which i send the table name and 2 column names as parameters:
EXECUTE IMMEDIATE 'select avg(#column1) from #Table1 where REF_D = #column2' into ATTR_AVG;
I have tried using the variables in combiations of '#', ':', '||' but nothing seems to work.
Has anyone used table names as a parameter. there are a few solutions here but for SQL Server
You can only use bind variables (denoted by colons) for values, not for parts of the structure. You will have to concatenate the table and column names into the query:
EXECUTE IMMEDIATE 'select avg(' || column1 | ') from ' || Table1
|| ' where REF_D = ' || column2 into ATTR_AVG;
Which implies REF_D is a fixed column name that can appear in any table you'll call this for; in a previous question that seems to be a variable. If it is actually a string variable then you'd need to bind and set that:
EXECUTE IMMEDIATE 'select avg(' || column1 | ') from ' || Table1
|| ' where ' || column2 || ' = :REF_D' into ATTR_AVG using REF_D;
If it's supposed to be a date you should make sure the local variable is the right type, or explicitly convert it.
You need to construct the executable statement using || (or else define it as one string containing placeholders that you can then manipulate with replace). Something like:
create or replace procedure demo
( p_table user_tab_columns.table_name%type
, p_column1 user_tab_columns.column_name%type
, p_column2 user_tab_columns.column_name%type )
is
attr_avg number;
begin
execute immediate
'select avg(' || p_column1 || ') from ' || p_table ||
' where ref_d = ' || p_column2
into attr_avg;
dbms_output.put_line('Result: ' || attr_avg);
end demo;
It's generally a good idea to build the string in a debugger-friendly variable first, i.e. something like:
create or replace procedure demo
( p_table user_tab_columns.table_name%type
, p_column1 user_tab_columns.column_name%type
, p_column2 user_tab_columns.column_name%type )
is
attr_avg number;
sql_statement varchar2(100);
begin
sql_statement :=
'select avg(' || p_column1 || ') from ' || p_table ||
' where ref_d = ' || p_column2;
execute immediate sql_statement into attr_avg;
dbms_output.put_line('Result: ' || attr_avg);
end demo;
Depending on what ref_d is, you may have to be careful with what you compare it to, so the above could require some more work, but hopefully it gives you the idea.
Edit: however see Alex Poole's answer for a note about the use of bind variables. If ref_d is a variable that may need to become:
sql_statement :=
'select avg(' || p_column1 || ') from ' || p_table ||
' where ' || p_column2 || ' = :b1';
execute immediate sql_statement into attr_avg using ref_d;
(The convention is to put the search expression on the right e.g. where name = 'SMITH' rather than where 'SMITH' = name, though they are the same thing to SQL.)

How to Refer to a Column by ID or Index Number

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;

Oracle Apex unique column alias on query generated by Pl/SQL

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...