is there any way to log all failed sql statements in oracle 10g - sql

is there any way to log all failed sql statements in oracle 10g to a table or file?
By failed I mean bad formated sql statement or sql statements that do not have permission for a table or object.

You may want to use Auditing like:
AUDIT SELECT TABLE, INSERT TABLE, DELETE TABLE, EXECUTE PROCEDURE
BY ACCESS
WHENEVER NOT SUCCESSFUL;
By ACCESS is for each statement (which seems like what you want). By SESSION would record one record per session (high volume environment).
Oracle's built in auditing has less overhead then a trigger. A trigger, which other answers contain, allows you to log the exact information you want. Auditing will also only catch hits on existing objects. If someone selects on a non-existent table (misspelled or whatnot) auditing will not catch it. The triggers above will.
A lot more info in the security guide: http://download.oracle.com/docs/cd/B19306_01/network.102/b14266/auditing.htm#i1011984

Rather than hit the system views, as in Demge's answer, there is an ora_sql_txt function that gives the relevant statement.
create or replace TRIGGER log_err after servererror on schema
DECLARE
v_stack VARCHAR2(2000) := substr(dbms_utility.format_error_stack,1,2000);
v_back VARCHAR2(2000);-- := substr(dbms_utility.format_error_backtrace,1,2000);
v_num NUMBER;
v_sql_text ora_name_list_t;
procedure track(p_text in varchar2) is
begin
insert into .... values (p_text);
end;
begin
v_stack := translate(v_stack,'''','"');
track(v_stack);
v_back := translate(v_back,'''','"');
if v_back is not null then track(v_back); end if;
v_num := ora_sql_txt(v_sql_text);
BEGIN
FOR i IN 1..v_num LOOP
track(to_char(i,'0000')||':'||v_sql_text(i));
END LOOP;
EXCEPTION
WHEN VALUE_ERROR THEN NULL;
END;
end;
In my own environment, I actually have 'TRACK' as a separate procedure that uses an autonomous transaction, rather than a block as above.
create or replace procedure track (p_text IN VARCHAR2) IS
PRAGMA AUTONOMOUS_TRANSACTION;
cursor c_user is
select sys_context('USERENV','CLIENT_INFO') client_info,
sys_context('USERENV','CURRENT_SCHEMA') curr_schema,
sys_context('USERENV','CURRENT_USER') curr_user,
sys_context('USERENV','DB_NAME') db_name,
sys_context('USERENV','HOST') host,
sys_context('USERENV','IP_ADDRESS') ip,
sys_context('USERENV','OS_USER') osuser,
sys_context('USERENV','SESSIONID') sessid,
sys_context('USERENV','SESSION_USER') sess_user,
sys_context('USERENV','TERMINAL') terminal
from dual;
user_rec c_user%rowtype;
v_mod VARCHAR2(48);
v_act VARCHAR2(32);
v_cli_info varchar2(64);
begin
open c_user;
fetch c_user into user_rec;
close c_user;
DBMS_APPLICATION_INFO.READ_MODULE (v_mod, v_act);
--DBMS_APPLICATION_INFO.READ_CLIENT_INFO(v_cli_info);
insert into track_detail
(id, track_time, detail, client_info, curr_schema, curr_user, db_name,
host, ip, osuser, sessid, sess_user, terminal, module, action)
values (track_seq.nextval, systimestamp, p_text,
user_rec.client_info, user_rec.curr_schema, user_rec.curr_user,
user_rec.db_name, user_rec.host, user_rec.ip,
user_rec.osuser, user_rec.sessid, user_rec.sess_user,
user_rec.terminal, v_mod, v_act);
commit;
end;

You can do this with a system trigger.
I directly copied this code from http://www.psoug.org/reference/system_trigger.html.
CREATE TABLE servererror_log (
error_datetime TIMESTAMP,
error_user VARCHAR2(30),
db_name VARCHAR2(9),
error_stack VARCHAR2(2000),
captured_sql VARCHAR2(1000));
CREATE OR REPLACE TRIGGER log_server_errors
AFTER SERVERERROR
ON DATABASE
DECLARE
captured_sql VARCHAR2(1000);
BEGIN
SELECT q.sql_text
INTO captured_sql
FROM gv$sql q, gv$sql_cursor c, gv$session s
WHERE s.audsid = audsid
AND s.prev_sql_addr = q.address
AND q.address = c.parent_handle;
INSERT INTO servererror_log
(error_datetime, error_user, db_name,
error_stack, captured_sql)
VALUES
(systimestamp, sys.login_user, sys.database_name,
dbms_utility.format_error_stack, captured_sql);
END log_server_errors;
/

Related

Trigger pl/sql insert data on table

i'm new in pl/sql. I'm trying to create a trigger that insert datas in specific tables.
I have datas that arrives in real-time on my table EV_48h. To know on which table I have to insert the data i have to know it Ref_equip (Ref_equip is on an other table named C_Equip).
I've made quickly this littre merise to be more understandable:
merise
As I said I have data that arrives on real-time on the table EV_48H and I have to put them automatically on the tables that are named 'EVV_'+Ref_equip.
So, here is my code. I don't have any error but it don't work. I know i missed of forget something but i don't know what.
TRIGGER "SIVO"."NEWtrigger3EV_48H"
BEFORE INSERT
ON SIVO.EV_48H
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
clef_var number(4,0);
ref_equip varchar2(40);
V_Nom_table varchar2(1000) ;
V_nom_seq Varchar2(2000) ;
stmt varchar2(200);
begin
SELECT clef_var
INTO :New.Clef_Var
FROM sivo.c_variable
WHERE Ref_Var= :new.Ref_Var;
-- Conversion des formats Date-Heure en DateHeure oracle
:New.EV_DATEAUTO := to_date(:New.EV_DATE || ' ' || :New.EV_HEURE, 'DD/MM/YY HH24:MI:SS');
stmt:='begin select clef_var into :New.Clef_Var From sivo.C_variable; end';
EXECUTE IMMEDIATE stmt using out clef_var;
IF clef_var is not null then
stmt :='begin select Ref_equip into :New.Ref_Equip FROM sivo.C_Equip WHERE Ref_var= :New.Ref_Var; end';
EXECUTE IMMEDIATE Stmt USING OUT Ref_Equip;
V_nom_table := 'EVV_'||Ref_Equip;
stmt :='insert into' ||V_nom_table || '(:New.Clef_Var, :New.Ev_DateAuto, :New.Ev_Valeur )';
EXECUTE IMMEDIATE stmt USING Ref_Equip;
ELSE
INSERT INTO SIVO.EV_48H_VAR_INCONNUES (REF_VAR, EV_DATE, EV_HEURE, EV_VALEUR)
VALUES ( :New.REF_VAR, :New.EV_DATE, :New.EV_HEURE, :New.EV_VALEUR);
end if;
END;
If someone can help me or put me on the right way. I don't know if I give all informations so tell me if I missed something.
Thanks
In your execute immediate you are missing the end of statement indicator colon after END
this should be
begin select clef_var into :New.Clef_Var From sivo.C_variable; end;
However there are other design choices that you should be aware of:
using execute immediate is handy but if you don't have to use it you shouldn't. The work can be done by a cursor or even a simple select statement if only one value will come back. In fact it appears you do the work twice. First you insert into the :new.clef_var then you do the same thing again with the execute immediate. Try commenting out the execute immediate.
by using execute immediate any errors are harder to track
using a trigger means the real time data source cannot end the transaction until the trigger completes. Why not run a scheduled job every minute to check for new data and process it? This breaks the transaction into two parts: data entry and data processing
is there any update of records that your process needs to capture?

Oracle procedure stops running

I have an Oracle stored procedure that is called from a .NET application. It runs for over 15 minutes. It is very simple code that inserts into a big table from a smaller table, then clears the smaller table.
My issue is that it runs for like 15 minutes, then it dies. No error messages in the log. Nothing. Just stops running. I have done a SQL trace, no evidence of an issue. Sometimes it works, sometimes it doesn't.
Some consistencies exist - when it does fail, it never gets to the Data Moved Successfully or Exception section. Just appears to stop running.
PROCEDURE C_X_AllocInsertData(
p_AccountTable VARCHAR2,
p_AllocationTable VARCHAR2,
p_YearMonth NUMBER,
p_versionPK NUMBER,
p_returncode OUT NUMBER)
AS PRAGMA AUTONOMOUS_TRANSACTION;
v_OutputTableRowCntSql varchar(32767);
BEGIN
p_returncode:=0;
C_X_VERSIONINSERTLOG(
p_VersionPk
,'Now moving the data from temp table to big table'
,'Now moving the data from temp table to big table'
);
--Now that all operations are complete, delete from account table
v_OutputTableRowCntSql := 'BEGIN delete from '||p_AccountTable||' where YEARMONTH= '||p_YearMonth||'; ';
--Then insert from the TMP table
v_OutputTableRowCntSql := v_OutputTableRowCntSql || 'insert into '||p_AccountTable|| ' SELECT * FROM ' || p_AllocationTable||'; ';
--Then clear the temp table
v_OutputTableRowCntSql := v_OutputTableRowCntSql || 'delete from '||p_AllocationTable||'; END;';
EXECUTE IMMEDIATE v_OutputTableRowCntSql;
C_X_VERSIONINSERTLOG(
p_VersionPk
,'Data moved successfully.'
,'Data moved successfully.'
);
commit;
EXCEPTION
WHEN OTHERS THEN
p_returncode:=-1;
C_X_VERSIONINSERTLOG(
p_VersionPk
,'Insert into big table failed for version #'||p_versionPK||'... Error message was: '||SQLERRM
,'Insert into big table failed. Error message was: '||SQLERRM||' Statement was: '||v_OutputTableRowCntSql
);
END;
It seems that C_X_VERSIONINSERTLOG procedure lacks appropriate error handing...I will add that. Here it is in its original form...could this be the issue?
PROCEDURE C_X_VERSIONINSERTLOG
(
P_VERSIONPK NUMBER,
P_LOGTEXT VARCHAR2,
P_ERROR_MSG VARCHAR2)
AS PRAGMA AUTONOMOUS_TRANSACTION;
v_VersionLogPk C_X_VERSIONLOG.VersionLogPk%TYPE;
BEGIN
SELECT C_X_VERSIONLOG_SEQ.nextval INTO v_VersionLogPk FROM DUAL;
INSERT
INTO C_X_VERSIONLOG
(
VersionLogPk,
VERSIONPK,
VERSIONLOGTEXT,
VERSIONLOGTECHNICALTEXT,
VERSIONLOGDATE
)
VALUES
(
v_VersionLogPk,
P_VERSIONPK,
P_LOGTEXT,
P_ERROR_MSG ,
CURRENT_TIMESTAMP
);
COMMIT;
END C_X_VERSIONINSERTLOG;

Delphi - Query running slow

My query,when run takes about 7 seconds to do what is supposed to.But,since its inserting about 30 records,I think it is too slow.Now,either I am running the query that is not written well or it does actually takes this much time. But that would be strange. The underlying database is SQLite and the query looks like this :
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do begin
Close;
SQL.Clear;
UNIQuery1.First;
while Uniquery1.EOF = false do begin
SQL.Text:= 'INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) VALUES (:a1,:a2,:a3,:a4)';
ParamByName('a1').asString := AdvOfficeStatusBar1.Panels[0].Text;
ParamByName('a2').asString := UniTable1.FieldByName('FIELD2').asString;
ParamByName('a3').asString := Uniquery1.FieldByName(',FIELD3').asString;
ParamByName('a4').Value := Uniquery1.FieldByName('FIELD4').Value;//boolean field true/false
Uniquery1.Next;
ExecSQL;
end;
end;
end;
So can someone tell me if this is OK or am I missing something ?
All fields are text except the 'a4' which is boolean (true/false).
The answer,modified (based on suuggestion from LS_dev):
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) VALUES (:a1,:a2,:a3,:a4)');
SQL.Prepare;
UniTransaction.AddConnection(UniConnection2);
UniTransaction.StartTransaction;
try
UNIQuery1.First;
while Uniquery1.EOF = false do begin
Params[0].asString := AdvOfficeStatusBar1.Panels[0].Text;
Params[1].asString := UniTable1.FieldByName('FIELD2').asString;
Params[2].asString := Uniquery1.FieldByName(',FIELD3').asString;
Params[3].Value := Uniquery1.FieldByName('FIELD4').Value;//boolean field true/false
Uniquery1.Next;
ExecSQL;
end;
UniTransaction.Commit;
finally
if UNIquery2.Connection.InTransaction then
UNIquery2.Connection.Rollback;
end;
end;
end;
Don't know Delphi, but will suggest some improvements:
You are not using a transaction. You should have something like something like auto-commit disabled and COMMIT command after all insertions;
Your SQL.Text:=... should probably be out of while. If this property set compiles SQL statement, putting it out of while will prevent unnecessary VDBE compilations;
If your intent is copying rows from one table to another (with a static field), you may doing using a single SQL command like INSERT INTO MYTABLE SELECT :a1, FIELD2, FIEDL3, FIELD4 FROM source_table, setting ParamByName('a1').asString := AdvOfficeStatusBar1.Panels[0].Text
This is generic DB usage improvement, hope gives you some direction.
Suggestion using unique SQL:
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do
begin
SQL.Clear;
SQL.Add('INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) SELECT ?,FIELD2,FIELD3,FIELD4 FROM UNIquery1_source_table');
Params[0].asString := AdvOfficeStatusBar1.Panels[0].Text;
ExecSQL;
end;
end;
Suggestion using improved DB handling:
procedure TForm1.cxButton1Click(Sender: TObject);
begin
with UNIquery2 do
begin
Close;
SQL.Clear;
SQL.Add('INSERT INTO MYTABLE (FIELD1,FIELD2,FIELD3,FIELD4) VALUES (:a1,:a2,:a3,:a4)');
SQL.Prepare;
UniTransaction.AddConnection(UniConnection2);
UniTransaction.StartTransaction;
UNIQuery1.First;
while Uniquery1.EOF = false do
begin
Params[0].asString := AdvOfficeStatusBar1.Panels[0].Text;
Params[1].asString := UniTable1.FieldByName('FIELD2').asString;
Params[2].asString := Uniquery1.FieldByName(',FIELD3').asString;
Params[3].Value := Uniquery1.FieldByName('FIELD4').Value;//boolean field true/false
Uniquery1.Next;
ExecSQL;
end;
UniTransaction.Commit;
end;
end;
If the SQL INSERT itself is slow, I recommend to test its execution speed in an interactive client first. Or write a simple test application which performs one hard-coded INSERT and measures its execution time.
Also you can use the debugger, logging or a profiler to find out the operation in your code which consumes time - it could be the Uniquery1.Next or the ExecSQL for example.

cannot perform DML operation inside a query

I cannot convince why I can't add DML operation inside Oracle Function especially inside cursor loop. I feel Oracle don't support DML operation inside cursor loop.
How can I do If I need to insert into table inside cursor loop? Create new store procedure inside it or something else?
Error Message : cannot perform DML operation inside a query
Here is my function,
CREATE OR REPLACE FUNCTION TEST_FUNC(U_ID IN VARCHAR2)
RETURN VARCHAR2
IS
V_MESSAGE VARCHAR2(30);
CURSOR C_PERSON (V_ID VARCHAR2) IS
SELECT NAME_UPPER
FROM TBL_PERSON
WHERE NAME_UPPER = V_ID;
BEGIN
FOR C_PERSON_CURSOR IN C_PERSON(U_ID)
LOOP
INSERT INTO TMP_PERSON(NAME) VALUES (C_PERSON_CURSOR.NAME_UPPER);
END LOOP;
RETURN V_MESSAGE;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END;
You can use DML inside a PL/SQL function - no problem. However, the function can only be called from PL/SQL, not from SQL - i.e. it can be called like this:
declare
l_message varchar2(30);
begin
l_message := test_func('123');
end;
... but not like this:
select test_func(empno) from emp;
That leads to the error message you posted.
Many people (including me) don't like functions that have "side effects" like this, but that is a matter of best practice and standards, not a technical issue.
You can perform DML operations inside an Oracle PL/SQL function and, although this is generally not a good practice, call it from SQL. The function has to be marked with a pragma AUTONOMOUS_TRANSACTION and the transaction has to be committed or rolled back before exiting the function (see AUTONOMOUS_TRANSACTION Pragma).
You should be aware that this kind of function called from SQL can dramatically degrade your queries performances. I recommend you use it only for audit purposes.
Here is an example script starting from your function:
CREATE TABLE TBL_PERSON (NAME_UPPER VARCHAR2(30));
CREATE TABLE TMP_PERSON (NAME VARCHAR2(30));
INSERT INTO TBL_PERSON (NAME_UPPER) VALUES ('KING');
CREATE OR REPLACE FUNCTION TEST_FUNC(U_ID IN VARCHAR2)
RETURN VARCHAR2
IS
PRAGMA AUTONOMOUS_TRANSACTION; -- Needed to be called from SQL
V_MESSAGE VARCHAR2(2000);
CURSOR C_PERSON (V_ID VARCHAR2) IS
SELECT NAME_UPPER
FROM TBL_PERSON
WHERE NAME_UPPER = V_ID;
BEGIN
FOR C_PERSON_CURSOR IN C_PERSON(U_ID)
LOOP
INSERT INTO TMP_PERSON(NAME) VALUES (C_PERSON_CURSOR.NAME_UPPER);
V_MESSAGE := SQL%ROWCOUNT
|| ' Person record successfully inserted into TMP_PERSON table';
END LOOP;
COMMIT; -- The current autonomous transaction need to be commited
-- before exiting the function.
RETURN V_MESSAGE;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END;
/
PROMPT Call the TEST_FUNC function and insert a new record into TMP_PERSON table
SELECT TEST_FUNC('KING') FROM DUAL;
PROMPT Content of the TMP_PERSON table
COL NAME FOR A30
SELECT * FROM TMP_PERSON;
When running the previous script we get the following output:
Table created.
Table created.
1 row created.
Function created.
Calling the TEST_FUNC function and insert a new record into TMP_PERSON table
TEST_FUNC('KING')
------------------------------------------------------------
1 Person record successfully inserted into TMP_PERSON table
Content of the TMP_PERSON table
NAME
------------------------------
KING

Execute Immediate within a stored procedure keeps giving insufficient priviliges error

Here is the definition of the stored procedure:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR) IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Here is the call:
CALL usp_dropTable('SOMESCHEMA', 'SOME_TABLE');
For some reason, I keep getting insufficient privileges error for the EXECUTE IMMEDIATE command. I looked online and found out that the insufficient privileges error usually means the oracle user account does not have privileges for the command used in the query that is passes, which in this case is DROP. However, I have drop privileges. I am really confused and I can't seem to find a solution that works for me.
Thanks to you in advance.
SOLUTION:
As Steve mentioned below, Oracle security model is weird in that it needs to know explicitly somewhere in the procedure what kind of privileges to use. The way to let Oracle know that is to use AUTHID keyword in the CREATE OR REPLACE statement. If you want the same level of privileges as the creator of the procedure, you use AUTHID DEFINER. If you want Oracle to use the privileges of the user currently running the stored procedure, you want to use AUTHID CURRENT_USER. The procedure declaration looks as follows:
CREATE OR REPLACE PROCEDURE usp_dropTable(schema VARCHAR, tblToDrop VARCHAR)
AUTHID CURRENT_USER IS
BEGIN
DECLARE v_cnt NUMBER;
BEGIN
SELECT COUNT(*)
INTO v_cnt
FROM all_tables
WHERE owner = schema
AND table_name = tblToDrop;
IF v_cnt > 0 THEN
EXECUTE IMMEDIATE('DROP TABLE someschema.some_table PURGE');
END IF;
END;
END;
Thank you everyone for responding. This was definitely very annoying problem to get to the solution.
Oracle's security model is such that when executing dynamic SQL using Execute Immediate (inside the context of a PL/SQL block or procedure), the user does not have privileges to objects or commands that are granted via role membership. Your user likely has "DBA" role or something similar. You must explicitly grant "drop table" permissions to this user. The same would apply if you were trying to select from tables in another schema (such as sys or system) - you would need to grant explicit SELECT privileges on that table to this user.
You should use this example with AUTHID CURRENT_USER :
CREATE OR REPLACE PROCEDURE Create_sequence_for_tab (VAR_TAB_NAME IN VARCHAR2)
AUTHID CURRENT_USER
IS
SEQ_NAME VARCHAR2 (100);
FINAL_QUERY VARCHAR2 (100);
COUNT_NUMBER NUMBER := 0;
cur_id NUMBER;
BEGIN
SEQ_NAME := 'SEQ_' || VAR_TAB_NAME;
SELECT COUNT (*)
INTO COUNT_NUMBER
FROM USER_SEQUENCES
WHERE SEQUENCE_NAME = SEQ_NAME;
DBMS_OUTPUT.PUT_LINE (SEQ_NAME || '>' || COUNT_NUMBER);
IF COUNT_NUMBER = 0
THEN
--DBMS_OUTPUT.PUT_LINE('DROP SEQUENCE ' || SEQ_NAME);
-- EXECUTE IMMEDIATE 'DROP SEQUENCE ' || SEQ_NAME;
-- ELSE
SELECT 'CREATE SEQUENCE COMPTABILITE.' || SEQ_NAME || ' START WITH ' || ROUND (DBMS_RANDOM.VALUE (100000000000, 999999999999), 0) || ' INCREMENT BY 1'
INTO FINAL_QUERY
FROM DUAL;
DBMS_OUTPUT.PUT_LINE (FINAL_QUERY);
cur_id := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.parse (cur_id, FINAL_QUERY, DBMS_SQL.v7);
DBMS_SQL.CLOSE_CURSOR (cur_id);
-- EXECUTE IMMEDIATE FINAL_QUERY;
END IF;
COMMIT;
END;
/
you could use "AUTHID CURRENT_USER" in body of your procedure definition for your requirements.
Alternatively you can grant the user DROP_ANY_TABLE privilege if need be and the procedure will run as is without the need for any alteration. Dangerous maybe but depends what you're doing :)