Oracle logoff trigger to get sql's executed by session - oracle

I have created a table which contains the host names of all the trusted sources. I have written a oracle log off trigger to fetch details of all the sql executed by that session if the connection's host is not amongst the snif_session table. I am taking the output to a utl_file output which contains the sid, hostname, time connected.
SQL> select * from snif_Session;
ALLOWED_HOST
--------------------------------------------------
RND1
WORKGROUP\RND1
The place where i am getting stuck is what query to use to get all the sql's executed by that particular session ( i can get the sid from v$mystat).
does this work best :
select a.sql_id
,b.sql_text
from dba_hist_active_sess_history a
,dba_hist_sqltext b
where a.sql_id=b.sql_id
or
select s.sid
, s.serial#
, a.sql_text
from v$session s
join v$sqlarea a
on a.hash_value = s.sql_hash_value ;
This is the code i have written (block) which i will be placing inside a trigger.
declare
machine_id varchar2(50);
val int;
auth_terminal varchar2(50);
check_machine varchar2(1000);
mydate char(50);
osuser_1 varchar2(50);
sid_1 int;
sql_query_1 varchar2(5000);
machine_1 varchar2(50);
trace_info UTL_FILE.FILE_TYPE;
begin
select machine into check_machine
from v$session
where sid in (select distinct(sid) from v$mystat) ;
select count(*) into val
from snif_session
where allowed_host=check_machine;
if ( 1=val) then
dbms_output.put_line(check_machine|| ' dont check host' );
else
dbms_output.put_line(check_machine || ' check host' );
end if;
select osuser,sid,machine
into osuser_1,sid_1,machine_1
from v$session
where sid in (select distinct(sid) from v$mystat);
SELECT TO_char(systimestamp,'mm/dd/yyyy HH24:MI:SS') into mydate
FROM DUAL;
dbms_output.put_line(mydate || sid_1 || ' ' || osuser_1 || ' '|| machine_1);
trace_info := UTL_FILE.FOPEN('UTL_DIR', 'trace_info_file.txt', 'W');
UTL_FILE.PUTF(trace_info,mydate||' '||sid_1||' '||osuser_1||' '|| machine_1);
UTL_FILE.FCLOSE(trace_info);
EXCEPTION
WHEN utl_file.invalid_path THEN
raise_application_error(-20000, 'ERROR: Invalid PATH FOR file.');
end;
I need to include the 'sql queries' also executed by session in the utl_file output.

"I need to include the 'sql queries' also executed by session"
Neither of your suggested queries will give you all the SQL executed by a session.
V$SESSION is a dynamic view, so it just shows what is happening in a session right now.
DBA_HIST_ACTIVE_SESS_HISTORY is a series of snapshots of running SQL. It is intended for performance profiling, and as such it is basically a random sub-set of active statements. Also, it is part of the Diagnostics and Tuning Pack: you will be in breach of your license if you use it without paying the additional charge.
It appears that what you really need is an audit trail. Instead of rolling your own, why not investigate the functionality Oracle already has? There's AUDIT to track DDL activity. There's Fine-grained Auditing to monitor lower-level DML. Find out more.

Related

How to delete sequences and procedures during logoff trigger?

Could you please help me in a unique situation I am in. I am receiving "ORA-30511: invalid DDL operation in system triggers" when dropping sequences and procedures during logoff trigger.
I need to delete tables, sequences and procedures of users before logoff event happens. I am writing the table details in DB_OBJECTS table upon create using a separate trigger. Below is my logoff trigger - could you please help me where I am doing wrong. Dropping tables is working fine in the below code. Only Dropping sequences and procedures is giving me "ORA-30511: invalid DDL operation in system triggers" error.
CREATE OR REPLACE TRIGGER DELETE_BEFORE_LOGOFF
BEFORE LOGOFF ON DATABASE
DECLARE
USER_ID NUMBER := SYS_CONTEXT('USERENV', 'SESSIONID');
BEGIN
FOR O IN (SELECT USER, OBJECT_NAME, OBJECT_TYPE
FROM DB_OBJECTS WHERE SID = USER_ID
AND USERNAME = USER AND SYSDATE > CREATED_DTTM) LOOP
IF O.OBJECT_TYPE = 'TABLE' THEN
EXECUTE IMMEDIATE 'DROP TABLE ' || O.USER || '.' || O.OBJECT_NAME || ' CASCADE CONSTRAINTS';
ELSIF O.OBJECT_TYPE = 'SEQUENCE' THEN
EXECUTE IMMEDIATE 'DROP SEQUENCE ' || O.USER || '.' || O.OBJECT_NAME;
ELSIF O.OBJECT_TYPE = 'PROCEDURE' THEN
EXECUTE IMMEDIATE 'DROP PROCEDURE ' || O.USER || '.' || O.OBJECT_NAME;
END IF;
END LOOP;
EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
END;
/
That's a simple one.
Error code: ORA-30511
Description: invalid DDL operation in system triggers
Cause: An attempt was made to perform an invalid DDL operation in a system trigger. Most DDL operations currently are not supported in system triggers. The only currently supported DDL operations are table operations and ALTER/COMPILE operations.
Action: Remove invalid DDL operations in system triggers.
That's why only
Dropping tables is working fine
succeeded.
Therefore, you can't do that using trigger.
You asked (in a comment) how to drop these objects, then. Manually, as far as I can tell. Though, that's quite unusual - what if someone accidentally logs off? You'd drop everything they created. If you use that schema for educational purposes (for example, every student gets their own schema), then you could create a "clean-up" script you'd run once class is over. Something like this:
SET SERVEROUTPUT ON;
DECLARE
l_user VARCHAR2 (30) := 'SCOTT';
l_str VARCHAR2 (200);
BEGIN
IF USER = l_user
THEN
FOR cur_r IN (SELECT object_name, object_type
FROM user_objects
WHERE object_name NOT IN ('EMP',
'DEPT',
'BONUS',
'SALGRADE'))
LOOP
BEGIN
l_str :=
'drop '
|| cur_r.object_type
|| ' "'
|| cur_r.object_name
|| '"';
DBMS_OUTPUT.put_line (l_str);
EXECUTE IMMEDIATE l_str;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
END LOOP;
END IF;
END;
/
PURGE RECYCLEBIN;
It is far from being perfect; I use it to clean up my Scott schema I use to answer questions on various sites so - once it becomes a mess, I run that PL/SQL code several times (because of possible foreign key constraint).
Other option is to keep a create user script(s) (along with all grant statements) and - once class is over - drop existing user and simply recreate it.
Or, if that user contains some pre-built tables, keep export file (I mean, result of data pump export) and import it after the user is dropped.
There are various options - I don't know whether I managed to guess correctly, but now you have something to think about.

How to create an alias from an select result in oracle?

I want to create an alias from an select result.I've tried to use oracle dynamic query but doesn't working.Here is my sql:
declare
v_a varchar2(50);
sql_smt varchar2(200);
begin
select TO_CHAR('#'||TO_CHAR(sysdate-30,'yyyymmdd')||'-'||TO_CHAR(sysdate+2,'yyyymmdd')) INTO v_a from dual;
sql_smt :='SELECT sysdate as :1 FROM dual';
execute immediate sql_smt using v_a;
end;
I want to reach to an result like in the photo.
Thanks for any help!
As you've tagged this for SQL Developer you could use (ab)use substitution variables for this; in your worksheet do:
column x_title new_value y_title noprint;
select TO_CHAR('#'||TO_CHAR(sysdate-30,'yyyymmdd')||'-'||TO_CHAR(sysdate+2,'yyyymmdd'))
as x_title
from dual;
set verify off
select sysdate as "&y_title" from dual;
which when you run as a script (F5) produces this in the Script Output window:
<blank lines ...>
#20190126-
----------
2019-02-25
and then if you run the last line again as a statement (control-enter) the Query Result window shows it as you wanted, from the image in your question.
You could also use the column command which makes the output in the Script Output closer to what you wanted when run as a script:
column sysdate heading &y_title
select sysdate from dual;
#20190126-20190227
------------------
2019-02-25
but then when run as a statement the Query Results window doesn't honour that heading.
Note that this is all client-specific functionality, not SQL - it will work in SQL Developer, and the script versions will work in SQL*Plus and SQLcl, but not in other clients (unless they have tried to feature-match SQL*Plus to some extent).
If you aren't only going to be viewing the results in one of those clients but actually want to end up, say, pulling them into an application over JDBC or whatever, then other solutions would be more appropriate. Generating a ref cursor with the column as the name you want would be fairly simple. But that's not what you've asked for...
OK, since you asked in a comment, you can open a ref cursor in an anonymous block:
var rc refcursor
declare
l_alias varchar2(50);
begin
select TO_CHAR('#'||TO_CHAR(sysdate-30,'yyyymmdd')||'-'||TO_CHAR(sysdate+2,'yyyymmdd'))
into l_alias
from dual;
open :rc for 'SELECT sysdate as "' || l_alias || '" FROM dual';
end;
/
PL/SQL procedure successfully completed.
print rc
#20190127-
----------
2019-02-26
or without the local variable:
var rc refcursor
begin
open :rc for 'SELECT sysdate as "'
|| TO_CHAR('#'||TO_CHAR(sysdate-30,'yyyymmdd')||'-'||TO_CHAR(sysdate+2,'yyyymmdd'))
|| '" FROM dual';
end;
/
PL/SQL procedure successfully completed.
print rc;
#20190127-
----------
2019-02-26
Again var[iable] and print are client-specific commands; and I don't think there's a way to get the results in the Query Results grid with this approach. But you can use the same anonymous block approach from other clients or applications; e.g. from JDBC you could have a statement as:
String sql = "begin open ? for 'SELECT sysdate as \"'"
+ "|| TO_CHAR('#'||TO_CHAR(sysdate-30,'yyyymmdd')||'-'||TO_CHAR(sysdate+2,'yyyymmdd'))"
+ "|| '\" FROM dual'; end;";
and then bind the ? parameter placeholder as a cursor type before executing. That probably isn't very useful though as you'd have to examine the metadata to find the column alias anyway, and you could generate it on the application side using the application language tools (e..g Java date manipulation).
You could also create a function that returns a ref cursor, which you could then call from a plain query (instead of an anonymous block), which would allow you to see it in the Query Results grid - possibly with an extra step to show the cursor result. But you made not need that extra level of code or want another object to be created in your DB.
Would something like this work?
declare
col_hdr varchar2(50);
sys_date varchar2(26);
sql_smt varchar2(200);
begin
select TO_CHAR('#'||TO_CHAR(sysdate-30,'yyyymmdd')||'-'||TO_CHAR(sysdate+2,'yyyymmdd')) INTO col_hdr from dual;
sql_smt :='SELECT sysdate ' || ' as "' || col_hdr || '" FROM dual';
dbms_output.put_line(sql_smt);
execute immediate sql_smt into sys_date;
dbms_output.put_line('done');
end;

Oracle manually update statistics on all tables

Is there a way to update all statistics for all tables regardless of the owner?
I found this sniplet, but I'm not sure if this will grab all tables....
BEGIN
FOR A IN ( SELECT owner FROM SYS.all_tables ) LOOP
execute immediate
EXEC dbms_stats.gather_schema_stats( 'A.owner', cascade='TRUE');
END LOOP;
END;
Use DBMS_STATS.GATHER_DATABASE_STATS:
begin
dbms_stats.gather_database_stats;
end;
/
No the DBMS_STATS package can do at most one schema at a time.
You can use the script below to gather stats for all objects types in all schemas. The one you listed has a couple of issues (needless execute immediate, `A.owner' is a string but it should be an object, etc).
You can add additional schemas to skip in the IN list as you probably don't want to do this for the built in schemas (they're mostly static anyway so it'd be waste). Also, you'll need to have the appropriate privileges for each schema you are gathering stats on (or be logged in as a DBA).
Gather stats on all objects (probably what you really want):
BEGIN
FOR rec IN (SELECT *
FROM all_users
WHERE username NOT IN ('SYS','SYSDBA'))
LOOP
dbms_stats.gather_schema_stats(rec.username);
END LOOP;
END;
Gather stats on just tables:
BEGIN
FOR rec IN (SELECT *
FROM all_tables
WHERE owner NOT IN ('SYS','SYSDBA'))
LOOP
dbms_stats.gather_table_stats(rec.owner, rec.table_name);
END LOOP;
END;
I did a modification to #sehrope procedure to skip locked stats and IOT tables as they will through exceptions.
BEGIN
FOR rec IN (SELECT a.owner, a.table_name
FROM all_tables a, dba_tab_statistics s
WHERE a.owner NOT IN ('SYS','SYSDBA')
AND
(a.iot_type IS NULL
OR
a.iot_type != 'IOT_OVERFLOW')
and a.owner = s.owner and a.table_name = s.table_name and s.STATTYPE_LOCKED is null)
LOOP
dbms_stats.gather_table_stats(rec.owner, rec.table_name);
END LOOP;
END;

Dropping connected users in Oracle database

I want to drop some users in Oracle DB using sqlplus but I am getting error:
SQL> DROP USER test CASCADE;
DROP USER test CASCADE
*
ERROR at line 1:
ORA-01940: cannot drop a user that is currently connected
I followed the link in SO to find out the sessions - Dropping a connected user from an Oracle 10g database schema
But when I ran the command I am not getting any results:
SQL> select sid,serial# from v$session where username = 'test';
no rows selected
Please help me how to drop users in this case.
Users are all capitals in v$session (and data dictionary views). If you match with capitals you should find your session to kill.
SELECT s.sid, s.serial#, s.status, p.spid
FROM v$session s, v$process p
WHERE s.username = 'TEST' --<<<--
AND p.addr(+) = s.paddr
/
Pass actual SID and SERIAL# values for user TEST then drop user...:
ALTER SYSTEM KILL SESSION '<SID>, <SERIAL>'
/
Solution :
login as sysdaba:
sqlplus / as sysdba
then:
sql>Shutdown immediate;
sql>startup restrict;
sql>drop user TEST cascade;
If you want to re-activate DB normally either reset the server or :
sql>Shutdown immediate;
sql>startup;
:)
Issue has been fixed using below procedure :
DECLARE
v_user_exists NUMBER;
user_name CONSTANT varchar2(20) := 'SCOTT';
BEGIN
LOOP
FOR c IN (SELECT s.sid, s.serial# FROM v$session s WHERE upper(s.username) = user_name)
LOOP
EXECUTE IMMEDIATE
'alter system kill session ''' || c.sid || ',' || c.serial# || ''' IMMEDIATE';
END LOOP;
BEGIN
EXECUTE IMMEDIATE 'drop user ' || user_name || ' cascade';
EXCEPTION WHEN OTHERS THEN
IF (SQLCODE = -1940) THEN
NULL;
ELSE
RAISE;
END IF;
END;
BEGIN
SELECT COUNT(*) INTO v_user_exists FROM dba_users WHERE username = user_name;
EXIT WHEN v_user_exists = 0;
END;
END LOOP;
END;
/
Do a query:
SELECT * FROM v$session s;
Find your user and do the next query (with appropriate parameters):
ALTER SYSTEM KILL SESSION '<SID>, <SERIAL>';
If you use RAC then you need to use GV$* views instead V$*.
Try to find your session by
select * from gv$session where username = 'test';
and then you can kill the session by
alter system kill session 'sid, serial#, #inst_id' immediate;
This can be as simple as:
SQL> ALTER SYSTEM ENABLE RESTRICTED SESSION;
SQL> DROP USER test CASCADE;
SQL> ALTER SYSTEM DISABLE RESTRICTED SESSION;
go to services in administrative tools and select oracleserviceSID and restart it
I was trying to follow the flow described here - but haven't luck to completely kill the session.. Then I fond additional step here:
http://wyding.blogspot.com/2013/08/solution-for-ora-01940-cannot-drop-user.html
What I did:
1. select 'alter system kill session ''' || sid || ',' || serial# || ''';' from v$session where username = '<your_schema>'; - as described below.Out put will be something like this:alter system kill session '22,15' immediate;
2. alter system disconnect session '22,15' IMMEDIATE ; - 22-sid, 15-serial - repeat the command for each returned session from previous command
3. Repeat steps 1-2 while select... not return an empty table
4. Call
drop user...
What was missed - call alter system disconnect session '22,15' IMMEDIATE ; for each of session returned by select 'alter system kill session '..
Sometimes Oracle drop user takes long time to execute. In that case user might be connected to the database. Better you can kill user session and drop the user.
SQL> select 'alter system kill session ''' || sid || ',' || serial# || ''' immediate;' from v$session where username ='&USERNAME';
SQL> DROP USER barbie CASCADE;
I had the same problem, Oracle config in default affects letter register. In exact my Scheme_Name was written all Capital letters. You can see your Scheme_Name on "Other Users" tab, if you are using Oracle S
Basically I believe that killing all sessions should be the solution, but...
I found similar discussion - https://community.oracle.com/thread/1054062 to my problem and that was I had no sessions for that users, but I still received the error. I tried also second the best answer:
sql>Shutdown immediate;
sql>startup restrict;
sql>drop user TEST cascade;
What worked for me at the end was to login as the user, drop all tables manually - select for creating drop statements is
select 'drop table ' || TABLE_NAME || ';' from user_tables;
(Needs to be re-run several times because of references)
I have no idea how is that related, I dropped also functions and sequences (because that was all I had in schema)
When I did that and I logged off, I had several sessions in v$session table and when I killed those I was able to drop user.
My DB was still started in restricted mode (not sure if important or not).
Might help someone else.
BTW: my Oracle version is Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
//'SYS' is username from where you wanted to kill session'
SELECT * FROM DBA_TAB_PRIVS WHERE GRANTEE = 'SYS';
**Step 1:**
CREATE OR REPLACE PROCEDURE sys.kill_session(p_sid NUMBER, p_serial NUMBER)
AS
v_user VARCHAR2(30);
BEGIN
SELECT MAX(username)
INTO v_user
FROM v$session
WHERE sid = p_sid
AND serial# = p_serial;
**Step 2**
create or replace procedure kill_session( p_sid in number, p_serial# in number)
is v_count pls_integer;
BEGIN
select count(*) into v_count
from V$session
where username = 'SYS'
and sid = p_sid
and serial# = p_serial# ;
if ( v_count = 1 )
then
execute immediate '
alter system kill session ''' ||
to_char(p_sid,'999999')||','||
to_char(p_serial#,'999999')||'''';
else
raise_application_error( -20001,
'You do not own session ''' ||
p_sid || ',' || p_serial# ||
'''' );
end if;
END;
/
**Step 3**
grant execute on kill_session to SYS;
**Step 4**
select inst_id, sid, serial#, username, action, program, service_name, con_id from gv$session where username like 'FCM_469';
Check there will be no sessions now
**Step 5**
DROP USER USER_345 CASCADE;
Output:User Dropped
Here's how I "automate" Dropping connected users in Oracle database:
# A shell script to Drop a Database Schema, forcing off any Connected Sessions (for example, before an Import)
# Warning! With great power comes great responsibility.
# It is often advisable to take an Export before Dropping a Schema
if [ "$1" = "" ]
then
echo "Which Schema?"
read schema
else
echo "Are you sure? (y/n)"
read reply
[ ! $reply = y ] && return 1
schema=$1
fi
sqlplus / as sysdba <<EOF
set echo on
alter user $schema account lock;
-- Exterminate all sessions!
begin
for x in ( select sid, serial# from v\$session where username=upper('$schema') )
loop
execute immediate ( 'alter system kill session '''|| x.Sid || ',' || x.Serial# || ''' immediate' );
end loop;
dbms_lock.sleep( seconds => 2 ); -- Prevent ORA-01940: cannot drop a user that is currently connected
end;
/
drop user $schema cascade;
quit
EOF

How to get the last executed SQL statement and bind variable values in oracle

I have written the following query to get the last executed SQL statement in the oracle database for a particular session. The SQL text does not contain the actual value of the bind variables. How to get the bind variable values along with the SQL text.
SELECT * FROM v$SQLTEXT_WITH_NEWLINES WHERE address =
(SELECT prev_sql_addr FROM v$session WHERE audsid = userenv('SESSIONID'))
ORDER BY piece;
To get the bind variables you will have to use the code below, you dont need to use tracing.
SELECT * FROM v$sql_bind_capture WHERE sql_id='';
or
SELECT NAME,POSITION,DATATYPE_STRING,VALUE_STRING
FROM v$sql_bind_capture WHERE sql_id='';
http://shaharear.blogspot.com/2009/02/find-bind-variable-value.html
I don't think the bind variables values are stored by default. Not considering the potential security problems (seeing other sessions actual work), the amount of data to store would be massive.
If you want to see the values of the bind variables, you should activate the trace for that session. You would do this by executing the following command in that session:
alter session set events '10046 trace name context forever, level 12';
More information on AskTom: 10046 tracing
if you are in sqlplus you can execute
select * from table
( dbms_xplan.display_cursor (null,null, 'ADVANCED'));
or if you are looking for SQL executed by someone else just put in their the SQL_ID and child cursor #:
select * from table
( dbms_xplan.display_cursor ('sql_id',child_cursor#, 'ADVANCED'));
as in
select * from table
( dbms_xplan.display_cursor ('a18asdr99x',0, 'ADVANCED'));
This method shows the only shows peeked bind variables. The only dependable way is tracing with bind variables
dbms_monitor.session_trace_enable(session_id => 127,
serial_num => 29,
waits => FALSE,
binds => TRUE)
but of course that has to be done before the query gets executed
Run the below query which takes the sql_id as the input parameter and will give the output with replaced bind variable values.
set serveroutput on;
DECLARE
v_fulltext CLOB;
v_sql_id VARCHAR2 (100);
CURSOR c1( v_sql_id varchar2)
IS
SELECT decode(substr(NAME,1,4),':SYS',replace(name,':',':"')||'"' ,NAME ) NAME, POSITION, datatype_string,nvl(VALUE_STRING,'NULL') value_string
FROM v$sql_bind_capture
WHERE sql_id = v_sql_id;
BEGIN
v_sql_id:= '&sql_id';
SELECT sql_fulltext
INTO v_fulltext
FROM v$sql
WHERE sql_id =v_sql_id AND ROWNUM = 1;
FOR rec IN c1(v_sql_id)
LOOP
IF substr(rec.datatype_string,1,8) = 'VARCHAR2'
THEN
SELECT REPLACE (v_fulltext,
rec.NAME,
'''' || rec.value_string || ''''
)
INTO v_fulltext
FROM DUAL;
END IF;
IF rec.datatype_string = 'NUMBER'
THEN
SELECT REPLACE (v_fulltext, rec.NAME, rec.value_string)
INTO v_fulltext
FROM DUAL;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_fulltext);
EXCEPTION
WHEN NO_DATA_FOUND
THEN DBMS_OUTPUT.PUT_LINE('NO SQL FOUND FOR THE SQL ID');
END;
/
Looking at BiPin's answer I modified it a bit to suit my needs.
I needed to figure out what parameters users were using when running a report in real time. Here's my solution which adds the childnumber to the query from v$sql_bind_capture.
declare
v_sql_id varchar(100);
v_fulltext clob;
v_childnumber number;
begin
v_sql_id := '&sql_id';
v_childnumber := '&childnumber';
SELECT LISTAGG(SQL_text, '') within group (order by piece)
INTO v_fulltext
FROM v$sqltext
WHERE sql_id =v_sql_id;
for I in (select name,VALUE_STRING from v$sql_bind_capture where sql_id = V_SQL_ID and child_number = V_CHILDNUMBER)LOOP
v_fulltext := regexp_replace(v_fulltext,i.name||' ',i.value_string);
end LOOP;
DBMS_OUTPUT.PUT_LINE(v_fulltext);
end;

Resources