I have question related to my ddl trigger:
create or replace trigger audit_ddl_db_trg after ddl on database
begin
DECLARE
n number;
stmt varchar2(4000);
sql_text ora_name_list_t;
begin
dbms_output.put_line(ora_sql_txt(sql_text));
n := ora_sql_txt(sql_text);
IF nvl(n,200)=200 THEN
Raise_application_error(-20001, 'ora_sql_txt does not catch any statement. sql_txt is not initialized');
ELSE FOR i IN 1..n LOOP
stmt := stmt || sql_text(i);
END LOOP;
dbms_output.put_line(stmt);
END IF;
IF ora_sysevent <> 'TRUNCATE'
THEN
if (ora_sysevent='TRUNCATE')
then
null; -- I do not care about truncate
else
insert into audit_ddl(date_of_change,osuser,current_user,host,terminal,owner,object_type,object_name,ora_sysevent,sqltext)
values(
systimestamp,
sys_context('USERENV','OS_USER') ,
sys_context('USERENV','CURRENT_USER') ,
sys_context('USERENV','HOST') ,
sys_context('USERENV','TERMINAL') ,
ora_dict_obj_owner,
ora_dict_obj_type,
ora_dict_obj_name,
ora_sysevent,
stmt
);
end if;
end if;
end;
end;
Trigger catches everything correctly, i do have everything populated well in my table, but i do have questions related to column current_user. No matter which user schema i use in database to do alter it writes SYS. How to populate this with correct user which has done alter on table?
Thanks!
sys_context('USERENV','CURRENT_USER') returns The name of the database user whose privileges are currently active, as per the oracle documentation.
You can use the SESSION_USER instead of CURRENT_USER.
Related
I have a system event trigger. It worked fine until I tried to move the code to a different database.
I made a mistake: forgot to export the original user, so I created a total new one.
So after I release my code I get the error:
Compilation errors for TRIGGER ORACLE_VERSION_CONTROLLER.TRG_CATCH_AFTER_DDL
Error: PLS-00201: identifier 'ORA_SQL_TXT' must be declared
Line: 24
Text: n := ora_sql_txt(sql_text);
Error: PL/SQL: Statement ignored
Line: 24
Text: n := ora_sql_txt(sql_text);
Anyone has an idea?
CREATE OR REPLACE TRIGGER trg_catch_after_ddl
AFTER DDL ON DATABASE
DECLARE
sql_text ora_name_list_t;
n pls_integer;
v_sql CLOB;
v_id NUMBER;
BEGIN
dbms_output.put_line(ora_sysevent);
IF ora_sysevent IN ('DROP', 'ALTER', 'ANALYZE') THEN
/*alter table must be handle*/
NULL; -- this is not finished
ELSE
IF ora_dict_obj_type = 'TABLE' THEN
v_sql := dbms_metadata.get_ddl(ora_dict_obj_type,
ora_dict_obj_name,
ora_login_user);
ELSE
n := ora_sql_txt(sql_text);
FOR i IN 1 .. n LOOP
v_sql := v_sql || sql_text(i);
END LOOP;
END IF;
INSERT INTO audit_log
(user_name, dll_type, object_name, object_type, object_script)
VALUES
(ora_login_user,
ora_sysevent,
ora_dict_obj_name,
ora_dict_obj_type,
v_sql)
RETURNING log_id INTO v_id;
pcd_source_writer(id => v_id);
END IF;
END trg_catch_after_ddl;
I am able to create this trigger from the non-sys user.
-- Execute from sys user
CREATE USER T IDENTIFIED BY <Password>
default tablespace <Default_tablespace>;
ALTER USER T QUOTA UNLIMITED ON <Default_tablespace>;
GRANT CONNECT, RESOURCE TO T;
GRANT ADMINISTER DATABASE TRIGGER TO T;
--
-- Execute from T user
CREATE TABLE AUDIT_LOG (
LOG_ID NUMBER
GENERATED ALWAYS AS IDENTITY,
USER_NAME VARCHAR2(4000),
DLL_TYPE VARCHAR2(4000),
OBJECT_NAME VARCHAR2(4000),
OBJECT_TYPE VARCHAR2(4000),
OBJECT_SCRIPT CLOB
);
CREATE OR REPLACE TRIGGER TRG_CATCH_AFTER_DDL AFTER DDL ON DATABASE DECLARE
SQL_TEXT ORA_NAME_LIST_T;
N PLS_INTEGER;
V_SQL CLOB;
V_ID NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE(ORA_SYSEVENT);
IF ORA_SYSEVENT IN (
'DROP',
'ALTER',
'ANALYZE'
) THEN
/*alter table must be handle*/
NULL; -- this is not finished
ELSE
IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
V_SQL := DBMS_METADATA.GET_DDL(ORA_DICT_OBJ_TYPE, ORA_DICT_OBJ_NAME, ORA_LOGIN_USER);
ELSE
N := ORA_SQL_TXT(SQL_TEXT);
FOR I IN 1..N LOOP
V_SQL := V_SQL || SQL_TEXT(I);
END LOOP;
END IF;
INSERT INTO audit_log
(user_name, dll_type, object_name, object_type, object_script)
VALUES
(ora_login_user,
ora_sysevent,
ora_dict_obj_name,
ora_dict_obj_type,
v_sql)
RETURNING log_id INTO v_id;
--
-- pcd_source_writer(id => v_id);
END IF;
END TRG_CATCH_AFTER_DDL;
/
--
-- Testing:
DROP SEQUENCE TEMP_SEQ;
CREATE SEQUENCE TEMP_SEQ START WITH 1 INCREMENT BY 1 MAXVALUE 100;
ALTER SEQUENCE TEMP_SEQ INCREMENT BY 2;
--
-- Result:
SELECT * FROM audit_log;
Output:
Hope, It will be useful to you.
Cheers!!
The solution was that the function need to be under sys user and give an execute privilege.
i want to create funtion that truncate all user objects,
this is my script
CREATE OR REPLACE Function Truncate_user ( name_in IN varchar2 )
return number
is
cnumber number;
v_str1 varchar2(200) := null;
cursor get_sql is
select
'drop '||object_type||' '||owner||'. '|| object_name|| DECODE(OBJECT_TYPE,'TABLE',' CASCADE CONSTRAINTS PURGE') v_str1
from DBA_objects
where object_type in ('TABLE','VIEW','PACKAGE','TYPE','PROCEDURE','FUNCTION','TRIGGER','SEQUENCE','SYNONYM')
AND owner=name_in
order by object_type,object_name;
begin
open get_sql;
loop
fetch get_sql into v_str1;
if get_sql%notfound
then cnumber :=0;
end if;
execute immediate v_str1;
end loop;
RETURN 1;
close get_sql;
end;
/
after the execution i got these errors
Erreur(7,1): PL/SQL: SQL Statement ignored
Erreur(9,6): PL/SQL: ORA-00942: Table ou vue inexistante
but when i execute this code without make a function ,the operation is done!
Your user does not have the priviliges to access DBA_OBJECTS
Maybe there are objects that requires quoted identifiers (e.g. using lowercase letters), try
select 'drop '||object_type||' '||owner||'. "'|| object_name||'"'|| ...
I have the below pl/sql which fetches and prints multiple columns from different tables and prints on console. However for a real time scenario, i need all the data to go into a temporary table. SO that user can do a Select * from the temp table and fetch the data retrieved. Can someone please confirm how can I do that?
SET SERVEROUTPUT ON
Declare
Cursor crsr(companyschema varchar2) is Select Person_Id,Birth_Name from salesdemo.PER_PERSON;
personId number;
bioBirthName varchar2(128);
personalBirthName varchar2(128);
usersSysId varchar2(256);
legalEntity number;
territoryId number;
country varchar2(256);
BEGIN
FOR c_schema IN
(SELECT company_schema
FROM sap.sf_companies c,
sap.sf_feature_map m
WHERE c.company_id = m.company_id
AND m.feature_id = 326
)
LOOP
BEGIN
open crsr(c_schema.company_schema);
if crsr%isopen then
loop
begin
fetch crsr into personId, bioBirthName;
exit when crsr%notfound;
dbms_output.Put_line('Schema '|| c_schema.company_schema);
dbms_output.Put_line('PersonId '|| personId);
dbms_output.Put_line('Bio Birth Name '|| bioBirthName);
execute immediate 'select Birth_Name from '||c_schema.company_schema||'EMP_PERSONAL_INFO_T where person_id = :1' INTO personalBirthName using personId;
dbms_output.Put_line('Personal Birth Name '|| personalBirthName);
execute immediate 'select users_sys_id from '||c_schema.company_schema||'EMP_EMPLOYMENT_INFO where person_id = :1' INTO usersSysId USING personId;
dbms_output.Put_line('UsersSysId '|| usersSysId);
execute immediate 'select company from '||c_schema.company_schema||'EMP_JOB_INFO_T where users_sys_id = :1 ' INTO legalEntity USING usersSysId ;
dbms_output.Put_line('Legal Entity '|| legalEntity);
execute immediate 'select territory_id from '||c_schema.company_schema||'FO_LEGAL_ENTITY_T where internal_code =:1' INTO territoryId USING legalEntity;
dbms_output.Put_line('Territory Id '|| territoryId);
execute immediate 'select territory_name from '||c_schema.company_schema||'Territory where territory_id = :1 ' INTO country USING territoryId;
dbms_output.Put_line('Country '|| country);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -00942 THEN
CONTINUE;
END IF;
end;
end loop;
end if;
close crsr;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -00942 THEN
CONTINUE;
END IF;
END;
END LOOP;
END;
Inserting console output data into a Temp Table from pl/sql
The output you see on the console is from the DBMS_OUTPUT.PUT_LINE procedure. If you want to insert the same out put into a table to use it for further processing, I would suggest a Global Temporary Table.
The data in a global temporary table is private, such that data inserted by a session can only be accessed by that session. The session-specific rows in a global temporary table can be preserved for the whole session, or just for the current transaction. The ON COMMIT clause indicates that the data should be deleted or preserved at the end of the transaction.
ON COMMIT DELETE ROWS
ON COMMIT PRESERVE ROWS
In your case, create the GTT once, and then in your PL/SQL code, insert the rows into the GTT.
For example,
INSERT INTO company_gtt
VALUES
(c_schema.company_schema, personId, bioBirthName, personalBirthName ....)
See more examples here http://oracle-base.com/articles/misc/temporary-tables.php
Trying to check is table exist before create in Oracle. Search for most of the post from Stackoverflow and others too. Find some query but it didn't work for me.
IF((SELECT count(*) FROM dba_tables where table_name = 'EMPLOYEE') <= 0)
THEN
create table EMPLOYEE
(
ID NUMBER(3),
NAME VARCHAR2(30) NOT NULL
)
END IF;
Which gives me error
Error: ORA-00900: invalid SQL statement
SQLState: 42000
ErrorCode: 900
Position: 1
I search for the syntax for IF condition, I think which is also write.
Please suggest me....
As Rene also commented, it's quite uncommon to check first and then create the table.
If you want to have a running code according to your method, this will be:
declare
nCount NUMBER;
v_sql LONG;
begin
SELECT count(*) into nCount FROM dba_tables where table_name = 'EMPLOYEE';
IF(nCount <= 0)
THEN
v_sql:='
create table EMPLOYEE
(
ID NUMBER(3),
NAME VARCHAR2(30) NOT NULL
)';
execute immediate v_sql;
END IF;
end;
But I'd rather go catch on the Exception, saves you some unnecessary lines of code:
declare
v_sql LONG;
begin
v_sql:='create table EMPLOYEE
(
ID NUMBER(3),
NAME VARCHAR2(30) NOT NULL
)';
execute immediate v_sql;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -955 THEN
NULL; -- suppresses ORA-00955 exception
ELSE
RAISE;
END IF;
END;
/
I know this topic is a bit old, but I think I did something that may be useful for someone, so I'm posting it.
I compiled suggestions from this thread's answers into a procedure:
CREATE OR REPLACE PROCEDURE create_table_if_doesnt_exist(
p_table_name VARCHAR2,
create_table_query VARCHAR2
) AUTHID CURRENT_USER IS
n NUMBER;
BEGIN
SELECT COUNT(*) INTO n FROM user_tables WHERE table_name = UPPER(p_table_name);
IF (n = 0) THEN
EXECUTE IMMEDIATE create_table_query;
END IF;
END;
You can then use it in a following way:
call create_table_if_doesnt_exist('my_table', 'CREATE TABLE my_table (
id NUMBER(19) NOT NULL PRIMARY KEY,
text VARCHAR2(4000),
modified_time TIMESTAMP
)'
);
I know that it's kinda redundant to pass table name twice, but I think that's the easiest here.
Hope somebody finds above useful :-).
Please try:
SET SERVEROUTPUT ON
DECLARE
v_emp int:=0;
BEGIN
SELECT count(*) into v_emp FROM dba_tables where table_name = 'EMPLOYEE';
if v_emp<=0 then
EXECUTE IMMEDIATE 'create table EMPLOYEE ( ID NUMBER(3), NAME VARCHAR2(30) NOT NULL)';
end if;
END;
declare n number(10);
begin
select count(*) into n from tab where tname='TEST';
if (n = 0) then
execute immediate
'create table TEST ( ID NUMBER(3), NAME VARCHAR2 (30) NOT NULL)';
end if;
end;
My solution is just compilation of best ideas in thread, with a little improvement.
I use both dedicated procedure (#Tomasz Borowiec) to facilitate reuse, and exception handling (#Tobias Twardon) to reduce code and to get rid of redundant table name in procedure.
DECLARE
PROCEDURE create_table_if_doesnt_exist(
p_create_table_query VARCHAR2
) IS
BEGIN
EXECUTE IMMEDIATE p_create_table_query;
EXCEPTION
WHEN OTHERS THEN
-- suppresses "name is already being used" exception
IF SQLCODE = -955 THEN
NULL;
END IF;
END;
BEGIN
create_table_if_doesnt_exist('
CREATE TABLE "MY_TABLE" (
"ID" NUMBER(19) NOT NULL PRIMARY KEY,
"TEXT" VARCHAR2(4000),
"MOD_TIME" TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
');
END;
/
Any solution which relies on testing before creation can run into a 'race' condition where another process creates the table between you testing that it does not exists and creating it. - Minor point I know.
-- checks for table in specfic schema:
declare n number(10);
begin
Select count(*) into n from SYS.All_All_Tables where owner = 'MYSCHEMA' and TABLE_NAME = 'EMPLOYEE';
if (n = 0) then
execute immediate
'create table MYSCHEMA.EMPLOYEE ( ID NUMBER(3), NAME VARCHAR2(30) NOT NULL)';
end if;
end;
Well there are lot of answeres already provided and lot are making sense too.
Some mentioned it is just warning and some giving a temp way to disable warnings. All that will work but add risk when number of transactions in your DB is high.
I came across similar situation today and here is very simple query I came up with...
declare
begin
execute immediate '
create table "TBL" ("ID" number not null)';
exception when others then
if SQLCODE = -955 then null; else raise; end if;
end;
/
955 is failure code.
This is simple, if exception come while running query it will be suppressed. and you can use same for SQL or Oracle.
Its no need declare and count apply too.
begin
for rec in (select 1 from user_tables where table_name = 'YOUR_TABLE')
-- or
-- for rec in (select 1 from all_tables where table_name = 'YOUR_TABLE' and owner = 'YOU')
loop
execute immediate 'create table your_table as (f1 char(1))';
end loop;
end;
/
Will be good mode create check function
create or replace function this_object_exists (p_obj_name user_objects.object_name%type) return boolean
is
begin
for rec in (select 1 from user_objects where object_name = upper(p_obj_name))
loop
return true;
end loop;
return false;
end this_object_exists;
And thus use code for check exists
.
.
.
.
INDEX PARTITION
TABLE SUBPARTITION
SEQUENCE
TABLE PARTITION
PROCEDURE
LOB PARTITION
LOB
INDEX SUBPARTITION
PACKAGE
PACKAGE BODY
TYPE BODY
TRIGGER
INDEX
TABLE
VIEW
FUNCTION
SYNONYM
TYPE
JOB
...
begin
if not this_object_exists('your_table') then
execute immediate 'create table your_table as (f1 char(1))';
end if;
end;
or
begin
if this_object_exists('your_table') then
execute immediate 'drop table your_table';
end if;
execute immediate 'create table your_table as (f1 char(1))';
end;
Here is a scenario.
I have around 300 tables in my database and I want to merge another database in my database. Both the databases have same tables but the datatype and no of columns vary.
Now how to convert data from other database to my database ?
eg.
db1: Table T1(col1 int,col2 char,......,col31 int)
db2: Table T1(col1 int,col2 char,......,col31 int,col32 char,col33 char )
Since datatype and no of column vary,I cant use
"insert into db1.tbl
select * from db2.tbl ".
I dont want to create script for each and every table . Help me out !!!
every morning I copy a large amount of tables from the production database into my datawarehouse. This is my "check & fix" procedure I run before each truncate + insert.
procedure check_and_fix_table(p_naam in varchar2)
/**
* check if columns have changed on PROD and create and execute the matching ALTER TABLE statement
*/
is
v_coltype varchar2(100);
v_sql varchar2(200);
function check_column(p_column in varchar2)
return boolean
is
v_dummy number;
begin
select 1 into v_dummy
from user_tab_cols tc
where tc.table_name = upper(p_naam)
and tc.column_name = p_column;
return true;
exception
when no_data_found then return false;
end;
begin
-- loop through all columns that are altered (if nothing altered, then nothing will happen
for i in (select tc.column_name
, tc.data_type
, tc.data_length
, tc.data_precision
from user_tab_cols#DB_LINK_TO_PRODUCTION tc
where tc.table_name = upper(p_naam)
and tc.column_name not like 'SYS_NC%' -- These columns are created by oracle for function based indexes
minus
select tc.column_name
, tc.data_type
, tc.data_length
, tc.data_precision
from user_tab_cols tc
where tc.table_name = upper(p_naam))
loop
-- create column type
if i.data_type in ('CHAR','VARCHAR2') then
v_coltype := i.data_type||'('||i.data_length||')';
elsif i.data_type = 'NUMBER' then
if i.data_precision is not null then
v_coltype := i.data_type||'('||i.data_precision||')';
else
v_coltype := i.data_type;
end if;
else -- DATE, CLOB, BLOB, etc
v_coltype := i.data_type;
end if;
-- check if the column is altered or added
if check_column(i.column_name) then
-- execute the ALTER TABLE to fix the column
v_sql := 'alter table '||p_naam||' modify '||i.column_name||' '||v_coltype;
else
-- add new column
v_sql := 'alter table '||p_naam||' add '||i.column_name||' '||v_coltype;
end if;
execute immediate v_sql;
-- logging change
prc_log(c_procedureid, 1, p_naam||' changed. Fixed by executing: '||v_sql);
end loop;
exception
when others then
prc_log(c_procedureid, 3, 'Error at copy_package.check_and_fix_table - '||substr(sqlerrm, 0, 1900));
end;
Then in my main procedure I use it like this (where p_naam is the tablename passed as parameter to the procedure):
check_and_fix_table(p_naam);
-- full copy of table from PROD
execute immediate 'truncate table ' || p_naam;
execute immediate 'insert /*+append*/ into ' || p_naam || ' select * from ' || p_naam || '#DB_LINK_TO_PRODUCTION';
Hope this helps :)