querying-soql: Salesforce SOQL Query Expert Use this skill when the user needs SOQL/SOSL authoring or optimization : natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance/safety improvements for Salesforce queries. When This Skill Owns the Task Use when the work involves: - files - query generation from natural language - relationship queries and aggregate queries - query optimization and selectivity analysis - SOQL/SOSL syntax and governor-aware design Delegate elsewhere when the user is: - performing bulk data operations → handling-sf-d…

+ totalRevenue);\n}\n```\n\n### Aggregate Field Aliases\n\n| Function | Default Alias | Example |\n|----------|---------------|---------|\n| `COUNT(Field)` | `expr0`, `expr1`, etc. | Use explicit alias: `COUNT(Id) cnt` |\n| `SUM(Field)` | `expr0`, `expr1`, etc. | Use explicit alias: `SUM(Amount) total` |\n| `AVG(Field)` | `expr0`, `expr1`, etc. | Use explicit alias: `AVG(Age) avgAge` |\n| `MIN(Field)` | `expr0`, `expr1`, etc. | Use explicit alias: `MIN(CreatedDate) earliest` |\n| `MAX(Field)` | `expr0`, `expr1`, etc. | Use explicit alias: `MAX(Amount) largest` |\n| `GROUP BY Field` | Field API name | Access with field name: `ar.get('Industry')` |\n\n---\n\n## Subquery Fields\n\n### Rule: Child relationship subqueries create nested lists\n\n### ❌ BAD: Accessing Subquery Fields Incorrectly\n\n```apex\n// Query with contact subquery\nList\u003cAccount> accounts = [\n SELECT Id, Name,\n (SELECT Id, Name FROM Contacts)\n FROM Account\n];\n\nfor (Account acc : accounts) {\n // ERROR: Contacts is a List, not a single Contact\n String contactName = acc.Contacts.Name;\n\n // ERROR: Cannot access unqueried field from subquery\n for (Contact c : acc.Contacts) {\n String email = c.Email; // Email not in subquery SELECT!\n }\n}\n```\n\n### ✅ GOOD: Proper Subquery Field Access\n\n```apex\n// Query all needed fields in subquery\nList\u003cAccount> accounts = [\n SELECT Id, Name,\n (SELECT Id, Name, Email, Phone FROM Contacts)\n FROM Account\n];\n\nfor (Account acc : accounts) {\n // Contacts is a List\u003cContact>\n List\u003cContact> contacts = acc.Contacts;\n\n if (contacts != null && !contacts.isEmpty()) {\n for (Contact c : contacts) {\n String name = c.Name; // OK - in subquery SELECT\n String email = c.Email; // OK - in subquery SELECT\n String phone = c.Phone; // OK - in subquery SELECT\n }\n }\n}\n```\n\n### Subquery Null Safety\n\n```apex\nList\u003cAccount> accounts = [\n SELECT Id, (SELECT Id FROM Contacts)\n FROM Account\n];\n\nfor (Account acc : accounts) {\n // Subquery result can be null if no child records\n if (acc.Contacts != null) {\n for (Contact c : acc.Contacts) {\n // Process contact\n }\n }\n\n // Or use null-safe size check\n Integer contactCount = acc.Contacts?.size() ?? 0;\n}\n```\n\n---\n\n## Validation Patterns\n\n### Pattern 1: Field-to-Query Mapping\n\nCreate a systematic approach to track field usage:\n\n```apex\npublic class AccountProcessor {\n // Document required fields at the top\n private static final Set\u003cString> REQUIRED_FIELDS = new Set\u003cString>{\n 'Id', 'Name', 'Industry', 'Description', 'AnnualRevenue',\n 'OwnerId', 'Owner.Name', 'Owner.Email'\n };\n\n // Single method for consistent querying\n public static List\u003cAccount> queryAccounts(Set\u003cId> accountIds) {\n return [\n SELECT Id, Name, Industry, Description, AnnualRevenue,\n OwnerId, Owner.Name, Owner.Email\n FROM Account\n WHERE Id IN :accountIds\n ];\n }\n\n public static void processAccounts(List\u003cAccount> accounts) {\n for (Account acc : accounts) {\n // All fields in REQUIRED_FIELDS are safe to access\n if (acc.Industry == 'Technology') {\n acc.Description = 'Tech: ' + acc.Name;\n }\n }\n }\n}\n```\n\n### Pattern 2: Selector Layer\n\nUse a selector pattern to centralize query field management:\n\n```apex\npublic class AccountSelector {\n\n // Default fields for most operations\n private static final List\u003cString> DEFAULT_FIELDS = new List\u003cString>{\n 'Id', 'Name', 'Industry', 'Type', 'OwnerId'\n };\n\n // Extended fields for detailed views\n private static final List\u003cString> DETAIL_FIELDS = new List\u003cString>{\n 'Id', 'Name', 'Industry', 'Type', 'OwnerId',\n 'Description', 'AnnualRevenue', 'NumberOfEmployees',\n 'BillingCity', 'BillingState', 'BillingCountry',\n 'Owner.Name', 'Owner.Email'\n };\n\n public List\u003cAccount> selectById(Set\u003cId> ids) {\n return selectByIdWithFields(ids, DEFAULT_FIELDS);\n }\n\n public List\u003cAccount> selectByIdDetailed(Set\u003cId> ids) {\n return selectByIdWithFields(ids, DETAIL_FIELDS);\n }\n\n private List\u003cAccount> selectByIdWithFields(Set\u003cId> ids, List\u003cString> fields) {\n String query = 'SELECT ' + String.join(fields, ', ') +\n ' FROM Account WHERE Id IN :ids';\n return Database.query(query);\n }\n}\n```\n\n### Pattern 3: Field Validation Helper\n\n```apex\npublic class SObjectFieldValidator {\n\n /**\n * Check if a field was queried on an sObject\n * @param obj The sObject to check\n * @param fieldName The API name of the field\n * @return true if the field is populated (was queried)\n */\n public static Boolean isFieldPopulated(SObject obj, String fieldName) {\n try {\n obj.get(fieldName);\n return true;\n } catch (SObjectException e) {\n return false;\n }\n }\n\n /**\n * Get field value with default if not queried\n * @param obj The sObject\n * @param fieldName The field API name\n * @param defaultValue Value to return if field not queried\n * @return The field value or default\n */\n public static Object getFieldOrDefault(SObject obj, String fieldName, Object defaultValue) {\n try {\n Object value = obj.get(fieldName);\n return value != null ? value : defaultValue;\n } catch (SObjectException e) {\n return defaultValue;\n }\n }\n}\n```\n\n---\n\n## Quick Reference: Field Coverage Checklist\n\nBefore running code that processes SOQL results:\n\n### Direct Fields\n- [ ] All fields in `if` conditions are queried\n- [ ] All fields on left side of assignments are queried\n- [ ] All fields passed to methods are queried\n- [ ] All fields used in map keys/values are queried\n\n### Relationship Fields\n- [ ] Parent fields use dot notation (e.g., `Account.Name`)\n- [ ] Parent object null checks before field access\n- [ ] Relationship traversal doesn't exceed 5 levels\n\n### Subqueries\n- [ ] Child records accessed as List, not single record\n- [ ] Subquery SELECT includes all accessed child fields\n- [ ] Null check before iterating subquery results\n\n### Dynamic Access\n- [ ] Fields accessed via `get(fieldName)` are queried\n- [ ] Dynamic queries include all needed fields\n\n---\n\n## Common LLM Mistakes Summary\n\n| Mistake | Example | Fix |\n|---------|---------|-----|\n| Query subset, use superset | Query `Id, Name`, use `Industry` | Add `Industry` to SELECT |\n| Forget relationship field | Use `c.Account.Name` without querying | Add `Account.Name` to SELECT |\n| Assume AccountId = Account | Query `AccountId`, access `Account.Name` | Query `Account.Name` explicitly |\n| Wrong subquery access | `acc.Contacts.Email` | `for (Contact c : acc.Contacts) { c.Email }` |\n| Missing subquery field | Subquery `SELECT Id`, use `Email` | Add `Email` to subquery SELECT |\n\n---\n\n## Reference\n\n- **SOQL Anti-Patterns**: See `references/anti-patterns.md` for general SOQL mistakes\n- **Selector Patterns**: See `references/selector-patterns.md` for query organization\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":14779,"content_sha256":"faeabea3abbeec4af5ef86725636f61d2d11216d3529e39d20dd12b611e066e2"},{"filename":"references/query-optimization.md","content":"\u003c!-- Parent: querying-soql/SKILL.md -->\n\n# Query Optimization & Governor Limits\n\n## Indexing Strategy\n\n**Indexed Fields** (Always Selective):\n- Id, Name, OwnerId, CreatedDate, LastModifiedDate, RecordTypeId\n- External ID fields, Master-Detail relationship fields\n- Lookup fields (when unique)\n\n**Standard Indexed Fields by Object**:\n- Account: AccountNumber, Site\n- Contact: Email\n- Lead: Email\n- Case: CaseNumber\n\n## Selectivity Rules\n\n```\nA filter is selective when it returns:\n- \u003c 10% of total records for first 1 million\n- \u003c 5% of total records for additional records\n- OR uses an indexed field\n```\n\n## Optimization Patterns\n\n```sql\n-- ❌ NON-SELECTIVE (scans all records)\nSELECT Id FROM Lead WHERE Status = 'Open'\n\n-- ✅ SELECTIVE (uses index + selective filter)\nSELECT Id FROM Lead\nWHERE Status = 'Open'\nAND CreatedDate = LAST_N_DAYS:30\nLIMIT 10000\n\n-- ❌ LEADING WILDCARD (can't use index)\nSELECT Id FROM Account WHERE Name LIKE '%corp'\n\n-- ✅ TRAILING WILDCARD (uses index)\nSELECT Id FROM Account WHERE Name LIKE 'Acme%'\n```\n\n## Query Plan Analysis\n\n```bash\n# Get query plan\nsf data query \\\n --query \"SELECT Id FROM Account WHERE Name = 'Test'\" \\\n --target-org my-org \\\n --use-tooling-api \\\n --plan\n```\n\n**Plan Output Interpretation**:\n- `Cardinality`: Estimated rows returned\n- `Cost`: Relative query cost (lower is better)\n- `Fields`: Index fields used\n- `LeadingOperationType`: How the query starts (Index vs TableScan)\n\n---\n\n## Governor Limits\n\n| Limit | Synchronous | Asynchronous |\n|-------|-------------|--------------|\n| Total SOQL Queries | 100 | 200 |\n| Records Retrieved | 50,000 | 50,000 |\n| Query Rows (queryMore) | 2,000 | 2,000 |\n| Query Locator Rows | 10 million | 10 million |\n\n### Efficient Patterns\n\n```sql\n-- ❌ Query all, filter in Apex\nSELECT Id, Name FROM Account\n-- Then filter 50,000 records in Apex\n\n-- ✅ Filter in SOQL\nSELECT Id, Name FROM Account\nWHERE Industry = 'Technology' AND IsActive__c = true\nLIMIT 1000\n\n-- ❌ Multiple queries in loop\nfor (Contact c : contacts) {\n Account a = [SELECT Name FROM Account WHERE Id = :c.AccountId];\n}\n\n-- ✅ Single query with Map\nMap\u003cId, Account> accounts = new Map\u003cId, Account>(\n [SELECT Id, Name FROM Account WHERE Id IN :accountIds]\n);\n```\n\n## SOQL FOR Loops\n\n```apex\n// For large datasets - doesn't load all into heap\nfor (Account acc : [SELECT Id, Name FROM Account WHERE Industry = 'Technology']) {\n // Process one record at a time\n // Governor: Uses queryMore internally (200 at a time)\n}\n\n// With explicit batch size\nfor (List\u003cAccount> accs : [SELECT Id, Name FROM Account]) {\n // Process 200 records at a time\n}\n```\n\n## Security Patterns\n\n### WITH SECURITY_ENFORCED\n\n```sql\n-- Throws exception if user lacks FLS\nSELECT Id, Name, Phone\nFROM Account\nWITH SECURITY_ENFORCED\n```\n\n### WITH USER_MODE / SYSTEM_MODE\n\n```sql\n-- Respects sharing rules (default in Apex)\nSELECT Id, Name FROM Account WITH USER_MODE\n\n-- Bypasses sharing rules (use with caution)\nSELECT Id, Name FROM Account WITH SYSTEM_MODE\n```\n\n### In Apex: stripInaccessible\n\n```apex\n// Strip inaccessible fields instead of throwing\nSObjectAccessDecision decision = Security.stripInaccessible(\n AccessType.READABLE,\n [SELECT Id, Name, SecretField__c FROM Account]\n);\nList\u003cAccount> safeAccounts = decision.getRecords();\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3298,"content_sha256":"bcd028a28f5d51e7c57bc08817fcb5a986fe63213370910352d076c3f6e8c298"},{"filename":"references/selector-patterns.md","content":"\u003c!-- Parent: querying-soql/SKILL.md -->\n# Selector Patterns: Query Abstraction in Vanilla Apex\n\nThis guide teaches query abstraction patterns using pure Apex — no external libraries required. These patterns improve testability, maintainability, and security compliance.\n\n---\n\n## Why Use a Selector Layer?\n\n### The Problem\n\nWithout abstraction, SOQL queries are scattered everywhere:\n\n```apex\n// In TriggerHandler.cls\nList\u003cAccount> accounts = [SELECT Id, Name FROM Account WHERE Id IN :accountIds];\n\n// In BatchJob.cls (duplicate!)\nList\u003cAccount> accounts = [SELECT Id, Name FROM Account WHERE Id IN :ids];\n\n// In ServiceClass.cls (slightly different fields!)\nList\u003cAccount> accounts = [SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds];\n```\n\n**Problems**:\n1. **Duplication**: Same query logic repeated\n2. **Inconsistency**: Different fields queried in different places\n3. **Fragility**: Field deletion breaks multiple classes\n4. **Testability**: Must create real records to test\n5. **Security**: FLS/sharing often forgotten\n\n### The Solution\n\nCentralize queries in Selector classes:\n\n```apex\n// Single source of truth\npublic class AccountSelector {\n public static List\u003cAccount> byIds(Set\u003cId> accountIds) {\n return [\n SELECT Id, Name, Industry\n FROM Account\n WHERE Id IN :accountIds\n WITH SECURITY_ENFORCED\n ];\n }\n}\n\n// Usage everywhere\nList\u003cAccount> accounts = AccountSelector.byIds(accountIds);\n```\n\n---\n\n## Pattern 1: Basic Selector Class\n\nThe simplest approach - a class with static query methods.\n\n```apex\n/**\n * AccountSelector - Centralized queries for Account object\n */\npublic inherited sharing class AccountSelector {\n\n // ═══════════════════════════════════════════════════════════════════\n // FIELD SETS (centralized field lists)\n // ═══════════════════════════════════════════════════════════════════\n\n private static final List\u003cSObjectField> STANDARD_FIELDS = new List\u003cSObjectField>{\n Account.Id,\n Account.Name,\n Account.Industry,\n Account.AnnualRevenue,\n Account.OwnerId\n };\n\n // ═══════════════════════════════════════════════════════════════════\n // QUERY METHODS\n // ═══════════════════════════════════════════════════════════════════\n\n /**\n * Query accounts by their IDs\n */\n public static List\u003cAccount> byIds(Set\u003cId> accountIds) {\n if (accountIds == null || accountIds.isEmpty()) {\n return new List\u003cAccount>();\n }\n return [\n SELECT Id, Name, Industry, AnnualRevenue, OwnerId\n FROM Account\n WHERE Id IN :accountIds\n WITH SECURITY_ENFORCED\n ];\n }\n\n /**\n * Query accounts by Owner\n */\n public static List\u003cAccount> byOwnerId(Id ownerId) {\n return [\n SELECT Id, Name, Industry, AnnualRevenue, OwnerId\n FROM Account\n WHERE OwnerId = :ownerId\n WITH SECURITY_ENFORCED\n LIMIT 1000\n ];\n }\n\n /**\n * Query accounts with their contacts\n */\n public static List\u003cAccount> withContactsByIds(Set\u003cId> accountIds) {\n return [\n SELECT Id, Name,\n (SELECT Id, FirstName, LastName, Email\n FROM Contacts\n WHERE IsActive__c = true\n LIMIT 50)\n FROM Account\n WHERE Id IN :accountIds\n WITH SECURITY_ENFORCED\n ];\n }\n}\n```\n\n**Usage**:\n```apex\n// Clean, readable, testable\nList\u003cAccount> accounts = AccountSelector.byIds(accountIdSet);\nList\u003cAccount> myAccounts = AccountSelector.byOwnerId(UserInfo.getUserId());\n```\n\n---\n\n## Pattern 2: Selector with Sharing Modes\n\nControl sharing rules at the selector level.\n\n```apex\n/**\n * ContactSelector with sharing mode control\n */\npublic class ContactSelector {\n\n // ═══════════════════════════════════════════════════════════════════\n // USER MODE (respects sharing rules - default)\n // ═══════════════════════════════════════════════════════════════════\n\n public inherited sharing class UserMode {\n public static List\u003cContact> byAccountIds(Set\u003cId> accountIds) {\n return [\n SELECT Id, FirstName, LastName, Email, AccountId\n FROM Contact\n WHERE AccountId IN :accountIds\n WITH USER_MODE\n ];\n }\n }\n\n // ═══════════════════════════════════════════════════════════════════\n // SYSTEM MODE (bypasses sharing - use carefully!)\n // ═══════════════════════════════════════════════════════════════════\n\n public without sharing class SystemMode {\n public static List\u003cContact> byAccountIds(Set\u003cId> accountIds) {\n return [\n SELECT Id, FirstName, LastName, Email, AccountId\n FROM Contact\n WHERE AccountId IN :accountIds\n WITH SYSTEM_MODE\n ];\n }\n }\n}\n\n// Usage\nList\u003cContact> visibleContacts = ContactSelector.UserMode.byAccountIds(ids);\nList\u003cContact> allContacts = ContactSelector.SystemMode.byAccountIds(ids);\n```\n\n---\n\n## Pattern 3: Mockable Selector (for Unit Tests)\n\nEnable query mocking without database calls.\n\n```apex\n/**\n * OpportunitySelector with mocking support\n */\npublic inherited sharing class OpportunitySelector {\n\n // Test-visible mock data\n @TestVisible\n private static List\u003cOpportunity> mockData;\n\n /**\n * Query opportunities by Account IDs\n * Returns mock data in tests if set\n */\n public static List\u003cOpportunity> byAccountIds(Set\u003cId> accountIds) {\n if (Test.isRunningTest() && mockData != null) {\n return mockData;\n }\n return [\n SELECT Id, Name, StageName, Amount, CloseDate, AccountId\n FROM Opportunity\n WHERE AccountId IN :accountIds\n WITH SECURITY_ENFORCED\n ];\n }\n\n /**\n * Set mock data for testing\n */\n @TestVisible\n private static void setMockData(List\u003cOpportunity> opportunities) {\n mockData = opportunities;\n }\n}\n```\n\n**Test Usage**:\n```apex\n@IsTest\nprivate class OpportunitySelectorTest {\n @IsTest\n static void testByAccountIds_returnsMockData() {\n // Arrange - no database records needed!\n List\u003cOpportunity> mockOpps = new List\u003cOpportunity>{\n new Opportunity(Name = 'Test Opp', StageName = 'Prospecting', Amount = 1000)\n };\n OpportunitySelector.setMockData(mockOpps);\n\n // Act\n List\u003cOpportunity> result = OpportunitySelector.byAccountIds(new Set\u003cId>());\n\n // Assert\n System.assertEquals(1, result.size());\n System.assertEquals('Test Opp', result[0].Name);\n }\n}\n```\n\n---\n\n## Pattern 4: Query Builder (Dynamic SOQL)\n\nFor complex, dynamic queries that vary at runtime.\n\n```apex\n/**\n * Dynamic query builder for flexible SOQL construction\n */\npublic inherited sharing class QueryBuilder {\n\n private String objectName;\n private Set\u003cString> fields = new Set\u003cString>();\n private List\u003cString> conditions = new List\u003cString>();\n private Map\u003cString, Object> bindings = new Map\u003cString, Object>();\n private String orderByClause;\n private Integer limitCount;\n\n /**\n * Constructor\n */\n public QueryBuilder(String objectName) {\n this.objectName = objectName;\n }\n\n /**\n * Add fields to select\n */\n public QueryBuilder selectFields(List\u003cString> fieldList) {\n fields.addAll(fieldList);\n return this;\n }\n\n /**\n * Add fields using SObjectField tokens (type-safe!)\n */\n public QueryBuilder selectFields(List\u003cSObjectField> fieldTokens) {\n for (SObjectField token : fieldTokens) {\n fields.add(String.valueOf(token));\n }\n return this;\n }\n\n /**\n * Add WHERE condition with binding\n */\n public QueryBuilder whereEquals(String field, Object value) {\n String bindName = 'bind' + bindings.size();\n conditions.add(field + ' = :' + bindName);\n bindings.put(bindName, value);\n return this;\n }\n\n /**\n * Add WHERE IN condition\n */\n public QueryBuilder whereIn(String field, Set\u003cId> ids) {\n String bindName = 'bind' + bindings.size();\n conditions.add(field + ' IN :' + bindName);\n bindings.put(bindName, ids);\n return this;\n }\n\n /**\n * Add ORDER BY\n */\n public QueryBuilder orderBy(String field, Boolean ascending) {\n orderByClause = field + (ascending ? ' ASC' : ' DESC');\n return this;\n }\n\n /**\n * Add LIMIT\n */\n public QueryBuilder setLimit(Integer count) {\n limitCount = count;\n return this;\n }\n\n /**\n * Build and execute the query\n */\n public List\u003cSObject> execute() {\n String query = buildQuery();\n return Database.queryWithBinds(query, bindings, AccessLevel.USER_MODE);\n }\n\n /**\n * Build query string (for debugging)\n */\n public String buildQuery() {\n List\u003cString> parts = new List\u003cString>();\n\n // SELECT\n if (fields.isEmpty()) {\n fields.add('Id');\n }\n parts.add('SELECT ' + String.join(new List\u003cString>(fields), ', '));\n\n // FROM\n parts.add('FROM ' + objectName);\n\n // WHERE\n if (!conditions.isEmpty()) {\n parts.add('WHERE ' + String.join(conditions, ' AND '));\n }\n\n // ORDER BY\n if (orderByClause != null) {\n parts.add('ORDER BY ' + orderByClause);\n }\n\n // LIMIT\n if (limitCount != null) {\n parts.add('LIMIT ' + limitCount);\n }\n\n return String.join(parts, ' ');\n }\n}\n```\n\n**Usage**:\n```apex\n// Fluent API for dynamic queries\nList\u003cSObject> results = new QueryBuilder('Account')\n .selectFields(new List\u003cSObjectField>{Account.Id, Account.Name, Account.Industry})\n .whereEquals('Industry', 'Technology')\n .whereIn('Id', accountIds)\n .orderBy('Name', true)\n .setLimit(100)\n .execute();\n\n// Debug the generated query\nSystem.debug(new QueryBuilder('Account').selectFields(...).buildQuery());\n// \"SELECT Id, Name, Industry FROM Account WHERE Industry = :bind0 AND Id IN :bind1 ORDER BY Name ASC LIMIT 100\"\n```\n\n---\n\n## Pattern 5: Bulkified Query Pattern\n\nThe Map-based lookup pattern for bulk operations.\n\n```apex\n/**\n * BulkQueryHelper - Reusable bulk query patterns\n */\npublic inherited sharing class BulkQueryHelper {\n\n /**\n * Get Accounts by ID as a Map (O(1) lookup)\n */\n public static Map\u003cId, Account> getAccountMapByIds(Set\u003cId> accountIds) {\n return new Map\u003cId, Account>([\n SELECT Id, Name, Industry\n FROM Account\n WHERE Id IN :accountIds\n WITH SECURITY_ENFORCED\n ]);\n }\n\n /**\n * Get Contacts grouped by AccountId\n */\n public static Map\u003cId, List\u003cContact>> getContactsByAccountId(Set\u003cId> accountIds) {\n Map\u003cId, List\u003cContact>> contactsByAccount = new Map\u003cId, List\u003cContact>>();\n\n for (Contact c : [\n SELECT Id, FirstName, LastName, Email, AccountId\n FROM Contact\n WHERE AccountId IN :accountIds\n WITH SECURITY_ENFORCED\n ]) {\n if (!contactsByAccount.containsKey(c.AccountId)) {\n contactsByAccount.put(c.AccountId, new List\u003cContact>());\n }\n contactsByAccount.get(c.AccountId).add(c);\n }\n\n return contactsByAccount;\n }\n}\n```\n\n**Usage in Trigger**:\n```apex\n// ❌ WRONG: Query per record\nfor (Opportunity opp : Trigger.new) {\n Account a = [SELECT Name FROM Account WHERE Id = :opp.AccountId];\n}\n\n// ✅ CORRECT: Bulk query with Map lookup\nSet\u003cId> accountIds = new Set\u003cId>();\nfor (Opportunity opp : Trigger.new) {\n accountIds.add(opp.AccountId);\n}\n\nMap\u003cId, Account> accountMap = BulkQueryHelper.getAccountMapByIds(accountIds);\n\nfor (Opportunity opp : Trigger.new) {\n Account a = accountMap.get(opp.AccountId);\n if (a != null) {\n // Use account data\n }\n}\n```\n\n---\n\n## Best Practices Summary\n\n| Practice | Benefit |\n|----------|---------|\n| Centralize in Selector classes | One place to update field lists |\n| Use `WITH SECURITY_ENFORCED` | Automatic FLS enforcement |\n| Return empty List, not null | Prevents NullPointerException |\n| Use `inherited sharing` | Respects caller's sharing context |\n| Make fields list a constant | Easy to update across queries |\n| Add null/empty checks | Prevent unnecessary queries |\n| Support mocking in tests | Faster tests, no database dependencies |\n\n---\n\n## When to Use Each Pattern\n\n| Scenario | Pattern |\n|----------|---------|\n| Simple, static queries | Pattern 1: Basic Selector |\n| Need sharing mode control | Pattern 2: Sharing Modes |\n| Heavy unit testing | Pattern 3: Mockable Selector |\n| Dynamic filters at runtime | Pattern 4: Query Builder |\n| Trigger/batch bulk operations | Pattern 5: Bulkified Query |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":13994,"content_sha256":"b0229c5b60b245c2fc7e1fe14f8e6a797ea6493f871beb55e7b33a65a564d151"},{"filename":"references/soql-reference.md","content":"\u003c!-- Parent: querying-soql/SKILL.md -->\n# SOQL Quick Reference\n\n## Query Structure\n\n```sql\nSELECT fields\nFROM object\n[WHERE conditions]\n[WITH filter]\n[GROUP BY fields]\n[HAVING conditions]\n[ORDER BY fields [ASC|DESC] [NULLS FIRST|LAST]]\n[LIMIT number]\n[OFFSET number]\n[FOR UPDATE]\n```\n\n---\n\n## Operators\n\n### Comparison Operators\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `=` | Equal | `Name = 'Acme'` |\n| `!=` | Not equal | `Status != 'Closed'` |\n| `\u003c` | Less than | `Amount \u003c 1000` |\n| `\u003c=` | Less than or equal | `Amount \u003c= 1000` |\n| `>` | Greater than | `Amount > 1000` |\n| `>=` | Greater than or equal | `Amount >= 1000` |\n| `LIKE` | Pattern match | `Name LIKE 'Acme%'` |\n| `IN` | In list | `Status IN ('New', 'Open')` |\n| `NOT IN` | Not in list | `Type NOT IN ('Other')` |\n| `INCLUDES` | Multi-select contains | `Skills__c INCLUDES ('Java')` |\n| `EXCLUDES` | Multi-select excludes | `Skills__c EXCLUDES ('Java')` |\n\n### Logical Operators\n\n| Operator | Description | Example |\n|----------|-------------|---------|\n| `AND` | Both conditions | `A = 1 AND B = 2` |\n| `OR` | Either condition | `A = 1 OR B = 2` |\n| `NOT` | Negate condition | `NOT (A = 1)` |\n\n### LIKE Patterns\n\n| Pattern | Matches |\n|---------|---------|\n| `'Acme%'` | Starts with \"Acme\" |\n| `'%Corp'` | Ends with \"Corp\" |\n| `'%test%'` | Contains \"test\" |\n| `'A_me'` | \"A\" + any char + \"me\" |\n\n---\n\n## Date Literals\n\n### Relative Dates\n\n| Literal | Description |\n|---------|-------------|\n| `TODAY` | Current day |\n| `YESTERDAY` | Previous day |\n| `TOMORROW` | Next day |\n| `THIS_WEEK` | Current week (Sun-Sat) |\n| `LAST_WEEK` | Previous week |\n| `NEXT_WEEK` | Next week |\n| `THIS_MONTH` | Current month |\n| `LAST_MONTH` | Previous month |\n| `NEXT_MONTH` | Next month |\n| `THIS_QUARTER` | Current quarter |\n| `LAST_QUARTER` | Previous quarter |\n| `NEXT_QUARTER` | Next quarter |\n| `THIS_YEAR` | Current year |\n| `LAST_YEAR` | Previous year |\n| `NEXT_YEAR` | Next year |\n| `THIS_FISCAL_QUARTER` | Current fiscal quarter |\n| `THIS_FISCAL_YEAR` | Current fiscal year |\n\n### N Days/Weeks/Months/Years\n\n| Literal | Description |\n|---------|-------------|\n| `LAST_N_DAYS:n` | Last n days |\n| `NEXT_N_DAYS:n` | Next n days |\n| `LAST_N_WEEKS:n` | Last n weeks |\n| `NEXT_N_WEEKS:n` | Next n weeks |\n| `LAST_N_MONTHS:n` | Last n months |\n| `NEXT_N_MONTHS:n` | Next n months |\n| `LAST_N_QUARTERS:n` | Last n quarters |\n| `NEXT_N_QUARTERS:n` | Next n quarters |\n| `LAST_N_YEARS:n` | Last n years |\n| `NEXT_N_YEARS:n` | Next n years |\n\n### Specific Dates\n\n```sql\n-- Date only\nWHERE CloseDate = 2024-12-31\n\n-- DateTime\nWHERE CreatedDate >= 2024-01-01T00:00:00Z\n```\n\n---\n\n## Aggregate Functions\n\n| Function | Description | Example |\n|----------|-------------|---------|\n| `COUNT()` | Count all rows | `SELECT COUNT() FROM Account` |\n| `COUNT(field)` | Count non-null values | `SELECT COUNT(Email) FROM Contact` |\n| `COUNT_DISTINCT(field)` | Count unique values | `SELECT COUNT_DISTINCT(Industry) FROM Account` |\n| `SUM(field)` | Sum of values | `SELECT SUM(Amount) FROM Opportunity` |\n| `AVG(field)` | Average of values | `SELECT AVG(Amount) FROM Opportunity` |\n| `MIN(field)` | Minimum value | `SELECT MIN(Amount) FROM Opportunity` |\n| `MAX(field)` | Maximum value | `SELECT MAX(Amount) FROM Opportunity` |\n\n---\n\n## Date Functions\n\n| Function | Returns | Example |\n|----------|---------|---------|\n| `CALENDAR_YEAR(date)` | Year (e.g., 2024) | `SELECT CALENDAR_YEAR(CloseDate) FROM Opportunity` |\n| `CALENDAR_QUARTER(date)` | Quarter (1-4) | `SELECT CALENDAR_QUARTER(CloseDate) FROM Opportunity` |\n| `CALENDAR_MONTH(date)` | Month (1-12) | `SELECT CALENDAR_MONTH(CloseDate) FROM Opportunity` |\n| `DAY_IN_MONTH(date)` | Day (1-31) | `SELECT DAY_IN_MONTH(CreatedDate) FROM Account` |\n| `DAY_IN_WEEK(date)` | Day (1=Sun, 7=Sat) | `SELECT DAY_IN_WEEK(CreatedDate) FROM Account` |\n| `DAY_IN_YEAR(date)` | Day (1-366) | `SELECT DAY_IN_YEAR(CreatedDate) FROM Account` |\n| `WEEK_IN_MONTH(date)` | Week (1-5) | `SELECT WEEK_IN_MONTH(CreatedDate) FROM Account` |\n| `WEEK_IN_YEAR(date)` | Week (1-53) | `SELECT WEEK_IN_YEAR(CreatedDate) FROM Account` |\n| `HOUR_IN_DAY(date)` | Hour (0-23) | `SELECT HOUR_IN_DAY(CreatedDate) FROM Account` |\n| `FISCAL_YEAR(date)` | Fiscal year | `SELECT FISCAL_YEAR(CloseDate) FROM Opportunity` |\n| `FISCAL_QUARTER(date)` | Fiscal quarter | `SELECT FISCAL_QUARTER(CloseDate) FROM Opportunity` |\n| `FISCAL_MONTH(date)` | Fiscal month | `SELECT FISCAL_MONTH(CloseDate) FROM Opportunity` |\n\n---\n\n## Relationship Queries\n\n### Child-to-Parent (Dot Notation)\n\n```sql\n-- Standard objects\nSELECT Contact.Name, Contact.Account.Name FROM Contact\n\n-- Custom objects (use __r)\nSELECT Child__c.Name, Child__c.Parent__r.Name FROM Child__c\n```\n\n### Parent-to-Child (Subquery)\n\n```sql\n-- Standard objects\nSELECT Id, (SELECT Id FROM Contacts) FROM Account\n\n-- Custom objects (use __r)\nSELECT Id, (SELECT Id FROM Children__r) FROM Parent__c\n```\n\n---\n\n## WITH Clauses\n\n| Clause | Description |\n|--------|-------------|\n| `WITH SECURITY_ENFORCED` | Enforce FLS (throws exception if no access) |\n| `WITH USER_MODE` | Respect sharing and FLS |\n| `WITH SYSTEM_MODE` | Bypass sharing rules |\n\n---\n\n## Governor Limits\n\n| Limit | Synchronous | Asynchronous |\n|-------|-------------|--------------|\n| Total SOQL Queries | 100 | 200 |\n| Records Retrieved | 50,000 | 50,000 |\n| QueryLocator Rows | 10,000,000 | 10,000,000 |\n| OFFSET Maximum | 2,000 | 2,000 |\n| Subqueries | 20 | 20 |\n| Relationship Depth | 5 levels | 5 levels |\n\n---\n\n## Index Usage\n\n### Always Indexed\n\n- `Id`\n- `Name`\n- `OwnerId`\n- `CreatedDate`\n- `LastModifiedDate`\n- `RecordTypeId`\n- External ID fields\n- Master-Detail fields\n\n### Selective Query Rules\n\n- Query is selective if WHERE returns \u003c 10% of first 1M records\n- Or uses an indexed field with \u003c 1M matching records\n- Non-selective queries on large tables fail\n\n---\n\n## CLI Commands\n\n```bash\n# Basic query\nsf data query --query \"SELECT Id, Name FROM Account LIMIT 10\" --target-org my-org\n\n# JSON output\nsf data query --query \"SELECT Id, Name FROM Account\" --target-org my-org --json\n\n# CSV output\nsf data query --query \"SELECT Id, Name FROM Account\" --result-format csv --target-org my-org\n\n# Bulk export (for large results, > 2,000 records)\nsf data export bulk --query \"SELECT Id, Name FROM Account\" --target-org my-org --output-file accounts.csv\n\n# Query plan (uses REST API explain endpoint)\nsf api request rest \"/query/?explain=SELECT+Id+FROM+Account+WHERE+Name='Test'\" --target-org my-org --json\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6512,"content_sha256":"773b8c90aebef4a7798afa5798b34bb89208b94bd2d3877671fdd9c4f8aa7d65"},{"filename":"references/soql-syntax-reference.md","content":"\u003c!-- Parent: querying-soql/SKILL.md -->\n\n# SOQL Syntax Reference\n\n## Basic Query Structure\n\n```sql\nSELECT field1, field2, ...\nFROM ObjectName\nWHERE condition1 AND condition2\nORDER BY field1 ASC/DESC\nLIMIT number\nOFFSET number\n```\n\n### Field Selection\n\n```sql\n-- Specific fields (recommended)\nSELECT Id, Name, Industry FROM Account\n\n-- All fields (avoid in Apex - use only in Developer Console)\nSELECT FIELDS(ALL) FROM Account LIMIT 200\n\n-- Standard fields only\nSELECT FIELDS(STANDARD) FROM Account\n```\n\n### WHERE Clause Operators\n\n| Operator | Example | Notes |\n|----------|---------|-------|\n| `=` | `Name = 'Acme'` | Exact match |\n| `!=` | `Status != 'Closed'` | Not equal |\n| `\u003c`, `>`, `\u003c=`, `>=` | `Amount > 1000` | Comparison |\n| `LIKE` | `Name LIKE 'Acme%'` | Wildcard match |\n| `IN` | `Status IN ('New', 'Open')` | Multiple values |\n| `NOT IN` | `Type NOT IN ('Other')` | Exclude values |\n| `INCLUDES` | `Interests__c INCLUDES ('Golf')` | Multi-select picklist |\n| `EXCLUDES` | `Interests__c EXCLUDES ('Golf')` | Multi-select exclude |\n\n### Date Literals\n\n| Literal | Meaning |\n|---------|---------|\n| `TODAY` | Current day |\n| `YESTERDAY` | Previous day |\n| `THIS_WEEK` | Current week (Sun-Sat) |\n| `LAST_WEEK` | Previous week |\n| `THIS_MONTH` | Current month |\n| `LAST_MONTH` | Previous month |\n| `THIS_QUARTER` | Current quarter |\n| `THIS_YEAR` | Current year |\n| `LAST_N_DAYS:n` | Last n days |\n| `NEXT_N_DAYS:n` | Next n days |\n\n```sql\n-- Created in last 30 days\nSELECT Id FROM Account WHERE CreatedDate = LAST_N_DAYS:30\n\n-- Modified this month\nSELECT Id FROM Contact WHERE LastModifiedDate = THIS_MONTH\n```\n\n---\n\n## Relationship Queries\n\n### Child-to-Parent (Dot Notation)\n\n```sql\n-- Access parent fields\nSELECT Id, Name, Account.Name, Account.Industry\nFROM Contact\nWHERE Account.AnnualRevenue > 1000000\n\n-- Up to 5 levels\nSELECT Id, Contact.Account.Owner.Manager.Name\nFROM Case\n```\n\n### Parent-to-Child (Subquery)\n\n```sql\n-- Get parent with related children\nSELECT Id, Name,\n (SELECT Id, FirstName, LastName FROM Contacts),\n (SELECT Id, Name, Amount FROM Opportunities WHERE StageName = 'Closed Won')\nFROM Account\nWHERE Industry = 'Technology'\n```\n\n### Standard Relationship Names\n\n| Object | Relationship Name | Example |\n|--------|-------------------|---------|\n| Account → Contacts | `Contacts` | `(SELECT Id FROM Contacts)` |\n| Account → Opportunities | `Opportunities` | `(SELECT Id FROM Opportunities)` |\n| Account → Cases | `Cases` | `(SELECT Id FROM Cases)` |\n| Contact → Cases | `Cases` | `(SELECT Id FROM Cases)` |\n| Opportunity → OpportunityLineItems | `OpportunityLineItems` | `(SELECT Id FROM OpportunityLineItems)` |\n\n### Custom Object Relationships\n\n```sql\n-- Custom relationship: add __r suffix\nSELECT Id, Name, Custom_Object__r.Name\nFROM Another_Object__c\n\n-- Child relationship: add __r suffix\nSELECT Id, (SELECT Id FROM Custom_Children__r)\nFROM Parent_Object__c\n```\n\n---\n\n## Aggregate Queries\n\n### Basic Aggregates\n\n```sql\n-- Count all records\nSELECT COUNT() FROM Account\n\n-- Count with alias\nSELECT COUNT(Id) cnt FROM Account\n\n-- Sum, Average, Min, Max\nSELECT SUM(Amount), AVG(Amount), MIN(Amount), MAX(Amount)\nFROM Opportunity\nWHERE StageName = 'Closed Won'\n```\n\n### GROUP BY\n\n```sql\n-- Count by field\nSELECT Industry, COUNT(Id)\nFROM Account\nGROUP BY Industry\n\n-- Multiple groupings\nSELECT StageName, CALENDAR_YEAR(CloseDate), COUNT(Id)\nFROM Opportunity\nGROUP BY StageName, CALENDAR_YEAR(CloseDate)\n```\n\n### HAVING Clause\n\n```sql\n-- Filter aggregated results\nSELECT Industry, COUNT(Id) cnt\nFROM Account\nGROUP BY Industry\nHAVING COUNT(Id) > 10\n```\n\n### GROUP BY ROLLUP\n\n```sql\n-- Subtotals\nSELECT LeadSource, Rating, COUNT(Id)\nFROM Lead\nGROUP BY ROLLUP(LeadSource, Rating)\n```\n\n---\n\n## Advanced Features\n\n### Polymorphic Relationships (What)\n\n```sql\n-- Query polymorphic fields\nSELECT Id, What.Name, What.Type\nFROM Task\nWHERE What.Type IN ('Account', 'Opportunity')\n\n-- TYPEOF for conditional fields\nSELECT\n TYPEOF What\n WHEN Account THEN Name, Phone\n WHEN Opportunity THEN Name, Amount\n END\nFROM Task\n```\n\n### Semi-Joins and Anti-Joins\n\n```sql\n-- Semi-join: Records that HAVE related records\nSELECT Id, Name FROM Account\nWHERE Id IN (SELECT AccountId FROM Contact)\n\n-- Anti-join: Records that DON'T HAVE related records\nSELECT Id, Name FROM Account\nWHERE Id NOT IN (SELECT AccountId FROM Opportunity)\n```\n\n### Format and Currency\n\n```sql\n-- Format currency/date in results\nSELECT FORMAT(Amount), FORMAT(CloseDate) FROM Opportunity\n\n-- Convert to user's currency\nSELECT Id, convertCurrency(Amount) FROM Opportunity\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4595,"content_sha256":"2c80ecd94dcaa96826dce424e35cba83212fe99ebc3c12b021c922030b6a6d0d"},{"filename":"scripts/post-tool-validate.py","content":"#!/usr/bin/env python3\n\"\"\"\nPost-Tool Validation Hook for querying-soql skill.\n\nThis hook runs AFTER Write or Edit tool completes and provides validation\nfeedback for SOQL files (*.soql).\n\nIntegrates:\n1. Static SOQL validation (syntax, best practices)\n2. Live Query Plan Analysis (if org connected)\n\nHook Input (stdin): JSON with tool_input and tool_response\nHook Output (stdout): JSON with optional output message\n\nThis hook is ADVISORY - it provides feedback but does not block operations.\n\"\"\"\n\nimport sys\nimport os\nimport json\n\n# Add script directory to path for imports\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\nsys.path.insert(0, SCRIPT_DIR)\n\n# Find shared modules (../../shared relative to querying-soql)\nPLUGIN_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR)) # querying-soql/\nSKILLS_ROOT = os.path.dirname(PLUGIN_ROOT) # sf-skills/\nSHARED_DIR = os.path.join(SKILLS_ROOT, \"shared\")\nsys.path.insert(0, SHARED_DIR)\n\n\ndef validate_soql_file(file_path: str) -> dict:\n \"\"\"\n Validate a .soql file with static analysis and live query plan.\n\n Args:\n file_path: Path to .soql file\n\n Returns:\n dict with validation results and output message\n \"\"\"\n output_parts = []\n file_name = os.path.basename(file_path)\n issues = []\n recommendations = []\n\n try:\n # Read file content\n with open(file_path, 'r') as f:\n content = f.read()\n\n if not content.strip():\n return {\"continue\": True}\n\n # ═══════════════════════════════════════════════════════════════════\n # PHASE 1: Static SOQL Validation\n # ═══════════════════════════════════════════════════════════════════\n static_result = validate_soql_static(content)\n issues.extend(static_result.get('issues', []))\n recommendations.extend(static_result.get('recommendations', []))\n\n # ═══════════════════════════════════════════════════════════════════\n # PHASE 2: Live Query Plan Analysis (if org connected)\n # ═══════════════════════════════════════════════════════════════════\n live_result = None\n org_name = None\n\n try:\n from code_analyzer.live_query_plan import LiveQueryPlanAnalyzer\n\n analyzer = LiveQueryPlanAnalyzer()\n if analyzer.is_org_available():\n org_name = analyzer.get_target_org()\n live_result = analyzer.analyze(content)\n\n if live_result.success:\n # Add live plan insights as issues/recommendations\n if not live_result.is_selective:\n issues.append({\n 'severity': 'WARNING',\n 'message': f'Non-selective query (cost: {live_result.relative_cost:.1f})',\n 'source': 'LivePlan'\n })\n\n # Add notes as recommendations\n for note in live_result.notes:\n recommendations.append(str(note))\n\n # Get optimization suggestions\n suggestions = analyzer.get_optimization_suggestions(live_result)\n recommendations.extend(suggestions)\n\n except ImportError:\n pass # Live analysis not available\n except Exception as e:\n pass # Don't fail on live analysis errors\n\n # ═══════════════════════════════════════════════════════════════════\n # PHASE 3: Format Output\n # ═══════════════════════════════════════════════════════════════════\n output_parts.append(\"\")\n output_parts.append(f\"🔍 SOQL Validation: {file_name}\")\n output_parts.append(\"═\" * 55)\n\n # Static analysis summary\n if static_result.get('has_where_clause'):\n output_parts.append(\"✅ Has WHERE clause\")\n else:\n output_parts.append(\"⚠️ Missing WHERE clause\")\n\n if static_result.get('has_limit'):\n output_parts.append(\"✅ Has LIMIT clause\")\n else:\n output_parts.append(\"⚠️ Missing LIMIT clause\")\n\n if static_result.get('has_hardcoded_ids'):\n output_parts.append(\"⚠️ Contains hardcoded IDs\")\n\n # Live Query Plan section\n output_parts.append(\"\")\n if live_result and live_result.success:\n output_parts.append(f\"🌐 Live Query Plan Analysis\")\n output_parts.append(f\" Org: {org_name}\")\n output_parts.append(f\" {live_result.icon} Selective: {live_result.is_selective}\")\n output_parts.append(f\" 📊 Relative Cost: {live_result.relative_cost:.2f} ({live_result.selectivity_rating})\")\n output_parts.append(f\" 📈 Operation: {live_result.leading_operation}\")\n\n if live_result.cardinality > 0:\n output_parts.append(f\" 📋 Cardinality: {live_result.cardinality:,} / {live_result.sobject_cardinality:,}\")\n\n if live_result.notes:\n output_parts.append(\"\")\n output_parts.append(\" 📝 Query Plan Notes:\")\n for note in live_result.notes[:3]:\n output_parts.append(f\" • {str(note)[:70]}\")\n elif org_name is None:\n output_parts.append(\"🌐 Live Query Plan: No org connected\")\n output_parts.append(\" Run 'sf org login web' to enable live analysis\")\n elif live_result and not live_result.success:\n output_parts.append(f\"🌐 Live Query Plan: Error\")\n output_parts.append(f\" {live_result.error[:60]}\")\n\n # Issues\n if issues:\n output_parts.append(\"\")\n output_parts.append(f\"⚠️ Issues ({len(issues)}):\")\n severity_icons = {\n 'CRITICAL': '🔴', 'HIGH': '🟠', 'MODERATE': '🟡',\n 'WARNING': '⚠️', 'LOW': '🔵', 'INFO': 'ℹ️'\n }\n for issue in issues[:5]:\n icon = severity_icons.get(issue.get('severity', 'INFO'), 'ℹ️')\n source = f\"[{issue.get('source', '')}]\" if issue.get('source') else \"\"\n output_parts.append(f\" {icon} {source} {issue.get('message', '')[:60]}\")\n\n # Recommendations\n unique_recs = list(dict.fromkeys(recommendations)) # Remove duplicates\n if unique_recs:\n output_parts.append(\"\")\n output_parts.append(\"💡 Recommendations:\")\n for rec in unique_recs[:5]:\n output_parts.append(f\" • {rec[:65]}\")\n\n output_parts.append(\"═\" * 55)\n\n return {\n \"continue\": True,\n \"output\": \"\\n\".join(output_parts)\n }\n\n except Exception as e:\n return {\n \"continue\": True,\n \"output\": f\"⚠️ SOQL validation error: {e}\"\n }\n\n\ndef validate_soql_static(content: str) -> dict:\n \"\"\"\n Perform static validation on SOQL content.\n\n Args:\n content: SOQL query string\n\n Returns:\n dict with validation flags and issues\n \"\"\"\n import re\n\n result = {\n 'is_valid': True,\n 'has_where_clause': False,\n 'has_limit': False,\n 'has_order_by': False,\n 'has_hardcoded_ids': False,\n 'uses_indexed_fields': False,\n 'issues': [],\n 'recommendations': []\n }\n\n # Remove comments\n clean = re.sub(r'--.*

querying-soql: Salesforce SOQL Query Expert Use this skill when the user needs SOQL/SOSL authoring or optimization : natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance/safety improvements for Salesforce queries. When This Skill Owns the Task Use when the work involves: - files - query generation from natural language - relationship queries and aggregate queries - query optimization and selectivity analysis - SOQL/SOSL syntax and governor-aware design Delegate elsewhere when the user is: - performing bulk data operations → handling-sf-d…

, '', content, flags=re.MULTILINE)\n clean = re.sub(r'//.*

querying-soql: Salesforce SOQL Query Expert Use this skill when the user needs SOQL/SOSL authoring or optimization : natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance/safety improvements for Salesforce queries. When This Skill Owns the Task Use when the work involves: - files - query generation from natural language - relationship queries and aggregate queries - query optimization and selectivity analysis - SOQL/SOSL syntax and governor-aware design Delegate elsewhere when the user is: - performing bulk data operations → handling-sf-d…

, '', clean, flags=re.MULTILINE)\n clean = re.sub(r'/\\*[\\s\\S]*?\\*/', '', clean)\n\n # Check for WHERE clause\n result['has_where_clause'] = bool(re.search(r'\\bWHERE\\b', clean, re.IGNORECASE))\n\n # Check for LIMIT\n result['has_limit'] = bool(re.search(r'\\bLIMIT\\s+\\d+', clean, re.IGNORECASE))\n\n # Check for ORDER BY\n result['has_order_by'] = bool(re.search(r'\\bORDER\\s+BY\\b', clean, re.IGNORECASE))\n\n # Check for hardcoded IDs (15 or 18 char alphanumeric in quotes)\n result['has_hardcoded_ids'] = bool(\n re.search(r\"'[a-zA-Z0-9]{15}'\", clean) or\n re.search(r\"'[a-zA-Z0-9]{18}'\", clean)\n )\n\n # Check for indexed fields in WHERE\n indexed_fields = ['Id', 'Name', 'OwnerId', 'CreatedDate', 'LastModifiedDate', 'RecordTypeId']\n where_match = re.search(r'\\bWHERE\\b(.*?)(?:\\bORDER\\b|\\bGROUP\\b|\\bLIMIT\\b|$)', clean, re.IGNORECASE | re.DOTALL)\n if where_match:\n where_clause = where_match.group(1)\n for field in indexed_fields:\n if re.search(rf'\\b{field}\\b', where_clause, re.IGNORECASE):\n result['uses_indexed_fields'] = True\n break\n\n # Syntax validation\n # Check for SELECT without FROM\n if re.search(r'\\bSELECT\\b', clean, re.IGNORECASE):\n if not re.search(r'\\bFROM\\b', clean, re.IGNORECASE):\n result['issues'].append({\n 'severity': 'HIGH',\n 'message': 'SELECT statement missing FROM clause'\n })\n result['is_valid'] = False\n\n # Check for SELECT *\n if re.search(r'\\bSELECT\\s+\\*', clean, re.IGNORECASE):\n result['issues'].append({\n 'severity': 'HIGH',\n 'message': 'SELECT * is not valid in SOQL - specify field names'\n })\n result['is_valid'] = False\n\n # Check for == instead of =\n if re.search(r'==', clean):\n result['issues'].append({\n 'severity': 'HIGH',\n 'message': 'Invalid operator \"==\" - use \"=\" in SOQL'\n })\n\n # Check for unbalanced parentheses\n if clean.count('(') != clean.count(')'):\n result['issues'].append({\n 'severity': 'HIGH',\n 'message': 'Unbalanced parentheses'\n })\n\n # Add recommendations\n if not result['has_where_clause']:\n result['recommendations'].append('Add WHERE clause for better query selectivity')\n\n if not result['has_limit']:\n result['recommendations'].append('Add LIMIT clause to prevent large result sets')\n\n if result['has_hardcoded_ids']:\n result['recommendations'].append('Avoid hardcoded IDs - use bind variables instead')\n\n if result['has_where_clause'] and not result['uses_indexed_fields']:\n result['recommendations'].append('Add an indexed field (Id, Name, CreatedDate) to WHERE for better performance')\n\n return result\n\n\ndef main():\n \"\"\"\n Main hook entry point.\n\n Reads hook input from stdin, validates SOQL files.\n \"\"\"\n try:\n # Read hook input from stdin\n hook_input = json.load(sys.stdin)\n\n # Extract file path from tool input\n tool_input = hook_input.get(\"tool_input\", {})\n file_path = tool_input.get(\"file_path\", \"\")\n\n # Check if operation was successful\n tool_response = hook_input.get(\"tool_response\", {})\n if not tool_response.get(\"success\", True):\n print(json.dumps({\"continue\": True}))\n return 0\n\n # Only validate .soql files\n result = {\"continue\": True}\n\n if file_path.lower().endswith(\".soql\"):\n result = validate_soql_file(file_path)\n\n # Output result\n print(json.dumps(result))\n return 0\n\n except json.JSONDecodeError:\n print(json.dumps({\"continue\": True}))\n return 0\n except Exception as e:\n print(json.dumps({\n \"continue\": True,\n \"output\": f\"⚠️ Hook error: {e}\"\n }))\n return 0\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12166,"content_sha256":"5f595c2bc76ec678660784b699a267ed9a6d1d274fb3bf8b33bfa9048c936b68"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"querying-soql: Salesforce SOQL Query Expert","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill when the user needs ","type":"text"},{"text":"SOQL/SOSL authoring or optimization","type":"text","marks":[{"type":"strong"}]},{"text":": natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance/safety improvements for Salesforce queries.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When This Skill Owns the Task","type":"text"}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"querying-soql","type":"text","marks":[{"type":"code_inline"}]},{"text":" when the work involves:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":".soql","type":"text","marks":[{"type":"code_inline"}]},{"text":" files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"query generation from natural language","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"relationship queries and aggregate queries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"query optimization and selectivity analysis","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"SOQL/SOSL syntax and governor-aware design","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Delegate elsewhere when the user is:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"performing bulk data operations → ","type":"text"},{"text":"handling-sf-data","type":"text","marks":[{"type":"link","attrs":{"href":"../handling-sf-data/SKILL.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"embedding query logic inside broader Apex implementation → ","type":"text"},{"text":"generating-apex","type":"text","marks":[{"type":"link","attrs":{"href":"../generating-apex/SKILL.md","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"debugging via logs rather than query shape → ","type":"text"},{"text":"debugging-apex-logs","type":"text","marks":[{"type":"link","attrs":{"href":"../debugging-apex-logs/SKILL.md","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Required Context to Gather First","type":"text"}]},{"type":"paragraph","content":[{"text":"Ask for or infer:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"target object(s)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"fields needed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"filter criteria","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"sort / limit requirements","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"whether the query is for display, automation, reporting-like analysis, or Apex usage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"whether performance / selectivity is already a concern","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Recommended Workflow","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Generate the simplest correct query","type":"text"}]},{"type":"paragraph","content":[{"text":"Prefer:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"only needed fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"clear WHERE criteria","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"reasonable LIMIT when appropriate","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"relationship depth only as deep as necessary","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. Choose the right query shape","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Need","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Default pattern","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"parent data from child","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"child-to-parent traversal","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"child rows from parent","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"subquery","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"counts / rollups","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"aggregate query","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"records with / without related rows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"semi-join / anti-join","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"text search across objects","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"SOSL","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Optimize for selectivity and safety","type":"text"}]},{"type":"paragraph","content":[{"text":"Check:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"indexed / selective filters","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"no unnecessary fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"no avoidable wildcard or scan-heavy patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"security enforcement expectations","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Validate execution path if needed","type":"text"}]},{"type":"paragraph","content":[{"text":"If the user wants runtime verification, hand off execution to:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"handling-sf-data","type":"text","marks":[{"type":"link","attrs":{"href":"../handling-sf-data/SKILL.md","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"High-Signal Rules","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"never use ","type":"text"},{"text":"SELECT *","type":"text","marks":[{"type":"code_inline"}]},{"text":" style thinking; query only required fields","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"do not query inside loops in Apex contexts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"prefer filtering in SOQL rather than post-filtering in Apex","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"use aggregates for counts and grouped summaries instead of loading unnecessary records","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"evaluate wildcard usage carefully; leading wildcards often defeat indexes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"account for security mode / field access requirements when queries move into Apex","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output Format","type":"text"}]},{"type":"paragraph","content":[{"text":"When finishing, report in this order:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Query purpose","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Final SOQL/SOSL","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why this shape was chosen","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optimization or security notes","type":"text","marks":[{"type":"strong"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Execution suggestion if needed","type":"text","marks":[{"type":"strong"}]}]}]}]},{"type":"paragraph","content":[{"text":"Suggested shape — use ","type":"text"},{"text":"references/soql-syntax-reference.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for exact syntax:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"Query goal: \u003csummary>\nQuery: \u003csoql or sosl>\nDesign: \u003crelationship / aggregate / filter choices>\nNotes: \u003cselectivity, limits, security, governor awareness>\nNext step: \u003crun in handling-sf-data or embed in Apex>","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Cross-Skill Integration","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Need","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Delegate to","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reason","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"run the query against an org","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"handling-sf-data","type":"text","marks":[{"type":"link","attrs":{"href":"../handling-sf-data/SKILL.md","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"execution and export","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"embed the query in services/selectors","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"generating-apex","type":"text","marks":[{"type":"link","attrs":{"href":"../generating-apex/SKILL.md","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"implementation context","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"analyze slow-query symptoms from logs","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"debugging-apex-logs","type":"text","marks":[{"type":"link","attrs":{"href":"../debugging-apex-logs/SKILL.md","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"runtime evidence","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"wire query-backed UI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"generating-lwc-components","type":"text","marks":[{"type":"link","attrs":{"href":"../generating-lwc-components/SKILL.md","title":null}}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"frontend integration","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Score Guide","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Score","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Meaning","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"90+","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"production-optimized query","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"80–89","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"good query with minor improvements possible","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"70–79","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"functional but performance concerns remain","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003c 70","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"needs revision before production use","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference File Index","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"File","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"When to read","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/soql-syntax-reference.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Syntax, operators, date literals, relationship query patterns","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/query-optimization.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Selectivity rules, indexing strategy, governor limits, security patterns","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/soql-reference.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Quick reference — operators, date functions, aggregate functions, WITH clauses","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/anti-patterns.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Common SOQL mistakes and their fixes — read before finalizing any query","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/selector-patterns.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Apex selector layer patterns — read when embedding queries in Apex classes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/field-coverage-rules.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Field coverage validation — read when generating SOQL used inside Apex code","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/cli-commands.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sf CLI query execution, bulk export, query plan commands","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/basic-queries.soql","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Starter query examples for common objects","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/relationship-queries.soql","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Parent-to-child and child-to-parent relationship query patterns","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/aggregate-queries.soql","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"COUNT, SUM, GROUP BY, ROLLUP query patterns","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/optimization-patterns.soql","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Selective filter and index-aware query patterns","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/bulkified-query-pattern.cls","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Apex Map-based bulk query pattern for trigger contexts","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"assets/selector-class.cls","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Full selector class implementation template","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"scripts/post-tool-validate.py","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Post-write hook — runs static SOQL validation and live query plan analysis after ","type":"text"},{"text":".soql","type":"text","marks":[{"type":"code_inline"}]},{"text":" file edits","type":"text"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"querying-soql","author":"@skillopedia","source":{"stars":438,"repo_name":"sf-skills","origin_url":"https://github.com/forcedotcom/sf-skills/blob/HEAD/skills/querying-soql/SKILL.md","repo_owner":"forcedotcom","body_sha256":"d38158f870ab2445736ee9f550ad31242fc072ee6b3124a24e7191bd40d7465a","cluster_key":"773da8297536fa7ab41c0328b3ae5f5188996f1286fd36e00109fe87995eb26d","clean_bundle":{"format":"clean-skill-bundle-v1","source":"forcedotcom/sf-skills/skills/querying-soql/SKILL.md","attachments":[{"id":"538b0b19-94e7-53c9-bfcf-622d01b28af4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/538b0b19-94e7-53c9-bfcf-622d01b28af4/attachment.md","path":"CREDITS.md","size":995,"sha256":"ed3a3d8325e304b4de3419a463f3f87ebee4eb53a0d32db3af8b8f4823309269","contentType":"text/markdown; charset=utf-8"},{"id":"d49d8e72-f2c2-5bb5-9280-98dea9c04cfa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d49d8e72-f2c2-5bb5-9280-98dea9c04cfa/attachment.md","path":"README.md","size":1725,"sha256":"4df707c4573f7ea7f2d70585eac1924b0ad1ed68ba4c7cdf3283e06344993c7b","contentType":"text/markdown; charset=utf-8"},{"id":"e443ee08-7ec6-542a-9544-9614f310248b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e443ee08-7ec6-542a-9544-9614f310248b/attachment.soql","path":"assets/aggregate-queries.soql","size":9578,"sha256":"e0c6356187b242f9511eb5237a4c499f03461f986043c4e566fc9aa42a5694d0","contentType":"text/plain; charset=utf-8"},{"id":"d58bdb47-50a8-5632-ab58-7bc2e19e0bbd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d58bdb47-50a8-5632-ab58-7bc2e19e0bbd/attachment.soql","path":"assets/basic-queries.soql","size":7628,"sha256":"aa1ca9353c5644e8d06a726e56143e14ef7ec38debc4194c4699f0097579991f","contentType":"text/plain; charset=utf-8"},{"id":"2919bf49-7765-51ec-959a-c5d066199b8f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2919bf49-7765-51ec-959a-c5d066199b8f/attachment.cls","path":"assets/bulkified-query-pattern.cls","size":10928,"sha256":"27badff6152eb6aed544a9481a194d81d0e85fc2d8df67c818868b4a6c2ac1ad","contentType":"text/x-tex"},{"id":"9067b329-bdad-5e2b-9d26-3890ba7c4919","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9067b329-bdad-5e2b-9d26-3890ba7c4919/attachment.soql","path":"assets/optimization-patterns.soql","size":11158,"sha256":"9ed63426f012a76569446324f43d8c2a877bd1e35f9bde95aeca00f6d20a96d4","contentType":"text/plain; charset=utf-8"},{"id":"2e07e8e0-6a91-557c-87e2-fe5cd7af2baa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2e07e8e0-6a91-557c-87e2-fe5cd7af2baa/attachment.soql","path":"assets/relationship-queries.soql","size":7725,"sha256":"37aded190eb371e3d9b2d5a937a1ee7ccb335cf727efcb772841556daa868bb6","contentType":"text/plain; charset=utf-8"},{"id":"269c20da-6cfe-5791-8038-f35c3018085e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/269c20da-6cfe-5791-8038-f35c3018085e/attachment.cls","path":"assets/selector-class.cls","size":8598,"sha256":"b53ccbd77050a9f9b7616d8bcfc059698d3e4f91b8bb185bb56a0c8c5fd9df1f","contentType":"text/x-tex"},{"id":"ac48119e-13b4-5daa-929e-a142e5b2f63d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ac48119e-13b4-5daa-929e-a142e5b2f63d/attachment.md","path":"references/anti-patterns.md","size":8862,"sha256":"e0beefa5175068965b0265280f0bd817030a2af091a1979396068ffad1ca98c7","contentType":"text/markdown; charset=utf-8"},{"id":"106327b9-ad88-57f8-a5c9-23007250060e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/106327b9-ad88-57f8-a5c9-23007250060e/attachment.md","path":"references/cli-commands.md","size":7357,"sha256":"a201b8a78593b99087385b9fbb8a6233cc9663ffcbf48b286717cb99f9a23a24","contentType":"text/markdown; charset=utf-8"},{"id":"aff3f610-1edf-5690-a757-2d55743c2fe6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aff3f610-1edf-5690-a757-2d55743c2fe6/attachment.md","path":"references/field-coverage-rules.md","size":14779,"sha256":"faeabea3abbeec4af5ef86725636f61d2d11216d3529e39d20dd12b611e066e2","contentType":"text/markdown; charset=utf-8"},{"id":"a1c371b2-3316-5694-9953-6dd265bb31c4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a1c371b2-3316-5694-9953-6dd265bb31c4/attachment.md","path":"references/query-optimization.md","size":3298,"sha256":"bcd028a28f5d51e7c57bc08817fcb5a986fe63213370910352d076c3f6e8c298","contentType":"text/markdown; charset=utf-8"},{"id":"0da1fe61-5f60-53f0-9834-f0dd5bad745c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0da1fe61-5f60-53f0-9834-f0dd5bad745c/attachment.md","path":"references/selector-patterns.md","size":13994,"sha256":"b0229c5b60b245c2fc7e1fe14f8e6a797ea6493f871beb55e7b33a65a564d151","contentType":"text/markdown; charset=utf-8"},{"id":"d113c179-f601-5e82-8695-fca3066a3cf3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d113c179-f601-5e82-8695-fca3066a3cf3/attachment.md","path":"references/soql-reference.md","size":6512,"sha256":"773b8c90aebef4a7798afa5798b34bb89208b94bd2d3877671fdd9c4f8aa7d65","contentType":"text/markdown; charset=utf-8"},{"id":"d8aeb4f1-3246-5116-a085-2ed9b51a8d1d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d8aeb4f1-3246-5116-a085-2ed9b51a8d1d/attachment.md","path":"references/soql-syntax-reference.md","size":4595,"sha256":"2c80ecd94dcaa96826dce424e35cba83212fe99ebc3c12b021c922030b6a6d0d","contentType":"text/markdown; charset=utf-8"},{"id":"a76c8234-69d1-5c29-b597-1414b8242642","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a76c8234-69d1-5c29-b597-1414b8242642/attachment.py","path":"scripts/post-tool-validate.py","size":12166,"sha256":"5f595c2bc76ec678660784b699a267ed9a6d1d274fb3bf8b33bfa9048c936b68","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"452a8b7896119b43890c50aed672e8418bcf918113ede831e8c2caf515cf940c","attachment_count":16,"text_attachments":12,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":4,"excluded_attachments":[]},"cluster_size":2,"skill_md_path":"skills/querying-soql/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"data-analytics","category_label":"Data"},"exact_dupes_collapsed_into_this":1},"license":"MIT","version":"v1","category":"data-analytics","metadata":{"version":"1.1"},"import_tag":"clean-skills-v1","description":"SOQL query generation, optimization, and analysis with 100-point scoring. Use this skill when the user needs SOQL/SOSL authoring or optimization: natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance or safety improvements for Salesforce queries. TRIGGER when: user writes, optimizes, or debugs SOQL/SOSL queries, touches .soql files, or asks about relationship queries, aggregates, or query performance. DO NOT TRIGGER when: bulk data operations (use handling-sf-data), Apex DML logic (use generating-apex), or report/dashboard queries."}},"renderedAt":1782987380650}

querying-soql: Salesforce SOQL Query Expert Use this skill when the user needs SOQL/SOSL authoring or optimization : natural-language-to-query generation, relationship queries, aggregates, query-plan analysis, and performance/safety improvements for Salesforce queries. When This Skill Owns the Task Use when the work involves: - files - query generation from natural language - relationship queries and aggregate queries - query optimization and selectivity analysis - SOQL/SOSL syntax and governor-aware design Delegate elsewhere when the user is: - performing bulk data operations → handling-sf-d…