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.
Related
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;
I have several Oracle functions that are similar to the one below. I don't know much about Oracle and although I have made in roads on a major query re-write. I'd like to ask for some help on how to convert this function to SQL Server 2008.
I have tried using the online conversion tool at www.sqlines.com and benefited from many pages there... but not successful in converting this function....
Thanks in advance, John
Oracle source:
function OfficeIDMainPhoneID(p_ID t_OfficeID)
return t_OfficePhoneID
is
wPhID t_OfficePhoneID;
wPhID1 t_OfficePhoneID;
cursor cr_phone
is
select Office_PHONE_ID,IS_PHONE_PRIMARY
from Office_PHONE
where Office_ID = p_ID
order by SEQ_NUMBER;
begin
wPhID :=NULL;
wPhID1:=NULL;
for wp in cr_phone
loop
if wPhID is NULL
then wPhID1:=wp.Office_PHONE_ID;
end if;
if wp.IS_PHONE_PRIMARY = 'Y'
then
wPhID:=wp.Office_PHONE_ID;
Exit;
end if;
end loop;
if wPhID is NULL
then wPhID:=wPhID1;
end if;
return(wPhID);
end OfficeIDMainPhoneID;
SQL Server attempt:
create function OfficeIDMainPhoneID(#p_ID t_OfficeID)
returns t_OfficePhoneID
as
begin
declare #wPhID t_OfficePhoneID;
declare #wPhID1 t_OfficePhoneID;
declare cr_phone cursor local
for
select Office_PHONE_ID,IS_PHONE_PRIMARY
from Office_PHONE
where Office_ID = #p_ID
order by SEQ_NUMBER;
set #wPhID =NULL;
set #wPhID1=NULL;
declare wp cursor for cr_phone
open wp;
fetch wp into;
while ##fetch_status=0
begin
if #wPhID is NULL
begin set #wPhID1=wp.Office_PHONE_ID;
end
if wp.IS_PHONE_PRIMARY = 'Y'
begin
set #wPhID=wp.Office_PHONE_ID;
Exit;
end
fetch wp into;
end;
close wp;
deallocate wp;
if #wPhID is NULL
begin set #wPhID=#wPhID1;
end
return(#wPhID);
end ;
To answer the question about the functions as written
If you just want to fix the cursor so it works, one problem is the two "fetch wp into;" statements. You are saying "fetch the data and put it into" and then not giving it anything to put it into. Declare a couple of variables, put the data into them, then later use the variables, not the code. You need one variable per item returned in your cursor definition, so one each for Office_PHONE_ID and IS_PHONE_PRIMARY.
Also, you are trying to declare variables (and the function) as t_OfficePhoneID, I suspect that should be something like INT OR BIGINT instead (whatever the table definition for the column is).
Declare #OP_ID INT, #ISPRIMARY CHAR(1) --Or whatever the column is
later (in two locations),
fetch wp into (#OP_ID, #ISPRIMARY);
then use #OP_ID instead of wp.Office_PHONE_ID, and so on.
HOWEVER, I would throw away all the code in the function after declaring #wPhID, and do something else. Cursors suck if you can get what you want with a simple set based request. If you work your way through the oracle code, it is doing the following:
Get the id of the first phone number marked primary (in sequence order). If it didn't find one of those, just get the id of the first non-primary phone number in sequence order. You can do that with the following
set #wPhID = select TOP 1 Office_PHONE_ID
from Office_PHONE
where Office_ID = #p_ID
order by CASE WHEN IS_PHONE_PRIMARY = 'Y' THEN 0 ELSE 1 END, SEQ_NUMBER;
Return #wPhID and you're done.
I used "CASE WHEN IS_PHONE_PRIMARY = 'Y' THEN 0 ELSE 1 END" in the order by because I don't know what other values are possible, so this will always work. If you know the only possible values are 'Y' and 'N', you could use something like the following instead
order by IS_PHONE_PRIMARY DESC, SEQ_NUMBER;
I am relatively new to PL/SQL and i am trying to create a trigger that will alert me after an UPDATE on a table Review. When it is updated I want to ge the username(User table), score(Review Table), and product name (Product Table) and print them out:
This is what I have so far:
three tables:
Review: score, userid,pid, rid
Users: userid,uname
Product: pid,pname
So Review can reference the other tables with forigen keys.
create or replace trigger userNameTrigger
after insert on review
for each row
declare
x varchar(256);
y varchar(256);
z varchar(256);
begin
select uname into x , pname into y , score into z
from review r , product p , users u
where r.pid = p.pid and r.userid = u.userid and r.rid =new.rid;
dbms_output.put_line('user: '|| X||'entered a new review for Product: '|| Y || 'with a review score of: '|| Z);
end;
The problem I am having is I cannot seem to figure out how to store the selected fields into the variables and output it correctly.
DDL:
Create Table Review
(
score varchar2(100)
, userid varchar2(100)
, pid varchar2(100)
, rid varchar2(100)
);
Create Table Users
(
userid varchar2(100)
, uname varchar2(100)
);
Create Table Product
(
pid varchar2(100)
, pname varchar2(100)
);
The first problem I can see is that you're missing a colon when you refer to new.rid. The second is that you're accessing the review table inside a row-level trigger on that same table, which will give you a mutating table error at some point; but you don't need to as all the data from the inserted row is in the new pseudorow.
create or replace trigger userNameTrigger
after insert on review
for each row
declare
l_uname users.uname%type;
l_pname product.pname%type;
begin
select u.uname into l_uname
from users u
where u.userid = :new.userid;
select p.pname
into l_pname
from product
where p.pid = :new.pid;
dbms_output.put_line('user '|| l_uname
|| ' entered a new review for product ' || l_pname
|| ' with a review score of '|| :new.score);
end;
The bigger problem is that the only person who could see the message is the user inserting tow row, which seems a bit pointless; and they would have to have output enabled in their session to see it.
If you're trying to log that so someone else can see it then store it in a table or write it to a file. As the review table can be queried anyway it seems a bit redundant though.
Having all your table columns as strings is also not good - don't store numeric values (e.g. scores, and probably the ID fields) or dates as strings, use the correct data types. It will save you a lot of pain later. You also don't seem to have any referential integrity (primary/foreign key) constraints - so you can review a product that doesn't exist, for instance, which will cause a no-data-found exception in the trigger.
It makes really no sense to use a trigger to notify themselves about changed rows. If you insert new rows into the table, then you have all info about them. Why not something like the block below instead a trigger:
create table reviews as select 0 as rid, 0 as userid, 0 as score, 0 as pid from dual where 1=0;
create table users as select 101 as userid, cast('nobody' as varchar2(100)) as uname from dual;
create table products as select 1001 as pid, cast('prod 1001' as varchar2(100)) as pname from dual;
<<my>>declare newreview reviews%rowtype; uname users.uname%type; pname products.pname%type; begin
insert into reviews values(1,101,10,1001) returning rid,userid,score,pid into newreview;
select uname, pname into my.uname, my.pname
from users u natural join products p
where u.userid = newreview.userid and p.pid = newreview.pid
;
dbms_output.put_line('user: '||my.uname||' entered a new review for Product: '||my.pname||' with a review score of: '||newreview.score);
end;
/
output: user: nobody entered a new review for Product: prod 1001 with a review score of: 10
In order to inform another session about an event you should use dbms_alert (transactional) or dbms_pipe (non transactional) packages. An example of dbms_alert:
create or replace trigger new_review_trig after insert on reviews for each row
begin
dbms_alert.signal('new_review_alert', 'signal on last rid='||:new.rid);
end;
/
Run the following block in another session (new window, worksheet, sqlplus or whatever else). It will be blocked until the registered signal is arrived:
<<observer>>declare message varchar2(400); status integer; uname users.uname%type; pname products.pname%type; score reviews.score%type;
begin
dbms_alert.register('new_review_alert');
dbms_alert.waitone('new_review_alert', observer.message, observer.status);
if status != 0 then raise_application_error(-20001, 'observer: wait on new_review_alert error'); end if;
select uname, pname, score into observer.uname, observer.pname, observer.score
from reviews join users using(userid) join products using (pid)
where rid = regexp_substr(observer.message, '\w+\s?rid=(\d+)', 1,1,null,1)
;
dbms_output.put_line('observer: new_review_alert for user='||observer.uname||',product='||observer.pname||': score='||observer.score);
end;
/
Now in your session:
insert into reviews values(2, 101,7,1001);
commit; --no alerting before commit
The another (observer) session will be finished with the output:
observer: new_review_alert for user=nobody,product=prod 1001: score=7
P.S. There was no RID in the Table REVIEW, so i'll just assume it was supposed to be PID.
create or replace trigger userNameTrigger
after insert on review
for each row
declare
x varchar2(256);
y varchar2(256);
z varchar2(256);
BEGIN
select uname
, pname
, score
INTO x
, y
, z
from review r
, product p
, users u
where r.pid = p.pid
and r.userid = u.userid
and r.PID = :new.pid;
dbms_output.put_line('user: '|| X ||'entered a new review for Product: '|| Y || 'with a review score of: '|| Z);
end userNameTrigger;
You just made a mistake on the INTO statement, you can just clump them together in one INTO.
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;
/
I have created a pipelined function which returns a table. I use this function like a dynamic view in another function, in a with clause, to mark certain records. I then use the results from this query in an aggregate query, based on various criteria. What I want to do is union all these aggregations together (as they all use the same source data, but show aggregations at different heirarchical levels).
When I produce the data for individual levels, it works fine. However, when I try to combine them, I get an ORA-12840 error: cannot access a remote table after parallel/insert direct load txn.
(I should note that my function and queries are looking at tables on a remote server, via a DB link).
Any ideas what's going on here?
Here's an idea of the code:
function getMatches(criteria in varchar2) return myTableType pipelined;
...where this function basically executes some dynamic SQL, which references remote tables, as a reference cursor and spits out the results.
Then the factored queries go something like:
with marked as (
select id from table(getMatches('OK'))
),
fullStats as (
select mainTable.id,
avg(nvl2(marked.id, 1, 0)) isMarked,
sum(mainTable.val) total
from mainTable
left join marked
on marked.id = mainTable.id
group by mainTable.id
)
The reason for the first factor is speed -- if I inline it, in the join, the query goes really slowly -- but either way, it doesn't alter the status of whatever's causing the exception.
Then, say for a complete overview, I would do:
select sum(total) grandTotal
from fullStats
...or for an overview by isMarked:
select sum(total) grandTotal
from fullStats
where isMarked = 1
These work fine individually (my pseudocode maybe wrong or overly simplistic, but you get the idea), but as soon as I union all them together, I get the ORA-12840 error :(
EDIT By request, here is an obfuscated version of my function:
function getMatches(
search in varchar2)
return idTable pipelined
as
idRegex varchar2(20) := '(05|10|20|32)\d{3}';
searchSQL varchar2(32767);
type rc is ref cursor;
cCluster rc;
rCluster idTrinity;
BAD_CLUSTER exception;
begin
if regexp_like(search, '^L\d{3}$') then
searchSQL := 'select distinct null id1, id2_link id2, id3_link id3 from anotherSchema.linkTable#my.remote.link where id2 = ''' || search || '''';
elsif regexp_like(search, '^' || idRegex || '(,' || idRegex || || ')*$') then
searchSQL := 'select distinct null id1, id2, id3 from anotherSchema.idTable#my.remote.link where id2 in (' || regexp_replace(search, '(\d{5})', '''\1''') || ')';
else
raise BAD_CLUSTER;
end if;
open cCluster for searchSQL;
loop
fetch cCluster into rCluster;
exit when cCluster%NOTFOUND;
pipe row(rCluster);
end loop;
close cCluster;
return;
exception
when BAD_CLUSTER then
raise_application_error(-20000, 'Invalid Cluster Search');
return;
when others then
raise_application_error(-20999, 'API' || sqlcode || chr(10) || sqlerrm);
return;
end getMatches;
It's very simple, designed for an API with limited access to the database, in terms of sophistication (hence passing a comma delimited string as a possible valid argument): If you supply a grouping code, it returns linked IDs (it's a composite, 3-field key); however, if you supply a custom list of codes, it just returns those instead.
I'm on Oracle 10gR2; not sure which version exactly, but I can look it up when I'm back in the office :P
To be honest no idea where the issue came from but the simplest way to solve it - create a temporary table and populate it by values from your pipelined function and use the table inside WITH clause. Surely the temp table should be created but I'm pretty sure you get serious performance shift because dynamic sampling isn't applied to pipelined functions without tricks.
p.s. the issue could be fixed by with marked as ( select /*+ INLINE / id from table(getMatches('OK'))) but surely it isn't the stuff you're looking for so my suggestion is confirmed WITH does something like 'insert /+ APPEND*/' inside it'.