Skip to main content
Build the future with Agentforce at TDX in San Francisco or on Salesforce+ on March 5–6. Register now.

从 SQL 迈向 SOQL

学习目标

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

  • 了解 Force.com 对象的优点。
  • 分辨 SQL 和 SOQL 有什么相似点和区别。
  • 用 Workbench 编写一个简单的 SOQL 语句。
  • 编写更复杂的关系查询。
  • 编写聚合查询。

认识 Force.com 对象

Force.com 平台提供了一个强大的数据库,有许多功能,使创建应用又快又简单。用过 SQL Server 后,您应该知道数据储存在表格和行中,而 Force.com 中的数据库用对象来储存数据。对象包含您希望表格具备的一切功能,还有一些额外的功能增强使它们更加强大,更加多功能。每个对象由一些字段组成,对应数据库中的列。数据储存在对象的记录中,对应数据库中的行。但是等等……还有更多!

有两种对象类型:

  1. 标准对象——Salesforce 的内置对象。常用 CRM 对象包括客户、联系人、业务机会和潜在客户。
  2. 自定义对象——您创建的新对象,用来储存您的应用程序独有的信息。自定义对象扩展标准对象提供的功能。比如,如果您正在构建一个应用程序来跟踪产品库存,您可能需要创建称作 Merchandise(商品)、Orders(订单)或 Invoices(发票)的自定义对象。

您很可能已经猜到了,对象可以有一些关系字段,定义一个对象中的记录如何与另一个对象中的记录关联起来。这些本质上是主键和外键,但是要灵活得多,使得设计和实施数据模型更加简单。

不管是标准的还是自定义的,Force.com 对象不仅提供储存数据的结构,而且赋能界面元素,使用户可以与数据互动,比如选项卡,页面上的字段布局,以及相关记录列表。对于标准功能,没有必要实施 ORM,为 CRUDing 数据编写 UI 或创建表格。这种标准功能由平台自动向您提供。对象还为一些功能提供内置的支持,比如权限管理、验证、公式和历史跟踪。一个对象的所有属性都用元数据来描述,使得通过可视化界面或编程创建和修改记录很容易。

您可以看出,对象比只是储存数据的容器用处要大得多。它们提供一系列丰富的功能,解放您的双手,让您可以专注于构建您的应用程序独有的功能。欲进一步了解如何创建自定义对象、字段、关系等等,请学习数据建模模块。

相似但不一样

作为 .NET 开发人员,您很可能已经习惯使用 SQL Server。您可能还非常熟悉用 SQL 编写临时查询。所以我们认为向您介绍一种类似的语言 SOQL 的最好办法是将两者进行比较一,SOQL 是专门为 Salesforce 设计的,全称是 Salesforce Object Query Language(Salesforce 对象查询语言)。

首先要知道尽管两者都称作查询语言,但是 SOQL 只能用来执行含 SELECT 语句的查询。SOQL 没有相当于 INSERT、UPDATE 和 DELETE 语句的东西。在 Salesforce 世界中,数据操作通过一套称作 DML (Data Manipulation Language) 的方法来处理。我们稍后详细介绍 DML。眼下,您只需要知道如何用 SOQL 提供的 SELECT 语句查询 Salesforce 数据。

您现在就会注意到的一个很大的区别是 SOQL 没有 SELECT * 之类的东西。因为 SOQL 返回 Salesforce 数据,而且那些数据存在于一个多租户环境中,每个人在某种程度上都在“共享数据库”,所以 * 之类的通配符会制造麻烦。坦率地说,发起一条新的 SQL 查询,输入 SELECT * FROM SOME-TABLE 太容易了,尤其是当您不知道表格的字段名称是什么时。这个操作可能会严重影响您的共享环境中的其他租户。您会在星期天早晨 7 点修剪草坪吗?那太粗鲁,太没礼貌了。

在 SOQL 中,您指定要返回的每个字段名称。除此之外,SOQL 提供的 SELECT 语句与 SQL 类似。您会发现写 SOQL 查询很容易。不过您要知道:尽管 SQL 和 SOQL 很相似,但是它们是不一样的。SOQL 不支持 SQL SELECT 语句支持的一些更先进的功能。但是在 Salesforce 平台上,您实际上不需要所有那些额外的功能。SOQL 以一种您熟悉的方式只提供您需要的东西。

通过 Workbench 写查询

说起写 SOQL 查询,您可能在想,“我该怎么做呢?”一种简单的开始方法是使用 Salesforce 基于网络的工具,称作 Workbench。我们知道 .NET 开发人员有多喜欢工具,而且这个强大的工具为管理员和开发人员提供许多方法,通过 Force.com API 来访问他们的组织。

现在我们将重点介绍利用 Workbench 来编写 SOQL 查询,但是如果您还有时间,可以多了解一下 Workbench,了解它的所有功能。我们相信您会喜欢它的。注册免费的 Developer Edition (DE) 组织,然后:

  1. 导航到 https://workbench.developerforce.com/login.php
  2. 对于 Environment(环境),选择 Production(生产)
  3. 从 API Version(API 版本)下拉菜单选择最新的 API 版本。
  4. 接受服务条款,然后单击登录 Salesforce
  5. 输入您的登录凭据,然后点击登录
  6. 要想让 Workbench 有权访问您的信息,单击 Allow(允许)
  7. 登录后,选择 queries(查询) > SOQL Query(SOQL 查询)
  8. 选择 Account(客户)作为对象。

请注意您选择了一个对象,而不是表格。数据储存在对象里面。实际上,它们称作 sObjects,即 Salesforce 中的对象,而且与 Force.com 平台紧密集成,使其简单易用。

  1. 按住 Ctrl 键,从“字段”列表中选择 CreatedDate(创建日期)、Name(姓名)、Phone(电话号码)、Type(类型)。在您选择对象和字段的时候,在文本框中为您创建了 SOQL 查询。如下所示:
    SELECT CreatedDate, Name, Phone, Type FROM Account
    
  2. 单击 Query(查询)查看以列表形式返回的结果。在结果中,查看 CreatedDate 字段中返回的值。

坏消息是,Salesforce 中的日期时间字段在 SOQL 中跟在 SQL 中一样复杂和不好用。好消息是,Salesforce 提供多种日期函数,使在 SOQL 中使用它们没有那么痛苦。在浏览文档的时候,请参阅 SOQL 如何处理货币字段,因为它们也略有不同,尤其是对于涉及多币别的组织。

筛选结果

SOQL 有两个必不可少的子句:SELECT 和 FROM。WHERE 子句是可选的。但是作为一名优秀的开发人员(我们相信您是的),您想在自己写的几乎每条查询中都加入一个 WHERE 子句。返回比需要的数据多的数据没有意义。

还是一样,查看操作方式最简单的方法是使用 Workbench。

  1. 登录 Workbench 后,选择 queries(查询) > SOQL Query(SOQL 查询)
  2. 选择 Contact(联系人)作为对象。
  3. 按住 Ctrl 键,从字段列表中选择 AccountId(客户 ID)、Email(电子邮件)、Id(ID)、LastName(姓)。在您选择对象和字段的时候,在文本框中为您创建了 SOQL 查询。
  4. 单击 Query(查询)查看以列表形式返回的结果。如下所示:
    SELECT AccountId, Email, Id, LastName FROM Contact
    

该查询返回组织中的所有联系人。对于开发组织,该列表可能比较小。但是对于大多数现实世界中的组织,返回的联系人数量可能有数千条,因此您应该总是考虑通过 WHERE 子句来筛选 SOQL 查询,尤其那些 Apex 代码中用到的。

  1. 从 Filter results by(筛选结果依据)下拉列表中,选择 Email(电子邮件)
  2. 按 Tab 键移到下一个字段,点击下拉框中的箭头显示可能的运算符列表。
  3. 从该列表中选择 contains(包含),然后按 Tab 键移到最后一个字段,输入 .net。构建的的查询如下所示:
    SELECT AccountId, Email, Id, LastName FROM Contact WHERE Email LIKE '%.net%'
    

您有没有注意到发生了什么?您构建的查询不包含 contains 这个词,它用的是 LIKE。该查询还包含要求的单引号(因为电子邮件是一个文本字段),还有首尾百分比符号,表示它是一个通配符搜索。

备注:使用通配符,尤其是首尾通配符,比如在上述例子中,不是好的做法。您会在后面一个单元进一步学习如何编写高效的查询,但是眼下要记得尽可能避免此类通配符搜索。

  1. 得到结果后,接着从 Sort results by(结果排序依据)下拉列表中选择 LastName(姓),按 LastName 对结果进行排序。
  2. 其他保留默认 A 到 Z 和空值优先的排序。您的查询现在如下所示:
    SELECT AccountId,Email,Id,LastName FROM Contact
      WHERE Email LIKE '%.net%' ORDER BY LastName ASC NULLS FIRST
    
  3. 单击 Query(查询)以排序列表形式返回结果。

在结果中我们希望您注意的重要东西是两个 ID 字段:AccountId 和 Id。这些字段包含一个唯一的 18 位字符串,是创建客户和联系人记录时平台自动分配的。AccountId 字段与这个特定联系人分配到其中的客户记录相关联。在 SQL 中,它属于外键关系。Id 字段与联系人关联。在 SQL 中,它代表主键。

尽管我们提出了外键关系,但是您现在可能在想在 SOQL 中如何连接表格。简而言之,您不需要。SOQL 没有等同于 JOIN 子句的东西。不过别担心,因为我们认为这是好事。

一种不同的 Join

我们相信听到这个您肯定不会惊讶,不过在连接多个对象和表格上 Salesforce 的处理方式与您的习惯性思维略有不同。您不是用 JOIN 子句来连接表格,而是编写所谓的关系查询。

“那又是什么?”,我们可以猜到您会这么问。很高兴您问了。

Salesforce 使用父-子关系来连接两个对象。跟 SQL 中一样,SOQL 用一个外键把这两个对象关联起来,但是在 SOQL 中,查询语法是不一样的。我们不会说谎。首先,使用这种新的语法可能感觉有点奇怪,因为您在处理的是对象,而不是行。不过一旦您习惯了基本操作,您会发现写关系查询比在 SQL 中写 joins 容易得多。

SOQL 有两种您需要记住的基本关系查询类型:

  • 子到父
  • 父到子

每种类型的操作方式不同。因为您已经见过一些客户和联系人对象的字段,这两者通常连接在一起,那我们就从它们开始吧。眼下需要知道的重要事项是客户是父级,联系人是子级。

编写子到父查询

假设您想写一条查询,返回客户和联系人信息。您的第一种方法是写一条子到父查询。这个关系查询使用“点记法”来访问来自父级的数据,即用一个点把关系名称与被查询的字段名称分开。

为了看看这到底如何操作,我们将一步一步编写一条关系查询,返回一个联系人列表,包含关联的客户名称。不过这一次,我们不用 Workbench,而是用 Developer Console 中的查询编辑器选项卡。

  1. 单击 设置 设置,然后单击 Developer Console
  2. 在 Developer Console 中,单击底部窗格中的 Query Editor(查询编辑器)选项卡。
  3. 删除现有代码,插入以下片段:
    SELECT FirstName, LastName, Account.Name FROM Contact
    
  4. 单击执行
  5. 查询结果包含三列。向下翻结果。Account.name 字段下面的有些结果为空,因为并非所有联系人都与某个客户相关联。

因为我们知道您很可能还习惯于 SQL 思维,请设想这种场景。假设您有两个 SQL 表格,客户和联系人,两个表格之间是一对多的关系,您将如何编写一条返回同样信息的 SQL 查询?

您当然会用到连接。不过在这个例子中,您需要一个右外连接,因为您想要一条同等的查询,返回所有联系人,包括那些没有关联客户的联系人。那看起来可能是这样的:

    SELECT c.FirstName, c.LastName, a.Name FROM Account a
    RIGHT JOIN Contact c ON (c.AccountId = a.Id)

所以现在我们希望您回头去看同等的 SOQL 查询,万一您忘了,它是这样的:

    SELECT FirstName, LastName, Account.Name FROM Contact

SOQL 查询看起来简单多了,不是吗?

说实话,关系查询有时候有点微妙,尤其是研究自定义对象的关系名称是什么的时候。欲进一步了解把 SQL 查询转化为 SOQL 查询,请参考这个实践培训视频

关于这些类型查询,需要注意的一件超级重要的事情是您可以用点记法向上跨越五级。所以您可以从子级到父级到祖父级再到曾祖父级,以此类推。

编写父到子查询

父到子查询也使用关系名称,但是用嵌套的选择查询来使用。跟上一个查询类型一样,最好通过举例来说明。

这一次我们将编写从父级客户对象出发的查询,让它包含嵌套的查询,也返回关于每个关联的联系人的信息。

  1. 在 Developer Console 中,单击底部窗格中的 Query Editor(查询编辑器)选项卡。
  2. 删除现有代码,插入以下片段:
    SELECT Name, (Select FirstName, LastName FROM Contacts) FROM Account
    

嵌套查询内部的关系名称使用复数名称 Contacts,而不是 Contact。理解这个细节很重要,因为它是人们容易犯错的地方。处理关系查询时,父到子关系名称必须是复数名称。

处理自定义对象时,关系名称不仅是复数,而且附加了两条下划线和一个 r。比如,自定义对象 My_Object__c 的关系名称是 My_Objects__r。

  1. 单击执行
  2. 查询结果包含三列。请注意下图中“联系人”列下面的结果如何显示。由于每个客户通常关联多个联系人,第一个和最后一个名称以 JSON 格式显示。

Developer Console 中显示的 SOQL 查询结果

还是一样,我们来看一下同一条查询在 SQL 中是怎么写的。对于这条查询,对应的 SQL 语句是一个左外连接,类似于这样:

    SELECT a.Name, c.FirstName, c.LastName
    FROM Account a
    LEFT JOIN Contact c ON (a.Id = c.AccountId)

SQL 查询得到所有客户记录以及与那些客户关联的任何联系人。SQL 中返回的输出格式跟 JSON 不一样。除此之外,SQL 和 SOQL 查询产生同样的结果。

此时此刻,您可能在疑惑 SOQL 是否支持别名。它支持,只是跟您在 SQL 中习惯的用法不一样。SOQL 没有 AS 关键词。在 SOQL 查询中您可以用别名来代表对象名称,但是对于字段名称,别名只适用于聚合查询,我们将在下一个单元中介绍。

记住,要深入了解这个主题,请观看资源部分的实践培训视频链接。

那聚合呢?

是的,SOQL 有聚合,而且用法跟您预想的差不多。使用聚合时要注意的重要事项是对于大部分函数,您的结果是按 AggregateResult 类型返回的。

就您能够使用的函数而言,SOQL 提供您在下表中看到的这些。关于每个函数的详细说明,请参见官方文档

SOQL 聚合函数

函数 描述
AVG() 返回数字字段的平均值
COUNT() 和 COUNT(fieldName) 和 COUNT_DISTINCT() 返回符合查询条件的行数
MIN() 返回字段的最小值
MAX() 返回字段的最大值
SUM() 返回数字字段的总和

要在 SQL 中得到一个名叫“客户”的表格的记录数量,您可以这么做:

SELECT COUNT(*) FROM Account

在 SOQL 中,同样的查询看起来是这样的:

SELECT COUNT() FROM Account

很相似,是吧?

区别在于您使用哪个版本的计数函数,因为它们返回不同的东西。不带字段名称的 COUNT() 函数是旧版本,在其他聚合函数之前可用。它返回一个整数,与 SQL 中提供的 count(*) 函数最相似。

Count(fieldName) 是一个更新的版本,返回行数,其中 fieldName 为非空值。区别在于它以 AggregateResults 列表的形式返回那个结果,而不是单个值。

我们来看它的具体操作。

  1. 在 Developer Console 中,单击底部窗格中的 Query Editor(查询编辑器)选项卡。
  2. 删除现有代码,插入以下片段:
    SELECT COUNT() FROM Account
    
  3. 单击执行。查询结果显示总行数,旁边有一个数字。
  4. 返回查询编辑器选项卡,把查询改为这样:
    SELECT COUNT(Id) FROM Account
    
  5. 单击执行。现在查询结果显示只返回了一行,还有一列显示记录总数。

在这之前,我们没有过多谈及您如何处理 SOQL 查询返回的数据。既然躲不过,何必拖延呢?让我们撸起袖子,去干一些与聚合数据有关的脏活累活吧。

我们将稍微提一下 Apex,不过如果您不能全部听懂,也不要担心。我们稍后会详细介绍。

  1. 在 Developer Console 中,选择 Debug(调试)> Open Execute Anonymous Window(打开执行匿名窗口)
  2. 删除现有代码,插入以下片段:
    List<AggregateResult> results  = [SELECT Industry, count(Id) total
        FROM Account GROUP BY Industry];
    for (AggregateResult ar :results) {
        System.debug('Industry:' + ar.get('Industry'));
        System.debug('Total Accounts:' + ar.get('total'));
    }
    

请注意随同 GROUP BY 子句我们如何使用别名来代表总数。在 SOQL 中,您只能在用到 GROUP BY 子句的聚合查询中用字段别名。

  1. 确保选中了 Open Log(打开日志),然后单击 Execute(执行)。加载一个选项卡,向您显示执行日志。
  2. 选择 Debug Only(仅限调试)选项,这样您在日志中只看到调试语句。

了解详细信息

除了 GROUP BY 子句,SOQL 还提供其他分组子句,比如 GROUP BY ROLLUP、GROUP BY CUBE 和 GROUPING。当查询返回来自多个相关表格的值时,这些子句用来检查结果很有用。GROUP BY CUBE 是一个很简洁的子句,让您可以把查询结果中所有分组字段的组合相加得到小计。要进一步了解这些子句,请看资源中的 GROUP BY 文档。

聚合函数还支持可选的 HAVING 子句,与 SQL Server 中的 HAVING 子句类似,所以对于这个您应该感到熟悉。它主要让您筛选聚合函数返回的结果。查阅资源,了解更多。

资源

在 Salesforce 帮助中分享 Trailhead 反馈

我们很想听听您使用 Trailhead 的经验——您现在可以随时从 Salesforce 帮助网站访问新的反馈表单。

了解更多 继续分享反馈