Execute immediate in netezza stored procedure is not inserting value to a table - sql-insert

When I am running this Netezza stored procedure, I am getting an error
attribute 'SOME_VALUE' not found
As per requirement I have to get value from one table (TABLE_A) and insert into another table (TABLE_B).
This is the procedure:
create or replace procedure my_proc()
returns boolean
execute as owner
language NZPLSQL
as
BEGIN_PROC
declare rec RECORD ;
BEGIN
for rec in SELECT * from TABLE_A loop
EXECUTE IMMEDIATE
'INSERT INTO TABLE_B(COLUMN_B)
values( '|| rec.COLUMN_A_OFTABLE_A || ')';
END LOOP;
END;
END_PROC;
execute my_proc()
Here below, I am able to insert a string. But I need to insert different value depending on other table as I mentioned above.
EXECUTE IMMEDIATE 'INSERT INTO TABLE_B(COLUMN_B) values( ''Y'');';

When building a string that you are going run EXECUTE IMMEDIATE against, you have be careful to have everything quoted properly. In your case it's thinking that it needs to treat SOME_VALUE as an attribute/column, and it can't any column with that name.
Wrap your column reference in quote_literal() and it will interpret the contents of your column and quote-escape it properly for you.
create or replace procedure my_proc()
returns boolean
execute as owner
language NZPLSQL
as
BEGIN_PROC
declare rec RECORD ;
BEGIN
for rec in SELECT * from TABLE_A loop
EXECUTE IMMEDIATE
'INSERT INTO TABLE_B(COLUMN_B)
values( '|| quote_literal(rec.COLUMN_A_OFTABLE_A) || ')';
END LOOP;
END;
END_PROC;
You can find some more information in the documentation here.
Note: I am assuming that you have some more complicated logic to implement in this stored procedure, because looping over row by row will be much, much slower that insert..select. Often by an order of magnitude.

Related

skip deleting records when a child record is found

i have a pl/sql procedure to modify/delete records based on a checkbox selection in my Apex application:
delete from s_objectif_operation where id_operation=:p124_id_operation;
for a in (select distinct id from s_objectif
where id in (
SELECT trim(regexp_substr(:P124_S_OBJECTIF, '[^:]+', 1, LEVEL)) str
FROM dual
CONNECT BY instr(:P124_S_OBJECTIF, ':', 1, LEVEL - 1) > 0
))
loop
insert into s_objectif_operation
(id_s_objectif, id_operation)
values
(a.id, :p124_id_operation);
end loop;
for every modification, this procedure deletes all the records and insert the correct ones back so i had to remove the "cascade on delete" option the foreign key constraint to suspend any child record removal but now the procedure is not working.
maybe "raise an exception" can work?
There is no need to delete all the records and re-inserting only the checked ones. That is a brute force approach and it works but it does not capture the real user action.
As an alternative you can just delete/insert the changes. To do that, create an additional page item P124_S_OBJECTIF_OLD and set it to the value P124_S_OBJECTIF with a computation after header (or any pre-rendering processing point after P124_S_OBJECTIF has been initialized). In your pl/sql code use APEX_STRING.SPLIT to process the checkbox values and the MULTISET operator to decide which values have been touched.
Then your pl/sql process code could look like this.
DECLARE
l_objectif_old apex_t_varchar2;
l_objectif_new apex_t_varchar2;
l_objectifs_added apex_t_varchar2;
l_objectifs_removed apex_t_varchar2;
BEGIN
l_objectif_old := apex_string.split(:P124_S_OBJECTIF_OLD,':');
l_objectif_new := apex_string.split(:P124_S_OBJECTIF,':');
l_objectifs_added := l_objectif_new MULTISET EXCEPT l_objectif_old;
l_objectifs_removed := l_objectif_old MULTISET EXCEPT l_objectif_new;
-- add new
FOR i IN 1 .. l_objectifs_added.COUNT LOOP
INSERT INTO s_objectif_operation (id_s_objectif, id_operation)
VALUES (l_objectifs_added(i), :P124_ID_OPERATION);
END LOOP;
-- delete old
FOR i IN 1 .. l_objectifs_removed.COUNT LOOP
BEGIN
DELETE FROM s_objectif_operation
WHERE id = l_objectifs_removed(i);
EXCEPTION WHEN OTHERS THEN
-- this will fire if there are child records. Add your own code.
NULL;
END;
END LOOP;
END;
Note that you might have to tweak the insert and delete statement to match your data structure.

PL/SQL How to iterate and update from an query inside a loop

I don't know how to iterate and update from a query result inside the loop. Is it possible to loop again from the query inside my first loop? Here is my code:
CREATE OR REPLACE PROCEDURE "myTEST" (sp_type in char)
IS
CURSOR c1 IS
SELECT SP_ID FROM CI_SP
WHERE SP_TYPE_CD = sp_type;
sp_id char(10);
item_id_eq CI_SP_EQ.ITEM_ID_EQ%type;
BEGIN
FOR sp_rec in c1
LOOP
DBMS_OUTPUT.PUT_LINE(sp_rec.sp_id);
SELECT ITEM_ID_EQ INTO item_id_eq FROM CI_SP_EQ
WHERE SP_ID = sp_rec.sp_id;
DBMS_OUTPUT.PUT_LINE('item id eq :' || item_id_eq);
-- iterate here for each item_id_eq
-- execute update for each item_id_eq also
END LOOP;
END myTEST;
Instead of looping twice you could just do a join between CI_SP & CI_SP_EQ and get it done in one shot:
CREATE OR REPLACE PROCEDURE "myTEST"(sp_type IN CHAR) IS
BEGIN
FOR item IN (SELECT item_id_eq
FROM ci_sp_eq JOIN ci_sp USING (sp_id)
WHERE sp_type_cd = sp_type) LOOP
-- do your stuff.
NULL;
END LOOP;
END mytest;
I think you wouldn't even need a PL/SQL block, just a simple UPDATE will do, but I don't exactly know what you're trying to do.
Some other comments:
Don't create objects enclosed in "quotes", the object name is now case sensitive. In your case, the compilation will fail because you've created procedure name as "myTEST" and end it with mytest, which Oracle will treat it as "MYTEST" and you'll get compile error because of syntax check fail
Use VARCHAR2 instead of CHAR, CHAR will pad spaces if the input doesn't match the length specifier and will lead to further problems

PL/SQL Execute immediate create table and insert data into it

Let's have a look on my source code:
CREATE OR REPLACE PROCEDURE MAKE_COPY_OF_CLASSROOMS AUTHID CURRENT_USER AS
TYPE classrooms_table_type IS TABLE OF classrooms%ROWTYPE INDEX BY PLS_INTEGER;
classrooms_backup classrooms_table_type;
CURSOR classrooms_cursor IS
SELECT *
FROM classrooms
WHERE year = 1
ORDER BY name;
v_rowcnt PLS_INTEGER := 0;
BEGIN
OPEN classrooms_cursor;
FETCH classrooms_cursor
BULK COLLECT INTO classrooms_backup;
CLOSE classrooms_cursor;
EXECUTE IMMEDIATE 'CREATE TABLE classrooms_copy AS (SELECT * FROM classrooms WHERE 1 = 2)';
--COPY ALL STORED DATA FROM classrooms_backup TO classrooms_copy
END MAKE_COPY_OF_classrooms;
I'm stucked for hours on trying to insert data from "classrooms_backup" into the table "classrooms_copy", which is created by EXECUTE IMMEDIATE command. It's necessary to create table "classrooms_copy" via EXECUTE IMMEDIATE command. I tried to create another EXECUTE command with for loop in it:
EXECUTE IMMEDIATE 'FOR i IN classrooms_backup.FIRST..classrooms_backup.LAST LOOP
INSERT INTO classrooms_copy(id,room_id,year,name)
VALUES(classrooms_backup(i).id,classrooms_backup(i).room_id,classrooms_backup(i).year,classrooms_backup(i).name);
END LOOP;';
But it's road to the hell. I'm retrieving an invalid SQL statement error.
Thanks for your help!
There's no need for much PL/SQL here. Also, try to avoid the keyword CURSOR - there's almost always a better way to do it.
create or replace procedure make_copy_of_classrooms authid current_user as
begin
execute immediate '
create table classrooms_copy as
select *
from classrooms
where year = 1
order by name
';
end make_copy_of_classrooms;
/

How i can pass column names from variables in plsql update statement

DECLARE
v_name A.TRANSACTION_TYPE%TYPE :='SALARY';
v_salary A.SALARY%TYPE := 1000;
BEGIN
update A set v_name= v_salary where EMPID = 517;
-- PL/SQL: ORA-00904: "v_name": invalid identifier
--update A set SALARY = 1000 where EMPID = 517;
END;
/
My idea is to update table columns , but these column names are stored in variable. Is there any way to pass column names from variable ? Is there any options apart from Execute Immediate
Not sure if this will work in your situation, but I've written solutions where I wrote a script in SQLPlus and it "wrote" (using dbms_output.put_line or even just prompt) another script that did queries, and the columns/tables in those queries was determined by the logic in the SQLPlus script. Then I would execute as a script the output from my first script, and it would execute dynamically generated queries without ever needing execute immediate.
The following idea may work for multiple columns that are typed the same... As written, it will update all columns every time for a given record, but only the column specified by v_name will be changed to the value set in v_value; the other columns are simply updated to their existing value. The idea can be played with using DECODE, NVL or other similar conditional operators.
declare
v_name varchar2(20):= 'SAL';
v_value emptest.sal%TYPE := 5000;
begin
update emptest
set sal = ( select case when v_name = 'SAL' then v_value else sal end from dual),
comm = ( select case when v_name = 'COMM' then v_value else comm end from dual)
where empno = 7369;
commit;
end;

PL/SQL Inserting 1 row for each result in a select

I am writing a PL/SQL Procedure that performs a select based on input variables and then inserts a row for each result in the select. I am having trouble debugging what is wrong with my query due my newness to PL/SQL. I know this must be easy, but I am stuck here for some reason. Thanks for your help!
CREATE OR REPLACE PROCEDURE setup_name_map(ranking_id IN NUMBER, class_string IN VARCHAR2)
IS
BEGIN
FOR rec IN (SELECT NAME_ID FROM PRODUCT_NAMES WHERE NAME = class_string)
LOOP
EXECUTE IMMEDIATE 'INSERT INTO NAME_RANKING (NAME_ID, RANKING_ID) VALUES (' || rec.NAME_ID || ', ' || ranking_id || ')';
END LOOP;
END;
According to the Oracle Developer Compiler... 'NAME_ID' is an invalid identifier. I've tried putting it in quotes but no dice. It also complains that loop index variables 'REC' use is invalid. Any help is much appreciated.
There is no need for dynamic SQL here:
BEGIN
FOR rec IN (SELECT NAME_ID FROM PRODUCT_NAMES
WHERE NAME = class_string)
LOOP
INSERT INTO NAME_RANKING (NAME_ID, RANKING_ID)
VALUES (rec.NAME_ID, ranking_id);
END LOOP;
END;
Better still you can avoid a slow row-by-row cursor approach like this:
BEGIN
INSERT INTO NAME_RANKING (NAME_ID, RANKING_ID)
SELECT NAME_ID, ranking_id FROM PRODUCT_NAMES
WHERE NAME = class_string;
END;
If you really did need the dynamic SQL you should not be concatenating values into it, but using bind variables:
BEGIN
FOR rec IN (SELECT NAME_ID FROM PRODUCT_NAMES
WHERE NAME = class_string)
LOOP
EXECUTE IMMEDIATE 'INSERT INTO NAME_RANKING
(NAME_ID, RANKING_ID) VALUES (:b1, :b2)
USING rec.NAME_ID, ranking_id;
END LOOP;
END;

Resources