INSERT AS SELECT in package where table has redaction - oracle

I've redacted columns on a table. When developer try to use a 'INSERT AS SELECT' command in a pkg they get :
ORA-28081: Insufficient privileges - the command references a redacted object.
Apart from granting exemption to the schema (defeating the use of redaction altogether) what can I do?
The developers use the package but shouldn't see the table contents
Here is the function :
FUNCTION Create****Transaction (******PackageUserId NUMBER)
return NUMBER
IS
/*****************************
Local Variable Definitions
******************************/
v_****tId NUMBER(15);
BEGIN
v_****Id := ***_PKG.GETNEXTAMSSEQNUM();
INSERT INTO ****_AGENCY_PACK_USER_TRANS ****
(
****_ID,
****_****_ID,
****_BUS_ID,
****_ROLE_ID,
****_DATE_FROM,
****_DATE_TO,
****_ABBR,
****_NOTE,
****_CREATED_BY,
****_DATE_CREATED,
****_AUDIT_ACTION,
****_AUDIT_DATE,
****_AUDIT_LOCATION,
****_AUDIT_USER,
****_VER_NUM,
****_AMSS_ID,
****_WEBSERVICE,
****_APR_ID,
****_ASSIGN_RULE_ALLOWED,
****_SUP_TAG,
****_CPR
)
(
select
v_****Id,
****_ID,
****_BUS_ID,
****_ROLE_ID,
****_DATE_FROM,
****_DATE_TO,
****_ABBR,
****_NOTE, (THIS IS REDACTED ON THE TABLE)
****_CREATED_BY,
****_DATE_CREATED,
****_AUDIT_ACTION,
****_AUDIT_DATE,
****_AUDIT_LOCATION,
****_AUDIT_USER,
****_VER_NUM,
****_AMSS_ID,
****_WEBSERVICE,
****_APR_ID,
****_ASSIGN_RULE_ALLOWED,
****_SUP_TAG,
****_CPR
FROM
***********_PACKAGE_USERS
WHERE ****_ID = *****PackageUserId
);
if SQL%ROWCOUNT = 0 THEN
dbms_output.put_line('ERROR - no rows inserted!!');
return 0;
else
return v_****Id;
end if;
END Create****ransaction;

Purpose of Redaction is to make sure no one with not having access to data should be able to view it.
Once you have applied redaction with policy 1=1 then it will be applicable for all users except SYS and it will not allow CTAS or insert as select. There is workaround or you can say a different way of providing access to rightful users.
Let me know if you need more details on this.

Related

Is there a hint to generate execution plan ignoring the existing one from shared pool?

Is there a hint to generate execution plan ignoring the existing one from the shared pool?
There is not a hint to create an execution plan that ignores plans in the shared pool. A more common way of phrasing this question is: how do I get Oracle to always perform a hard parse?
There are a few weird situations where this behavior is required. It would be helpful to fully explain your reason for needing this, as the solution varies depending why you need it.
Strange performance problem. Oracle performs some dynamic re-optimization of SQL statements after the first run, like adaptive cursor sharing and cardinality feedback. In the rare case when those features backfire you might want to disable them.
Dynamic query. You have a dynamic query that used Oracle data cartridge to fetch data in the parse step, but Oracle won't execute the parse step because the query looks static to Oracle.
Misunderstanding. Something has gone wrong and this is an XY problem.
Solutions
The simplest way to solve this problem are by using Thorsten Kettner's solution of changing the query each time.
If that's not an option, the second simplest solution is to flush the query from the shared pool, like this:
--This only works one node at a time.
begin
for statements in
(
select distinct address, hash_value
from gv$sql
where sql_id = '33t9pk44udr4x'
order by 1,2
) loop
sys.dbms_shared_pool.purge(statements.address||','||statements.hash_value, 'C');
end loop;
end;
/
If you have no control over the SQL, and need to fix the problem using a side-effect style solution, Jonathan Lewis and Randolf Geist have a solution using Virtual Private Database, that adds a unique predicate to each SQL statement on a specific table. You asked for something weird, here's a weird solution. Buckle up.
-- Create a random predicate for each query on a specific table.
create table hard_parse_test_rand as
select * from all_objects
where rownum <= 1000;
begin
dbms_stats.gather_table_stats(null, 'hard_parse_test_rand');
end;
/
create or replace package pkg_rls_force_hard_parse_rand is
function force_hard_parse (in_schema varchar2, in_object varchar2) return varchar2;
end pkg_rls_force_hard_parse_rand;
/
create or replace package body pkg_rls_force_hard_parse_rand is
function force_hard_parse (in_schema varchar2, in_object varchar2) return varchar2
is
s_predicate varchar2(100);
n_random pls_integer;
begin
n_random := round(dbms_random.value(1, 1000000));
-- s_predicate := '1 = 1';
s_predicate := to_char(n_random, 'TM') || ' = ' || to_char(n_random, 'TM');
-- s_predicate := 'object_type = ''TABLE''';
return s_predicate;
end force_hard_parse;
end pkg_rls_force_hard_parse_rand;
/
begin
DBMS_RLS.ADD_POLICY (USER, 'hard_parse_test_rand', 'hard_parse_policy', USER, 'pkg_rls_force_hard_parse_rand.force_hard_parse', 'select');
end;
/
alter system flush shared_pool;
You can see the hard-parsing in action by running the same query multiple times:
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
select * from hard_parse_test_rand;
Now there are three entries in GV$SQL for each execution. There's some odd behavior in Virtual Private Database that parses the query multiple times, even though the final text looks the same.
select *
from gv$sql
where sql_text like '%hard_parse_test_rand%'
and sql_text not like '%quine%'
order by 1;
I think there is no hint indicating that Oracle shall find a new execution plan everytime it runs the query.
This is something we'd want for select * from mytable where is_active = :active, with is_active being 1 for very few rows and 0 for maybe billions of other rows. We'd want an index access for :active = 1 and a full table scan for :active = 0 then. Two different plans.
As far as I know, Oracle uses bind variable peeking in later versions, so with a look at the statistics it really comes up with different execution plans for different bind varibale content. But in older versions it did not, and thus we'd want some hint saying "make a new plan" there.
Oracle only re-used an execution plan for exactly the same query. It sufficed to add a mere blank to get a new plan. Hence a solution might be to generate the query everytime you want to run it with a random number included in a comment:
select /* 1234567 */ * from mytable where is_active = :active;
Or just don't use bind variables, if this is the problem you want to address:
select * from mytable where is_active = 0;
select * from mytable where is_active = 1;

PL/SQL - How can I check if a Package / Procedure / Function is being used?

Hello to whoever sees this.
First of all, my knowledge on PL/SQL is pretty much basic to layman. I can read the code, do queries and use PL/SQL Developer to do my research. That's all I'm needed to do.
All I need to know is if a Package/Procedure/Function is being used and/or, last time it was used. Is there a way to see it through queries or functionality of PL/SQL Developer?
Side note: I've found the question bellow, but it didn't fit my needs/ didn't fully understand how the awnsers there could be of use:
How can you tell if a PL/SQL Package, Procedure, or Function is being used?
You have a choice between homespun and use Oracle's built-in facility.
The most reliable and accessible way I have come across for this type of stat is the home-spun code logging into into the procedures of interest by a simple NOLOGGING INSERT into a suitable table. Don't bother trying a complex summing as that will add overhead, just a simple INSERT and then use the reporting side to summarize as required. You can add timestamps and basic session info too .
Another novel approach I have seen which I quite liked was where they created a sequence corresponding to each procedure of interest and simply selected NEXTVAL at the start of each procedure. This won't give you historic/time based stats; you would add that to the reporting side, and you will need to understand the effect the CACHE on the sequence could have.
Personally, I like to see some sort of logging in code as it helps with support/debug issues, providing real insight into the day-in-the-life of production system. Such information can drive improvements.
Having said all that, Oracle does maintain a rich set of stats but the extraction and interpretation can be esoteric. If you have access to OEM/Grid COntrol (or whatever they call the web-based management console these days), you can see stats between time-frames, and drill down to individual statements and procedures. But this takes a little practice and know-how before you know what to look for and how to get it.
You can try rolling your own queries specifically targeting the procedures of interest. You would start with V$SQL to get the SQL_ID of the statements, then link this to DBA_HIST_SQLSTAT which maintains a snapshot history of statistics including total executions and executions since last snapshot.
If you search for DBA_HIST_SQLSTAT and execution I'm sure you will soon find a query to get you started. You will need to be granted access to these views if you are not the DBA.
If all you want is the number of times it has been used (from some arbitrary reset time) and the date it was last used, create a couple of private package variables.
package body package_name as
F1_Use_Count integer := 0;
F1_Last_Used timestamp;
...
function F1 ...
F1_Use_Count := F1_Use_Count + 1;
F1_Last_Used := SysTimestamp;
... the rest of the function
end F1;
-- You also want to add
function Get_F1_Use_Count return integer is begin
return F1_Use_Count;
end;
function Get_F1_Last_Used return timestamp is begin
return F1_Last_Used;
end
proc Reset_F1_Stats is begin
F1_Use_Count := 0;
F1_Last_Used := null;
end;
-- Or all three above could be done with
proc Get_Reset_F1_Stats( Use_count out integer, Use_Date out timestamp ) is begin
Use_count := F1_Use_Count;
Use_Date := F1_Last_Used;
F1_Use_Count := 0;
F1_Last_Used := null;
end;
end package_name;
EDIT:
To "session-proof" the action, write the values into a table instead of package variables.
CREATE TABLE Access_Stats(
Proc_Id VARCHAR2( 32 ) NOT NULL,
Access_Count INTEGER DEFAULT 0 NOT NULL,
Access_Date DATE DEFAULT SYSDATE NOT NULL,
CONSTRAINT PK_TEST PRIMARY KEY( Proc_Id )
);
Inside the package body:
Proc Set_Stats( PName Access_Stats.Proc_Id%type ) is begin
MERGE INTO Access_Stats a
USING(
SELECT 1 FROM dual ) tt
ON( a.Proc_Id = Upper( PName ))
WHEN MATCHED THEN
UPDATE
SET access_count = access_count + 1,
access_date = SYSDATE
WHEN NOT MATCHED THEN
INSERT( Proc_Id, access_count )
VALUES( Upper( PName ), 1 );
Commit;
END;
Then in all the functions and procedures you want to track, just make sure they start out with a call to the Set_Stats proc:
Proc WhatEver...
Set_Stats( 'whatever' );
...
The getters would also have to be changed to read from the table. I would change the Get_Reset_F1_Stats proc to a more general Get_Reset_Stats version:
proc Get_Reset_Stats(
PName Access_Stats.Proc_Id%type,
Use_count out integer,
Use_Date out timestamp )
is begin
Select Access_Count, Access_Date
into Use_count, Use_Date
From Access_Stats
where Proc_Id = Upper( PName );
update Access_Stats
set Access_Count = 0
where Proc_Id = Upper( PName );
end;
The Get_Reset_Stats could just delete the row for the procedure being tracked, but by resetting the count to zero, the date of the reset is maintained. If you add other fields such as the user who executed the proc, the person who last executed (or reset) the procedure/function being tracked can also be maintained.
My standard caveat applies: the code shown above is designed more for illustration and is not presented as production-ready code. Modify to fit your own particular standards.
You cannot tell with 100% certainty
You can see the last time a package/procedure was compiled.
you can see what other objects depend on it
you can search through the packaged code for dynamic sql that refers to it as this will not show up in the dependencies
you cannot tell if a report calls the object unless you look in the reports
you cannot tell if an external script calls it unless you search in the scripts
you cannot tell if the object is called by an external program

Parameter for IN query oracle [duplicate]

This question already has an answer here:
Oracle: Dynamic query with IN clause using cursor
(1 answer)
Closed 8 years ago.
SELECT * FROM EMPLOYEE
WHERE EMP_NAME IN (:EMP_NAME);
This is my query and now the EMP_NAME parameter I would like to send it as a list of strings.
When I run this query in SQL developer it is asked to send the EMP_NAME as a parameter, Now I want to send 'Kiran','Joshi' (Basically, I want to fetch the details of the employee with employee name either Kiran or Joshi. How should I pass the value during the execution of the query?
It works when I use the value Kiran alone, but when I concatenate with any other string it won't work. Any pointers in this?
I tried the one below
'Kiran','Joshi'
The above way doesn't work as understood this is a single parameter it tries the employee with the name as 'Kiran',Joshi' which won't come. Understandable, but in order to achieve this thing, how can I go ahead?
Any help would be really appreciated.
Thanks to the people who helped me in solving this problem.
I could get the solution using the way proposed, below is the approach
SELECT * FROM EMPLOYEE WHERE EMP_NAME IN (&EMP_NAME)
I have tried in this way and following are the scenarios which I have tested and they are working fine.
Scenario 1:
To fetch details of only "Kiran", then in this case the value of EMP_NAME when sql developer prompts is given as Kiran. It worked.
Scenario 2:
To fetch details of either "Kiran" or "Joshi", then the value of EMP_NAME is sent as
Kiran','Joshi
It worked in this case also.
Thanks Kedarnath for helping me in achieving the solution :)
IN clause would be implicitly converted into multiple OR conditions.. and the limit is 1000.. Also query with bind variable means, the execution plan will be reused.. Supporting bind variables for IN clause will hence affect the bind variable's basic usage, and hence oracle limits it at syntax level itself.
Only way is like name in (:1,:2) and bind the other values..
for this, you might dynamic SQL constructing the in clause bind variables in a loop.
Other way is, calling a procedure or function(pl/sql)
DECLARE
v_mystring VARCHAR(50);
v_my_ref_cursor sys_refcursor;
in_string varchar2='''Kiran'',''Joshi''';
id2 varchar2(10):='123'; --- if some other value you have to compare
myrecord tablename%rowtype;
BEGIN
v_mystring := 'SELECT a.*... from tablename a where name= :id2 and
id in('||in_string||')';
OPEN v_my_ref_cursor FOR v_mystring USING id2;
LOOP
FETCH v_my_ref_cursor INTO myrecord;
EXIT WHEN v_my_ref_cursor%NOTFOUND;
..
-- your processing
END LOOP;
CLOSE v_my_ref_cursor;
END;
IN clause supports maximum of 1000 items. You can always use a table to join instead. That table might be a Global Temporary Table(GTT) whose data is visible to thats particular session.
Still you can use a nested table also for it(like PL/SQL table)
TABLE() will convert a PL/Sql table as a SQL understandable table object(an object actually)
A simple example of it below.
CREATE TYPE pr AS OBJECT
(pr NUMBER);
/
CREATE TYPE prList AS TABLE OF pr;
/
declare
myPrList prList := prList ();
cursor lc is
select *
from (select a.*
from yourtable a
TABLE(CAST(myPrList as prList)) my_list
where
a.pr = my_list.pr
order by a.pr desc) ;
rec lc%ROWTYPE;
BEGIN
/*Populate the Nested Table, with whatever collection you have */
myPrList := prList ( pr(91),
pr(80));
/*
Sample code: for populating from your TABLE OF NUMBER type
FOR I IN 1..your_input_array.COUNT
LOOP
myPrList.EXTEND;
myPrList(I) := pr(your_input_array(I));
END LOOP;
*/
open lc;
loop
FETCH lc into rec;
exit when lc%NOTFOUND; -- Your Exit WHEN condition should be checked afte FETCH iyself!
dbms_output.put_line(rec.pr);
end loop;
close lc;
END;
/

Why I'm getting the ORA-01003: no statement parsed error?

Why am I getting this error and what does it mean by no statement parsed.
ORA-01003: no statement parsed
Here is the code:
PROCEDURE ORIGINAL_TABLE.UPDATE_GROUPS IS
-- cursor loaded with the swam groups
CURSOR cursor1 IS
SELECT ID, NEW_DESCRIPTION
FROM NEW_TABLE.NEW_GROUP_TABLE#DB_LINK.X;
BEGIN
FOR C1_REC IN cursor1 LOOP
UPDATE
ORIGINAL_TABLE."GROUPS"
SET
GROUP_ID = C1_REC.ID
WHERE
ORIGINAL_TABLE."GROUPS".DESCRIPTION = C1_REC.NEW_DESCRIPTION;
IF (SQL%ROWCOUNT = 0) THEN
INSERT INTO
ORIGINAL_TABLE.GROUPS("GROUP_ID", "DESCRIPTION")
VALUES (C1_REC.ID, C1_REC.NEW_DESCRIPTION);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;
What I try to do with the code above is to update and old table with the values from a new table and in case that the new group doesn't exist insert it.
Update: Changed %ROWCOUNT > 0 for %ROWCOUNT = 0
Use MERGE statement, it does update/insert stuff more efficiently and pay attention your plsql doesn't provide it is intended for. It tries to make an update statement and if a record found it inserts another record. In order to fix it use
IF (SQL%ROWCOUNT = 0)
I presume the reason of the issue is the . in DBLINK name.
Moreover I would suggest to get rid of quotes for tables/fields just in case as well as schema name.
Another words delete all ORIGINAL_TABLE.
merge into groups g
using (
SELECT ID, NEW_DESCRIPTION
FROM NEW_TABLE.NEW_GROUP_TABLE#DB_LINK.X
) nt
on (nt.NEW_DESCRIPTION = g.description )
when matched then update set g.group_id = nt.id
when non matched then insert(GROUP_ID, DESCRIPTION)
values(nt.id, nt.NEW_DESCRIPTION)

Merge magento customers

Is there a way to merge customer accounts in Magento? I have a customer who's email got corrupted (or so they say) so they created a new account. I'd like to be able to merge the history of the old customer into the new. Is this possible?
Assuming you have permission to create a MySQL stored routine and you have the information_schema database at your disposal, the following code will merge customers. Note that after creating the routine, you'll need to run it with the entity_id's of the customers as the parameters.
drop procedure if exists merge_customers;
delimiter //
create procedure merge_customers(
id1 int unsigned, -- the customer to keep
id2 int unsigned) -- the record to delete
begin
declare done int default false;
declare tbl_name, col_name varchar(255);
declare c cursor for
select table_name, column_name
from information_schema.key_column_usage
where table_schema = database()
and referenced_table_name = 'customer_entity'
and referenced_column_name = 'entity_id';
declare continue handler for not found set done = true;
open c;
read_loop: loop
fetch c into tbl_name, col_name;
if done then
leave read_loop;
end if;
set #str = concat(
'UPDATE IGNORE ', tbl_name,
' SET ', col_name, ' = ', id1,
' WHERE ', col_name, ' = ', id2
);
prepare stmt from #str;
execute stmt;
deallocate prepare stmt;
end loop;
close c;
delete from customer_entity where entity_id = id2;
end//
delimiter;
You could refund the orders of one account (which doesn't actually refund any charges) and submit identical ones under the other account using, say, the Check payment method (which doesn't actually make any charges). That way you have a paper trail of activity which wouldn't happen if you altered the database directly.
Also, ask yourself could this person be up to no good? How certain are you they are who they say they are? Simply knowing their pet's name or whichever secret question only proves they have seen the first person's facebook page. What does it even mean for an email to get "corrupted"?
I'm looking for the same thing. Definitely a needed extension.
I currently change the Customer_ID manually in phpMyAdmin in both tables sales_flat_order & sales_flat_order_grid. You can find the customer ID in your magento Admin, second column in the customers section.
But it is VERY time consuming, it would be so great if someone could write a module, I'd pay for it.

Resources