I've had to implement this solution before, and it's tricky. You can't use a cross filter to do this in a report, and the obvious workaround (a Rollup Summary Field from Campaign Member to Contact) isn't supported.
There's no way to do it in a fully generalizable way (i.e., you can run it on any Campaign) with vanilla Salesforce functionality without either writing some Apex or using Excel and an INDEX/MATCH construct. If you're focusing on a single Campaign that will never change, there's probably a third solution using Declarative Lookup Rollup Summaries.
Option 1: Apex
You can build this as a Visualforce report by running two separate Apex queries like the below. I'm assuming campaignId
is defined, and your Campaigns use the Status value 'Registered'.
List<Contact> members;
members = [SELECT Id, AccountId
FROM Contact
WHERE Id IN (SELECT ContactId
FROM CampaignMember
WHERE CampaignId = :campaignId
AND Status = 'Registered')]; // Your status here...
Set<Id> accountIds = new Set<Id>();
for (Contact c : members) {
accountIds.add(c.AccountId);
}
List<Account> accounts;
accounts = [SELECT Id, ... [additional fields]
FROM Account
WHERE Id NOT IN :accountIds];
// Do something with list of Accounts and display in Visualforce.
Option 2: Excel
The easier, but less automated, way to approach this challenge is to create two reports and correlate them in Excel. I would probably try this with a Campaigns with Campaign Members report, on the one hand, and an Accounts and Contacts report on the other. Then an INDEX/MATCH construct in your Accounts and Contacts report can determine whether each Contact is a campaign member, and you can create a pivot table to summarize and filter the data by Account.