How to skip/continue delete statement when the table does not exist? - sql

I want to write execute block that I can use for multiple databases with similar structure, some don't have 1 or more tables. I use something like this
execute block as
begin
delete from table1;
delete from table2;
delete from table3;
delete from table4;
delete from table5;
end
and this works for base with all tables, but when table is missing the execute block stops. I want to use this execute block when there is missing table so I don't have to commit the missing one.

You can't do it like this. A table referenced in a delete PSQL statement must exist for the execute block to successfully compile before it can even run.
Instead you will need to use statement blocks containing a execute statement to execute the statements dynamically and a when any exception handler to continue on any error.
For example, something like:
execute block as
begin
begin
execute statement 'delete from table1';
when any do
begin
-- ignore error
end
end
begin
execute statement 'delete from table2';
when any do
begin
-- ignore error
end
end
-- etc
end
You could also check for existence in the system tables before executing the delete dynamically. For example, something like:
execute block
as
declare variable tablename varchar(64);
begin
for select trim(rdb$relation_name)
from rdb$relations
where rdb$relation_name in ('TABLE1', 'TABLE2', 'TABLE3', 'TABLE4', 'TABLE5')
into tablename do
begin
execute statement 'delete from "' || replace(tablename, '"', '""') || '"';
end
end
The table names in the IN-clause must match the name as stored (for unquoted object names like table1 that means uppercase TABLE1). The replace(tablename, '"', '""') is just for completeness to escape possible double quotes in table names.

Related

MERGE with Oracle Stored Procedure - PL/SQL

I am trying to insert a records into my table CAT_BOM_ITEM from table TMP_BOM_STEEL8. If there are new records from the source I want the target table to be updated.
I have a procedure created and I am using a merge query inside it.
create or replace PROCEDURE SP_LOAD_CAT_BOM_MATERIALS AS
BEGIN
DELETE FROM BI_ODS.CAT_BOM_ITEM;
INSERT INTO BI_ODS.CAT_BOM_ITEM
(
ID_BOM_ITEM,
PT_PART,
PS_COMP,
IPD_PART,
SEPARADOR,
ORG_ID,
DB_ID,
LOADDATE
)
SELECT
A.ID_SEPARADOR,
A.PT_PART,
A.PS_COMP,
A.IPD_PART,
A.SEPARADOR,
A.ORG_ID,
A.DB_ID,
A.LOADDATE
FROM TMP_BOM_STEEL8 A;
COMMIT;
EXECUTE IMMEDIATE
'
Merge Into BI_ODS.CAT_BOM_ITEM B
USING
(SELECT
ID_SEPARADOR,
PT_PART,
PS_COMP,
IPD_PART,
SEPARADOR,
ORG_ID,
DB_ID,
LOADDATE
FROM TMP_BOM_STEEL8 ) A
ON (A.ID_SEPARADOR = B.ID_BOM_ITEM
AND A.DB_ID = B.DB_ID
AND A.ORG_ID = B.ORG_ID)
WHEN MATCHED THEN UPDATE SET
A.PT_PART = B.PT_PART
A.PS_COMP= B.PS_COMP
A.IPD_PART= B.IPD_PART
A.SEPARADOR = B.SEPARADOR
WHEN NOT MATCHED THEN INSERT (
ID_SEPARADOR,
PT_PART,
PS_COMP,
IPD_PART,
SEPARADOR,
ORG_ID,
DB_ID,
LOADDATE)
VALUES (
A.ID_SEPARADOR,
A.PT_PART,
A.PS_COMP,
A.IPD_PART,
A.SEPARADOR,
A.ORG_ID,
A.DB_ID,
A.LOADDATE);
';
COMMIT;
When i compile the procedure this is the error:
ORA-00933: SQL command not properly ended
ORA-06512: at "BI_ODS.SP_LOAD_CAT_BOM_MATERIALS", line 28
ORA-06512: at line 2
Can someone help in solving this.
Thanks in advance.
You need to:
Remove the ; semi-colon from the end of the string you are passing to EXECUTE IMMEDIATE;
Add commas at the end of each assignment in the UPDATE clause of the MERGE statement;
Swap the left- and right-terms in the UPDATE assignments as you are updating B from A (rather than vice versa);
Change INSERT ( ID_SEPARADOR, to INSERT ( ID_BOM_ITEM,; and
Add END; to terminate the stored procedure.
You also don't need to use EXECUTE IMMEDIATE and you shouldn't COMMIT in a stored procedure (as it prevents you from being able to ROLLBACK multiple statements; instead, use COMMIT in the PL/SQL block you are using to call the procedure that way you can control when the COMMIT occurs and can chain several procedures together and potentially roll them all back if needed).
db<>fiddle here
You should have an END statement at the end of your procedure, but I don't see anything else particularly wrong. You don't need EXECUTE IMMEDIATE and that seems to be the line it's objecting to, so try removing that.

Conditionally calling sql scripts in sql plus

I have two scripts which needs to be executed depending on whether a table exists or not in my database.
So I created a 3rd script as below which checks the condition and calls the respective script.
[Because my installer cannot reach db and it can only call one script while installation]
declare
cnt number;
begin
select count(*)
into cnt
from all_tables where table_name = 'VQ_REPORT_LAUNCHER';
if (cnt>0) then
begin
#VQ_Alter_Script.sql;
end;
else
begin
#VQ_Create_Script.sql;
end;
end if;
END;
I get the below error -
ERROR at line 10:
ORA-06550: line 10, column 1:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:
Note - When I execute my create/alter scripts directly from sql plus it works.
Only when I try to execute them through a 3rd script using IF-ELSE , i get the above error in sql plus.
You can use substitution variables to decide which script to run.
column script_name new_value script_name
select case count(*)
when 0 then 'VQ_Create_Script.sql'
else 'VQ_Alter_Script.sql'
end as script_name
from all_tables
where table_name = 'VQ_REPORT_LAUNCHER';
#&script_name
or if only part of the name changes you could do:
column script_type new_value script_type
select case count(*) when 0 then 'Create' else 'Alter' end as script_type
from all_tables
where table_name = 'VQ_REPORT_LAUNCHER';
#VQ_&script_type._Script.sql
You can add settings like set termout off and set termout on around the query part to hide it if you want, and use set verify to decide whether to show the substitution happening.
Depending on which user you run this as, you might want to either check against user_tables rather than all_tables, or include the expected table owner as part of the filter, so you don't accidentally pick up a table with the same name in the wrong schema.

Issue with trigger for archiving

Trying to make a trigger that puts data into an archive table when a column called COMPLETION_STATUS goes from incomplete to complete, the dbms is a placeholder for the insert but I'm getting the following errors in the if statement
Error(6,1): PLS-00103: Encountered the symbol enter code here"SELECT" when expecting one of the following: begin function pragma procedure subtype type current cursor delete exists prior The symbol "begin" was substituted for "SELECT" to continue.
Error(9,1): PLS-00103: Encountered the symbol "IF" when expecting one of the following: * & - + ; / at for mod remainder rem and or group having intersect minus order start union where connect || multiset The symbol ";" was substituted for "IF" to continue.
Error(13,4): PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following: ( begin case declare end exception exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge
Code:
create or replace TRIGGER ARCHIVING_TRIG
BEFORE UPDATE OF COMPLETION_STATUS ON PROJECT_DATA
BEGIN
DECLARE COMPLETION_STATUS1 VARCHAR2(9);
SELECT COMPLETION_STATUS into COMPLETION_STATUS1
FROM PROJECT_DATA WHERE COMPLETION_STATUS = 'complete'
IF COMPLETION_STATUS1 = 'complete'
THEN
DBMS.output('123');
END IF;
END;
The DECLARE block should be before the BEGIN block.
The SELECT ... statement needs to be terminated with a semicolon (;).
It's dbms_output.put_line() not dbms.output();
You're trying to assign the result of a query that potentially can return more than one row to a scalar variable.
The rows selected from project_data have no relation to the one(s) that triggered the trigger.
I suggest you use something like:
CREATE TRIGGER archiving_trig
AFTER UPDATE
ON project_data
FOR EACH ROW
WHEN (old.completion_status <> 'complete'
AND new.completion_status = 'complete')
BEGIN
dbms_output.put_line('Trigger fired for ID ' || :new.id);
END;
db<>fiddle
I think maybe AFTER is the better time, because you want to archive the row after the status was successfully changed.
Because of the WHEN the trigger will only fire if completion_status has been changed from something other than 'complete' to 'complete'. But you maybe also need to have a method of removing entries from the archive when the status changes from 'complete' to something else. That isn't covered here.
Declaring it as FOR EACH ROW let's you access the values of the updated row via :new. That way you don't need a query to select that nor a variable to select into.
I guess you need this:
create table PROJECT_DATA_NEW as select * from PROJECT_DATA where 1=2;
CREATE OR REPLACE TRIGGER ARCHIVING_TRIG
AFTER UPDATE
ON PROJECT_DATA
FOR EACH ROW
DECLARE
status number;
BEGIN
status:=0;
select 1 into status from PROJECT_DATA where
:new.COMPLETION_STATUS='complete' and
:old.COMPLETION_STATUS='incomplete'
if (status=1) then
insert into PROJECT_DATA_NEW values(:old.column1,
:old.column2,
:old.column3,
:old.column4,
:old.column5,....etc);
end if;
END;
/

Use variable inside "from" in firebird

I try use variable inside "from" SELECT COUNT(*) FROM :T_NAME, but is not work. How can i fix this? This my code:
SET TERM ^ ;
CREATE OR ALTER PROCEDURE Myfunction
RETURNS(
T_NAME varchar(100),
NUM_RECORDS integer
)
AS
BEGIN
FOR SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
WHERE (RDB$SYSTEM_FLAG <> 1 OR RDB$SYSTEM_FLAG IS NULL) AND RDB$VIEW_BLR IS NULL
ORDER BY RDB$RELATION_NAME
INTO :T_NAME
DO
BEGIN
SELECT COUNT(*) FROM :T_NAME
INTO :NUM_RECORDS;
SUSPEND;
END
END^
SET TERM ; ^
You can't parametrize object names. You will need to dynamically build the query (which if you aren't careful may leave you open to SQL injection).
Specifically you need to use:
...
BEGIN
EXECUTE STATEMENT 'select count(*) from "' || T_NAME || '"'
INTO :NUM_RECORDS;
SUSPEND;
END
This should be safe except for table names that contain double quotes. I haven't explicitly checked the behavior with single quotes in an object name, which technically is possible. You may need to take extra steps to protect against these forms of SQL injection if you use this in real production code.

pgSQL, dynamic drop tables statement

i want to dynamically delete some tables from database. I select them first, and then i am trying to use delete statement but it doesn't works.
tmp TEXT;
FOR tmp IN SELECT names from Garbage
LOOP
DROP TABLE tmp;
END LOOP;
but unfortuntly i got errors at drop statement. It always trying to delete table "tmp" instead of deleting the value of tmp(which are some strings like "table1" "table2").
You will need a dynamic query to be executed. To do that, you need to construct your query and call EXECUTE:
DECLARE
tmp TEXT;
...
BEGIN
...
FOR tmp IN SELECT names FROM Garbage
LOOP
EXECUTE 'DROP TABLE ' || quote_ident(tmp);
END LOOP;
...
Notice that I used quote_ident to escape the name properly, it is better and safer. But, if you created a table, let's say named MyTable but didn't quoted (double-quotes) it on creation time, PostgreSQL also store its name as mytable, so quote_ident('MyTable') will generate "MyTable" which doesn't exists, you should take care of it (e.g. always use the lowercase names or always quote your IDs).