Oracle equivalent for SQL Server INSERTED and DELETED tables - sql

I am in the process of migrating a SQL Server database to Oracle, where I have to convert SQL Server procedure which uses special tables called INSERTED and DELETED in SQL Server.
As per my understanding these tables hold copies the data of last inserted/deleted records.
(find the msdn article here: http://msdn.microsoft.com/en-us/library/ms191300.aspx)
Are there any similar tables in Oracle to achieve this..? Please advise.
UPDATE:
Thanks for your answers and comments ,I think I need to explain the situation some more. Here is the full story to understand the real scenario;
Data base contains tables and shadow tables (shadow has an additional column).
When a table is updated same changes should be recorded in relevant shadow table with some additional data.
For this purpose they are having triggers for each table (these triggers copy data to relevant shadow table).
The above mentioned procedure generates these triggers dynamically for each and every table.
Now the real problem is I don't have the knowledge about the columns as triggers are dynamically generated for each table.
Basically I can’t get value like: NEW.col_1 or: OLD.col_1 as APC mentioned. Can I.?
Or else I have to write all those triggers manually using prefixes: NEW and: OLD rather than trying to generate them dynamically.
I am using Oracle 11g

Oracle triggers use pseudo-records rather than special tables. That is, instead of tables we can access the values of individual columns.
We distinguish pseudo-records in the affected table from records in (other) tables by using the prefixes :NEW and :OLD . Oracle allows us to declare our own names for these, but there is really no good reason for abandoning the standard.
Which column values can we access?
Action :OLD :NEW
------ ---- ----
INSERTING n/a Inserted value
UPDATING Superseded value Amended value
DELETING Deleted value n/a
You will see that :OLD is the same as the MSSQL table DELETED and :NEW is the same as table INSERTED
So, to trigger a business rule check when a certain column is updated:
create or replace trigger t23_bus_check_trg
before update on t23
for each row
begin
if :NEW.col_1 != :OLD.col_1 then
check_this(:NEW.col_1 , :OLD.col_1);
end if;
end t23_bus_check_trg;
There's a whole chapter on records in the PL/SQL Reference. Find out more.

There are many differences between Sql Server triggers and Oracle triggers. In Oracle, you can declare statement level or row level triggers. Sql Server only has statement level. In Oracle, you can declare before triggers or after triggers. Sql Server only has after triggers.
If you're going to be working with Oracle, although later versions have the compound trigger, get used to working with row level triggers. There you have the pseudo row designation of :old and :new, kinda like Deleted and Inserted except it's just the one row of data. It's like being in a cursor loop, something you can do in Sql Server, but cursor perform so poorly in Sql Server, developers go to great lengths to avoid them. They are commonly used in Oracle.
The general rule of thumb is this: if you need to examine the data and possibly alter it before it goes to the table, use a "before" trigger. If you want to perform an audit or logging procedure, use an "after" trigger.
The page I linked to above gives a lot of technical details, but it is absolutely atrocious at giving usable examples. For that, just google "oracle trigger tutorial" and you should get lots of handy, easy-to-learn-from examples.

Thanks for the answers and comments. here is the complete solution to my problem.If some one meet the exact problem this will help.
create or replace PROCEDURE CreateTrackingTriggers
(
-- take the target table and shadow user as agruments
v_TableName IN NVARCHAR2 DEFAULT NULL,
v_ShadowUser IN NVARCHAR2 DEFAULT 'SHADOW_USER'
)
AUTHID CURRENT_USER -- grant permission to create triggers
AS
v_TriggerName NVARCHAR2(500);
v_ColList NVARCHAR2(2000);
v_ColList_shadow NVARCHAR2(2000);
v_SQLCommand VARCHAR2(4000);
v_ColName NVARCHAR2(500);
v_ColSize NUMBER(10,0);
v_Prefix NVARCHAR2(500);
v_count NUMBER(1,0);
BEGIN
DECLARE
-- define a cursor to get the columns of the target table. order by COLUMN_ID is important
CURSOR Cols
IS SELECT COLUMN_NAME , CHAR_COL_DECL_LENGTH FROM USER_TAB_COLS
WHERE TABLE_NAME = upper(v_TableName) order by COLUMN_ID;
-- define a cursor to get the columns of the target shadow table order by COLUMN_ID is important
CURSOR Shadow_Cols
IS SELECT COLUMN_NAME , CHAR_COL_DECL_LENGTH FROM ALL_TAB_COLS
WHERE TABLE_NAME = upper(v_TableName) and upper(owner)=upper(v_ShadowUser) order by COLUMN_ID;
BEGIN
-- generate the trigger name for target table
v_TriggerName := 'TRG_' || upper(v_TableName) || '_Track' ;
-- check v_count , determine whether shdow table exist if not handle it
select count(*) into v_count from all_tables where table_name = upper(v_TableName) and owner = upper(v_ShadowUser);
-- iterate the cursor. generating column names prefixing ':new.'
OPEN Cols;
FETCH Cols INTO v_ColName,v_ColSize;
WHILE Cols%FOUND
LOOP
BEGIN
IF v_ColList IS NULL THEN
v_ColList := ':new.'||v_ColName ;
ELSE
v_ColList := v_ColList || ',' || ':new.'||v_ColName;
END IF;
FETCH Cols INTO v_ColName,v_ColSize;
END;
END LOOP;
CLOSE Cols;
-- iterate the cursor. get the shadow table columns
OPEN Shadow_Cols;
FETCH Shadow_Cols INTO v_ColName,v_ColSize;
WHILE Shadow_Cols%FOUND
LOOP
BEGIN
IF v_ColList_shadow IS NULL THEN
v_ColList_shadow := v_ColName;
ELSE
v_ColList_shadow := v_ColList_shadow || ',' || v_ColName;
END IF;
FETCH Shadow_Cols INTO v_ColName,v_ColSize;
END;
END LOOP;
CLOSE Shadow_Cols;
-- create trigger command. This will generate the trigger that dupilicates target table's data into shdow table
v_SQLCommand := 'CREATE or REPLACE TRIGGER '||v_TriggerName||'
AFTER INSERT OR UPDATE OR DELETE ON '||upper(v_TableName)||'
REFERENCING OLD AS old NEW AS new
FOR EACH ROW
DECLARE
ErrorCode NUMBER(19,0);
BEGIN
-- v_ColList_shadow : shdow table column list
-- v_ColList : target table column list with :new prefixed
INSERT INTO '|| v_ShadowUser ||'.'||upper(v_TableName)||'('||v_ColList_shadow||') values ('||v_ColList||');
EXCEPTION
WHEN OTHERS THEN ErrorCode := SQLCODE;
END;';
EXECUTE IMMEDIATE v_SQLCommand;
END;
END;

Related

PL/pgSQL dynamic query in stored function to return any table's column names

I am trying to write a stored function with a dynamic query that returns all column names from a table that can then be used to create a dynamic query for a joined view trigger function. But struggling to create a stored function with a dynamic query returning column_name from information_schema.
Here is the SQL query I was hoping to convert to a stored function passing the table_name and table_schema as function parameters:
select
column_name
from
information_schema.columns
where
table_name = 'projects' -- to be replaced by parameter
and table_schema = 'public'; -- to be replaced by parameter
I (think I now) understand the basics of needing to use Execute and Format for neatness, but only got a result with passing a table name. This post had a good example of passing a table name: Refactor a PL/pgSQL function to return the output of various SELECT queries
The idea would be to dynamically get the columns then process into a function based on this scratch dynamic query...
DO $$
DECLARE
item varchar;
column_name varchar default 'name';
table_name varchar default 'projects';
temp_string varchar default '';
begin
FOR item IN execute format('SELECT %I FROM %I',column_name,table_name)
loop
temp_string := temp_string || ',NEW.' || item;
END LOOP;
RAISE NOTICE '%', temp_string;
END$$;
And ultimately into the trigger function for views based on a table with a foreign key join. I.e. so the INSERT and UPDATE code is dynamically created for any parent table of a view with a join:
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO projects VALUES(NEW.id,NEW.name);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE projects SET id=NEW.id, name=NEW.name WHERE id=OLD.id;
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
DELETE FROM projects WHERE id=OLD.id;
RETURN NULL;
END IF;
RETURN NEW;
END;
$function$
And finally work out how to deal with foreign key columns.
End result is the parent table can be updated via the view in QGIS. Is this even possible?
I am not exactly sure I understand what you are after but I think parent table can be updated via the view indicates the goal. If so you are headed in the wrong direction entirely and none of what you are seeking is needed. What you want is an instead of trigger on the view(s). The fiddle here demonstrates an instead of trigger on a view generated with a join, typically these are not cannot normally be updated.
Your idea to dynamically get the columns then process ... and ultimately into the trigger function for views seems extremely ambitious. A better approach may be to build a template for the trigger and associated functions then make the necessary specific column changes. Your trigger(s) must
exist well before any DML action on the views.

BULK COLLECT/FORALL statements with dynamic query and table name- Oracle PL/SQL

I need help in optimizing this query to use bulk collect and forall statements. I have created backup tables (BCK_xxxx) to copy all data from original tables (ORIG_xxx) but I am having problems converting this to bulk collect. Most examples I saw in BULK collect includes already defining the table name and structure using %rowtype. However, I have hundreds of tables to backup so I need my query specifically the table name to be dynamic. This my original query that inserts/deleted data one by one without bulk collect and takes a lot of time:
DECLARE
--select all table names from backup tables (ex: BCK_tablename)
CURSOR cur_temp_tbl IS
SELECT table_name
FROM all_tables
WHERE OWNER = 'BCKUP'
ORDER BY 1;
--select all table names from original tables (ex: ORIG_tablename)
CURSOR cur_original_tbl IS
SELECT table_name
FROM all_tables
WHERE OWNER = 'ORIG'
ORDER BY 1;
l_tbl_nm VARCHAR2(30 CHAR);
BEGIN
--first loop to delete all tables from backup
FOR a IN cur_temp_tbl LOOP
l_tbl_nm := a.table_name;
EXECUTE IMMEDIATE 'DELETE FROM '|| l_tbl_nm;
l_deleted_cnt := l_deleted_cnt +1;
END LOOP;
--second loop to insert data from original to backup
FOR b IN cur_original_tbl LOOP
l_tbl_nm := b.table_name;
CASE
WHEN INSTR(l_tbl_nm,'ORIG_') > 0 THEN
l_tbl_nm := REPLACE(l_tbl_nm,'ORIG_','BCK_');
ELSE
l_tbl_nm := 'BCK_' || l_tbl_nm;
END CASE;
EXECUTE IMMEDIATE 'INSERT INTO ' || l_tbl_nm || ' SELECT * FROM ' || b.table_name;
l_inserted_cnt := l_inserted_cnt +1;
END LOOP;
dbms_output.put_line('Deleted/truncated tables from backup :' ||l_deleted_cnt);
dbms_output.put_line('No of tables inserted with data from original to backup :' ||l_inserted_cnt);
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
dbms_output.put_line(l_tbl_nm);
END;
I am thinking of including the code below to add after my second loop but I am having problems how to declare the 'cur_tbl' cursor and 'l_tbl_data' TABLE data type. I am unable to use rowtype since the tablename should be dynamic and will change in each iteration of my second loop that will list all table names from original table:
TYPE CurTblTyp IS REF CURSOR;
cur_tbl CurTblTyp;
TYPE l_tbl_t IS TABLE OF tablename.%ROWTYPE;
l_tbl_data l_tbl_t ;
OPEN cur_tbl FOR 'SELECT * FROM :s ' USING b.table_name;
FETCH cur_tbl BULK COLLECT INTO l_tbl_data LIMIT 5000;
EXIT WHEN cur_tbl%NOTFOUND;
CLOSE cur_tbl;
FORALL i IN 1 .. l_tbl_data .count
EXECUTE IMMEDIATE 'insert into '||l_tbl_nm||' values (:1)' USING
l_tbl_data(i);
Hope you can help me and suggest how I can make this code much simpler. Thanks a lot.
It looks like you want to remove all rows from the existing backup tables, then re-copy the entire contents from the original tables to the backup tables. If this is correct, using DELETE for the deletion and any loop operation for the insert will be slow.
First, to remove the data, use TRUNCATE. Since you are going to repopulate, use the REUSE STORAGE option. This is the most efficient way to delete all rows from the table.
TRUNCATE TABLE <backup table> REUSE STORAGE;
Second, to repopulate, just INSERT with a SELECT.
INSERT INTO <backup table> SELECT * FROM <orig table>;
You can use these in your loops as you loop by table. No need to cursor through the table rows as this will be faster.
If you have a new table, you can do something similar with a CTAS...
CREATE TABLE <backup table> AS SELECT * FROM <orig_table>;
There is a 3rd option in addition to the delete and truncate options: that's rename/drop. You rename the old back up tables, recreate the new backups (CTAS). If the create - insert is successful you drop the renamed tables, if the new backup fails you rename the prior old backups back to the initial backup names. You basically trade temporary usage of disk space for redo logs.
You don't need bulk processing, CTAS is still faster than bulk processing.
Have you used FORCE DELETE?
It was first introduced by the Oracle Master J.B.E
it is used to delete the data and ignores the constraint that the table may have and is a lot more faster than other delete statements.
FORCE DELETE FROM <table_name>;

How to delete data from specified tables at once in oracle sql

I have more than 40 tables in RATOR_MONITORING schema for which the table name is starting from 'TEMP_'. I want to delete data from all such tables at once in a single query instead of using delete statement for each and every table. I dont even want to generate statements. I can create anonymous block if required but dont know how to do that. I tried below query but its not working.
Delete from RATOR_MONITORING_CONFIGURATION.'%TEMP_';
If you want to delete all the rows, then better use TRUNCATE, it will reset the high watermark. But remember, truncate is a DDL statement, and thus there will be an implicit commit; With DELETE you can commit manually after validation.
Although, I would not do that in a production environment. If it is something you are doing in test environment to build test data, then you could (ab)use EXECUTE IMMEDIATE.
For example, execute the following anonymous block as RATOR_MONITORING user:
DECLARE
v_sql VARCHAR2(100);
BEGIN
FOR i IN
(SELECT table_name FROM user_tables where table_name like 'TEMP%'
)
LOOP
v_sql := 'TRUNCATE TABLE '||i.table_name;
EXECUTE immediate v_sql;
END LOOP;
END;
/
By the way, using a good text editor, it won't take more than a minute to build DELETE/TRUNCATE statements and do it in pure SQL.

Create table and call it from sql

I have a PL/SQL function which creates a new temporary table. For creating the table I use execute immediate. When I run my function in oracle sql developer everything is ok; the function creates the temp table without errors. But when U use SQL:
Select function_name from table_name
I get an exceptions:
ORA-14552: cannot perform a DDL, commit or rollback inside a query or DML
ORA-06512: at "SYSTEM.GET_USERS", line 10
14552. 00000 - "cannot perform a DDL, commit or rollback inside a query or DML "
*Cause: DDL operations like creation tables, views etc. and transaction
control statements such as commit/rollback cannot be performed
inside a query or a DML statement.
Update
Sorry, write from tablet PC and have problems with format text. My function:
CREATE OR REPLACE FUNCTION GET_USERS
(
USERID IN VARCHAR2
)
RETURN VARCHAR2
AS
request VARCHAR2(520) := 'CREATE GLOBAL TEMPORARY TABLE ';
BEGIN
request := request || 'temp_table_' || userid ||
'(user_name varchar2(50), user_id varchar2(20), is_administrator varchar2(5)') ||
' ON COMMIT PRESERVE ROWS';
EXECUTE IMMEDIATE (request);
RETURN 'true';
END GET_USERS;
The error is explicit:
ORA-14552: cannot perform a DDL, commit or rollback inside a query or DML
In Oracle, you can't commit inside a query. A likely explanation is that it would make no sense since a query in Oracle is atomic (either succeeds entirely or makes no change) and this couldn't work if you commit in the middle of a DML. For a select query, all rows must be returned from a single logical point-in-time and if you commit in the middle of a select you would have inconsistent results.
Since DDL in Oracle issue an implicit commit, you can't make DDL inside a query.
This should not be a problem in your case though: SQL server-like temporary tables are not equivalent to the GLOBALLY temporary table in Oracle. There is a reason why temp tables in Oracle are always prefixed with GLOBALLY: they are visible to all sessions although the data in the temporary table is private to each session.
In Oracle creating a temporary table is a relatively expensive operation and you should not create individual temporary tables: all sessions should that do the same job should use the same common structure. Instead of creating multiple temporary tables, in Oracle you should create the table once and reuse it in all procedures. If you are going to need it later, why drop it?
In any case, if you decide to do multiple DDL that depend upon a SELECT, you could do it in a PLSQL block instead of a SELECT query:
DECLARE
l VARCHAR2(100);
BEGIN
FOR cc IN (SELECT col FROM tab) LOOP
l := create_temp_table(cc.col);
END LOOP;
END;
I tested below solution on Oracle 10g XE, it works for me.
Create function:
CREATE OR REPLACE FUNCTION GET_USERS
(
USERID IN VARCHAR2
)
RETURN VARCHAR2
AS
request VARCHAR2(255) := 'CREATE GLOBAL TEMPORARY TABLE ';
BEGIN
request := request || 'temp_table_' || userid ||
'(user_name varchar2(50), user_id varchar2(20), is_administrator varchar2(5))' ||
' ON COMMIT PRESERVE ROWS';
EXECUTE IMMEDIATE request;
RETURN 'true';
END GET_USERS;
Run function:
SET SERVEROUTPUT ON
DECLARE
RESULT VARCHAR(255);
BEGIN
RESULT:=gET_USERS('ADMIN3');
dbms_output.put_line(result);
END;
and select from temporary table:
SELECT * FROM temp_table_admin3;

Create/alter from SQL stored procedure

I want to call create table/ alter table command from a procedure. Is it possible?
My requirement is to change the datatype of a column in all tables. So, I am just getting the column name from user_tab_cols. Now I want to create a temp table which requires create statement .. but i am unable to use that within a proc.
Can anyone please help me out?
I presume from the reference to USER_TAB_COLUMNS that this is Oracle. ALTER and CREATE statements are DDL, which we cannot execute directly in PL/SQL. However, there are a couple of ways around this restriction: EXECUTE IMMEDIATE and DBMS_UTILITY.EXEC_DDL(). I will use EXECUTE IMMEDIATE in the following example.
begin
for lrec in ( select table_name from user_tab_columns
where column_name = 'UNIVERSAL_COLUMN_NAME')
loop
execute immediate 'alter table '||lrec.table_name||
' modify UNIVERSAL_COLUMN_NAME varchar2(255)';
end loop;
end;
Note that the usual restrictions apply: the new datatype has to be compatible with the existing datatype (unless the column is empty), and things are trickier with some specilaized datatypes like CLOBs.
edit
I haven't addressed the CREATE TABLE statement. The principle is the same, it is just longer to type out. Besides, I am not entirely clear how it applies to your prior requirement to change the datatype of those columns.
you can generate the query as string and execute it with 'exec' keyword.