Salesforce documentation gives the following rules for non-mandatory lookup fields:

  • When the target is deleted, by default fields that reference it are set to null.
  • You can tell it to prevent deletion of an object if it is referenced by lookups.
  • If you phone Salesforce you can be allowed to set up cascade delete.

The documentation is not clear about mandatory lookup fields, but I'd hope that Salesforce maintains the constraint by preventing deletion.

It looks as if it is impossible for an ID read from a lookup field to not resolve to a real record, but code I have inherited seems to want to check this by performing a SOQL query to see if the record exists. This costs SOQL governor.

Before I delete this code, is there any situation where it may be needed? When does Salesforce perform these constraint checks? Is it possible for the target to be deleted in the same transaction and the result to be wrong in a trigger? Salesforce performs triggers at the point of the DML operation, so I could only see this happening if either

  • The Lookup is a lookup to self
  • Code in the trigger performs the delete

We'd not do the latter. Maybe some other person will, but it sounds unlikely.

So is there something I'm missing? I assume Salesforce transactions see the state of the database including all changes already made in the transaction, so the code is not trying to determine the state of the system before the transaction.

  • 1
    Self lookups and cycles are detected by SF and not allowed. Is the code in "with sharing" scenario? Maybe it tries to check "is current user allowed to see the lookup's target record" rather than "to be sure the record really exists".
    – eyescream
    Commented Dec 20, 2013 at 12:07
  • Thanks for that pointer. I'll have to check. There are a number of examples. Some of it is in library code so I guess could be called from a mix of scenarios. Others I should be able to work out on a case by case basis. Commented Dec 20, 2013 at 13:51
  • Where it's in trigger validation and the trigger does not specify sharing does the trigger inherit the sharing-ness of the caller? We use queues, with related objects also owned by the queue. so I wonder if it's checking that related things are in the same queue by virtue of being visible to the user. Commented Dec 20, 2013 at 14:04
  • 1
  • 1
    Yes, the trigger would switch to a without sharing context, and any methods it calls will respect the class's definition. Of interesting note: a class with no sharing mode specified will inherit from it's caller (acts as without sharing if it's the top level call). Commented Jan 4, 2014 at 0:16

4 Answers 4


It is possible that I can read an ID from a field, and when querying for the item with the given ID I find no result. This will happen when Sharing Rules prevent the user seeing the item and the code is running "With Sharing".

The Sharing-ness of the code switches depending on the call tree, so as code enters a class that is marked with or without sharing permissions change. The permission does not change when execution enters a class that does not have a with or without sharing declaration.

Code generally runs Without Sharing, so this problem is only likely to occur if "With Sharing" has been explicitly turned on somewhere. (Or if the user calls anonymous apex). Trigger code starts Without Sharing, even if the code which called the DML was With Sharing.

For Sharing see Salesforce documentation on Sharing keywords.

  • 1
    To save the extra effort of a query specifically to check this you can do a cross-object query, e.g. lookup__r.name as well as lookup__c. If(lookup__c != null && lookup__r.name == null) the user has no sharing access. Commented Jan 3, 2014 at 21:38
  • Can you please edit your comment. It looks like an edit to the question, and not like an answer.
    – Saariko
    Commented Jan 5, 2014 at 16:08
  • 1
    Would losing the bottom paragraphs help? The first paragraph really gives the answer as it describes the situation where an ID can be apparently invalid. I can add information from the other answers about triggers as that is also useful. Commented Jan 5, 2014 at 20:24

Running into this issue now and I'm going to document it here.

We have custom fields on the Account record that point to other Account records. We periodically refresh our partial sandbox, which takes a hunk of Account records over from production. Not all of those Account records get brought over though, and so we have broken references. The fields appear empty when you look at the record page, but you'll soon find out they aren't if you try to update something on the record - you'll get an error thrown for that field. If you go into edit mode and look at the problem field, it will say 'Unknown record'. You can clear it out or set it to a valid reference and the issue will resolve, or relax the lookup filter on it (which was causing the update error here).

I am currently looking for a smart way to detect these errant references but it's looking pretty grim, but that's beyond the scope of this question I believe. I do not know if this is typical behavior, either, but it is happening to me and may very well happen to someone else.

  • 1
    thanks man... same thing here and I was like.. WTF ?? just ran a report and there are more than 20k records with the same issue ROFL anyway, you're the only right answer over all the internet :D tks again!
    – DVNube
    Commented Jun 17, 2023 at 5:40
  • @DVNube Glad I was able to assist! Commented Oct 8, 2023 at 15:24

Any value you observe in a lookup field will resolve to a live database record that can be referenced. The instant a record is deleted, all lookups will return a null value instead of the previously referenced ID value. Therefore, it is safe to assume that any ID value you observe in a lookup field is a legitimate record. However, just because the user can see an ID value doesn't mean they have the right to update that record, so one must always check for database save errors by exception or by partial save.

There are times when an ID value may be invalid, such as when a developer uses a text field to store references instead of lookup fields. For example, to store a reference to an event, you can't use a lookup field, so you'd have to necessarily use a normal text field. Opportunity Line Items are another example; in fact, many standard "detail" records can't have a lookup assigned to them. In this case, it's usually better to query before you update, although it's not required, because the database will still inform you of invalid ID values during a create or update call.

However, there is one specific use for querying records before updating: if you're worried about concurrency, it is highly adviseable to at least perform a SELECT ... FOR UPDATE command to force those records to be locked, granting your code exclusive access to those records for the duration of the transaction. This reduces the possibility of your values being overwritten at the same moment that another user is trying to modify those same records.

  • I think the explanation concerning sharing rules above is interesting. Maybe a test would prove it. We do have some IDs in text form, which we do have to check. These are lookups. I've read about FOR UPDATE, but it is not used in the code I'm currently looking at. Does it block the other user, or cause them an exception? Commented Dec 20, 2013 at 17:41
  • 1
    @RichardCorfield It prevents concurrent updates, which can cause an error for the other user in the form of "Another user is currently editing this record. Please try again." It prevents race conditions at the expense that some updates may fail. Ideally, you should minimize locking time, especially on long processes, such as waiting on a callout or processing a large amount of data. It will also cause your queries to appear to execute slower, but you'll gain the peace of mind knowing that you won't have stale data.
    – sfdcfox
    Commented Dec 20, 2013 at 18:04
  • Thanks - so rather than blocking the other user their request will fail. Fits in with other aspects of the design that seem aimed to reduce time taken. So it will hold the lock during a callout? It doesn't seem to allow suspended transactions as we can't callout after DML, but holding a lock makes sense. Commented Dec 22, 2013 at 14:03
  • 2
    The documentation on "FOR UPDATE" suggests that you can induce a lock before calling out; the callout limitation applies specifically to callouts performed after an insert, update, delete, merge, convertLead, or undelete operation. While it is well known that you can't callout in a trigger, for example, few developers realize you could, for example, query some records with a lock in a Visualforce page, then perform a callout, and finally perform an update. Similarly, Batch classes, Scheduled classes, and web service methods are not restricted this way.
    – sfdcfox
    Commented Dec 22, 2013 at 15:36
  • 1
    @RichardCorfield you can write your own answer that summarizes your findings (I didn't do much, it was just a rough idea) and accept it. And separately from that you can upvote sfdcfox's answer. This will improve our statistic of answered questions and get us closer to leaving the beta stage ;)
    – eyescream
    Commented Jan 2, 2014 at 19:09

Yes, It's possible. enter image description here

The synced quote as per screenshot got deleted but field "Synced Quote" at Opp still having reference. This would lead to any further updates on Opportunity with Invalid Cross Reference Field Exception.

  • Timing issue or failure in cascade perhaps? Commented Jul 7, 2022 at 16:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .