How can I insert into one table and update another table with the same sql statement? - sql

The requirement to do it in one statement is because of how the program handles sql statements. The sql statement is stored in a column of another table, and executed through an open on a recordset. The open responds with an error of invalid character if a semi-colon is in the statement.
The scenario: Under certain conditions, I want to update a particular field in one record in database A, and record the fact of that change in a log table by an insert.
Here's an example using two statements:
update data_table a set field1='new value' where identifier=10;
insert into log_table (action_taken)
values('record ' || a.identifier || ' had field1 changed to ' || a.field1);
Is there any way to do this?

Put them both in a stored procedure and execute call the stored procedure.

I think you are looking for TRIGGERS. Without knowing what database you are using I can only guess.
Here is information about triggers for MySQL.
A trigger is tied to a table to start on a specific event, such as INSERT, UPDATE or DELETE. The trigger can then run one or more SQL statements.
And here is how you create a trigger

Make a batch call to the SGDB you can do what you have done, if you are using java or .net both of the their SQL API support sql batch commands.
update data_table a set field1='new value' where identifier=10;
GO
insert into log_table (action_taken)
values('record ' || a.identifier || ' had field1 changed to ' || a.field1);
GO
But if you are going to do this two operations at the same time all the time you should accept krefftc answer because it's the best way

If your database happens to be Oracle you may be able to use an anonymous PL/SQL block if for some reason you can't/don't want to create a stored procedure:
BEGIN
update data_table a set field1='new value' where identifier=10;
insert into log_table (action_taken)
values('record ' || a.identifier || ' had field1 changed to ' || a.field1);
END;

Related

If I pass a where clause as a parameter will that prevent SQL Injection?

I created an Oracle proc where I create a dynamic sql statement based on the parameters supplied to the proc.
I've done some testing and it appears that I can't perform sql injection.
Is there anything additional I should be safe guarding against?
SELECT 'UPDATE ' || p_table || ' SET MY_FIELD = ''' || p_Value || ''' ' || p_Where
INTO query_string
FROM DUAL;
EDIT:
Scenarios that I've tried.
1. WHERE SOME_VAL IN ('AAA','BBB') - This works
2. WHERE SOME_VAL IN ('AAA','BBB') OR SOME_VAL2 = '123' - This works.
3. WHERE SOME_VAL IN ('AAA','BBB'); DROP TABLE TEST_TABLE; - This errors out.
4. WHERE SOME_VAL IN ('AAA','BBB') OR (DELETE FROM TEST_TABLE) - This errors out.
It depends on how and by whom your procedure is being invoked. Usually you need to worry about SQL injection for something that is open to large number of users in production. And that should not be the case for any database procedure. If your database procedure is accessible by large number of users, then you have potential for malicious use by someone.
In your case, you can mitigate this risk by creating mapping of parameters to hide actual schema object names and some validation.
For example change parameter p_table to table_name as input parameter. Then using case statement map to actual table name. I am giving you example of table name here because you should really restrict who can access which table from db.
CREATE OR REPLACE PROCEDURE test_proc(table_name IN VARCHAR)
IS
p_table varchar2(100);
BEGIN
CASE table_name
WHEN 'A' THEN p_table:='db_table_a';
WHEN 'B' THEN p_table:='db_table_b';
ELSE RAISE 'Invalid table name parameter';
END CASE;
SELECT 'UPDATE ' || p_table || ' SET MY_FIELD = ''' || p_Value || ''' '
|| p_Where
INTO query_string
FROM DUAL;
END;
You should do similar mapping and validation for other parameters too.
SQL injection always opens Pandora's box.
You should always assume a user can break out of a dynamic SQL statement. With full SQL access you should then assume a user can find a way to escalate privileges and own your database. (Depending on how paranoid you are, it might be safe to assume privilege escalation is impossible as long as your database and schemas are constantly patched and thoroughly hardened. In practice the vast majority of Oracle databases are not sufficiently patched and hardened.)
Below are a few simple examples that should scare you. And you should also assume that there are many hackers who are more clever than I am and have better attacks.
Sample Schema
First let's create a simple table with some data for a realistic test.
drop table test1;
create table test1(my_field varchar2(100), some_val varchar2(100));
insert into test1 values('A', 'AAA');
commit;
Obviously Dangerous Function
Are all of the existing functions safe?
create or replace function dangerous_function return number is
pragma autonomous_transaction;
begin
delete from test1;
commit;
return 1;
end;
/
If not, what is stopping the user from calling it like this?
--Safe static part:
update test1
set my_field = 'b'
--Dangerous dynamic part:
where some_val IN ('AAA')
and 1 = (select dangerous_function from dual)
Luckily creating an autonomous function is unusual and you can probably check the code. But can you guarantee the application will not create one in the future?
Custom Function in SQL
Even if there are no objects a clever user can turn your UPDATE into other DML:
--Safe static part:
update --+ WITH_PLSQL
test1
set my_field = 'b'
--Dangerous dynamic part:
where some_val IN ('AAA')
and 1 = (
with function dangerous_function return number is
pragma autonomous_transaction;
begin
delete from test1;
commit;
return 1;
end;
select dangerous_function from dual
);
I did cheat a little, the above code only works for me with the --+ WITH_PLSQL hint. Without that hint the code throws the error ORA-32034: unsupported use of WITH clause. But that's only a version limitation that might be lifted in the future. Or there might be some clever way to work around it, sometimes hints can break out of their part of the query and reference other sections.
Why Risk It?
Maybe there is a safe way to do it. But why risk it? Everybody in the IT world understands SQL injection bugs now. If you mess up and cause an exploit there will be no sympathy for you.

Oracle equivalent for SQL Server INSERTED and DELETED tables

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;

How to check in Trigger what has been given in the where clause?

I am writing an INSTEAD OF UPDATE trigger and I want to identify what columns has been given to the WHERE clause of the UPDATE statement that triggers the trigger.
For example,
Let's say that we have the table below
table_name
--COL1
--COL2
--COL3
--COL4
I want, when an update is performed
e.g.UPDATE table_name SET COL1=VAL1,COL2=VAL2 WHERE COL3=VAL3
to be able to say in my trigger
CREATE or replace TRIGGER DEVICES_VIEW_TR
INSTEAD OF UPDATE ON DEVICES_VW
BEGIN
IF (COL3 has been given in the where clause) THEN
variable=getValueOf(COL3);
ELSEIF (COL4 has been given in the where clause) THEN
variable=getValueOf(COL4);
END IF;
END;
/
Can this be done?
Thanks
You can use the UPDATING('column name') in your trigger:
-- in INSTEAD OF trigger body:
IF updating('COL1') THEN
-- some operation
END IF;
Check this for an example: Example of using UPDATING
You could use the NEW and OLD pseudorecords and run a comparison of the values
if :NEW.COL3 <> :OLD.COL3 THEN ...
Triggers don't know anything about the statement that invoked them, so you'll have to use some kind of out-of-band signalling, e.g. change your application to set some globals in a database package, or use an application context.

Inserting query results into table named by a variable

I have a stored procedure which runs many queries and obtains a lot of values which it stores in variables. I want this procedure to be able to be able to insert the query results into a table which is given by the user. I would store the given table name as a varchar parameter but how can I insert into this table?
At compile time, Oracle is saying that the table does not exist.
Presumably it's telling you that a table with the name of the variable you're using doesn't exist, which of course it won't. The actual table may or may not exist at compile time; since it's flexible it's probably safer to assume it might not. Either way you don't know what it will be so you need to use dynamic SQL to achieve this.
As mentioned in a comment on another answer, you have to be careful about SQL injection. Normally you'd want to use bind variables in the dynamic SQL, but you can't use binds for object names, so you have to concatenate it. Hopefully you're using 11g, which includes the dbms_assert package.
Here's a simple example:
create or replace procedure p42 (table_name varchar2) as
begin
execute immediate 'insert into '
|| dbms_assert.qualified_sql_name(table_name)
|| ' select * from dual';
end;
/
I can then create a table after the procedure already exists, and successfully call the procedure:
create table t42 (dummy varchar2(1));
exec p42('t42');
select * from t42;
DUMMY
-----
X
Your real query will obviously be more complicated, and should use bind variables for any filter values you're passing in along with the target table name.
The advantage of the dbms_assert call is that it will error if something illegal is passed in, and if something nasty is passed:
exec p42('t42 select ''Y'' from dual union all');
ORA-44004: invalid qualified SQL name
ORA-06512: at "SYS.DBMS_ASSERT", line 207
ORA-06512: at "STACKOVERFLOW.P42", line 3
ORA-06512: at line 1
If the procedure simply concatenated the passed value:
execute immediate 'insert into ' || table_name || ' select * from dual';
... then that same call would insert two rows into the table:
exec p42('t42 select ''Y'' from dual union all');
select * from t42;
DUMMY
-----
Y
X
Which is something to worry about if the data integrity is at all important to you. If you can't use dbms_assert then you could try to check that the passed name actually exists in all_tables and doesn't contain anything like a union etc. but it's much safer to acknowledge that you won't think of all the possible attacks and let the built-in function do the hard work for you.
Try something like this:
exec ('insert into ' + #tblname + ' (col) values (123)')

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.