面向管理员的 SOQL 查询

SOQL(Salesforce Object Query Language)是面向管理员和开发者的数据查询语言。本文从必备的 SELECT/FROM 子句讲起,覆盖 WHERE、LIMIT、ORDER BY 过滤与排序,再教你如何在 Apex 类中用列表和 for 循环处理查询结果,最后深入标准对象与自定义对象的关系查询、绑定变量和聚合函数,一步步掌握从 Salesforce 取数的完整技能。...

📅 2026/10/11 ✍️ ponybai 🏷️ salesforce, headless

SOQL 查询入门(01 Get Started with SOQL Queries)

slide_2
slide_3

本单元认识 Salesforce Object Query Language(SOQL,读作「sockle」)——它从 Salesforce 数据库读取记录数据。你将学习必备的 SELECT 与 FROM 子句,以及可选的 WHERE、LIMIT、ORDER BY 子句。

完成本单元后,你将能够:

  • 用必备子句构建一条有效的 SOQL 查询;
  • 按多个条件过滤查询结果;
  • 对查询结果排序。

注意:写代码时务必使用字段的 API 名称,而不是字段标签。

什么是 SOQL?(What Is SOQL?)

slide_4

SOQL 是从 Salesforce 数据库读取记录数据的语言。在之前的模块里,你学过用 DML 语句插入、更新和删除记录;而要读取库里已存在的数据,就轮到 SOQL 出场了。

创建 SOQL 查询很像创建报表:

  1. 我要显示哪些字段?
  2. 这些字段在哪个对象里?

查询里的答案就是字段和对象。例如 SELECT Name, Email FROM Contact——SELECT 和 FROM 是关键字,Name、Email 是字段,Contact 是标准对象。这条查询读取联系人记录里的特定信息。

必备子句:SELECT(Required SOQL Clauses — SELECT)

slide_5

任何 SOQL 查询的根基是两个子句:SELECT 和 FROM。可以把子句想成拼图的一块——它是完整查询的一部分。

每条查询都有以 SELECT 关键字开头的 SELECT 子句,它指定记录中的一个或多个字段(用逗号分隔)。例如 SELECT Name, Email 返回 Name 和 Email 两个字段。再次强调:这里永远用字段 API 名称,而不是字段标签。

必备子句:FROM(Required SOQL Clauses — FROM)

slide_6

第二个必备子句是 FROM。

以 FROM 关键字开头,FROM 子句指定数据库里的一个对象。这里我们想要 Contact 对象的数据。把两条子句合起来,SELECT Name, Email FROM Contact 就明确告诉 Salesforce:从 Contact 对象的记录里返回 Name 和 Email 字段。

动手:用 Query Editor 构建查询(Build a Query Using the Query Editor)

slide_7

本单元在 Developer Console 的 Query Editor 里构建查询(建议新建一个 Trailhead Playground 跟着做)。步骤:

  1. 打开 Developer Console;
  2. 点击 File | Open | Objects | Contact,打开 Contact 对象;
  3. 在底部面板选择 Query Editor 标签;
  4. 用 Ctrl/Cmd + 点击 选中 Email、Name、Languages__c 字段;
  5. 点 Query——编辑器会自动把字段 API 名称写进查询;
  6. 点 Execute 执行。

执行后结果精确返回你请求的字段,而且 Query Editor 还会额外返回记录的 Id 作为「赠品」。

可选子句(Optional SOQL Clauses)

slide_8

SELECT 和 FROM 是必备的,但 SOQL 还有可选子句来细化查询:

  • WHERE——设置记录必须满足的条件;
  • LIMIT——限制返回记录的最大数量;
  • ORDER BY——按某字段对结果排序。

当数据量很大时,这些子句能帮你返回子集、回答精确的问题,而不用把整库记录都拉出来。

WHERE 子句(WHERE)

slide_9

WHERE 子句像列表视图里的过滤器一样过滤记录。例如只想找名字是 Stella 的联系人,就在查询末尾加上 WHERE FirstName = 'Stella'。多个条件用三个运算符组合:

  • AND——返回同时满足两个条件的记录,如 FirstName = 'Stella' AND LastName = 'Pavlova';
  • OR——返回满足任一条件的记录,如 LastName = 'James' OR LastName = 'Barr';
  • IN——返回匹配三个及以上值中任意一个的记录,如 LastName IN ('James','Barr','Forbes'),它能把一长串 OR 条件简化成一条。

LIMIT 子句(LIMIT)

slide_10

LIMIT 关键字设置返回记录的最大数量。测试大数据集时很有用,能避免等待超大的结果集。

例如在查询末尾加上 LIMIT 5:SELECT Email, Name FROM Contact LIMIT 5,Query Results 面板就只显示 5 条结果。随着你深入学习 SOQL,会发现更精准的避免返回过多记录的方法,但 LIMIT 是最容易的起点。

ORDER BY 子句(ORDER BY)

slide_11

拿到结果后如何组织它们?用 ORDER BY 子句按某个字段的值排序,像电子表格一样:

  • ASC——升序(默认);
  • DESC——降序;
  • NULLS FIRST | LAST——把空值记录排在开头或结尾。

例如 SELECT Name, Email FROM Contact ORDER BY Name ASC LIMIT 5 按姓名升序返回 5 个联系人的 Name 和 Email。

而 SELECT Name, Email FROM Contact ORDER BY Email NULLS LAST 按邮箱升序返回结果,并把没有邮箱的记录都放到末尾。掌握了这些,你就能读取海量数据、用 LIMIT 精确取数、用 ORDER BY 随心组织结果了。

在 Apex 类中创建 SOQL 查询(02 Create SOQL Queries in Apex Classes)

slide_12
slide_13

本单元把 SOQL 从 Query Editor 搬到 Apex 类里,在类和方法中运行查询,并用列表和 for 循环处理返回的数据。

完成本单元后,你将能够:

  • 在方法中创建 SOQL 查询;
  • 处理 SOQL 查询返回的数据。

引言:查询结果存进列表(Introduction)

slide_14

SOQL 查询总是以列表(List)的形式返回数据,所以先创建一个 Apex 列表来装结果。声明列表需要三样东西:

  • List 保留字;
  • 尖括号里的数据类型(如 Contact);
  • 新列表的名字(如 listOfContacts)。

再用等号 = 把查询结果赋给列表;查询写在方括号 [ ] 里,语句以分号 ; 结尾。

在 Developer Console 测试代码(Test Your Code in the Developer Console)

slide_15

用 Execute Anonymous 窗口测试查询并把结果送到 Debug 日志:

  1. 点 Debug | Open Execute Anonymous Window;
  2. 把查询结果赋给列表:List<Contact> listOfContacts = [SELECT FirstName, LastName FROM Contact LIMIT 2];
  3. 下一行发到日志:System.debug(listOfContacts);
  4. 勾选 Open Log,点 Execute;
  5. 在日志底部勾选 Debug Only。

日志会显示检索到的联系人。注意:即使查询找不到任何结果,它仍会返回一个列表——只是列表为空。

背后发生了什么?(What's Actually Happening Here?)

slide_16

代码运行时按三步走:

  1. 先执行查询——找到所有 Contact 记录并读取每个记录的 FirstName 和 LastName;
  2. 把选中的数据加入名为 listOfContacts 的列表;
  3. 最后 System.debug 显示列表内容,方便你核对结果。

在 Apex 中运行 SOQL 查询(Run SOQL Queries in Apex)

slide_17

你的用户不会在 Developer Console 里跑查询,所以要在组织的用户界面里返回数据,这就需要 Apex 类和方法。步骤:

  1. 点 File | New | Apex Class,命名为 ContactUtility;
  2. 第 2 行加一个方法 public static void viewContacts(){ };
  3. 在方法体内运行查询并赋给列表:List<Contact> listOfContacts = [SELECT FirstName, LastName FROM Contact];
  4. 保存类。

之后随时可以调用 ContactUtility.viewContacts(); 来获取数据。

用 for 循环遍历列表(Using For Loops to Iterate Through a List)

slide_18

现在数据在 listOfContacts 里,我们用 for 循环逐个处理列表项。声明 for 循环需要一个变量名、它的数据类型,以及要遍历的列表名:

循环里不直接引用具体对象,而是用一个变量作为占位符,一次代表一个列表项。比如用 con 代表变量,数据类型是 Contact,列表是 listOfContacts,合起来就是:for (Contact con : listOfContacts) { ... }。

声明 for 循环(Declare a For Loop)

slide_19

在 viewContacts 方法里,紧跟 SOQL 查询之后粘贴这段代码并保存:

for(Contact con : listOfContacts) {
    //loop body
}

至此三步就位:① 查询选择数据 → ② 列表存储数据 → ③ for 循环逐个处理联系人。

在 for 循环中处理列表项(Process List Items in the For Loop)

slide_20

循环体内部,用点号记法(dot notation)访问字段(object.field,如 con.FirstName),并用加号 + 做拼接(concatenation);字面文本要放进单引号里,并注意在文本首尾加空格。

把循环体替换成:

String fullName = 'First Name: ' + con.FirstName + ', Last Name: ' + con.LastName;
System.debug(fullName);

保存后,用 ContactUtility.viewContacts(); 运行。日志会显示每个联系人,例如 First Name: Rose, Last Name: Gonzalez。用 SOQL + for 循环 + 拼接,你成功完成了取数、存列表、遍历、生成结果的完整流程。

用标准对象创建关系查询(03 Create Relationship Queries with Standard Objects)

slide_21
slide_22

本单元在标准对象之间做跨对象查询——用关系查询同时取父对象和子对象的字段,并用子查询过滤结果。

完成本单元后,你将能够:

  • 描述两种关系查询类型;
  • 识别两个对象之间的关系;
  • 创建子到父查询与父到子查询;
  • 用子查询过滤结果。

关于关系查询(About Relationship Queries)

slide_23

FROM 子句只能指定一个对象,但通过关系查询(relationship query)可以访问两个相关对象——把它想成跨对象查询。需求示例:「获取所有联系人,以及每个联系人的客户名」。Contact 没有 Account Name 字段——客户名其实是相关 Account 对象上的 Name 字段。在 Apex 里就用关系查询,而用哪种关系查询取决于两个对象如何关联:

  • 要 子(Child)对象的记录并带上父对象字段 → 用子到父(child-to-parent)查询;
  • 要 父(Parent)对象的记录并带上子对象字段 → 用父到子(parent-to-child)查询。

识别对象之间的关系(Identify the Objects' Relationship)

slide_24

查询前先搞清楚谁是谁的父对象。主-从(master-detail)关系就是父到子关系:主对象是父、明细对象是子,一个主对象可以有多个明细。不确定关系时,去 Object Manager 里探索子对象:

例如 Contact 有一个 AccountId 字段,类型是 Lookup(Account)。记住代码里要用字段名(AccountId),不是字段标签(Account Name)。

在 AccountId 字段的详情里,Child Relationship Name 是 Contacts(复数)——这确认了 Contact 是 Account 的子对象,也给了我们父到子查询所需的关系名。

创建子到父查询(Create a Child-to-Parent Query)

slide_25

要返回所有联系人(子)和各自客户(父)的名字,就用子到父查询:查询子对象,并用点号记法取父对象字段。

SELECT Name, Account.Name FROM Contact

这条语句查询 Contact 对象;SELECT 里 Name 是联系人的名字,Account.Name 是相关 Account 的 Name 字段。执行后第一列是联系人名,第二列是它所属客户的名字。

创建父到子查询(Create a Parent-to-Child Query)

slide_26

反过来,要「每个客户的名字 + 每个相关联系人的名字」,就用父到子查询,其中包含一个子查询(subquery)——子查询是用圆括号包裹、嵌套在另一条查询里的 SELECT 语句。

先把需求拆成两部分:主查询 SELECT Name FROM Account,子查询 (SELECT Name FROM Contact)。

把子查询当作主查询的第二个字段插进去,并用子关系名 Contacts 替换对象名 Contact:

SELECT Name, (SELECT Name FROM Contacts) FROM Account

运行父到子查询(Run the Parent-to-Child Query)

slide_27

执行 SELECT Name, (SELECT Name FROM Contacts) FROM Account 时,查询分三步:

  1. 选一个客户,取它的 Name(主查询/外层查询);
  2. 看该客户的相关联系人,逐个取 Name(子查询/内层查询);
  3. 移到下一个客户重复,直到取完所有客户。

结果里每个客户名下是一组联系人,这种「键-值」集合就像你在对象导向模块里学过的 map——键是字段名(如 Name),值是对应的数据。

用子查询过滤(Filtering with a Subquery)

slide_28

假设只想找「有姓 Forbes 的联系人」的客户——这需要在主查询的 WHERE 子句里放进一个(子对象的)子查询,用它的结果当过滤条件,等价于报表里的交叉过滤器(cross filter)。

先让子查询返回姓 Forbes 的联系人的 AccountId;再在主查询里用 WHERE Id IN (子查询) 只保留 Id 出现在该集合里的客户:

SELECT Name, (SELECT Name FROM Contacts)
FROM Account
WHERE Id IN (SELECT AccountId FROM Contact WHERE LastName = 'Forbes')

这条查询做四件事:① 找出姓 Forbes 的联系人的 AccountId;② 在主 WHERE 里匹配 Account.Id;③ 取每个客户的联系人名字;④ 最终返回「有 Forbes 联系人」的客户及它们的全部联系人。

小结(Summary)

slide_29

现在你已能为标准对象选择正确的关系查询类型:

  • 子到父——查子对象,用点号记法带出父字段;
  • 父到子——查父对象,用子查询带出子字段;
  • WHERE 子查询——按子对象的值过滤父对象。

下一单元把同样的思路应用到自定义对象上。

用自定义对象创建关系查询(04 Create Relationship Queries with Custom Objects)

slide_30
slide_31

本单元查询 DreamHouse 房地产示例应用里的自定义对象,学习用 __r 关系后缀和 Schema Builder 识别关系。

完成本单元后,你将能够:

  • 读懂 Schema Builder 图表识别对象关系;
  • 识别查询里要用的自定义关系名;
  • 为自定义对象创建子到父和父到子查询。

安装 DreamHouse 包(Install a Package)

slide_32

本模块后续示例和动手活动都用 DreamHouse 房地产应用。先在 Trailhead Playground 里安装它的托管包并导入示例数据:

  1. 打开 Install a Package 标签(或用 Playground Starter);
  2. 粘贴包 ID 04t3h000004mBpiAAE,点 Install;
  3. 选择 Install for Admins Only,如提示第三方访问则选授予访问并继续。

装完后,打开 DreamHouse 应用,进 Settings 点 Import Data 导入示例数据,并在 Properties 标签熟悉示例房产。

探索 DreamHouse 数据模型(Explore the DreamHouse Schema)

slide_33

之前只查了标准对象,现在用 Schema Builder 理解 DreamHouse 的数据模型:

  1. Setup → Object Manager → Schema Builder;
  2. 点 Clear All,在 Select 列表选 Custom Objects;
  3. 选 Broker__c 和 Property__c,点 Auto-Layout;
  4. 在 View Options 里开启 Display Element Names 查看 API 名称。

自定义对象 API 名以 __c 结尾。Property__c 有一个指向 Broker__c 的 Broker__c 查找字段。

查看该字段详情,Child Relationship Name 是 Properties——这确认 Property__c 是 Broker__c 的子对象。

查询自定义对象(Query Custom Objects)

slide_34

查询自定义对象和查标准对象很像,但有几处不同。需求:「获取所有房产的地址、图片和指派经纪人」,于是查询 Property__c 自定义对象:

SELECT Address__c, Picture__c, Broker__c FROM Property__c

结果里 Broker__c 列显示的是记录 Id,而不是可读的名字——因为 Broker__c 是查找(lookup)字段,查找字段存的是相关对象的 Id。要拿到经纪人的名字,就需要一条关系查询。

创建子到父查询(自定义对象)(Child-to-Parent Query)

slide_35

尝试写 Broker__c.Name 会报错:「Didn't understand relationship 'Broker__c'」。原因:Broker__c 是 Property 上的自定义字段,而我们要的是 Broker__c 这个相关对象。

解决办法——把字段后缀从 __c 改成 __r,得到自定义关系名 Broker__r:

SELECT Address__c, Picture__c, Broker__r.Name FROM Property__c

现在结果里就显示每位经纪人的 Name 了。

创建父到子查询(自定义对象)(Parent-to-Child Query)

slide_36

反过来要「所有经纪人的名字 + 每人名下房产的地址和价格」,就查父对象 Broker__c,并在子查询里用子关系名 Properties 加上 __r 后缀得到 Properties__r:

SELECT Name, (SELECT Address__c, Price__c FROM Properties__r) FROM Broker__c

结果返回每个经纪人及其名下房产的地址和价格。开发查询时,Query Editor 是测试和微调 SOQL 的好地方。

使用绑定变量与聚合函数(05 Use Bind Variables and Aggregate Functions)

slide_37
slide_38

本单元用 绑定变量(bind variables)让查询动态化,并用聚合函数汇总数据,再对结果分组和过滤。

完成本单元后,你将能够:

  • 插入 Apex 绑定变量使查询动态化;
  • 用聚合函数汇总数据;
  • 对结果应用分组;
  • 识别何时用 WHERE、何时用 HAVING。

绑定变量(Bind Variables)

slide_39

静态查询永远返回同样的结果——比如「低于 20 万美元的房产」。绑定变量让一条查询服务不同预算的买家:它是用在 SOQL 查询里的 Apex 变量,作为「稍后提供具体值」的占位符,类似于方法的参数。

语法上在变量前加冒号 ::

WHERE Price__c < :maxHomeValue

查询运行前先给变量赋值(根据用户输入),例如 DreamHouse 的 Property Explorer 里 Max Price 滑块滑动时,就通过绑定变量更新代码里的值,查询随之返回对应的房产列表。

聚合函数(Aggregate Functions)

slide_40

报表里你用汇总字段(roll-up summary)计算相关记录的值,SOQL 里则用聚合函数做同样的计算:

  • COUNT()——与字段关联的行数;
  • COUNT_DISTINCT()——匹配条件的唯一行数;
  • MIN() / MAX()——最小值 / 最大值(还能处理日期、时间甚至选项列表顺序);
  • AVG() / SUM()——数值字段的平均值 / 总和。

聚合函数写在 SELECT 子句里,如 SELECT COUNT(City__c) FROM Property__c 返回 12(房产总数)。

注意:COUNT() 会数包括重复值的所有值;COUNT_DISTINCT() 只数唯一值且区分大小写(Cambridge 和 cambridge 算两个不同值)。

对聚合结果分组(Group the Results of Aggregate Functions)

slide_41

算出聚合值后,你可能想像汇总报表那样分组展示,这时用 GROUP BY 子句。例如想知道哪些经纪人卖出(已成交)过房产:

SELECT MAX(Status__c), Broker__r.Name
FROM Property__c
GROUP BY Broker__r.Name

这条查询按经纪人分组房产,并列出每位经纪人名下「最靠后」的 Status__c 值(即最接近最终成交状态的选项列表值)。

过滤聚合结果(Filter the Results of Aggregate Functions)

slide_42

要进一步缩小范围,用 HAVING 子句——它过滤的是聚合函数的结果。接着上面的查询,只保留卖出过房产的经纪人:

SELECT MAX(Status__c), Broker__r.Name
FROM Property__c
GROUP BY Broker__r.Name
HAVING MAX(Status__c) = 'Closed'

于是只剩状态为 Closed 的经纪人。判断用 WHERE 还是 HAVING 的规则很简单:

  • WHERE——过滤没有聚合函数的查询里的记录;
  • HAVING——过滤聚合之后的结果。

SOQL 是访问 Salesforce 组织数据的利器,去大胆查询吧!


文章来源:Trailhead - SOQL for Admins