<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>The Enforcer.net &#187; Data Loader</title>
	<atom:link href="http://theEnforcer.net/category/data-loader/feed/" rel="self" type="application/rss+xml" />
	<link>http://theEnforcer.net</link>
	<description>a force.com blog</description>
	<lastBuildDate>Fri, 30 Jul 2010 07:02:18 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Forcing DataLoader to extract numbers when using databaseWrite</title>
		<link>http://theEnforcer.net/2010/06/forcing-dataloader-to-extract-numbers-when-using-databasewrite/</link>
		<comments>http://theEnforcer.net/2010/06/forcing-dataloader-to-extract-numbers-when-using-databasewrite/#comments</comments>
		<pubDate>Wed, 09 Jun 2010 07:34:34 +0000</pubDate>
		<dc:creator>The Enforcer</dc:creator>
				<category><![CDATA[Data Loader]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://theEnforcer.net/?p=237</guid>
		<description><![CDATA[Oh boy, I love debugging!
And today I really had to debug DataLoader!
I do a lot of stuff with DataLoader — see my previous blogs on the topic. But I&#8217;ve always uploaded to Salesforce from a PostgreSQL database — never extracted.
DataLoader allows a job to be configured where a SOQL query is run, the data is [...]]]></description>
			<content:encoded><![CDATA[<p>Oh boy, I love debugging!</p>
<p>And today I really had to debug DataLoader!</p>
<p>I do a lot of stuff with DataLoader — see my <a href="http://theenforcer.net/category/data-loader/">previous blogs on the topic</a>. But I&#8217;ve always uploaded <em>to</em> Salesforce from a PostgreSQL database — never <em>extracted</em>.</p>
<p>DataLoader allows a job to be configured where a SOQL query is run, the data is extracted from Salesforce and the results are inserted into a database. The documentation is woeful on this whole topic, but fortunately DataLoader comes with sample <code>process-conf.xml</code> and <code>database-conf.xml</code> files that contain some examples.</p>
<p>The basic method is:</p>
<ul>
<li><code>process-conf.xml</code> defines the job and includes the SOQL in a <code>extractionSOQL</code> parameter</li>
<li>An <code>SDL</code> file defines the field mapping between Salesforce and the output</li>
<li><code>database-conf.xml</code> defines the SQL statement to use, plus the parameter types</li>
</ul>
<p><strong>First hint:</strong> The Field names in the <code>SDL</code> file are case-sensitive. Use &#8220;ID&#8221; instead of &#8220;Id&#8221; and the field won&#8217;t come across.</p>
<p><strong>Second hint:</strong> All fields come across in quotes. This totally destroys any attempt to load numbers!</p>
<p>Here&#8217;s part of the database-conf.xml that I used:</p>
<pre class="brush: plain;">
&lt;bean id=&quot;extractMegaphoneQuery&quot;
      class=&quot;com.salesforce.dataloader.dao.database.SqlConfig&quot; singleton=&quot;true&quot;&gt;
    &lt;property name=&quot;sqlString&quot;&gt;
        &lt;value&gt;
            INSERT INTO renewals_megaphone (
               period, megaphone_date, opportunity_name, reason_lost, owner, expiry_date, product, years_owned)
            VALUES (@period@, @megaphone_date@, @opportunity_name@, @reason_lost@, @owner@,
                    @expiry_date@, @product@, @years_owned@)
        &lt;/value&gt;
    &lt;/property&gt;
    &lt;property name=&quot;sqlParams&quot;&gt;
        &lt;map&gt;
            &lt;entry key=&quot;period&quot;           value=&quot;bigint&quot;/&gt;
            &lt;entry key=&quot;megaphone_date&quot;   value=&quot;java.sql.Date&quot;/&gt;
            &lt;entry key=&quot;opportunity_name&quot; value=&quot;java.lang.String&quot;/&gt;
            &lt;entry key=&quot;reason_lost&quot;      value=&quot;java.lang.String&quot;/&gt;
            &lt;entry key=&quot;owner&quot;            value=&quot;java.lang.String&quot;/&gt;
            &lt;entry key=&quot;expiry_date&quot;      value=&quot;java.sql.Date&quot;/&gt;
            &lt;entry key=&quot;product&quot;          value=&quot;java.lang.String&quot;/&gt;
            &lt;entry key=&quot;years_owned&quot;      value=&quot;integer&quot;/&gt;
        &lt;/map&gt;
    &lt;/property&gt;
&lt;/bean&gt;
</pre>
<p>You&#8217;ll notice I have two fields that are numbers — <code>period</code> (bigint) and <code>years_owned</code> (integer). When trying to load these fields into my database, I kept getting the message:</p>
<p><code>Sql error: ERROR: column "period" is of type bigint but expression is of type character varying.<br />
org.postgresql.util.PSQLException: ERROR: <span style="color: #ff0000;">column "period" is of type bigint but expression is of type character varying</span></code></p>
<p>There was no way that I could convince DataLoader that the field was numeric — I tried lots of <code>java.lang.Integer</code> type entries in the <code>sqlParams</code>, but didn&#8217;t help.</p>
<p>I then changed the output to <code>csvWrite</code> instead of <code>databaseWrite</code> so that I could see the output, and it came through like this:</p>
<p><code>"PERIOD","MEGAPHONE_DATE","OPPORTUNITY_NAME","REASON_LOST","OWNER","EXPIRY_DATE","PRODUCT","YEARS_OWNED"<br />
"<span style="color: #ff0000;">83823.0</span>","2010-06-08","Joe Smith","Invalid","John","2010-03-14","a0624004000qRtkgA2","<span style="color: #ff0000;">3.0</span>"</code></p>
<p>You&#8217;ll notice that the first and last columns are meant to be numbers, but they are coming through in quotation marks. That&#8217;s what was causing the database load to fail!</p>
<p>So, I updated my SQL to convert the strings to numbers:</p>
<pre class="brush: plain;">
    &lt;property name=&quot;sqlString&quot;&gt;
        &lt;value&gt;
            INSERT INTO renewals_megaphone (
               period, megaphone_date, opportunity_name, reason_lost, owner, expiry_date, product, years_owned)
            VALUES (@period@::numeric, @megaphone_date@, @opportunity_name@, @reason_lost@, @owner@, @expiry_date@, @product@, @years_owned@::numeric)
        &lt;/value&gt;
    &lt;/property&gt;
</pre>
<p>This successfully cast the strings into numbers and they finally got stored in the database (which automatically ignored the decimal places when converting to <code>bigint</code> and <code>integer</code>).</p>
<p>I hope the blog post saves other people similar problems!</p>
<h3>The Bottom Line</h3>
<ul class="nomargin">
<li>DataLoader can extract data and insert it into a database</li>
<li>Numbers are quoted on export and cause database errors</li>
<li>I type-cast the <code>string</code> into <code>numeric</code> to enable the database to load it</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://theEnforcer.net/2010/06/forcing-dataloader-to-extract-numbers-when-using-databasewrite/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Making Owner available in Formulas</title>
		<link>http://theEnforcer.net/2010/06/making-owner-available-in-formulas/</link>
		<comments>http://theEnforcer.net/2010/06/making-owner-available-in-formulas/#comments</comments>
		<pubDate>Tue, 08 Jun 2010 07:37:26 +0000</pubDate>
		<dc:creator>The Enforcer</dc:creator>
				<category><![CDATA[Apex]]></category>
		<category><![CDATA[Data Loader]]></category>
		<category><![CDATA[Force.com IDE]]></category>
		<category><![CDATA[formulas]]></category>

		<guid isPermaLink="false">http://theEnforcer.net/?p=228</guid>
		<description><![CDATA[I was configuring DataLoader to export a list of Opportunities, and I went to select the &#8220;Owner Name&#8221;. However, only OwnerID is available on an Opportunity.
&#8220;No problem!&#8221; I think to myself, as I go and create a Custom Field with a formula equal to Owner.Alias.
&#8220;What?&#8221; I say in surprise. &#8220;It won&#8217;t let me access a [...]]]></description>
			<content:encoded><![CDATA[<p>I was configuring DataLoader to export a list of Opportunities, and I went to select the &#8220;Owner Name&#8221;. However, only <code>OwnerID</code> is available on an Opportunity.</p>
<p>&#8220;No problem!&#8221; I think to myself, as I go and create a Custom Field with a formula equal to <code>Owner.Alias</code>.</p>
<p>&#8220;What?&#8221; I say in surprise. &#8220;It won&#8217;t let me access a field on the Owner object!&#8221;</p>
<p><img class="aligncenter size-full wp-image-229" title="NoLink" src="http://theEnforcer.net/wp-content/uploads/2010/06/NoLink.gif" alt="NoLink" width="302" height="136" /></p>
<p>Mmm. This is strange. Then a Google Search reveals an 18-month old Ideas request to <a href="http://sites.force.com/ideaexchange/apex/ideaview?id=08730000000BrqaAAC" target="_blank">Make &#8220;Owner Id&#8221; Look up fields available for formulas</a>.</p>
<p>Oh dear.</p>
<p>Well, that&#8217;s a shame, but it&#8217;s easily solved! I created:</p>
<ul>
<li>A field called <code>Owner_Link__c</code> of type <code>Lookup(User)</code></li>
<li>A Trigger to copy<code> OwnerId</code> to <code>Owner_Link__c</code> when the Owner is changed</li>
<li>A test for the Trigger</li>
</ul>
<p><strong>Trigger:</strong></p>
<pre class="brush: java;">
trigger Update_OwnerLink_on_Owner_Update on Opportunity (before update, before insert) {

  // When 'Owner' field is changed, update 'OwnerLink' too

	// Loop through the incoming records
	for (Opportunity o : Trigger.new) {

		// Has Owner chagned?
		if (o.OwnerID != o.Owner_Link__c) {
			o.Owner_Link__c = o.OwnerId;
		}
	}
}
</pre>
<p><strong>Test:</strong></p>
<pre class="brush: java;">
public with sharing class TriggerTest_OwnerLink {

	static TestMethod void testOwnerLink() {

		// Grab two Users
		User[] users = [select Id from User limit 2];
		User u1 = users[0];
		User u2 = users[1];

		// Create an Opportunity
		System.debug('Creating Opportunity');
		Opportunity o1 = new Opportunity(CloseDate = Date.newInstance(2008, 01, 01), Name = 'Test Opportunity', StageName = 'New', OwnerId = u1.Id);
		insert o1;

		// Test: Owner_Link should be set to user 1
		Opportunity o2 = [select id, OwnerId, Owner_Link__c from Opportunity where Id = &amp;#58o1.Id];
		System.assertEquals(u1.Id, o2.OwnerId);
		System.assertEquals(u1.Id, o2.Owner_Link__c);

		// Modify Owner
		o2.OwnerId = u2.Id;
		update o2;

		// Test: Owner_Link should be set to user 2
		Opportunity o3 = [select id, OwnerId, Owner_Link__c from Opportunity where Id = &amp;#58o2.Id];
		System.assertEquals(u2.Id, o3.OwnerId);
		System.assertEquals(u2.Id, o3.Owner_Link__c);
	}
}
</pre>
<p>This then gave me a new Owner object on my Opportunity on which I could create Formulas:</p>
<p><img src="http://theEnforcer.net/wp-content/uploads/2010/06/Result.png" alt="Result" title="Result" width="626" height="139" class="aligncenter size-full wp-image-234" /></p>
<p>I could also use it in the DataLoader by referring to <code>Owner_Link__r.Alias</code>.</p>
<p>Hooray!</p>
<p>Easy to solve, but it&#8217;s a shame it was necessary.</p>
<h3>The Bottom Line</h3>
<ul class="nomargin">
<li>Formulas can&#8217;t access <code>Opportunity.Owner</code> fields</li>
<li>Create a &#8217;shadow&#8217; field to hold Owner and populate it via a Trigger</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://theEnforcer.net/2010/06/making-owner-available-in-formulas/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Loading Contacts into Campaigns</title>
		<link>http://theEnforcer.net/2008/08/loading-contacts-into-campaigns/</link>
		<comments>http://theEnforcer.net/2008/08/loading-contacts-into-campaigns/#comments</comments>
		<pubDate>Mon, 18 Aug 2008 05:28:22 +0000</pubDate>
		<dc:creator>The Enforcer</dc:creator>
				<category><![CDATA[Data Loader]]></category>
		<category><![CDATA[campaigns]]></category>

		<guid isPermaLink="false">http://theenforcer.net/?p=50</guid>
		<description><![CDATA[Today I had to create campaigns to track Trade Show attendees. I&#8217;ve written previously about creating campaigns from Webex attendee files, but my Trade Show source files were not as friendly. Each Trade Show organiser gave us lists in different formats — the best one contained complete Name, Address, Company, Title and Email information, while [...]]]></description>
			<content:encoded><![CDATA[<p>Today I had to create campaigns to track <strong>Trade Show attendees</strong>. I&#8217;ve <a href="http://theenforcer.net/2008/07/webex/">written previously</a> about creating campaigns from Webex attendee files, but my Trade Show source files were not as friendly. Each Trade Show organiser gave us lists in <strong>different formats</strong> — the best one contained complete Name, Address, Company, Title and Email information, while the worst had just First Name and Email (yes, really!).</p>
<p>My requirements were:</p>
<ul>
<li>Load information from <strong>disparate source documents</strong></li>
<li>Create <strong>Contacts</strong>, not Leads (we don&#8217;t use Leads)</li>
<li><strong>Don&#8217;t overwrite</strong> existing Contact records (existing customers are populated from an External system, with more accurate information than Trade Show visitor data)</li>
<li>Add them to a <strong>Campaign</strong> for tracking purposes</li>
</ul>
<h3>Importing Contacts</h3>
<p>The first step was quite easy with the help of the <strong>Salesforce.com Data Loader</strong> (known as <strong>LexiLoader</strong> on the Mac). It can import Contact records from a CSV file. The trick here, however, was to use <strong>INSERT rather than UPSERT</strong>. My system is configured to <a href="http://theenforcer.net/2008/08/using-email-as-an-external-id-for-contacts/">use Email as an External ID for Contacts</a>, so the import will fail for records where there is an existing Contact with the same Email address. This is good — it lets me create new records only where the Contact already does not exist.</p>
<p>Also as part of the import, I added a column for <strong>RecordTypeId</strong>. This is because I have two Record Types for my contacts:</p>
<ul>
<li><strong>Manually</strong> created contacts (which let users edit most fields) — this is the one I selected, and</li>
<li><strong>Automatically</strong> created contacts (which come from my external system, and don&#8217;t let users edit fields since they are slaved from my external system)</li>
</ul>
<p>I also added a column for <strong>AccountId</strong>, which is quite important. Any Contact records that aren&#8217;t linked to an Account are only visible to the record creator. In our case, we don&#8217;t actually use Accounts (I&#8217;ll blog about that in future), so I just linked all the new Contacts to a &#8216;Default Account&#8217; to make them visible.</p>
<h3>Adding Contacts to Campaigns</h3>
<p>There&#8217;s lots of ways to add Campaign Members to Campaigns, either from Lead/Contact records or from the Campaign record. In this case, I wanted to add them in bulk, so I used the <strong>Manage Members</strong> button:</p>
<p><img class="aligncenter" title="Manage Members" src="http://theenforcer.net/wp-content/uploads/2008/08/managecampaigns.png" alt="" width="576" height="258" /></p>
<p>To load in bulk, I wanted to use an &#8220;Import File&#8221; capability, but I didn&#8217;t want to load Leads. So, the <strong>Add Members &#8211; Import File</strong> option was no good, since it only loads records as Leads. Instead, I used the <strong>Update Status &#8211; Import File</strong> option which can not only <strong>update</strong> a status, but also <strong>associate a Contact with a Campaign</strong>. In other words, it can take existing Contacts and add them to a Campaign — Perfect!</p>
<p>Unfortunately, <strong>Import File</strong> wants a file that contains the <strong>Salesforce Contact ID</strong>. This is a problem, since the ID is generated within the Force.com platform and is not in my CSV file. The online hints for that command suggest creating a Report on campaign members, saving as CSV then importing them to change the status. This is not an option for me since some of the people are already in Salesforce.com as existing Contacts and others I&#8217;ve just created. So, there&#8217;s no identifier that lets me select them all in a Report.</p>
<p>So, here&#8217;s a trick&#8230; After using DataLoader, two files are created. A <strong>success file </strong>contains a copy of all records that were successfully loaded, <strong>with a new ID column inserted</strong>. I can use this file with the Import command to update all the statuses. This takes care of all the Contacts that were created during the original Insert of Contacts. Fantastic!</p>
<p>But what about the Contacts that already existed, and therefore caused an error during the Insert? Well, the ID is actually supplied in the error message (eg <code>duplicate value found: EmailAddr__c duplicates value on record with id: 0032000000Fs7Xp</code>) and can be extracted using a RIGHT(B2,15) type of spreadsheet command. Just put that value in a new column, import the file and you&#8217;re done!</p>
<p>(To slightly simplify things, it&#8217;s also possible to load just one file — the original CSV file! Since all records now exist, every row will generate an error. You can then just process the <code>error</code> file rather than separately loading the <code>success</code> and <code>error</code> files.)</p>
<p><img class="aligncenter size-full wp-image-52" title="Mass Update Anything" src="http://theenforcer.net/wp-content/uploads/2008/08/massupdate.png" alt="" width="364" height="254" /><br />
I&#8217;ll admit that during this process I stuffed up — I imported attendees to the wrong campaign. To remove them, I could have deleted the Campaign and started again. Instead, I used a favourite utility called <a href="http://www.salesforce.com/appexchange/detail_overview.jsp?id=a033000000331IjAAI">Mass Delete Anything</a> (there&#8217;s a Windows and a Mac version). I just created an appropriate SOQL command to identify the offending records, then Mass Deleted them. Very handy, but also very dangerous. Use with care on your Production systems.</p>
<p>The end result of all this&#8230; My Campaigns can now report counts of Total Contacts for the Trade Shows. When combined with a Trigger I&#8217;ve got that identifies people when they purchase products, I can track the conversion rate for Trade Show attendees, all without having to manually type any Contact information.</p>
<h3>The Bottom Line</h3>
<ul class="nomargin">
<li>You can&#8217;t import Contacts directly into Campaigns, but you can import a list of existing Contacts to <em>link</em> to a Campaign</li>
<li>DataLoader saves the day again, especially with the information saved in its <code>success</code> and <code>error</code> output files</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://theEnforcer.net/2008/08/loading-contacts-into-campaigns/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Using Email as an External ID for Contacts</title>
		<link>http://theEnforcer.net/2008/08/using-email-as-an-external-id-for-contacts/</link>
		<comments>http://theEnforcer.net/2008/08/using-email-as-an-external-id-for-contacts/#comments</comments>
		<pubDate>Thu, 14 Aug 2008 05:29:16 +0000</pubDate>
		<dc:creator>The Enforcer</dc:creator>
				<category><![CDATA[Data Loader]]></category>

		<guid isPermaLink="false">http://theenforcer.net/?p=46</guid>
		<description><![CDATA[I love upserts. They are a combination of Inserts and Updates. The key to using Upsert is to nominate a field that contains an unique identifier for the record. While importing, if a record already exists with the same identifier, the record is Updated. If no such record exists, a new record is Inserted.
A good [...]]]></description>
			<content:encoded><![CDATA[<p>I love <strong>upserts</strong>. They are a combination of Inserts and Updates. The key to using <strong>Upsert</strong> is to nominate a field that contains an <em>unique identifier</em> for the record. While importing, if a record already exists with the same identifier, the record is <strong>Updated</strong>. If no such record exists, a new record is <strong>Inserted</strong>.</p>
<p>A good identifier is, of course, the Salesforce ID that is assigned to each record. However, there&#8217;s two significant problems in using the Salesforce ID:</p>
<ul>
<li>It varies across instances (eg sandbox, production, developer account), which means that you can&#8217;t import the same IDs in different systems</li>
<li>It is generated by the Force.com platform — you can&#8217;t create it yourself</li>
</ul>
<h3>The Good News</h3>
<p>Enter the <strong>External ID</strong>. This is a flag that can be set on Custom Fields that identifies a field as being an Unique Identifer, and allows that field to be used in Upserts.</p>
<p><img class="aligncenter" title="External ID" src="http://theenforcer.net/wp-content/uploads/2008/08/externalid.png" alt="" width="653" height="181" /></p>
<p>My favourite use for an External ID is in storing the unique identifier from an External System (eg a customer database). If I load my customers into Salesforce.com, I can then use Upsert to reload them next time without creating duplicate records. The external system doesn&#8217;t even need to know that Salesforce exists, and certainly doesn&#8217;t have to store the Salesforce IDs.</p>
<h3>The Bad News</h3>
<p>I load a lot of customer information, and the best identifier for customers is their Email Address. Unfortunately, the Email field on Contact records is a <strong>Standard field</strong> and only <strong>Custom fields</strong> can be defined as an External ID.</p>
<p>To get around this limitation, I&#8217;ve created my own Email field (<code>EmailAddr__c</code>) that I define as an External ID. However, this leads to further problems:</p>
<ul>
<li>Salesforce.com uses the normal &#8220;Email&#8221; field for sending emails (obvious, eh?)</li>
<li>Lots of existing lists, views and reports use the standard Email field</li>
<li>Things get very confusing when you have more than one &#8216;Email&#8217; field</li>
</ul>
<p>So, I created a Workflow that keeps the two fields in Sync. I import into the <code>EmailAddr__c</code> field and then get the workflow to update the stadnard <code>Email</code> field.</p>
<p><img class="aligncenter" title="Workflow" src="http://theenforcer.net/wp-content/uploads/2008/08/emailworkflow1.png" alt="" width="733" height="176" /></p>
<p><img class="aligncenter" title="Field Update" src="http://theenforcer.net/wp-content/uploads/2008/08/emailworkflow2.png" alt="" width="736" height="204" /></p>
<p>I also change the label of the standard <code>Email</code> field to &#8220;<code>(Unused Email Field)</code>&#8221; so that I don&#8217;t accidentally select it in Page Layouts.</p>
<p>What&#8217;s that you say? Why don&#8217;t I just use the normal <code>Email</code> field since the Workflow sets it to the same value? Well, the answer lies in the <strong>field type</strong>. The standard <code>Email</code> field is of type &#8220;Email&#8221;, which means that Salesforce.com performs validation to ensure the address &#8216;looks like&#8217; an email field. However, my external system does not have similar checking. Therefore, I want to use the email address from my external system as an unique identifier <em>even if it is a badly formatted address</em>.</p>
<p>I&#8217;ve found that by importing directly into the Email field, the Data Loader will reject a record if it is a badly-formed email address, but copying it from my <code>EmailAddr__c</code> field into the Email field seems to avoid this checking. At times I also had the situation where <code>EmailAddr__c</code> had a value, but Email was blank. So, I find it a lot safer to use my custom email field, since I know it will always have a value. (We&#8217;ve since improved our external system to do similar validation on the email field, but we&#8217;ve still got lots of historical records with the problem.)</p>
<h3>The Bottom Line</h3>
<ul class="nomargin">
<li>We can now use an Email Address as an External ID</li>
<li>We can now Upsert on Email Addresses</li>
<li>It&#8217;s a little messy, but it works</li>
<li>I wish Salesforce.com would let us define Standard fields as External IDs</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://theEnforcer.net/2008/08/using-email-as-an-external-id-for-contacts/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Missed Opportunities (A case for data cleansing)</title>
		<link>http://theEnforcer.net/2008/05/missed-opportunities-a-case-for-data-cleansing/</link>
		<comments>http://theEnforcer.net/2008/05/missed-opportunities-a-case-for-data-cleansing/#comments</comments>
		<pubDate>Wed, 14 May 2008 07:04:19 +0000</pubDate>
		<dc:creator>The Enforcer</dc:creator>
				<category><![CDATA[Data Loader]]></category>

		<guid isPermaLink="false">http://theenforcer.net/?p=19</guid>
		<description><![CDATA[I arrived at work today to get a mysterious message from one of my users. Opportunities, for some strange reason, were suddenly appearing in the system and being marked as already Won!
While this is great from a sales perspective (we all like Won opportunities!), it should not have been happening from a data perspective. Perhaps [...]]]></description>
			<content:encoded><![CDATA[<p>I arrived at work today to get a <strong>mysterious message</strong> from one of my users. Opportunities, for some strange reason, were suddenly appearing in the system and being marked as <strong>already Won</strong>!</p>
<p>While this is great from a sales perspective (we all like Won opportunities!), it should not have been happening from a data perspective. Perhaps a bit of explanation is in order&#8230;</p>
<div class="pbSubheader tertiaryPalette">
<h3>What it Does</h3>
</div>
<p>My employer (<a href="http://www.atlassian.com/">a great Australia software company</a>) sells software. Actually, since people can download and trial our software for free, it would be more accurate to say that we sell <strong>software licenses</strong>. Each year, we encourage our customers to renew their software maintenance so that they can get support and upgrades. So, our primary use for Salesforce.com is to <strong>track Renewal Opportunities</strong>.</p>
<p>These opportunities are perfect for <strong>automation</strong>. Our existing licensing system keeps track of customers, so I use the <strong>Salesforce.com Data Loader</strong> to import a list of products that have expired and we have dedicated <strong>Renewal Specialists</strong> who contact the customers to see if they would like to renew. We typically get around 70% renewing, so they must like our software! (Unlike other Enterprise Software companies, they can keep using our software even if they don&#8217;t pay annual maintenance.)</p>
<div class="pbSubheader tertiaryPalette">
<h3>How it Works</h3>
</div>
<p>The basic system is:</p>
<ul>
<li>An <strong>SQL view</strong> on our customer database looks for expired licenses</li>
<li>The <strong>Data Loader </strong>uses this view to extract information from the database and load it directly into Salesforce as an <strong>Opportunity</strong> object</li>
<li>I use an <strong>Upsert</strong> operation with my licensing system supplying an <strong>External ID</strong>. This means that I can load new and existing opportunities in one batch, without my external system even having to know about Salesforce. As long as I supply the same External ID, Salesforce will <strong>automatically update existing records or create new ones</strong> if they don&#8217;t exist.</li>
<li>The data is <strong>reloaded overnight</strong> to show any information that has updated or any Opportunities that have closed. In fact, the only way to mark an Opportunity as Won is via the DataLoader &#8212; staff can&#8217;t claim a Closed/Won situation unless the external system says it has actually been sold.</li>
</ul>
<p>So, why did we start getting Opportunities appearing long after expiry, when they had already been won?</p>
<p>Well, I tracked it down to the fact that I&#8217;m only creating Opportunities for customers in the <strong>Americas region</strong>, since that&#8217;s where our Renewal Specialist staff are located. It appears that some of our customers had incorrect address information so the system couldn&#8217;t understand their <strong>Country</strong>. My little SQL function that converts Country into Region didn&#8217;t realise that the Opportunities were in the Americas Region since they were missing the right country information.</p>
<p>Fortunately, we&#8217;ve had some staff <strong>manually cleaning our customer data</strong>. It just happened that they fixed the address information for a couple of customers who had expired licenses. This then caused the Opportunity to <em>magically</em> appear in Salesforce &#8212; later than should have happened. I managed to track this down from some &#8216;Modified date&#8217; fields we&#8217;ve got on our data that I use for incremental loading (more on that topic another day). Plus, the automatic &#8216;Created by&#8217; field on records gave great information on <em>when</em> records appeared.</p>
<h3>The Bottom Line</h3>
<ul class="nomargin">
<li>Clean data is good</li>
<li>Automation is great, but can miss some things that humans would notice</li>
<li>Audit tracking of fields and records can help &#8216;piece together&#8217; clues in mysterious situations</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://theEnforcer.net/2008/05/missed-opportunities-a-case-for-data-cleansing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
