/* --------------------------------------------------------------------------- $Revision: 1.1.2.2.2.3 $ $Date: 2015/05/28 11:00:17 $ SAS VERSION: 9.4 | NAME: cxt_ecvprocessmatch | DESCRIPTION: | This macro is called four times by CXACREATEPROFILE to perform | common features for each type of match-processing: | - match on unique operand against customer-profile | - match on non-unique operand against customer-profile | - match on unique operand against master hash | - match on non-unique operand against master hash | | Its primary tasks are to: | - test the match validity | - handle the cookies and other operands from the matching record | - update the incoming record with data from the match +-----------------------------------------------------------------+ | PARAMETERS: | unique=, Y or N for match on unique operand. | custmatch= Y or N for match on customer-profile (N means match | on master hash) +----------------------------------------------------------------+ HISTORY: userid yyyymmdd defectid desc keadam 20131010 S0946771 Where cxa_ecvMsg uses old msgX parms, rewrote as individual calls. keadam 20131017 S0946771 Programmatically converted all messaging to use sasmsg function. keadam 20131101 S1027328 Calls to cxa_ecvMsg mistakenly had the EMPHASIS= parm before the value of the msg parm keadam 20150121 S1145468 Changes to handle precision for SESSION_SK when it's in SAS ------------------------------------------------------------------------------ Copyright (c) 2005-2015, SAS Institute Inc., Cary, NC, USA, All Rights Reserved ------------------------------------------------------------------------------ */ %macro cxt_ecvProcessMatch( unique=, custmatch= ); /*May get set to Y if the match is invalidated.*/ invalidMatch='N'; /*Create messages in the log about the type of match.*/ %local txtUnique txtCustmatch; %if &unique=Y %then %let txtUnique=unique; %else %let txtUnique=non-unique; %if &custmatch=Y %then %let txtCustmatch=customer-profile table; %else %let txtCustmatch=MASTER hash; if tentative_match=1 then do; /*%cxt_ecvMsg("Processing tentative match on &txtUnique operand against &txtCustmatch..",emphasis=STRONG)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_58_note","DQUOTE","&txtUnique","&txtCustmatch."),emphasis=STRONG) /*%cxt_ecvMsg("Operand is " ||compress(match_operand)||', incoming record # '||put(_n_,best.)||" of current batch (&batchNo).",emphasis=STRONG)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_55_note","DQUOTE",compress(match_operand),put(_n_,best.),"&batchNo"),emphasis=STRONG) /*%cxt_ecvMsg("Operand code " ||compress(put(match_operand_code,best.))|| ", value of md5 hash is "||compress(match_operand_hash)||".",emphasis=STRONG)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_54_note","DQUOTE",compress(put(match_operand_code,best.)),compress(match_operand_hash)),emphasis=STRONG) /*%cxt_ecvMsg('Matched customer-id is ' ||compress(put(mcustomer_sk,best.))|| '.'),emphasis=STRONG)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_40_note","DQUOTE",compress(put(mcustomer_sk,best.))),emphasis=STRONG) end; /*Debugging feature.*/ %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) %cxt_ecvIncRecDebug(mode=FIELDS,showBoth=N) /***********IS THIS A VALID MATCH - COULD BE VARIOUS REASONS WHY NOT**************/ /*Is the matched cust id in the IGNORE-CUSTOMER hash? If so, we have already matched that rec, and have all the info in the incoming record. This only applies when matching against the customer- profile. The reason is that since this customer record has already been used in a match, all of its information would now be in the master. Since we match against master first, that would have already happened for this incoming record. Hence there is nothing to be gained from matching against the customer record whose data is now in the master record we just matched against. */ %if &custmatch=Y %then %do; %cxt_ecvHashX(ignore,FIND,rcVar=rc,macRcVar=dataErrs,errOK=Y) if rc=0 then do; invalidMatch='Y'; /*%cxt_ecvMsg('tentative match invalidated since this customer profile record is already in the master (i.e. already matched).',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_68_note","DQUOTE"),emphasis=LIGHT) end; %end; %else %do; supersede_cust_id=mcustomer_sk; %cxt_ecvHashX(supsede,FIND,rcVar=rc,macRcVar=dataErrs,errOK=Y) if rc=0 then do; invalidMatch='Y'; /*%cxt_ecvMsg('tentative match invalidated since this master has been replaced with a different customer-id and will vanish.',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_69_note","DQUOTE"),emphasis=LIGHT) end; %end; /*Match is valid.*/ if invalidMatch='N' then do; /*First pull the customer information for the matched customer-id into the _vals_ array, and the M prefixed fields such as minsert_dttm. Note that this overwrites the MINSERT_dttm value from the unique/non-unique operand match source but they should be the same, so that is okay.*/ %if &custmatch=Y %then %do; /*%cxt_ecvMsg('Implementing keyed search against customer fact table, key mcustomer_sk='||compress(put(mcustomer_sk,best.))|| '.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_30_note","DQUOTE",compress(put(mcustomer_sk,best.))),emphasis=N) set &cust_ProfileSource(rename=(customer_sk=mcustomer_sk /*%put NOTE: Temporarily not using date variables from customer-fact since date assignments need fix.;*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %put %sysfunc(sasmsg(&ecv_cxaMsgDset,_ecvNOTEmsg_label,NOQUOTE)) %sysfunc(sasmsg(&ecv_cxaMsgDset,_cxaecv_match_67_note,NOQUOTE)); /*DATESFIX*/ /* insert_dttm=minsert_dttm update_dttm=mupdate_dttm */ /*DATESFIXEND*/ )) key=mcustomer_sk/unique; /*%cxt_ecvMsg('return code from keyed search was '||compress(put(_iorc_,best.))|| '.');*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_59_note","DQUOTE",compress(put(_iorc_,best.))),emphasis=N) if _iorc_ ne 0 then do; /*put 'ERR' 'OR: Failed to read customer-profile record for customer_sk=' mcustomer_sk +(-1) '.';*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ do;length _put_rec_ $1000;drop _put_rec_;_put_rec_=sasmsg("&ecv_cxaMsgDset","_ecverrormsg_label","DQUOTE")|| sasmsg("&ecv_cxaMsgDset","_cxaecv_match_19_error","DQUOTE",mcustomer_sk);put _put_rec_;end; call symput('dataErrs','1'); stop; end; %end; %else %do; /*Pull in the information from the matched master into the _vals_ array, and also the mupdates_ array, indicating which field already updated in this processing session. This pulls in INSERT_dttm and UPDATE_dttm rather than M values, unlike pulling in from cust profile. We do not care about UPDATE_dttm since will set it to today, but we should set MINSERT_dttm to INSERT_dttm from the master, for consistency.*/ findHashMarker=9;/*- if hash error the put _all_ will let us identify which call.*/ %cxt_ecvHashX(master,FIND,rcVar=rc,macRcVar=dataErrs) minsert_dttm=insert_dttm; %end; /*Do not consider this a valid match if matching on a non-unique id operand, and the matched record has values for unique ids, where those fields have different values already on the incoming record. Reason is we never want to use non-unique id matches to modify unique-ids. This is somewhat complex, the reason being that operands are classified as unique, not ids, in the metadata. Hence a unique id might be USER_ID, but the operand might be upcase(USER_ID). Only way we can identify fields to classify as unique ids is to look at unique operands, and the list of fields involved in creating them. If there is only 1 field code listed in the list of field codes for that operand, and visitor_id is not in the operand, and the operand is unique, then the field code listed for that operand denotes a unique id. Then we have to find out in which position in the _VALS_ and _RENAMS_ arrays this unique field is, so that we can test if we are trying to set a value to a unique id which already has one. To get the field name of the unique id to test, we need to loop through the count of unique operands, and for each counter, find the operand code for that. Once we have that, and we have verified it is an operand with a single unique field, we use the cxafldnm format to convert the field-name code into a field-name. To find out where it is in the arrays, we use the $fldtoar format. */ %if &unique=N %then %do; /*If this is the cookie operand, then if &cxa_ecvCookieNoOperandClash=Y, we will not run this loop, and instead, run the follow-on loop, since we do not want to validate a match on cookie if the match has clashes in ANY field used in operands.*/ %if &cxa_ecvCookieNoOperandClash=Y %then %do; if match_operand_code ne &cookie_oper_code then do; %end; loopOpers=1; /*Loop through unique operands. They are the first operands.*/ do until(loopOpers>=&tot_cnt_uniq_opers or invalidMatch='Y'); /*Get the operand code for the loopOperth operand.*/ operCode=symgetn(compress('mo_code'||put(loopOpers,4.))); /*Check if only one field code in operand.*/ if scan(symget(compress('mo_fldlis'||put(operCode,4.))),2,' ')='' and /*Check if operand does not also contain cookie, in which case it cannot be a single-field unique operand.*/ not index(lowcase(symget(compress('mo_'||put(operCode,4.)))),'visitor_id') then do; /*This gives us the position in the arrays of the field for the unique id associated with this operand.*/ posArray=input(put(symget(compress('mo_fldlis'||put(operCode,4.))),$fldtoar.),best.); /*Now check if there is a unique id clash.*/ if not missing(_renams_{posArray}) then if not missing(_vals_{posArray}) then if left(trim(_renams_{posArray})) ne left(trim(_vals_{posArray})) then do; /*If yes, print out explanation in message log.*/ invalidMatch='Y'; tempchar=left(trim(_names_{posArray})); tempchar2=left(trim(_vals_{posArray})); tempchar3=left(trim(_renams_{posArray})); /*%cxt_ecvMsg('Tentative match invalidated. Non-unique operand would introduce inconsistencies in unique ids.',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_71_note","DQUOTE"),emphasis=LIGHT) /*%cxt_ecvMsg('Field '||left(trim(tempchar))||', incoming '||left(trim(tempchar3))||', attempt to replace with '||left(trim(tempchar2))|| '.',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_22_note","DQUOTE",left(trim(tempchar)),left(trim(tempchar3)),left(trim(tempchar2))),emphasis=LIGHT) end; end; loopOpers=loopOpers+1; end; %if &cxa_ecvCookieNoOperandClash=Y %then %do; end; /*For matching on cookie, if &cxa_ecvCookieNoOperandClash set to Y, then we do not allow a match if it would introduce any clashes in fields used in operands.*/ else if match_operand_code=&cookie_oper_code then do; /*Loop through all fields.*/ do loopFields=1 to &count_fields; /*Check if field is in list of operand fields.*/ if _names_{loopFields} in (%cxt_ecv_catq(&all_fields_in_match_opers)) then do; /*Get the operand code for the loopOperth operand.*/ if not missing(_renams_{loopFields}) then if not missing(_vals_{loopFields}) then if left(trim(_renams_{loopFields})) ne left(trim(_vals_{loopFields})) then do; /*If yes, print out explanation in message log.*/ invalidMatch='Y'; tempchar=left(trim(_names_{loopFields})); tempchar2=left(trim(_vals_{loopFields})); tempchar3=left(trim(_renams_{loopFields})); /*%cxt_ecvMsg('Tentative match invalidated. Match on cookie not allowed to introduce inconsistencies in any field used in operands.',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_70_note","DQUOTE"),emphasis=LIGHT) /*%cxt_ecvMsg('Field '||left(trim(tempchar))||', incoming '||left(trim(tempchar3))||', attempt to replace with '||left(trim(tempchar2))|| '.',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_22_note","DQUOTE",left(trim(tempchar)),left(trim(tempchar3)),left(trim(tempchar2))),emphasis=LIGHT) end; end; end; end; %end; %end; end; if invalidMatch = 'Y' then do; /*If matched against customer, but that match invalidated for one of the reasons above - e.g. inconsistencies in fields used in operands, then we already pulled in the information from the customer profile, such as USER_ID etc., and that is in the _vals_ array, and the real variable names (USER_ID etc.) that go into the master. There's a possibility then that the information from this invalidated match might actually get into the master. So what we need to do is pull in the information from the master hash so that the fields pulled in from customer profile get overwritten.*/ %if &custmatch=Y %then %do; %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) /*%cxt_ecvMsg('We matched against a customer from customer-profile but later invalidated it. We would have pulled in values like USER_ID from that match.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_78_note","DQUOTE"),emphasis=N) /*%cxt_ecvMsg('We now need to undo that, by restoring the values from their renamed values.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_79_note","DQUOTE"),emphasis=N) do i=1 to &count_fields; _vals_{i}=_renams_{i}; end; %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) %end; end; if invalidMatch ne 'Y' then do; /*%cxt_ecvMsg('********THIS IS A CONFIRMED MATCH***********',emphasis=Y)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_4_note","DQUOTE"),emphasis=Y) %cxt_ecvIncRecDebug(mode=FIELDS,showBoth=Y) /*This just contains a list of match-operands so that we can know which match-operands are successful in matching.*/ output work.match_operand_performance; /*Throughout the matching process, customer_sk is used only to store what will be the finally assigned customer-id after the incoming record has all opportunities to match. mcustomer_sk will store the customer-id of whatever record the incoming record currently matches against. customer_sk starts off with a provisional negative value. If it never matches, then at the end of the matching process for that record, it gets assigned the next free customer-id. If a record matches, and customer_sk is negative, then it gets assigned the value of mcustomer_sk. That will only ever be changed, potentially, the first time the record matches against either either a customer record, or against an incoming record that previously matched against a customer record. If it has already matched against one of those types of records, and then matches another, the matched mcustomer_sk is said to die.*/ %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) if customer_sk<0 then do; customer_sk=mcustomer_sk; insert_dttm=minsert_dttm; %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) /*%cxt_ecvMsg('Incoming record now has customer_sk=' ||compress(put(customer_sk,best.))|| '.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_34_note","DQUOTE",compress(put(customer_sk,best.))),emphasis=N) end; else if NEWOLD='NEW' /*Means current record has never matched against either either a customer record, or against an incoming record that previously matched against a customer record.*/ then do; %if &custmatch=Y %then %do; /*********Special Processing for Records Incorrectly Assigned a New Customer Id*********/ /*Special situation is when we get here, and also MATCHEDMAST=1. That means a master record already exists, and it should be replaced with the new cust-id from the match. We need to put that cust-id (i.e. the replaced customer-id) into the SUPSEDE hash to make sure we do not match against it again. This is actually customer_sk, which is about to get overwritten. So we put it in as SUPERSEDE_CUST_ID.*/ if matchedmast=1 then do; supersede_cust_id=customer_sk; %cxt_ecvHashX(supsede,ADD,rcVar=rc,macRcVar=dataErrs,errOK=Y) /*Since that former record(s) will also be in the sessions hash, but now under the wrong customer id, we need to update them with the correct customer-id.*/ /*First save mcustomer_sk, since, in order to pull records from sessions, we need to use mcustomer_sk as the key, and that means setting it equal to supersede_cust_id*/ save_custid=mcustomer_sk; mcustomer_sk=supersede_cust_id; /*This hash contains session_csk. We do not want to overwrite current value, so temporarily save it.*/ /*tempnum=session_sk;*//*keadam 20150121 S1145468*/ tempchar4=session_csk;/*keadam 20150121 S1145468*/ %cxt_ecvHashX(sessions,FIND,rcVar=rc,macRcVar=dataErrs,errOK=N) /*If rc was 0, above call ends data-step, so we know it was found. Remove it so we can replace with correct one.*/ %cxt_ecvHashX(sessions,REMOVEDUP,rcVar=rc,macRcVar=dataErrs,errOK=N) /*Now set correct one.*/ mcustomer_sk=save_custid; %cxt_ecvHashX(sessions,ADD,rcVar=rc,macRcVar=dataErrs,errOK=N) /*May be more than one session record, so loop.*/ found=1; do until (found=0); mcustomer_sk=supersede_cust_id; %cxt_ecvHashX(sessions,FIND,rcVar=rc,macRcVar=dataErrs,errOK=Y) if rc=0 then do; /*Remove it so we can replace with correct one.*/ %cxt_ecvHashX(sessions,REMOVEDUP,rcVar=rc,macRcVar=dataErrs,errOK=N) /*Now set correct one.*/ mcustomer_sk=save_custid; %cxt_ecvHashX(sessions,ADD,rcVar=rc,macRcVar=dataErrs,errOK=N) end; else found=0; end; /*session_sk=tempnum;*//*keadam 20150121 S1145468*/ session_csk=tempchar4;/*keadam 20150121 S1145468*/ mcustomer_sk=save_custid; end; /*********End special Processing for Records Incorrectly Assigned a New Customer Id*****/ customer_sk=mcustomer_sk; insert_dttm=minsert_dttm; /*%cxt_ecvMsg('Incoming record now has customer_sk='||put(customer_sk,8.)||".")*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_34_note","DQUOTE",put(customer_sk,8.)),emphasis=N) %end; %else %do; /*If newold and mnewold are both 'NEW' then we've identified that two "new" customers are the same person here, so one of them should disappear - the latest one - mcustomer_sk.*/ if mnewold='NEW' and newold='NEW' then do; supersede_cust_id=mcustomer_sk; %cxt_ecvHashX(supsede,ADD,rcVar=rc,macRcVar=dataErrs,errOK=Y) /*%cxt_ecvMsg('HFX4ECV case #1 - If newold and mnewold are both NEW then we have identified that two new customers are the same person here,')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_25_note","DQUOTE"),emphasis=N) /*%cxt_ecvMsg('So one of them should disappear - the latest one - mcustomer_sk.');*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_64_note","DQUOTE"),emphasis=N) /*%cxt_ecvMsg('Original matched customer_sk wins: '||put(customer_sk,8.)||'.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_56_note","DQUOTE",put(customer_sk,8.)),emphasis=N) /*%cxt_ecvMsg('Later new matched customer_sk gets superseded: '||put(mcustomer_sk,8.)||'.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_35_note","DQUOTE",put(mcustomer_sk,8.)),emphasis=N) mcustomer_sk=customer_sk; end; else do; customer_sk=mcustomer_sk; insert_dttm=minsert_dttm; /*%cxt_ecvMsg('Incoming record now has customer_sk='||put(customer_sk,8.)||'.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_34_note","DQUOTE",put(customer_sk,8.)),emphasis=N) %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) end; %end; end; else do; /*%cxt_ecvMsg('Incoming record customer_sk unchanged after match, =' ||compress(put(customer_sk,best.))|| '.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_33_note","DQUOTE",compress(put(customer_sk,best.))),emphasis=N) end; /*Flag operand as having been successfully used in a match, so will not use this operand again.*/ match_operands_used{operand_counter}='Y'; save_cur_hash=match_operand_hash;/*Necessary because match_operand_hash will acquire values from incoming cookie hash during checking to see if cookies attached to match are already in incoming cookie hash.*/ save_cur_code=match_operand_code; /*************IF WE MATCHED AGAINST MASTER REC************/ %if &custmatch=N %then %do; matchedmast=1; /*Did we match a master record marked as having previously matched a customer profile record?*/ if mNEWOLD='OLD' then do; matchedcust=1; end; /*Situation where session matches a master rec that originally matched cust profile but has just matched a new master rec too. We do not want the new master to survive, so put id into obliterate dataset.*/ else do; if mNEWOLD='NEW' and newold='OLD' and mcustomer_sk ne customer_sk then do; output work.obliterate; /*%cxt_ecvMsg('Session matched master rec that originally matched cust profile, but just matched new master rec too.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_62_note","DQUOTE"),emphasis=N) /*%cxt_ecvMsg('We do not want the latter to survive afterwards, so put mcustomer_sk in work.obliterate: '||put(mcustomer_sk,8.)||'.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_77_note","DQUOTE",put(mcustomer_sk,8.)),emphasis=N) mNEWOLD='OLD'; end; end; NEWOLD=mNEWOLD;/*I.e. the status of this record takes on the status of the matched record, since MNEWOLD can only change to OLD if matched against a customer record, or a master record that previously matched a customer record.*/ %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) /******************ADD COOKIES FROM MATCHED MASTER REC TO INCOMING COOKIE HASH****************/ /*Need to add cookies of matched cust-id (mcustomer_sk) into the INCOMING COOKIE hash. Look for them by mcustomer_sk in the REVERSE OPERAND MASTER hash.*/ /*Deal with cookies in INCOMING first. We will need to loop through cookies in INCOMING, and, for each, loop through all cookie related operands.*/ mmatch_operand_code=&cookie_oper_code; firstcookie3=0; do until (rcCookie ne 0); /*Look in reverse operand hash on mcustomer_sk and mmatch_operand_code, to get mmatch_operand_hash for cookies associated with this customer id. Each time we find one, we want to add it to INCOMING COOKIE hash if not already there.*/ if firstcookie3=0 then do; %cxt_ecvHashX(roperand,FIND,rcVar=rcCookie,macRcVar=dataErrs,errOK=Y) end; else do; %cxt_ecvHashX(roperand,FIND_NEXT,rcVar=rcCookie,macRcVar=dataErrs,errOK=Y) end; /*No need to use find_next since once we remove found item, we have to look again for first remaining item.*/ if rcCookie=0 then do; firstcookie3=1; /*Is cookie already in incoming cookie hash?*/ firstcookie2=0; foundcookie2=0; do until(rc2 ne 0 or foundcookie2=1/*I.e. either found match in INCOMING or reached end of hash with no find.*/); if firstcookie2=0 then do; /*Look in INCOMING to see if it is already there. Get match_operand_hash.*/ %cxt_ecvHashX(incoming,FIND,rcVar=rc2,macRcVar=dataErrs,errOK=Y) end; else do; /*Look in next record INCOMING to see if it is already there. Get match_operand_hash.*/ %cxt_ecvHashX(incoming,FIND_NEXT,rcVar=rc2,macRcVar=dataErrs,errOK=Y) end; firstcookie2=1; /*Was cookie found in incoming? IF so RC2=0. Now check to see if the mmatch_operand_hash we are looking for is the match_operand_hash we just found in INCOMING.*/ if rc2=0 then do; /*If yes, cookie from reverse cookie, was found in incoming, so set foundcookie2 to 1.*/ if mmatch_operand_hash=match_operand_hash then foundcookie2=1; end; end; /*If cookie was not in incoming cookie hash, add it. Incoming hash uses key match_operand_hash. Cookie we want to add is in mmatch_operand_hash (which is what roperand contains).*/ match_operand_hash=mmatch_operand_hash; if foundcookie2=0 then do; %cxt_ecvHashX(incoming,ADD,rcVar=rc,macRcVar=dataErrs) end; end; end; /******************end ADD COOKIES FROM MATCHED MASTER REC TO INCOMING COOKIE HASH****************/ /*Also, for the rest of the matching process for this incoming record, we do not want to waste processing time matching back to the same master record again, since that would mean we are just matching the same master twice. So we need to flush the operand records in the MASTER hashes (NUNIQUE/UNIQUE and ROPERAND). Once all matching is complete for this incoming record, the complete list of operands will get back out to those two hashes (all cookies will be in the INCOMING COOKIE hash. We need to do this for the customer_sk of the master record just matched, i.e. mcustomer_sk. We can find all operands to be deleted for this MASTER by looking in the REVERSE OPERAND hash by mcustomer_sk and MMATCH_OPERAND_CODE. Each time we pull it, we delete it both from ROPERAND, and then also delete the same record in the ID hashs by match_operand_code and match_operand_hash. Full reasoning: ROPERAND - uses mcustomer_sk mmatch_operand_code as keys and gives mmatch_operand_hash NUNIQUE - uses match_operand_code match_operand_hash as keys and gives mcustomer_sk Loop through match-operand-codes. For each operand: NOTE: for deleting in ROPERAND mcustomer_sk already ok. Set mmatch_operand_code to operI Loop until no matches (loop through matching in ROPERAND on mcustomer_sk mmatch_operand_code (Note - could be no matches since not all operands may have been constructed for this id) To delete in ROPERAND Do find on ROPERAND (mcustomer_sk mmatch_operand_code) If there, pulls in mmatch_operand_hash. Delete in ROPERAND (REMOVEDUP or REMOVE). To delete in master hashes UNIQUE and NUNIQUE We need to do find on MATCH_OPERAND_CODE and MATCH_OPERAND_HASH. Set match_operand_hash to mmatch_operand_hash, and match_operand_code to mmatch_operand_code. Save mcustomer_sk If unique operand, Do find on UNIQUE - should bring in mcustomer_sk, but already saved so no problem. Do REMOVE to kill record in UNIQUE. If non-unique operand, Do find on NUNIQUE - should bring in mcustomer_sk If mcustomer_sk=saved mcustomer_sk, then do REMOVEDUP. (same operand value could be for multiple ids since non-unique)*/ /*Save matched customer-id since could get reassigned during finds on hashes.*/ save_custid=mcustomer_sk; /*Now loop through operands.*/ do operI=1 to &operc; /*Find match for customer-id and match-operand in ROPERAND hash, on mmatch_operand_code and mcustomer_sk.*/ mmatch_operand_code=symgetn('mo_code'||left(put(operI,best.))); mUnique=symget('mo_uniq'||left(put(mmatch_operand_code,best.))); /*Need to loop since could be more than one match.*/ do until (rc_roperand ne 0); %cxt_ecvHashX(roperand,FIND,rcVar=rc_roperand,macRcVar=dataErrs,errOK=Y) /*Not all operands will be in there, so okay if no value returned. If record returned, then mmatch_operand_hash is returned.*/ if rc_roperand=0 then do; /*Found an operand record for this type of operand. Now have information to use it to look it up in the UNIQUE or NUNIQUE hash. But first delete it in the ROPERAND hash.*/ /*If unique operand, do REMOVE. If non-unique, do REMOVEDUP.*/ if munique='Y' then do; %cxt_ecvHashX(roperand,REMOVE,rcVar=rc2,macRcVar=dataErrs) end; else do; %cxt_ecvHashX(roperand,REMOVEDUP,rcVar=rc2,macRcVar=dataErrs) end; /*Now look up in UNIQUE or NUNIQUE by match_operand_hash and match_operand_code.*/ match_operand_hash=mmatch_operand_hash; match_operand_code=mmatch_operand_code; /*Unique operands.*/ if munique='Y' then do; findHashMarker=6;/*- if hash error the put _all_ will let us identify which call.*/ /*shashi add errOK=Y in cxt_ecvHashX call to avoid data step erroring out*/ /*%cxt_ecvHashX(unique,FIND,rcVar=rc3,macRcVar=dataErrs)*/ %cxt_ecvHashX(unique,FIND,rcVar=rc3,macRcVar=dataErrs,errOK=Y) if rc3=0 then do; /*If found, delete it.*/ %cxt_ecvHashX(unique,REMOVE,rcVar=rc4,macRcVar=dataErrs) end; end; /*Now non-unique*/ else do; /*Will have to loop through non-unique until we find record with the correct customer-id.*/ first_find=0; do until (rc3 ne 0); if first_find=0 then do; findHashMarker=7;/*- if hash error the put _all_ will let us identify which call.*/ /*shashi add errOK=Y in cxt_ecvHashX call to avoid data step erroring out */ /*%cxt_ecvHashX(nunique,FIND,rcVar=rc3,macRcVar=dataErrs)*/ %cxt_ecvHashX(nunique,FIND,rcVar=rc3,macRcVar=dataErrs,errOK=Y) end; else do; findHashMarker=8;/*- if hash error the put _all_ will let us identify which call.*/ %cxt_ecvHashX(nunique,FIND_NEXT,rcVar=rc3,macRcVar=dataErrs) end; if rc3=0 then do; first_find=1; /*Is this the correct record?*/ if mcustomer_sk=save_custid then do; %cxt_ecvHashX(nunique,REMOVEDUP,rcVar=rc4,macRcVar=dataErrs) /*Set rc3 to non 0 so can exit loop.*/ rc3=99; end; end; end; end; /*Restore mcustomer_sk*/ mcustomer_sk=save_custid; end; end; end;/*End loop through operands to pull operand records to delete.*/ %end; /*************IF THIS MATCH WAS AGAINST A CUST PROFILE REC************/ %else %if &custmatch=Y %then %do; %cxt_ecvHashX(ignore,ADD,rcVar=rc,macRcVar=dataErrs) /*Put matched mcustomer_sk into IGNORE hash so we will not use it again.*/ matchedcust=1; %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) if NEWOLD='OLD' then do; /*%cxt_ecvMsg('Matched record customer_sk=' ||compress(put(mcustomer_sk,best.))|| ' will "die".',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_41_note","DQUOTE",compress(put(mcustomer_sk,best.))),emphasis=LIGHT) nonsurvivor_cust_dim=mcustomer_sk; %cxt_ecvHashX(change,ADD,rcVar=rc,macRcVar=dataErrs)/*Add non_survivor_cust_dim to CHANGE hash which will just contain all non-survivors for this incoming record.*/ end; /*NEWOLD indicates the match status of the incoming record. It is NEW if it has never matched against a customer record, or another master that matched a customer record. Here, since we matched a customer profile record, we set it to OLD.*/ NEWOLD='OLD'; %cxt_ecvIncRecDebug(newold mnewold customer_sk mcustomer_sk matchedcust matchedmast minsert_dttm insert_dttm) /******************ADD COOKIES FROM MATCHED CUST REC TO INCOMING COOKIE HASH****************/ /*Need to add all cookies associated with the matched cust-id record to the INCOMING COOKIE hash. Get them from non-unique ids table. Set match code to the one for cookie to match just those records.*/ _iorc_=0; mmatch_operand_code=&cookie_oper_code; cookieCount=0;/*Must be at least one cookie retrieved. If not we give an error.*/ do until(_iorc_ ne 0); /*We search that table for the ones for match_operand_code for cookie, and the matched customer-id.*/ /*%cxt_ecvMsg('Implementing keyed search against non-unique ids customer dataset for cookies, keys ' ||"mmatch_operand_code="||compress(mmatch_operand_code)||"mcustomer_sk ="||compress(put(mcustomer_sk,best.))|| '.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_31_note","DQUOTE",compress(mmatch_operand_code),compress(put(mcustomer_sk,best.))),emphasis=N) set &nuniqueSource(keep=customer_sk match_operand_code match_operand_hash rename=(customer_sk=mcustomer_sk match_operand_code=mmatch_operand_code match_operand_hash=mmatch_operand_hash )) key=custcook; /*%cxt_ecvMsg('return code from keyed search was '||compress(put(_iorc_,best.))|| '.');*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_59_note","DQUOTE",compress(put(_iorc_,best.))),emphasis=N) select(_iorc_); when (%sysrc(_sok)) do; /*%cxt_ecvMsg('match found.');*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_39_note","DQUOTE"),emphasis=N) cookieCount=cookieCount+1; /*Add cookie to incoming cookie hash if it is not already there.*/ firstcookie=0; foundcookie=0; /*Look to see if it is in incoming cookie hash, by doing find and find_next on sillykey (always 1). We are looking to see if our cookie pulled from the ids table (mmatch_operand_hash) is one of the match_operand_hash values in the incoming cookie hash.*/ do until(rc ne 0 or foundcookie=1); if firstcookie=0 then do; %cxt_ecvHashX(incoming,FIND,rcVar=rc,macRcVar=dataErrs,errOK=Y) end; else do; %cxt_ecvHashX(incoming,FIND_NEXT,rcVar=rc,macRcVar=dataErrs,errOK=Y) end; if rc=0 then do; /*We have pulled a cookie from incoming, for comparison.*/ firstcookie=1; /*Note that mmatch_operand_hash is the value of the cookie we pulled from the non-unique ids table for this customer.*/ if mmatch_operand_hash=match_operand_hash then foundcookie=1; end; end; /*If looped through all cookies in the incoming cookie hash, and did not find the cookie we pulled from the ids table (for our matched record), then add it to incoming.*/ if foundcookie=0 then do; /*Set match_operand_hash to the value of the cookie we retrieved from the non-unique id table, and add it to the incoming cookie hash.*/ match_operand_hash=mmatch_operand_hash; %cxt_ecvHashX(incoming,ADD,rcVar=rc,macRcVar=dataErrs) end; end; otherwise do; if cookieCount=0 then do; /* The observation does not exist in the master data set. */ /*put 'ERR' 'OR: No cookies pulled for matching record. This is not possible.';*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ do;length _put_rec_ $1000;drop _put_rec_;_put_rec_=sasmsg("&ecv_cxaMsgDset","_ecverrormsg_label","DQUOTE")|| sasmsg("&ecv_cxaMsgDset","_cxaecv_match_43_error","DQUOTE");put _put_rec_;end; /*put 'ERR' 'OR: Aborting macro.';*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ do;length _put_rec_ $1000;drop _put_rec_;_put_rec_=sasmsg("&ecv_cxaMsgDset","_ecverrormsg_label","DQUOTE")|| sasmsg("&ecv_cxaMsgDset","_cxaecv_errchk_1_error","DQUOTE");put _put_rec_;end; dataErrs=dataErrs+1; go to endDstep; end; else do; %cxt_ecv_genWordErr(mode=ERRSET0) /*%cxt_ecvMsg('No more cookies in non-unique operands table for this matched customer. This is okay.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_48_note","DQUOTE"),emphasis=N) end; end; end; end; /******************END ADD COOKIES FROM MATCHED CUST REC TO INCOMING COOKIE HASH****************/ %end; /*Restore values*/ match_operand_hash=save_cur_hash; match_operand_code=save_cur_code; /*********************UPDATE FIELDS*****************************/ madeChanges=0;/*Might be needed for future enhancements.*/ do loopFields=1 to &count_fields; if /*Situations are as follows: - match against customer profile update field if its _updates_ field='' and value of field from match ne '' -i.e. never update field from customer profile unless it is null on incoming record since incoming record always has most recent information. - match against master - incoming field null then update from match - incoming field not null -and flagged as new, and master also flagged as new then update from master (since always take first matching master values) -and flagged as new, and master flaged as old do not update incoming field -and flagged as old, and master flagged as NEW update from master -and flagged as old, and master flagged as old do not update Boils down to update if matched master is NEW. */ /*In all cases we update if incoming null, and match not.*/ (missing(_renams_{loopFields}) /*Field null on incoming record.*/ and _names_{loopFields} ne 'COOKIE_ONLY_FLG'/*Do not update this field this way - gets set at end.*/ and not missing(_vals_{loopFields})) /*Not null on matched record.*/ %if &custmatch=N %then %do; or ( not missing(_renams_{loopFields}) and mupdates_{loopFields}='N' ) %end; /***************************************************/ /*Place holder for additional then ifs. One such will be a check per field of the most recent update date. There may also be future sanitation checks - i.e. do not update tel no if data just has a misplaced digit etc.*/ /*then if*/ /***************************************************/ then do; %cxt_ecvIncRecDebug(UPDATEINC,mode=UPDATE) _renams_{loopFields}=_vals_{loopFields}; %if &custMatch=Y %then %do; _updates_{loopFields}='O'; %end; %else %do; _updates_{loopFields}=mupdates_{loopFields}; %end; madeChanges=madeChanges+1; end; end; /*%cxt_ecvMsg('Updated '||left(trim(madeChanges))|| ' fields in incoming record from match.')*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_73_note","DQUOTE",left(trim(madeChanges))),emphasis=N) /*********************end UPDATE FIELDS*************************/ end; /*%cxt_ecvMsg('Exiting CXAprocessCustomerMatch.',emphasis=LIGHT)*//*keadam 20131015 S0946771*/ /*keadam 20131015 S0946771*//*Replacement messaging call is below.*/ %cxt_ecvMsg(sasmsg("&ecv_cxaMsgDset","_cxaecv_match_18_note","DQUOTE"),emphasis=LIGHT) %mend cxt_ecvProcessMatch;