Update table after executing Alter Table - oracle

I'm trying to execute this code:
DECLARE
CURSOR select_grupos IS SELECT * FROM grupo_musical;
grupo grupo_musical%ROWTYPE;
contador NUMBER;
TYPE ciudades IS TABLE OF VARCHAR2(30);
capitales_vascas ciudades;
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE GRUPO_MUSICAL ADD ciudad_origen VARCHAR2(30)';
capitales_vascas := ciudades('Bilbao', 'Donostia', 'Gasteiz');
contador := 0;
FOR grupo IN select_grupos LOOP
grupo.ciudad_origen := capitales_vascas(MOD(contador, 3) + 1);
UPDATE grupo_musical SET ROW=grupo WHERE id_grupo=grupo.id_grupo;
contador := contador + 1;
END LOOP;
END;
/
It says that I have to declare 'ciudad_origen'. I understand that is because when I create the cursor, Alter table is not finished. Is it any other way I can program this?
Thank you.

I understand that is because when I create the cursor, Alter table is not finished
Exactly. That's why dynamic SQL is evil. Why would you want to alter the table in PL/SQL? What's wrong with altering it at SQL level and writing a "normal" code afterwards?
If you insist on doing it that way, then you'll have to switch everything to dynamic SQL because - as you noticed - you can't reference a column that doesn't exist yet.
So, why is it evil? Because dynamic SQL is difficult to debug and maintain. Mind all single quotes you have to escape (or use the q-quoting mechanism), poorly formatted code (no GUI built-in formatter will touch code within quotes which are - as far as it is concerned - just a string), etc.
I suggest you alter table first, write PL/SQL code next.

Related

How to handle exceptions in for loop for insert rows using a stored procedure? Oracle PLSQL

I'm coding a complex PLSQL block (complex for me hahaha) to insert rows using information from the FOR LOOP CURSOR and add parameters to insert using a stored procedure. The current problem is there are around 200 rows to be inserted but when a simple row fail to insert all rows inserted broke and oracle execute a ROLLBACK command (I think so). So... How could I handle exceptions to insert succefully all rounds I can and when any rows fail show it in screen? Thanks
FOR i IN c_mig_saldos LOOP
IF i.tipo_comprobante = 'P' THEN -- Nota de debito (positivo)
v_cmp_p.prn_codigo := 'VIV';
v_cmp_p.tcm_codigo := 'NRA';
v_cmp_p.cmp_fecha_emision := TRUNC(SYSDATE);
v_cmp_p.cmp_fecha_contable := TRUNC(SYSDATE);
v_cmp_p.cmp_observacion := 'GENERACION AUTOMATICA DE SALDOS';
v_cmp_p.cli_codigo := i.cli_codigo;
v_tab_dco_p(1).cnc_codigo := 'VIA';
v_tab_dco_p(1).dco_precio_unitario := i.total_final;
v_tab_dco_p(1).dco_cantidad := 1;
v_tab_dco_p(1).dco_importe := i.total_final;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
COMMIT;
END IF;
END LOOP;
-- Insert a new row using stored procedure but when a itereted fail, no rows has inserted in table
begin
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
exception
when others then --this could be made more specific but you didn't say what type of error you were getting
-- Log to a table so you can export failed inserts later.
-- Make sure log table cols are large enough to store everything that can possibly be inserted here...
ErrorLogProc(the, cols, you, want, to, see, and, SQLERRM);
end;
In the ErrorLogProc() I'd recommend a couple things, here is a snippet of some things I do in my error logging proc that you may find helpful (it's just a few snippets, not intended to fully work, but you should get the idea)...
oname varchar2(100);
pname varchar2(100);
lnumb varchar2(100);
callr varchar2(100);
g1B CHAR(2) := chr(13)||chr(10);
PRAGMA AUTONOMOUS_TRANSACTION; --important!
begin
owa_util.who_called_me(oname, pname, lnumb, callr);
--TRIM AND FORMAT FOR ERRORLOG
lv_errLogText := 'Package: '||pname||' // Version: '||version_in||' // Line Number: '||lnumb||' // Error: ';
lv_string1 := mid(errStr_in,1,4000-Length(lv_errLogText));
lv_errLogText := lv_errLogText||lv_string1;
lv_errLogText := lv_errLogText||g1B||'Error Backtrace: '||replace(dbms_utility.format_error_backtrace,'ORA-', g1b||'ORA-');
insertIntoYourErrorLogTable(lv_errLogText, and, whatever, else, you, need);
commit;
To keep this simple, since there's not enough information to know the what and why of the question, this will kick out some text information about failures as desired.
SQL> set serveroutput on
Then here's an anonymous PL/SQL block:
BEGIN
FOR i IN c_mig_saldos
LOOP
-- not relevant
BEGIN
PKG_COMPROBANTES.PRC_INSERTAR_COMPROBANTE(v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p);
EXCEPTION
-- The goal of this is to ignore but output information about your failures
WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('whatever you want about v_cmp_p, v_tab_dco_p, v_tab_pgc_p, v_tab_apl_p, v_tab_mar_p or SQLERRM/SQLCODE or the error stack - not enough info in the question');
END;
END LOOP;
END;
/
Note: I have removed the commit from the execution.
Then if you like what you see...
SQL> commit;
Ideally, if I knew more about why the insert failures were expected to occur and what I wanted to do about them, I would not insert them in the first place.
Agree with comment that more information is needed, but a couple of things to consider:
Does this need to be done as a loop - if you can write your query as a select statement, then you can do a simple insert without the need for PLSQL, which would be simpler and likely quicker (set based SQL vs row-by-row PLSQL)
You say a ROLLBACK is occuring - you have a commit inside your IF statement, so any records which make it into your IF statement and succesfully make it through your insert procedure will be committed i.e. they will not be rolled back; You should consider if some records you think are being rolled back are actually not making it into the IF statement at all
Can you provide example data, or an example of the error you are receiving?

Writing a PL SQL change script (sql developer/oracle)

I have an assignment for uni now where I need to write a database change script and then a rollback script. I should also do some simple checks whether the changes has been done or not. I have spent enormous time by writing the scripts because I am not skilled in plsql. The prodcut is here:
-- the check could be more extensive, e.g. checking the type of the column
declare
titleExists number;
begin
select count(*) into titleExists
from user_tab_columns
where table_name = 'TITLE'
and column_name = 'TITLE';
if titleExists > 0 then
execute immediate 'alter table title rename column title to name';
end if;
end;
/
declare
typeExists number;
begin
select count(*) into typeExists
from user_tab_columns
where table_name = 'TITLE'
and column_name = 'TYPE'
and data_type = 'CHAR';
if typeExists > 0 then
execute immediate 'alter table title add (new_type varchar2(12) check (new_type in (''business'', ''mod_cook'', ''psychology'', ''popular_comp'', ''trad_cook'')))';
execute immediate 'update title set new_type = trim(type)';
execute immediate 'alter table title modify (new_type not null)';
execute immediate 'alter table title drop column type';
execute immediate 'alter table title rename column new_type to type';
end if;
end;
/
The first part renames a column and the second part changes columns type and adds a check, basically turns a char column into an enum.
I would really like to know whehter I need to put every alteration in execute immediate block. Is there a simpler way of writing this?
There are a number of issues with the script, but keeping myself limited to the question whether to use execute immediate:
There is not really a simpler way of writing this. The PL/SQL is modifying the database it runs on, so adding for instance the update title occur as a hard-coded section in the PL/SQL is not possible (it would not compile).
Also, sometimes it is possible to merge multiple statements executed through execute immediate in one big PL/SQL being send over. But PL/SQL itself does not support the DDL statements, so you need some way of dynamic SQL.
Note also that each DDL does an implicit commit, so your update is always executed and committed by the following alter table statement.
I think you have done great being this your first PL/SQL assignment.

How to store sysdate value in a variable in triggers in oracle?

I have a trigger written as below:
create or replace
TRIGGER impt_downloadproc
before delete ON A
declare
storedate nvarchar2(80);
storetime nvarchar2(80);
sequel string(2000);
BEGIN
storedate := to_char(sysdate,'YYYYMMDD');
storetime := to_char(sysdate,'HH24MISS');
sequel:='create table B_'||storedate||'_'||storetime||' as select * from ipcsdd_download_process';
execute immediate sequel;
END;
What I am trying to do in my trigger is : Before someone/something deletes record(s) from A table,
create a backup table B_yyyymmdd_hhmmss and backup the records to this table.
But I get error in line :storedate := to_char(sysdate,'YYYYMMDD');
I dont understand what is the problem.
I would expect a different error- you cannot commit in a trigger.
If you really want to then you'll need to use an autonomous transaction (which is mostly not a good idea)
So, your code should look something like this:
create or replace
TRIGGER impt_downloadproc
before delete ON A
declare
pragma autonomous_transaction; -- see this line
storedate nvarchar2(80);
storetime nvarchar2(80);
sequel string(2000);
BEGIN
storedate := to_char(sysdate,'YYYYMMDD');
storetime := to_char(sysdate,'HH24MISS');
sequel:='create table B_'||storedate||'_'||storetime||' as select * from ipcsdd_download_process';
execute immediate sequel;
END;
here is an example

Execute immediate inside FORALL statement

I have a procedure in which 100 tables have to be updated one by one. All tables have the same column to be updated. For improving the performance I am trying to use Execute Immediate with FORALL but I am getting a lot of compilation errors.
Is it syntactically possible to update 100 different tables inside a FORALL statement using Execute immediate.
My code looks something like this.
Declare
TYPE u IS TABLE OF VARCHAR2(240) INDEX BY BINARY_INTEGER;
Table_List u;
FOR somecursor IN (SELECT variable1, variable2 FROM SomeTable)
LOOP
BEGIN
Table_List(1) := 'table1';
Table_List(2) := 'table2';
......
......
table_list(100):= 'table100';
FORALL i IN Table_List.FIRST .. Table_List.LAST
EXECUTE IMMEDIATE 'UPDATE :1 SET column = :3 WHERE column = :2'
USING Table_List(i), somecursor.variable1, somecursor.variable2 ;
end loop;
I hope people can understand what I am trying to do through this code. If something is big time wrong please suggest me what exactly is the syntax and if it can be done in some other efficient way also.
Thanks a lot for all the help which comes my way.
(1) No, you can't use a bind variable for the table name.
(2) When you're using EXECUTE IMMEDIATE, this implies Dynamic SQL - but FORALL requires that only one statement to be executed. As soon as you specify a different table, you're talking about a different statement (regardless of whether the tables' structures happen to be equivalent or not).
You're going to have to do this in an ordinary FOR loop.
Just a guess, but I don't think you can use a bind variable as a table name. Have you tried:
EXECUTE IMMEDIATE 'UPDATE ' || Table_List(i) || ' SET column = :2 WHERE column = :3' ...

How do I use CREATE OR REPLACE?

Am I correct in understanding that CREATE OR REPLACE basically means "if the object exists, drop it, then create it either way?"
If so, what am I doing wrong? This works:
CREATE TABLE foo (id NUMBER,
title VARCHAR2(4000) DEFAULT 'Default Title')
And this doesn't (ORA-00922: missing or invalid option):
CREATE OR REPLACE TABLE foo (id NUMBER,
title VARCHAR2(4000) DEFAULT 'Default Title')
Am I doing something stupid? I don't seem to be able to find much documentation about this syntax.
This works on functions, procedures, packages, types, synonyms, trigger and views.
Update:
After updating the post for the third time, I'll reformulate this:
This does not work on tables :)
And yes, there is documentation on this syntax, and there are no REPLACE option for CREATE TABLE.
One of the nice things about the syntax is that you can be sure that a CREATE OR REPLACE will never cause you to lose data (the most you will lose is code, which hopefully you'll have stored in source control somewhere).
The equivalent syntax for tables is ALTER, which means you have to explicitly enumerate the exact changes that are required.
EDIT:
By the way, if you need to do a DROP + CREATE in a script, and you don't care for the spurious "object does not exist" errors (when the DROP doesn't find the table), you can do this:
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE owner.mytable';
EXCEPTION
WHEN OTHERS THEN
IF sqlcode != -0942 THEN RAISE; END IF;
END;
/
There is no create or replace table in Oracle.
You must:
DROP TABLE foo;
CREATE TABLE foo (....);
CREATE OR REPLACE can only be used on functions, procedures, types, views, or packages - it will not work on tables.
Following script should do the trick on Oracle:
BEGIN
EXECUTE IMMEDIATE 'drop TABLE tablename';
EXCEPTION
WHEN OTHERS THEN
IF sqlcode != -0942 THEN RAISE;
END IF;
END;
-- To Create or Replace a Table we must first silently Drop a Table that may not exist
DECLARE
table_not_exist EXCEPTION;
PRAGMA EXCEPTION_INIT (table_not_exist , -00942);
BEGIN
EXECUTE IMMEDIATE('DROP TABLE <SCHEMA>.<TABLE NAME> CASCADE CONSTRAINTS');
EXCEPTION WHEN table_not_exist THEN NULL;
END;
/
Does not work with Tables, only functions etc.
Here is a site with some examples.
A usefull procedure for oracle databases without using exeptions (under circumstances you have to replace user_tables with dba_tables and/or constrain the tablespace in the query):
create or replace procedure NG_DROP_TABLE(tableName varchar2)
is
c int;
begin
select count(*) into c from user_tables where table_name = upper(tableName);
if c = 1 then
execute immediate 'drop table '||tableName;
end if;
end;
If you are doing in code then first check for table in database
by using query
SELECT table_name
FROM user_tables
WHERE table_name = 'XYZ'
if record found then truncate table otherwise create Table
Work like Create or Replace.
You can use CORT (www.softcraftltd.co.uk/cort). This tool allows to CREATE OR REPLACE table in Oracle.
It looks like:
create /*# or replace */ table MyTable(
... -- standard table definition
);
It preserves data.
So I've been using this and it has worked very well: - it works more like a DROP IF EXISTS but gets the job done
DECLARE
VE_TABLENOTEXISTS EXCEPTION;
PRAGMA EXCEPTION_INIT(VE_TABLENOTEXISTS, -942);
PROCEDURE DROPTABLE(PIS_TABLENAME IN VARCHAR2) IS
VS_DYNAMICDROPTABLESQL VARCHAR2(1024);
BEGIN
VS_DYNAMICDROPTABLESQL := 'DROP TABLE ' || PIS_TABLENAME;
EXECUTE IMMEDIATE VS_DYNAMICDROPTABLESQL;
EXCEPTION
WHEN VE_TABLENOTEXISTS THEN
DBMS_OUTPUT.PUT_LINE(PIS_TABLENAME || ' NOT EXIST, SKIPPING....');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
RAISE;
END DROPTABLE;
BEGIN
DROPTABLE('YOUR_TABLE_HERE');
END DROPTABLE;
/
Hope this helps
Also reference:
PLS-00103 Error in PL/SQL Developer
'Create or replace table' is not possible. As others stated, you can write a procedure and/or use begin execute immediately (...). Because I don't see an answer with how to (re)create the table, I putted a script as an answer.
PS: in line of what jeffrey-kemp mentioned: this beneath script will NOT save data that is already present in the table you are going to drop. Because of the risk of loosing data, at our company it is only allowed to alter existing tables on the production environment, and it is not allowed to drop tables. By using the drop table statement, sooner or later you will get the company police standing at your desk.
--Create the table 'A_TABLE_X', and drop the table in case it already is present
BEGIN
EXECUTE IMMEDIATE
'
CREATE TABLE A_TABLE_X
(
COLUMN1 NUMBER(15,0),
COLUMN2 VARCHAR2(255 CHAR),
COLUMN3 VARCHAR2(255 CHAR)
)';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -955 THEN -- ORA-00955: object name already used
EXECUTE IMMEDIATE 'DROP TABLE A_TABLE_X';
END IF;
END;
I would do something like this
begin
for i in (select table_name from user_tables where table_name = 'FOO') loop
execute immediate 'drop table '||i.table_name;
end loop;
end;
execute immediate 'CREATE TABLE FOO (id NUMBER,
title VARCHAR2(4000)) ';
If this is for MS SQL.. The following code will always run no matter what if the table exist already or not.
if object_id('mytablename') is not null //has the table been created already in the db
Begin
drop table mytablename
End
Create table mytablename (...

Resources