PLSQL how to store a result of a select statement - plsql

I need to delete data from many tables based on one parameter
The problem is that two tables are related to each other so in order to delete data properly i need to store id's somewhere.
-- i would like to store temp data
-- this one is only for convienience to avoid repeating same select many times
create table ztTaryfa as select zt_taryfa from tw_zbiory_taryfy
where 1=2;
-- this one is mandatory but I dont know how to make it work
Create table wnioskiId as select poli_wnio_id_wniosku from polisy
where 1=2;
Begin
-- fill temp tables
insert into ztTaryfa (
select zt_taryfa from tw_zbiory_taryfy
where zt_zbior = :zbiorId);
insert into wnioskiId (
select poli_wnio_id_wniosku from polisy
where poli_taryfa_id in ztTaryfa);
- regular deletion
delete from POLISY_OT where ot_poli_id in (
select poli_id from polisy
where poli_taryfa_id in ztTaryfa);
commit;
delete from DANE_RAPORTOWE where DR_RPU_ID in (
select RPU_ID from ROZLICZ_PLIK_UBEZP where RPU_ROZLICZ_PLIK_ID in (
select RP_ID from ROZLICZ_PLIK
where RP_ZBIOR_ID = :zbiorId ));
commit;
-- and here we go I need to delete data from POLISY first
delete from POLISY where poli_taryfa_id in ztTaryfa;
commit;
-- but by doing it I lose ids which i need here,
-- so I have to store them somehow and use them here.
delete from WNIOSKI where wnio_id in wnioskiId;
commit;
End;
-- and now lets get rid off temp tables
drop table ztTaryfa;
commit;
drop table wnioskiId;
commit;
To sum up i just need to know how to store somewhere between Begin and End a result of a select query which I can later use in delete statement.
Sounds but I tried so many different methods and all seems to not work.
What u see above is just a 1/3 of the script so I rly would like to make it all simple to use with one parameter.
Thanks you in advance.

You can use global types as simple as this:
create or replace type myrec is object (myid number);
create or replace type mytemp_collection is table of myrec;
declare
v_temp_collection mytemp_collection;
begin
v_temp_collection := mytemp_collection();
select myrec (t.field_type_id ) bulk collect into v_temp_collection from fs_field_types t
where mod(t.field_type_id+1,3)=0; -- for example
FOR i IN 1 .. v_temp_collection.count LOOP
DBMS_OUTPUT.put_line(v_temp_collection(i).myid);
End loop;
delete fs_field_types_back t where t.field_type_id in (select myid from table(v_temp_collection));
end;
Change select and where clause according to your business.

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 List of items, check if record exists, if yes update if not create

Im trying to learn PL/SQL and I was given an assignment which I am not sure how to tackle.
I am given a list of orders. I want to check my ORDER table for each of them in the following way:
Check if order exists, if no create a record
Check if order fullfilled (0 or 1)
If order is not fullfilled (0), update to 1
I put together a script which I think can do this for one order, but I'm sure it's not very good:
DECLARE
tmp NUMBER;
tmp2 NUMBER;
o_id NUMBER := 999;
BEGIN
/*Checking if order exists */
SELECT COUNT (*)
INTO tmp
FROM ORDERS
WHERE ORDERID = o_id;
IF ( tmp = 0 ) THEN
/* INSERT HERE */
END IF;
SELECT FULLFILLED INTO tmp2
FROM ORDERS
WHERE ORDERID = o_id;
IF (tmp2 = 0) THEN
/* UPDATE... */
END IF;
end;
I would appreciate any advice, what should I look into to make this script efficient? Thank you.
MERGE statement is what you need. It is based on SELECT statement and let's you UPDATE or INSERT data using it's WHEN (NOT) MATCHED THEN clauses. Here's a good explanation with some examples: Oracle Base MERGE Statement.
Here's also some code snippet you might find useful:
DECLARE
o_id NUMBER := 999;
BEGIN
MERGE INTO ORDERS o
USING
(SELECT o_id AS orderid FROM dual) o_id
ON
(o.orderid = o_id.orderid)
WHEN MATCHED THEN
UPDATE SET
o.fulfilled = CASE WHEN o.fulfilled = 0 THEN 1 ELSE o.fulfilled END
WHEN NOT MATCHED THEN
INSERT (fulfilled, <some_other_columns>)
VALUES (1, <values_for_other_columns>);
END;
/
Please read up on the merge statement: https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_9016.htm
Also called an "upsert". Basically if the row does not exist, insert. If it does, update.
It does what you are trying to do in one statement.

PLSQL FOR loop while executing CURSOR

I would like to know if there's any option to iterate a table while performing SELECT values into a CURSOR.
For example:
I have a table TEMP_NUMBERS which contains only numbers (single column).
I have to perform a SELECT from each number in the table (I do not know the amount of rows in the table in advance).
Here is basically what I'm attempting to do. Obviously this does not work, but can I do some kind of a workaround?
I need to SELECT the data into the p_cv_PermsNotifs which is a RETURN REF CURSOR.
IF NOT p_cv_PermsNotifs%ISOPEN THEN OPEN p_cv_PermsNotifs FOR
FOR i IN 1..TEMP_NUMBERS.NUMBER.COUNT LOOP
SELECT DISTINCT
SEC_USER_ROLE.ENTITY_TYP_CODE,
SEC_USER_ROLE.ENTITY_ID
FROM
SEC_USER_ROLE
WHERE
SEC_USER_ROLE.ENTITY_ID = i
END LOOP;
END IF;
Also tried this:
IF NOT p_cv_PermsNotifs%ISOPEN THEN OPEN p_cv_PermsNotifs FOR
SELECT DISTINCT
SEC_USER_ROLE.ENTITY_TYP_CODE,
SEC_USER_ROLE.ENTITY_ID
FROM
SEC_USER_ROLE
WHERE
SEC_USER_ROLE.ENTITY_ID IN
(SELECT * FROM TABLE (lv_ListOfEntities))
END IF;
Where lv_ListOfEntities is table of NUMBER indexed by BINARY INTEGER.
But I'm getting "ORA-22905: cannot access rows from a non-nested table item"
Thanks in advance.
In> Hey if you pass a single number at a time, everytime the refcursor
will be overwritten by the next value. So at the end you will only get
the value for last number in the refcursor. A better way is to use
some basic PL/SQL Bulk COLLECT logic which will give you the desired
output.
Hope this helps
--Creating sql type
CREATE OR REPLACE TYPE lv_num_tab IS TABLE OF NUMBER;
--plsql block
var p_lst refcursor;
DECLARE
lv_num lv_num_tab;
BEGIN
SELECT COL1 BULK COLLECT INTO lv_num FROM TEMP_NUMBERS;
OPEN p_lst FOR
SELECT DISTINCT SEC_USER_ROLE.ENTITY_TYP_CODE,
SEC_USER_ROLE.ENTITY_ID
FROM SEC_USER_ROLE
WHERE SEC_USER_ROLE.ENTITY_ID IN
(SELECT * FROM TABLE(cast(lv_num as lv_num_tab))
);
END;

Create and insert a row into the table in stored procedure

create or replace procedure sample
as
ID VARCHAR(20);
BEGIN
execute immediate
'CREATE GLOBAL TEMPORARY TABLE UPDATE_COLUMN_NO_TP
(
NAME VARCHAR2(256)
)';
INSERT INTO UPDATE_COLUMN_NO_TP
SELECT SRC_PK_COLUMNS.PK_KEY
FROM SRC_PK_COLUMNS
WHERE NOT EXISTS (
SELECT 1
FROM TGT_PK_COLUMNS
WHERE TGT_PK_COLUMNS.ID = SRC_PK_COLUMNS.ID);
END;
Error is:
The table is no exist.
So, I want a best solution for this scenario. In my stored procedure I have 10 temporary tables. All are all dynamic creations and inserts.
Table UPDATE_COLUMN_NO_TP not exists at compile time, so you got the error.
If you created a table dynamically, you should access it dynamically.
And pay attention to Mat's comment about essence of GTT.
execute immediate '
INSERT INTO UPDATE_COLUMN_NO_TP
SELECT SRC_PK_COLUMNS.PK_KEY
FROM SRC_PK_COLUMNS
WHERE NOT EXISTS (
SELECT 1
FROM TGT_PK_COLUMNS
WHERE TGT_PK_COLUMNS.ID = SRC_PK_COLUMNS.ID
)
';

How to generate columns dynamically?

I have table with certain number of columns.
I want to populate other table with the data of a particular column of Table1 as columns of table2 dynamically.
When I say dynamically I mean to say that when ever any data is added to the column of Table1 the table2 is populated with as many number of columns.
Changing the schema on the fly really isn't a good idea, for a number of reasons. From what you've described, I think you would be better off using a view for this. A view will give you the dynamic capabilities you're looking for with fewer side effects.
See this article:
How to create a view in SQL Server
I will once again repeat the disclaimer that this is a bad idea, many things can go wrong, and I'm certain there is a better solution to whatever underlying problem you're trying to solve. That said, to answer the explicit question anyway, here is an example of how to do this:
USE tempdb;
GO
CREATE TABLE dbo.Table1(Description VARCHAR(32));
CREATE TABLE dbo.Table2(ID INT);
GO
CREATE TRIGGER dbo.CatchNewTable1Data
ON dbo.Table1
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += CHAR(13) + CHAR(10) +
'ALTER TABLE dbo.Table2 ADD '
+ QUOTENAME(d) + ' VARCHAR(255);' -- guessing on destination data type
FROM
(
SELECT DISTINCT d = LEFT([Description], 128) -- identifier <= 128
FROM inserted AS i
WHERE NOT EXISTS
(
SELECT 1 FROM sys.columns
WHERE name = LEFT(i.[Description], 128)
AND [object_id] = OBJECT_ID('dbo.Table2')
)
) AS x;
EXEC sp_executesql #sql;
END
GO
Now, let's try it out! Try a column that already exists, a multi-row insert where one of the columns already exists, a multi-row insert with dupes, etc. I am not posting a value > 255 nor am I dealing with any fancy characters that will cause a problem. Why? Because ultimately I don't want you to use this solution, I want to solve the real problem. But for the googlers I want to show that there is a solution to the stated problem.
-- does nothing:
INSERT dbo.Table1 SELECT 'ID';
-- only adds column 'foo':
INSERT dbo.Table1 SELECT 'ID'
UNION ALL SELECT 'foo';
-- adds both of these columns:
INSERT dbo.Table1 SELECT 'bar'
UNION ALL SELECT 'splan foob';
-- only adds one of these:
INSERT dbo.Table1 SELECT 'blat'
UNION ALL SELECT 'blat';
SELECT * FROM dbo.Table2;
Results:
ID foo bar splan foob blat
----------- ------------ ------------ ------------ ------------
Don't forget to clean up:
DROP TABLE dbo.Table1, dbo.Table2;

Resources