/** * RELATIONSHIP QUERY PATTERNS * * SOQL supports two types of relationships: * 1. Child-to-Parent (dot notation) - Traverse up * 2. Parent-to-Child (subquery) - Traverse down */ // ═══════════════════════════════════════════════════════════════════════════ // CHILD-TO-PARENT (DOT NOTATION) // ═══════════════════════════════════════════════════════════════════════════ -- Basic parent field access SELECT Id, Name, Account.Name, Account.Industry FROM Contact -- Multiple levels up (max 5 levels) SELECT Id, Name, Account.Name, Account.Owner.Name, Account.Owner.Manager.Name FROM Contact -- Filter by parent field SELECT Id, Name, Email FROM Contact WHERE Account.Industry = 'Technology' AND Account.AnnualRevenue > 1000000 -- Custom object parent relationship (__r suffix) SELECT Id, Name, Custom_Parent__r.Name, Custom_Parent__r.Status__c FROM Custom_Child__c -- Standard lookup relationships SELECT Id, Subject, WhatId, What.Name FROM Task WHERE What.Type = 'Account' SELECT Id, CaseNumber, Account.Name, Contact.Name FROM Case WHERE Account.Industry = 'Healthcare' // ═══════════════════════════════════════════════════════════════════════════ // PARENT-TO-CHILD (SUBQUERIES) // ═══════════════════════════════════════════════════════════════════════════ -- Basic child records SELECT Id, Name, (SELECT Id, FirstName, LastName, Email FROM Contacts) FROM Account LIMIT 100 -- Multiple child relationships SELECT Id, Name, (SELECT Id, Name, Email FROM Contacts), (SELECT Id, Name, Amount, StageName FROM Opportunities), (SELECT Id, CaseNumber, Subject FROM Cases) FROM Account WHERE Industry = 'Technology' LIMIT 50 -- Filtered child records SELECT Id, Name, (SELECT Id, Name, Amount FROM Opportunities WHERE StageName = 'Closed Won' AND Amount > 10000 ORDER BY Amount DESC LIMIT 5) FROM Account -- Custom child relationship (__r suffix, plural) SELECT Id, Name, (SELECT Id, Name, Status__c FROM Custom_Children__r) FROM Custom_Parent__c -- Order in subquery SELECT Id, Name, (SELECT Id, FirstName, LastName, CreatedDate FROM Contacts ORDER BY CreatedDate DESC LIMIT 10) FROM Account // ═══════════════════════════════════════════════════════════════════════════ // COMMON RELATIONSHIP NAMES // ═══════════════════════════════════════════════════════════════════════════ -- Account relationships SELECT Id, Name, (SELECT Id FROM Contacts), -- Standard (SELECT Id FROM Opportunities), -- Standard (SELECT Id FROM Cases), -- Standard (SELECT Id FROM Notes), -- Standard (SELECT Id FROM Attachments), -- Standard (SELECT Id FROM Tasks), -- Standard (SELECT Id FROM Events) -- Standard FROM Account LIMIT 10 -- Contact relationships SELECT Id, Name, (SELECT Id FROM Cases), (SELECT Id FROM Tasks), (SELECT Id FROM Events), (SELECT Id FROM OpportunityContactRoles) FROM Contact LIMIT 10 -- Opportunity relationships SELECT Id, Name, (SELECT Id FROM OpportunityLineItems), (SELECT Id FROM OpportunityContactRoles), (SELECT Id FROM Tasks) FROM Opportunity LIMIT 10 // ═══════════════════════════════════════════════════════════════════════════ // SEMI-JOINS AND ANTI-JOINS // ═══════════════════════════════════════════════════════════════════════════ -- Semi-join: Accounts WITH contacts SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Contact) -- Semi-join: Accounts WITH opportunities over $100k SELECT Id, Name FROM Account WHERE Id IN ( SELECT AccountId FROM Opportunity WHERE Amount > 100000 AND StageName = 'Closed Won' ) -- Anti-join: Accounts WITHOUT contacts SELECT Id, Name FROM Account WHERE Id NOT IN (SELECT AccountId FROM Contact WHERE AccountId != null) -- Anti-join: Accounts WITHOUT opportunities SELECT Id, Name FROM Account WHERE Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId != null) -- Anti-join: Leads not converted SELECT Id, Name, Email FROM Lead WHERE IsConverted = false AND Id NOT IN (SELECT LeadId FROM Contact WHERE LeadId != null) -- Multiple semi-joins SELECT Id, Name FROM Account WHERE Id IN (SELECT AccountId FROM Contact WHERE Email != null) AND Id IN (SELECT AccountId FROM Opportunity WHERE Amount > 50000) // ═══════════════════════════════════════════════════════════════════════════ // POLYMORPHIC RELATIONSHIPS (TYPEOF) // ═══════════════════════════════════════════════════════════════════════════ -- Basic polymorphic query SELECT Id, Subject, Who.Name, What.Name FROM Task WHERE Who.Type = 'Contact' AND What.Type = 'Opportunity' -- TYPEOF for conditional fields SELECT Id, Subject, TYPEOF What WHEN Account THEN Name, Industry, AnnualRevenue WHEN Opportunity THEN Name, Amount, StageName WHEN Case THEN CaseNumber, Subject, Status END FROM Task WHERE What.Type IN ('Account', 'Opportunity', 'Case') -- TYPEOF with ELSE clause SELECT Id, TYPEOF Owner WHEN User THEN FirstName, LastName, Email WHEN Group THEN Name ELSE Name END FROM Account // ═══════════════════════════════════════════════════════════════════════════ // ADVANCED PATTERNS // ═══════════════════════════════════════════════════════════════════════════ -- Get accounts with contact count > 5 (requires Apex filtering) -- Note: SOQL can't filter by aggregate of subquery SELECT Id, Name, (SELECT Id FROM Contacts) FROM Account -- Then filter in Apex: WHERE Contacts.size() > 5 -- Get hierarchical data (self-reference) SELECT Id, Name, ParentId, Parent.Name, Parent.Parent.Name FROM Account WHERE ParentId != null -- Get users with role hierarchy SELECT Id, Name, Manager.Name, Manager.Manager.Name FROM User WHERE IsActive = true AND ManagerId != null