How to select all DML statement done to a table - oracle

With this query, I can see which table where modified last hour.
select * from ALL_TAB_MODIFICATIONS where timestamp> sysdate-1/24
Now I want to know what has been insert, updates, deletes in a table. And if possible with the user id.
How do I do that?
I've tried to use audit.
https://www.ibm.com/docs/en/svgaa?topic=c-enabling-auditing-oracle-resource
in sqlplus
SHOW PARAMETER AUDIT_TRAIL
DB --OK
AUDIT ALL BY SEV BY ACCESS
audit succeeded
I've tried to see the content of all the audit table (SELECT view_name FROM dba_views WHERE view_name LIKE 'DBA%AUDIT%';)
for instance :
select * from dba_audit_exists;
but they are empty

Related

how to track and modify an in-flight oracle query before execution?

My Application is sending below query to Oracle.
SELECT * FROM EMPLOYEE WHERE DATE > (SYSDATE - 1) order by employee_id
I cannot change this query from the application. I am looking for a way for oracle to monitor the queries, change it to below query format and return the result.
SELECT * FROM EMPLOYEE WHERE DATE > (SYSDATE - 1) and Currently_employed = 'YES' order by employee_id
Thank You.
Subrat
If the application can connect to the database as a different user then the table owner, you could also implement a Virtual Private Database policy to modify the query in-flight. See here:
https://oracle-base.com/articles/8i/virtual-private-databases
You could also, if the application is connecting as a separate user, create a view as suggested by #ekochergin, and have a synonym "EMPLOYEE" in the application's user schema point to the view in the data owner's schema.
If the application is connecting as the data owner, your options are much more limited. Renaming the table and replacing it with a view as suggested would be the easiest option. If you've got a lot of money to invest in Oracle's Database Application Firewall appliance you could also modify the query in-flight with a security policy there.
You might needed to rename employee table to somewhat like "EMP_TABLE" and create a view named "EMPLOYEE" using
create view employee as select * from emp_table where currently_employed = 'YES';
Please test it against a test instance before implementing on live
Use the SQL Translation Framework if you only need to convert a small number of statements. If you need to modify many statements then you should look into the options described in the other answers, such as Virtual Private Database, views, or synonyms.
For this sample schema:
create table employee
(
employee_id number,
hire_date date,
currently_employed varchar2(3)
);
insert into employee values(1, sysdate, 'NO');
insert into employee values(1, sysdate, 'YES');
commit;
Create the following translator profile and then create the specific translation:
begin
dbms_sql_translator.create_profile('EMPLOYEE_TRANSLATOR_PROFILE');
dbms_sql_translator.register_sql_translation
(
profile_name => 'EMPLOYEE_TRANSLATOR_PROFILE',
sql_text => q'[SELECT * FROM EMPLOYEE WHERE HIRE_DATE > (SYSDATE - 1) order by employee_id]',
translated_text => q'[SELECT * FROM EMPLOYEE WHERE HIRE_DATE > (SYSDATE - 1) and Currently_employed = 'YES' order by employee_id]'
);
end;
/
The translation profile must be enabled in each session. Since you have no control over the application, you can create a logon profile that will automatically run the commands to enable the translation:
--Logon trigger that enables profiles.
--I'm not sure why, but you must create this trigger as SYS.
create or replace trigger sys.logon_trg
after logon on database
--Add your username here:
when (user in ('JHELLER'))
begin
execute immediate 'alter session set sql_translation_profile = jheller.employee_translator_profile';
execute immediate q'[alter session set events = '10601 trace name context forever, level 32']';
end;
/
Now, when the application runs the original query that would normally return two rows, it will run the second query that only returns one row:
SQL> SELECT * FROM EMPLOYEE WHERE HIRE_DATE > (SYSDATE - 1) order by employee_id;
EMPLOYEE_ID HIRE_DATE CUR
----------- --------- ---
1 12-FEB-21 YES
But be careful of tiny syntax changes that will prevent the translation. For example, if SELECT is changed to select, the query will not be replaced and will return two rows again:
SQL> select * FROM EMPLOYEE WHERE HIRE_DATE > (SYSDATE - 1) order by employee_id;
EMPLOYEE_ID HIRE_DATE CUR
----------- --------- ---
1 12-FEB-21 YES
1 12-FEB-21 NO

How to check if table has specific grants before inserting a record in a table?

Scenario: I have a config table which includes meta-data about log tables (e.g. Table_name, table_owner). I want to add a trigger to this config table which essentially checks if the table record that is to be inserted, has the specific grant (delete) if not, then don't allow that to happen. How can I go about this?
Another way to do it is to forget about triggers but use a VIEW...WITH CHECK OPTION to control that no one inserts data they shouldn't.
Suppose your config table looks like this:
CREATE TABLE my_config
( owner VARCHAR2(30) NOT NULL,
table_name VARCHAR2(30) NOT NULL,
other_stuff VARCHAR2(500),
CONSTRAINT my_config_pk PRIMARY KEY ( owner, table_name ) );
Create a view to handle inserts
The conditions of the view limit it to only include tables on which the current user has DELETE privileges.
The WITH CHECK OPTION will ensure than no one may use the view to insert or update data that does not satisfy the view.
CREATE VIEW my_config_ins_v
AS
SELECT * FROM my_config c
WHERE EXISTS ( SELECT 'user has delete privs'
FROM user_tab_privs p
WHERE p.owner = c.owner
AND p.grantee = user
AND p.table_name = c.table_name
AND p.privilege = 'DELETE' )
WITH CHECK OPTION;
Try it out
INSERT INTO my_config_ins_v VALUES ('USER_1','TABLE_I_HAVE_ACCESS_TO', 'STUFF');
-- 1 row inserted.
INSERT INTO my_config_ins_v VALUES ('SYS','OBJ$', 'STUFF');
-- ORA-01402: view WITH CHECK OPTION where-clause violation
Naturally, for this to be effective, you cannot
GRANT INSERT ON my_config TO anyone; -- don't do this
Instead:
GRANT INSERT ON my_config_ins_v TO anyone;

SELECT AS OF a version before column drop

1/ Does FLASHBACK and SELECT AS OF/ VERSION BETWEEN use the same source of history to fall back to ? This question is related to the second question.
2/ I am aware that FLASHBACK cannot go back before a DDL change.
My question is for SELECT AS OF, would it be able to select something before a DDL change.
Take for example
CREATE TABLE T
(col1 NUMBER, col2 NUMBER)
INSERT INTO T(col1, col2) VALUES('1', '1')
INSERT INTO T(col1, col2) VALUES('2', '2')
COMMIT;
SLEEP(15)
ALTER TABLE T DROP COLUMN col2;
SELECT * FROM T
AS OF SYSTIMESTAMP - INTERVAL '10' SECOND;
Would the select return 2 columns or 1 ?
Pardon me I do not have a database at hand to test.
Any DDL that alter the structure of a table invalidates any existing undo data for the table. So you will get the error 'ORA-01466' unable to read data - table definition has changed.
Here is a simple test
CREATE TABLE T
(col1 NUMBER, col2 NUMBER);
INSERT INTO T(col1, col2) VALUES('1', '1');
INSERT INTO T(col1, col2) VALUES('2', '2');
COMMIT;
SLEEP(15)
ALTER TABLE T DROP COLUMN col2;
SELECT * FROM T
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '60' SECOND);
ERRROR ORA-01466 upon executing the above select statement.
However DDL operations that alter the storage attributes of a table do no invalidate undo data so that you can still use flashback query.
1) FLASHBACK TABLE and SELECT .. AS OF use the same source, UNDO. There is also FLASHBACK DATABASE - although it uses the same mechanism it uses a separate source, flashback logs that must be optionally configured.
2) Flashback table and flashback queries can go back before a DDL change if you enable a flashback archive.
To use that feature, add a few statements to the sample code:
CREATE FLASHBACK ARCHIVE my_flashback_archive TABLESPACE users RETENTION 10 YEAR;
...
ALTER TABLE t FLASHBACK ARCHIVE my_flashback_archive;
Now this statement will return 1 column:
SELECT * FROM T;
And this statement will return 2 columns:
SELECT * FROM T AS OF SYSTIMESTAMP - INTERVAL '10' SECOND;

How to correctly make a public synonym

This is a pretty silly one, but I need help.
I have a table owned by mydbowner. It is named mydbowner.mytable. I tried to make a public synonym by issuing the command:
CREATE OR REPLACE PUBLIC SYNONYM mytable FOR mydbowner.mytable;
When I do this, and I query the table I get:
ORA-01775: looping chain of synonyms
How do I make this synonym without having the problem.
I think Justin is on the right track. What I think it actually means is that mydbowner.mytable doesn't exist.
Here's an example:
SQL> conn mbobak
Enter password:
Connected.
SQL> drop table mytable;
drop table mytable
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> create public synonym mytable for mbobak.mytable;
Synonym created.
SQL> select * from mytable;
select * from mytable
*
ERROR at line 1:
ORA-01775: looping chain of synonyms
I think what's happening is that Oracle tries to resolve mytable, there is no mytable in mbobak schema, so it looks for it in PUBLIC, it finds it, and sees that it points to mbobak.mytable. But, mbobak.mytable doesn't exist, so, it looks for mytable in PUBLIC, and there's the loop.
And in fact, if you create mytable, the error goes away:
SQL> create table mytable as select * from dual;
Table created.
SQL> select * from mytable;
D
-
X
1 row selected.
SQL> drop table mytable;
Table dropped.
SQL> select * from mytable;
select * from mytable
*
ERROR at line 1:
ORA-01775: looping chain of synonyms
Yes, I realize that doesn't really entirely make sense, as, once the public synonym resolved to mbobak.mytable, and that's not found, it seems to me, it should return an error ORA-942 "table or view does not exist", which makes far more sense to me.
But, this does seem to be how it works.
QED
Hope that helps.
The error you're getting implies that mydbowner.mytable is not, in fact a table. What does
SELECT object_type
FROM all_objects
WHERE owner = 'MYDBOWNER'
AND object_name = 'MYTABLE'
return?

How to duplicate all data in a table except for a single column that should be changed

I have a question regarding a unified insert query against tables with different data
structures (Oracle). Let me elaborate with an example:
tb_customers (
id NUMBER(3), name VARCHAR2(40), archive_id NUMBER(3)
)
tb_suppliers (
id NUMBER(3), name VARCHAR2(40), contact VARCHAR2(40), xxx, xxx,
archive_id NUMBER(3)
)
The only column that is present in all tables is [archive_id]. The plan is to create a new archive of the dataset by copying (duplicating) all records to a different database partition and incrementing the archive_id for those records accordingly. [archive_id] is always part of the primary key.
My problem is with select statements to do the actual duplication of the data. Because the columns are variable, I am struggling to come up with a unified select statement that will copy the data and update the archive_id.
One solution (that works), is to iterate over all the tables in a stored procedure and do a:
CREATE TABLE temp as (SELECT * from ORIGINAL_TABLE);
UPDATE temp SET archive_id=something;
INSERT INTO ORIGINAL_TABLE (select * from temp);
DROP TABLE temp;
I do not like this solution very much as the DDL commands muck up all restore points.
Does anyone else have any solution?
How about creating a global temporary table for each base table?
create global temporary table tb_customers$ as select * from tb_customers;
create global temporary table tb_suppliers$ as select * from tb_suppliers;
You don't need to create and drop these each time, just leave them as-is.
You're archive process is then a single transaction...
insert into tb_customers$ as select * from tb_customers;
update tb_customers$ set archive_id = :v_new_archive_id;
insert into tb_customers select * from tb_customers$;
insert into tb_suppliers$ as select * from tb_suppliers;
update tb_suppliers$ set archive_id = :v_new_archive_id;
insert into tb_suppliers select * from tb_suppliers$;
commit; -- this will clear the global temporary tables
Hope this helps.
I would suggest not having a single sql statement for all tables and just use and insert.
insert into tb_customers_2
select id, name, 'new_archive_id' from tb_customers;
insert into tb_suppliers_2
select id, name, contact, xxx, xxx, 'new_archive_id' from tb_suppliers;
Or if you really need a single sql statement for all of them at least precreate all the temp tables (as temp tables) and leave them in place for next time. Then just use dynamic sql to refer to the temp table.
insert into ORIGINAL_TABLE_TEMP (SELECT * from ORIGINAL_TABLE);
UPDATE ORIGINAL_TABLE_TEMP SET archive_id=something;
INSERT INTO NEW_TABLE (select * from ORIGINAL_TABLE_TEMP);

Resources