PL/SQL Concatenation - oracle

i have a procedure that have in entry two parameters
CREATE_PARTITION( yearSource IN VARCHAR2 , yearDestination IN VARCHAR2 )
when i want to insert the yearSource concatenated with another string , nothing is inserted in the table
i declare the variable yearAA which take the yearSource and i concatenate it with a 'AA'
yearAA varchar2(30) := yearSource||'AA';
to insert it i use :
execute immediate ' INSERT INTO MOUADTEST2018 VALUES('||yearAA||')';
result : Nothing inserted
and it shows this message ORA-00984: column not allowed here

You have to quote the value. As it is, if yearAA is 2018AA, your code gets interpreted as:
execute immediate 'INSERT INTO MOUADTEST2018 VALUES(2018AA)';
Which tries to run
INSERT INTO MOUADTEST2018 VALUES(2018AA)
Which throws an error because 2018AA isn't in quotes, so Oracle thinks it must be an identifier (like a column name).
You could change your code to
execute immediate 'INSERT INTO MOUADTEST2018 VALUES('''||yearAA||''')';
But this isn't a best practice, since it allows SQL injection. Instead, use bind variables.
execute immediate 'INSERT INTO MOUADTEST2018 VALUES(:1)' using yearAA;
Also, I don't think you need execute immediate, so you can just do:
insert into MOUADTEST2018 values(yearAA);

Related

Open cursor for dynamic table name in PL/SQL procedure

I want to create procedure, that will use cursor, which is the same for arbitrary tables. My current one looks like this:
create or replace
PROCEDURE
some_name(
p_talbe_name IN VARCHAR2,
p_chunk_size IN NUMBER,
p_row_limit IN NUMBER
) AS
CURSOR v_cur IS
SELECT common_column,
ora_hash(substr(common_column, 1, 15), p_chunk_size) as chunk_number
-- Here it can find the table!
FROM p_table_name;
TYPE t_sample IS TALBE OF v_cur%rowtype;
v_sample t_sample;
BEGIN
OPEN v_cur;
LOOP FETCH v_cur BULK COLLECT INTO v_sample LIMIT p_row_limit;
FORALL i IN v_sample.first .. v_sample.last
INSERT INTO chunks VALUES v_sample(i);
COMMIT;
EXIT WHEN v_cur%notfound;
END LOOP;
CLOSE v_cur;
END;
The problem is that it cannot find the table named p_table_name which I want to parametrize. The thing is that I need to create chunks based on hashes for common_column which exists in all intended tables. How to deal with that problem? Maybe there is the equivalent oracle code that will do the same thing? Then I need the same efficiency for the query. Thanks!
I would do this as a single insert-as-select statement, complicated only by the fact you're passing in the table_name, so we need to use dynamic sql.
I would do it something like:
CREATE OR REPLACE PROCEDURE some_name(p_table_name IN VARCHAR2,
p_chunk_size IN NUMBER,
p_row_limit IN NUMBER) AS
v_table_name VARCHAR2(32); -- 30 characters for the tablename, 2 for doublequotes in case of case sensitive names, e.g. "table_name"
v_insert_sql CLOB;
BEGIN
-- Sanitise the passed in table_name, to ensure it meets the rules for being an identifier name. This is to avoid SQL injection in the dynamic SQL
-- statement we'll be using later.
v_table_name := DBMS_ASSERT.ENQUOTE_LITERAL(p_table_name);
v_insert_sql := 'insert into chunks (common_column_name, chunk_number)'||CHR(10)|| -- replace the column names with the actual names of your chunks table columns.
'select common_column,'||CHR(10)||
' ora_hash(substr(common_column, 1, 15), :p_chunk_size) AS chunk_number'||CHR(10)||
'from '||v_table_name||CHR(10)||
'where rownum <= :p_row_limit';
-- Used for debug purposes, so you can see the definition of the statement that's going to be run.
-- Remove before putting the code in production / convert to proper logging code:
dbms_output.put_line(v_insert_sql);
-- Now run the statement:
EXECUTE IMMEDIATE v_insert_sql USING p_chunk_size, p_row_limit;
-- I've included the p_row_limit in the above statement, since I'm not sure if your original code loops through all the rows once it processes the
-- first p_row_limit rows. If you need to insert all rows from the p_table_name into the chunks table, remove the predicate from the insert sql and the extra bind variable passed into the execute immediate.
END some_name;
/
By using a single insert-as-select statement, you are using the most efficient way of doing the work. Doing the bulk collect (which you were using) would use up memory (storing the data in the array) and cause extra context switches between the PL/SQL and SQL engines that the insert-as-select statement avoids.

invalid identifier error while using NVL in EXECUTE IMMEDIATE SELECT

create or replace PROCEDURE P_STAGE_LOAD(
in_S IN VARCHAR2,
in_D IN VARCHAR2,
in_T IN VARCHAR2)
IS
v_ERRM VARCHAR2(32000);
BEGIN
IF in_TBL_NAME ='TRAN_CUSTOMER' THEN
EXECUTE immediate 'INSERT INTO TRAN_CUSTOMER
(CUSTOMER_ID,AUTH_ID,OTHER_ID,TRAN_TYPE,AUDIT_TS)
SELECT CUSTOMER_ID,AUTH_ID,OTHER_ID,NVL(TRAN_TYPE,'PRIMARY'),
SYSDATE
FROM '||in_S||'.'||in_T||'#'||in_D;
EXECUTE immediate 'COMMIT';
This gives error as Invalid Identifier in run time. However the SELECT query runs fine alone.
I want to handle NULL of TRAN_TYPE, and decode to get as PRIMARY when NULL.
The target table TRAN_CUSTOMER has this as NOT NULL Column.
Is there anything missing?
You need to use two single quotes for PRIMARY as first single quote is ending the EXECUTE IMMEDIATE string.
Use something like below in that line.
EXECUTE immediate 'INSERT INTO TRAN_CUSTOMER
(CUSTOMER_ID,AUTH_ID,OTHER_ID,TRAN_TYPE,AUDIT_TS)
SELECT CUSTOMER_ID,AUTH_ID,OTHER_ID,NVL(TRAN_TYPE,''PRIMARY''),
SYSDATE
FROM '||in_S||'.'||in_T||'#'||in_D;

EXECUTE IMMEDIATE using column names from dual

In my package, I have a procedure like this:
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO T1(SELECT COL.F0, COL.F1 FROM DUAL)';
END IF;
END;
for table T1, I need only two columns from dual: COL.F0 & COL.F1.
When I execute this statement, I get "COL"."F1" is an invalid identifier.
In the same procedure, for inserting values into table T2, my statement might look like this:
EXECUTE IMMEDIATE 'INSERT INTO T2(SELECT COL.F0, COL.F1, COL.F4 FROM
DUAL)';
I will run into a similar problem again. Can you suggest me a way to solve this problem without using INTO clause?
Firstly, the INSERT AS SELECT syntax does not have parentheses () around the query.
If you use EXECUTE IMMEDIATE, the statement is a string executed outside the context of the procedure so it cannot refer to the parameters. You would need to supply them as bind variables, e.g.:
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO T1 AS SELECT :1, :2 FROM DUAL'
USING COL.F0, COL.F1;
END;
However, I would question whether you need to use dynamic SQL at all - you can run the insert directly:
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
INSERT INTO T1 AS SELECT COL.F0, COL.F1 FROM DUAL;
END;
In addition, in this case you could use a single row insert statement instead of running an "insert as select":
PROCEDURE sp_directUpdate(COL C%ROWTYPE) IS
BEGIN
INSERT INTO T1 VALUES (COL.F0, COL.F1);
END;
P.S. if this is supposed to do an insert, why is the procedure called "directUpdate"? :)

ORACLE: Cursor with dynamic query - throws error "invalid identifier" for cursor field

I have a logic to implement where I have to use dynamic sql(column names and where clause is decided on the fly).So here my cursor(emp_ref_cursor) has a dynamic sql, and has 3 cursor fields(emp_id,emp_name,dept).
Using these cursor fields in WHERE clause I am trying to execute another dynamic sql inside the loop.Bt oracle isn't able to identify the cursor field and throws an error like "ORA-00904: "EMP_REC"."EMP_ID": invalid identifier" though I am able to output emp_rec.emp_id through DBMS_OUTPUT.
NOTE: Please don't comment on the code quality this is not the actual code.
This is just used to describe the problem. I can't post the actual code due to
some compliance related stuff.
DECLARE
emp_ref_cursor sys_refcursor;
v_sql varchar2(3900);
TYPE emp_rec_type IS RECORD (emp_id number,emp_name varchar2(100),dept_id varchar2(100));
emp_rec emp_rec_type;
v_dept_id number:='1234';
v_dob varchar2(100);
v_desig varchar2(100);
x_dynamic_col_1 varchar2(100):='dob'; --dynamic column(based on some condition)
x_dynamic_col_2 varchar2(100):='designation'; --dynamic column(based on some condition)
x_dynamic_col_3 varchar2(100):='emp_id'; --dynamic column(based on some condition)
BEGIN
v_sql:='SELECT emp_id,emp_name,dept FROM employee WHERE dept_id=' || v_dept_id;
OPEN emp_ref_cursor FOR v_sql;
LOOP
FETCH emp_ref_cursor INTO emp_rec;
exit WHEN emp_ref_cursor%NOTFOUND;
stmt:='SELECT ' || x_dynamic_col_1 || ',' || x_dynamic_col_2 || '
FROM employee A
WHERE emp_id=emp_rec.' || x_dynamic_col_3;
DBMS_OUTPUT.PUT_LINE(stmt);
--Prints the SQL query as expected
DBMS_OUTPUT.PUT_LINE('emp_rec.emp_id:'||emp_rec.emp_id);
--Displays the value!!!
execute immediate stmt into v_dob, v_desig;
--But why is it saying emp_rec.emp_id is invalid identifier??
END LOOP;
END;
You have emp_rec defined as a local PL/SQL variable. None of the PL/SQL data is in scope to the dynamic SQL execution. When it is executed it as if you tried to run the statement - as it is displayed by your dbms_output standalone in a separate SQL context. If you did that it would be clear that emp_rec doesn't exist to the query.
You refer to it you would need to use a bind variable:
WHERE emp_id=:dynamic_col_3';
And then execute it with:
execute immediate stmt using emp_rec.emp_id;
But you can't use the x_dynamic_col_3 local variable in the using clause. Since - in this example anyway - the query would also need to change to use a different table column is the dynamic record field changed - that doesn't seem too much of a problem. But you said the where clause will change on the fly too. In that case you could have another local variable that you set to the relevant x field before the executin.
You have incorrect using of EXECUTE IMMEDIATE. You don't need to put INTO clause to SQL query. Use this instead:
stmt:='SELECT ' || x_dynamic_col_1 || ',' || x_dynamic_col_2 || '
FROM employee A
WHERE emp_id=emp_rec.' || x_dynamic_col_3;
execute immediate stmt into v_dob, v_desig;

execute query defined in column as String

Hello again I need some help,
I have table where in column "query" is defined query statement. I would like to run it and as output get the result.For example:
Create table table1
(
ID Number,
Query Varchar2(400)
)
insert into table1(id,query) values (1,'select name from table2 where table2.id=table1.id and table2.type = variable');
create table table2
(ID number,
Name varchar2(400),
Type Varchar2(400)
)
insert into table2 values (1,'Mathew','M');
insert into table2 values (1,'Thomas','G');
insert into table2 values (2,'Jerry','P');
For now query :
'select name from table2 where table2.id=table1.id and table2.type = variable'
should return only "Mathew" (assuming variable as 'M' - procedure variable input)
As procedure input I want to have variable which I will replace somehow in query statement.
Could you give me some tips how to handle with that?
------------Edit
I did stmh like that:
create or replace procedure queryrun
(var1 varchar2) as
str VARCHAR2(200);
BEGIN
execute immediate 'select replace(query,''variable'','''||var1||''') from table1' into str;
dbms_output.put_line('Value is '||str);
END;
But as result it present query... no result of select statement...
You are only selecting your query, not running it; and you're replacing the string "'variable'" - including the single quotes - with your value, but your original query string doesn't have the single quotes around it - so nothing matches.
You should not really substitue a hard-coded value anyway. Change your stored query to include a bind variable placeholder instead:
insert into table1(id,query)
values (1,'select name from table2 where table2.id=table1.id and table2.type = :variable');
Although that query is invalid anyway - you don't have table1 defined in the from clause or a join clause. When you have a valid query you can run standalone, use that, but with a bind variable (denoted by the leading colon).
But let's assume you have a valid query string in your table, that will only return one row, say:
insert into table1(id,query)
values (1,'select name from table2 where type = :variable');
Your procedure then needs a local variable to hold that query string. You select your query into that using static SQL, and then use dynamic SQL via execute immediate to run the query from that string, and provide the bind value with the using clause. The result goes into another local variable, which you are already doing.
So a simple version might look like this:
create or replace procedure queryrun (p_var1 varchar2) as
l_query table1.query%type;
l_name table2.name%type;
begin
select query into l_query from table1 where id = 1;
execute immediate query into l_name using p_var1;
dbms_output.put_line('Value is ' || l_name);
end;
This is obviously rather contrived. If you have multiple queries in your table, and perhaps pass a second ID variable into the procedure to choose which one to run, they would all have to take a single bind variable, and would have to all be able to put the result into the same type and size of result variable. You're also restricted to queries that return exactly one row. You can adapt and extend this of course, but hopefully this will get you started.
You can have bind variable and use plsql execute immediate.
Examples:
http://www.dba-oracle.com/t_oracle_execute_immediate.htm

Resources