Quantcast
Channel: Cloud Clod » soql

Salesforce SOQL Subquery and Custom Objects with Apex

$
0
0

Recently I was dealing with attempting to query a custom object and also its child custom object using a subquery.  The only place that the official Apex Language Reference mentions subqueries is in the Dynamic DML section, and it doesn’t provide any concrete examples for a SOQL statement with a subquery, wrapped in Apex using Custom Objects.

Here’s an example that would have saved me some time.

Two objects,

  • ObjectMaster__c with no new fields (relevant to this example)
  • ObjectDetail__c with one relevant new field
    • Type Master Detail to ObjectMaster__c, API Name ObjectMaster__c, Child Relationship Name on the ObjectMaster__c custom object is “ObjectDetails” (this is important).

A sample Soql query to pull this data is as follows:

Select
  Name,
  (Select Name from ObjectDetails__r)
From
  ObjectMaster__c

This is how the results look in Soql Xplorer:

The following Apex code — tested from the Debug log — queries these SObjects and instantiates them.

for (ObjectMaster__c master : [
    Select
      Name,
      (Select Name from ObjectDetails__r)
    From
      ObjectMaster__c]) {
  System.debug('Here is an ObjectMaster record: '+master);
  for (ObjectDetail__c detail : master.ObjectDetails__r) {
    System.debug('Here is an ObjectDetail record: '+detail);
  }
}

It works!


Using SOQL Subquery to Count Child Records

$
0
0

Short post, just wanted to post a real world application of previous post I made, Subquerying in SOQL.

I recently extended the Product2 object with an object that I would have LIKED to be a master-detail relationship, but since that cannot occur with a Product2, I had to emulate some functionality (see the edit at the bottom of this post for a correction). Ideally I’d create a rollup COUNT() field on product that summed the number of Child__c objects. To do this, I created a number field and the following trigger on Child__c:

trigger PopulateChild on Child__c (after insert, after delete) {
	List childList = Trigger.isInsert ? Trigger.new : Trigger.old;

	List childIds = new List();
	for (Child__c p : childList) {
		childIds.add(p.Product__c);
	}
	
	List products = [
			select
				id,
				(select id from child__r),
				child_count__c
			from
				product2
			where
				id in :childIds];

	for (Product2 product : products) {
		product.child_count__c = product.child__r.size();
	}
	update products;
}

Hacktastic!

*edit 2010-12-01* So this example fell apart, and there is some misinformation contained. You can have a custom object with a custom field that is a master-detail to a standard object. What you cannot do is have a standard object have a custom field that is a master-detail to a custom object. That is what my use case contained, and in copying the details over and changing names, some objects swapped. The principle of emulating a count rollup with a lookup field, a trigger and a SOQL Subquery are still correct, however.

System.QueryException: List has no rows for assignment to SObject

$
0
0

I’ve run across this oddity a few times. When you construct a SOQL query thusly

Account a = [select id from account where id = '01pL00000000XXX'];

And if the ID returns no rows, instead of setting the resulting Account record to null, it throws a System.QueryException. If there is a row returned, it works great.

System.QueryException: List has no rows for assignment to SObject

I can’t imagine why this occurs. In effect, this forces the best practice to do one of the following:

  1. Surround your query with a try/catch block:
    try {
      Account a = [select id from account where id = '01pL00000000XXX'];
    }
    catch (System.QueryException e) {
      System.debug('caught ya!');
    }
  2. Return a list of results instead of a singular result:
    List<Account> a = [select id from account where id = '01pL00000000XXX'];
    if (a.size() == 0) {
      System.debug('nothin!');
    }

Only Aggregate Expressions Use Field Aliasing

$
0
0

If you’ve ever come across the following error in a SOQL statement in Salesforce:

Save error: only aggregate expressions use field aliasing, Force.com save problem

The issue is that you forgot a comma in your SOQL query! Indeed this will throw that error

SELECT
  Name
  Id
FROM
  Account

whereas this will not

SELECT
  Name,
  Id
FROM
  Account

Using Batch Apex with non-dynamic (static) SOQL

$
0
0

If you look at the Using Batch Apex guide in the developer docs, all of the examples use Dynamic SOQL, that is SOQL that can be constructed using a String, as in

List<Account> accounts = Database.query('select id from account limit 10');

in contrast to non-dynamic soql or static soql, as in

List<Account> accounts = [select id from account limit 10];

The start() method of batch apex either returns a Database.QueryLocator or a Iterable<sObject>. In the examples that return a Database.QueryLocator, the examples all create it via

return Database.getQueryLocator(query);

If you want to use non-dynamic soql, for example if you want to take advantage of the out of the box binding Apex variables, then you’ll need a way to convert from [ ] to a String. It’s sort of hidden in another part of the developer docs, but I was able to find an API reference of sorts for Database.QueryLocator. The way to run the start method for non-dynamic soql would be by following this pattern:

return Database.getQueryLocator([select id from account]);

It should be noted that if [select id from account] returns more than the max number of rows (currently 10,000), that this will NOT throw a Governor Limit Exception.

SQLForce: an option for Select Distinct in Salesforce

$
0
0

Today I was trying to get a list of distinct values in a field in Salesforce having a certain criteria. If SOQL supported ‘select distinct’, this could be accomplished quite trivially. Of course you can’t do ‘select distinct’ with the Salesforce Object Query Language.

A blog post from Jeff Douglas resonates in my mind from a few months ago on how to use SQLForce to accomplish this.

Connecting was a cinch, and issuing the command “select distinct whoid from task where field__c != null” got the job done. This command takes a considerable amount of time longer than just a select for a large set of records. But is it longer than performing your own de-duping process? It depends on the number of records and fields.

I queried an object that has 1.6 million rows. With my filter criteria on a cached set of data for this object, it took about 8 minutes to download 650k records with Data Loader with the query ‘select whoid from task where field__c != null’. By simply adding the ‘distinct’ keyword and pulling these records into a local file with SQLForce with the following command:

select distinct whoid from task where field__c != null output "distinct_whoid.csv";

The operation took 25 minutes and returned 165k records.

Not a terrible result for a one-off requirement. Good work, Greg. Obligatory good guy Greg meme:
good guy greg

How to Select Archived Tasks and Events in SOQL

$
0
0

I recently encountered an issued where I could see a Task from within Salesforce.com, but I could not select it with the API. After some further research, I realized that it was qualified as an archived activity — that is, its ActivityDate field was more than 365 days old.

Unfortunately there’s no uniform way to select these through the API and on the platform. developerforce.com user Jayant pointed me at the ALL ROWS query append which enables these results to show up in apex, but if you attempt to run it from the api (Eclipse/Force.com IDE, AJAX Api, etc.), you’ll get a malformed query message similar to “ALL ROWS not allowed in this context”. You can use the queryAll() function instead of the simple query() function for the desired result in this context.

Remember that ALL ROWS and queryAll returns deleted records as well, so if you don’t want these, be sure to include isDeleted = false in your soql query.

SOQL/SOSL: Using NOT LIKE

$
0
0

I’ve had to struggle to remember how to do this enough times that I think it’s worth documenting.

Using the LIKE keyword to filter records in a SOQL or SOSL query is a great way to do searching using a partial string match. Here’s an example:

  select Id, Name
  from Account
  where BillingState = 'WA'
    and Name like '%ray%'

But to exclude using the LIKE operator is a little more counter intuitive. Here’s the correct way to do it:

Good

  select Id, Name
  from Account
  where BillingState = 'WA'
    and (not Name like '%ray%')

Note that if you do any similar variants which you think might work, they will fail with the following error:

MALFORMED_QUERY … unexpected token: ‘not’

or

MALFORMED_QUERY … unexpected token: ‘like’

Here are some ways which you’d think might work, but do not.

Bad

  select Id, Name
  from Account
  where BillingState = 'WA'
    and not Name like '%ray%'

Bad

  select Id, Name
  from Account
  where BillingState = 'WA'
    and not (Name like '%ray%')

Bad

  select Id, Name
  from Account
  where BillingState = 'WA'
    and Name not like '%ray%'