Bmob Query Language(简称 BQL) 是 Bmob 为查询 API 定制的一套类似 SQL 查询语法的子集和变种,主要目的是降低大家学习 Bmob 查询的 API 的成本,可以使用传统的 SQL 语法来查询 Bmob 应用内的数据。

本文档将详细介绍 BQL 的语法和常见用法。

介绍及基本语法

最基本的一个查询:

查询某个 class 下的 100 条数据:

  1. select * from GameScore

等价于(以 Android 为例):

  1. //查询GameScore的100条数据
  2. BmobQuery<GameScore> query = new BmobQuery<GameScore>();
  3. query.findObjects(context, new FindListener<GameScore>() {
  4. @Override
  5. public void onSuccess(List<GameScore> object) {
  6. // TODO Auto-generated method stub
  7. }
  8. @Override
  9. public void onError(int code, String msg) {
  10. // TODO Auto-generated method stub
  11. }
  12. });

select一个完整的语法形式类似这样:

  1. select [查询字段列表,多个以逗号隔开] from [class 名称]
  2. [where [条件列表]
  3. [limit skip,limit
  4. [order by [排序字段列表] [asc |desc]]]]

举例:

  1. //查询结果只包含 name,playScore 以及内置字段(objectId,createdAt等)
  2. select name,playScore from GameScore
  3. //根据 name 查找
  4. select * from GameScore where name='smile'
  5. //根据 name 和 playScore 同时查找
  6. select * from GameScore where name is exists and playScore > 80 and playScore <= 100
  7. //分页查找,从第 10 条开始向后查找 10 条数据
  8. select * from GameScore limit 10,10
  9. //根据 score 和 name 排序
  10. select * from GameScore order by -playScore,+name

注:当查询的表为系统表(目前系统表有User、Installation、Role)时,需要带上下划线_

比如,你想查询的是用户smile的信息,则:

  1. `select * from _User where username= smile`

查询条件

比较查询

查询指定信息的对象,用 = 比较符:

  1. select * from GameScore where name='smile'

查询不等于指定信息的对象,用!=比较符:

  1. select * from GameScore where name!='smile'

也可以用<>运算符来表示不等于。

比较日期,使用date函数来转换,比如查询特定时间之前创建的对象:

  1. select * from GameScore where createdAt < date('2015-05-20 00:00:00')

date 函数接收的日期格式必须是 yyyy-MM-dd HH:mm:ss。更多内置函数请看最后的 内置函数

一些常见的查询运算符:

Key Operation 对应Bmob的查询API
= 等于 addWhereEqualTo
!= 或者 <> 不等于 addWhereNotEqualTo
< 小于 addWhereLessThan
<= 小于等于 addWhereLessThanOrEqualTo
> 大于 addWhereGreaterThan
>= 大于等于 addWhereGreaterThanOrEqualTo
[not] like 模糊查询 addWhereStartsWith或addWhereEndsWith
[not] regexp 正则匹配 addWhereMatches
[not] in(子查询或者数组) 包含或者不包含 addWhereMatchesQuery或者addWhereDoesNotMatchQuery
is [not] exists 这个Key有值或者不存在值 addWhereExists或addWhereDoesNotExists

注:比较运算符可以用在日期字符串数字甚至对象上。

模糊查询

注:模糊查询只对付费用户开放,付费后可直接使用。

模糊查询可以使用 like,比如查询名字以 smile 开头的对象

  1. select * from GameScore where name like 'smile%'

% 表示模糊匹配的位置占位符。

like 本质上是转成regexp的正则匹配查询,因此上面的例子还可以写成:

  1. select * from GameScore where name regexp 'smile.*'

否定形式,以not like 来查询名字不以 smile 开头的对象:

  1. select * from GameScore where name not like 'smile%'

等价于not regexp:

  1. select * from GameScore where name not regexp 'smile.*'

值是否存在查询

只返回 name 字段值存在的对象:

  1. select * from GameScore where name is exists

反之,使用 is not exists

数组查询

假设hobby表示一个人的爱好,其字段类型是List对应服务端的数组Array类型,如我们想查询有游泳爱好的人:

  1. select * from Person where hobby ='游泳'"

查询既爱好游泳也爱好看书的人:

  1. select * from Person where hobby all ('游泳','看书')

注:all 表示数组完全匹配。

子查询

1、使用 in来做子查询,后面跟的可以是一个列表,例如 查询游戏名为地铁跑酷、部落冲突两种游戏的记录:

  1. select * from GameScore where game in ('地铁跑酷','部落冲突')

当然,如果想查询的不在列表里,那可以使用not in:

  1. select * from GameScore where game not in ('地铁跑酷','部落冲突')

2、in后面还可以是一个子查询:

比如:要查询游戏得分大于10的玩家信息

这里需要username的值必须要在子查询产生的数组中,因此,我在GameScore表中新建了一个name字段来表示玩家的姓名

  1. select * from _User where username in (select name from GameScore where playScore>10)

比如:查询年龄大于20的玩家的游戏信息

  1. select * from GameScore where name in (select username from _User where age>20)

3、子查询另一种常见形式:使用= 或 !=后面跟一条查询语句:

比如:查询游戏得分大于80的玩家

  1. select * from _User where username =(select name from GameScore where playScore>10)
  2. select * from _User where username !=(select name from GameScore where playScore<=10)

注:子查询的语句也受上限 1000 条记录的限制

地理位置查询

我们可以通过 BQL 进行地理位置查询。

附近的人

使用near来查询附近的人

比如我想查询指定地理位置附近的玩家(从近到远排序),假设 gps 字段是 BmobGeoPoint类型:

  1. select * from GameScore where gps near [112.934755,24.52065]

注:[112.934755,24.52065] 是经纬度坐标。必须是经度在前,纬度在后

也可以使用 geopoint 函数来创建:

  1. select * from GameScore where gps near geopoint(112.934755,24.52065)

注:只有在地理位置信息查询里才可以使用 [longitude, latitude]这样的语法。在其他查询里将被作为数组类型。

限定搜索范围

为了限定搜索的最大距离,还可以使用 max distance 单位来限定,比如,搜索附近1公里内的人:

  1. select * from GameScore where gps near [112.934755,24.52065] max 1 km

注:单位包括 miles(英里)、radians(弧度),默认是弧度。

查询矩形范围

如果想查询某个矩形框内的对象,可以使用within [西南坐标] and [东北坐标]的语法:

  1. select * from GameScore where gps within [102.934755,24.52065] and [112.934755,24.52065]

查询个数

使用 count 查询来返回符合查询条件的数目。

比如:查询整张表的记录数:

  1. select count(*) from GameScore

注:count 不支持distinct等语法。仅限count(*)和count(objectId)

查询总的记录数并返回得分在10-20之间的游戏分数信息

  1. select count(*) from GameScore where playScore>10 and where playScore<20

查询个数的同时可以返回对象,使用,隔开:

  1. select count(*),* from GameScore

也可以返回特定的字段:

比如:查询GameScore表中的总数并返回每条记录的游戏名

  1. select count(*),game from GameScore

关系查询

有如下几种方式来查询对象之间的关系数据

1、如果您想获取对象,而这个对象的一个字段对应了另一个对象, 您可以用一个 where 查询, 自己构造一个 Pointer 函数, 和其他数据类型一样。

例如: Weibo类中有一个User(继承自BmobUser)类型的author字段表示这条微博的发送方,现在你想查询当前用户发布的所有微博信息,那么可以这样写:

  1. select include author,* from Weibo where author = pointer('_User', '6720c14c28')

pointer内置函数接收 className 和 objectId。

2、如果您想获取对象, 这个对象的一个字段指向的对象(必须是BmobPointer类型)是符合另一个查询的, 您可以使用in查询。

例如, 假设您有一个 Weibo 类和一个 Comment 类, 每个Comment 都有一个指向它的 Weibo 的 weibo字段(BmobPointer类型), 现在您想查询带有图片的 Weibo 的 Comment列表:

  1. select * from Comment where weibo in (select * from Weibo where image is exists)

注:默认的 limit 是 100 而且最大的 limit 是 1000,这个限制同样适用于内部的查询, 所以对于较大的数据集您可能需要细心地构建查询来获得期望的行为。

3、如果 Weibo 类下有一个BmobRelation类型的comment字段,它存储了这条微博所有的评论列表。你可以查询到这些评论信息,因为他们都关联到了同一条微博:

  1. select * from Comment where related comment to pointer('Weibo', '262f6ace1a')

基本的查询形式是 releated <key> to <pointer>

4、如果某个字段是 Pointer ,默认查询的时候,只会返回 {__type: ‘Pointer’, objectId: ‘xxxxxx’, className:’Weibo’} 这些基本信息,如果希望同时将这个对象的其他信息查询下来,可以使用include

1)、采用 include <key> 就可以将某个 Pointer字段关联查询出来:

比如查询 Comment 同时将 author带下来:

  1. select include author, * from Comment

2)、多个字段要多次 include:

  1. select include weibo,include author from Comment

3)、还可以支持嵌套的 include 查询

比如 Weibo 里还有一个 Pointer 指向 author(表示这条微博是谁发的):

  1. select include weibo.author,* from Comment

复合查询

使用 andor进行复合查询

比如:查询游戏得分在10-15之间的数据,可以使用and查询:

  1. select * from GameScore where playScore>10 and playScore<=15

如果你想再加个条件:再查询分数为0的数据,可以使用or查询:

  1. select * from GameScore where playScore>10 and playScore<=15 or playScore=5

由于and的优先级高于or,因此,上面的查询也可以用括号来明确地表示这种优先级:

  1. select * from GameScore where (playScore>10 and playScore<=15) or playScore=5

占位符

查询条件wherelimit 子句支持占位符,也就是可以用问号 ? 替代值,值的列表通过 SDK 提供的方法传入,具体请参考各 SDK 用法。

普通查询

例如:

  1. select * from GameScore where name=? and playScore>? limit ?,?

内置函数

占位符查询中用到的内置函数,请看最后的 占位符查询用到的内置函数

例如:

  1. select * from GameScore where createdAt > date(?) and player = pointer(?,?) and gps near geopoint(?,?)

注意:

和统计查询有关的group byorder byhaving等字句是不支持占位符的。

正确写法:

  1. select sum(playScore),count(*) from GameScore group by game having _sumPlayScore>200

错误写法:

  1. select sum(playScore),count(*) from GameScore group by ? having ?

注:我们推荐使用占位符的方式来使用 BQL,查询语句可以通过预编译得到缓存,降低 BQL 的转换开销。

限定返回值

通过 limit 语句来限定返回结果大小,比如限定返回 10 个:

  1. select * from GameScore limit 10

可以设定从第m+1个元素开始,例如从第 11 个元素(包含)开始往后取 10 个:

  1. select * from GameScore limit 10,10

注:如果查询语句没有设定 limit ,默认的 limit 是 100 而且最大的 limit 是 1000。

排序

通过 order 语句来排序,order 语句只能出现在最后,不能在 wherelimit 之前。

比如:按照得分降序排列(得分高的在前):

  1. select * from GameScore order by playScore desc

也可以写成:

  1. select * from GameScore order by -playScore

注:加号表示升序,减号表示降序。

多个字段组合排序

比如:得分高的在前(playScore降序),当得分一样的情况下,再按照名称(字母顺序)更小的在前(name升序):

  1. select * from GameScore order by -playScore,name

同样的语句可以写成:

  1. select * from GameScore order by playScore,+name desc

或者

  1. select * from GameScore order by -playScore,name asc

注:没有写上明确的加号或者减号的字段,将根据最后的 desc 或者 asc 来决定采用升序还是降序。

统计查询

统计查询只适用于 Number 类型的列,支持 sum (计算总和), average (计算平均值), max (计算最大值), min (计算最小值)

举几个例子(以sum求和):

计算总和

比如:查询GameScore表所有玩家的得分总和:

  1. select sum(playScore) from GameScore

分组计算总和

比如:按玩家分组,获取每个玩家的总得分:

  1. select sum(playScore) from GameScore group by name

添加过滤条件

比如 按玩家分组,获取每个玩家的总得分,并且总得分大于100的:

  1. select sum(playScore) from GameScore group by name having _sumPlayScore > 100

统计分组记录数

同时支持返回每个组用于计算总和的总数:

  1. select sum(playScore),count(*) from GameScore group by name having _sumPlayScore > 100

注:

1、查询语句中含有sumgroup by的时候不支持返回表中的其他列,只返回group by后面接的列名、sum的列名为 (_sum+(首字母大写的列名)) 以及有count(*)会返回的 _count等数据。所以只应该用于统计的功能时候使用。

2、其他关键字和sum的使用类似,只需要以上的sum换为average,max, min,其中average返回的平均值列名为:(_avg+(首字母大写的列名))

内置函数介绍

BQL提供了一些内置函数来方便地创建 pointergeopointdatefile等类型:

基本BQL查询用到的内置函数

Key Operation
date(‘yyyy-MM-dd HH:mm:ss’) 创建日期类型
pointer(className,objectId) 创建 Pointer
geopoint(经度,维度) 创建 GeoPoint
file(filename,url) 创建 file 类型
current_timestamp() 创建当前日期

占位符查询用到的内置函数

如果使用占位符查询,比较特殊,其内置函数的形式如下

Key Operation
date(?) 创建日期类型
pointer(?,?) 创建 Pointer
geopoint(?,?) 创建 GeoPoint
file(?,?) 创建 file 类型

举例子(以Android为例):

1、Date类型:

  1. String dateString = "2015-05-12";
  2. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  3. Date date = null;
  4. try {
  5. date = sdf.parse(dateString);
  6. } catch (ParseException e) {
  7. }
  8. String sql = "select * from GameScore where createdAt < date('"+new BmobDate(date).getDate()+"')"

等价于:(以下是REST API文档中定义的创建Date类型的JSON对象)

  1. select * from GameScore where createdAt < {'__type': 'Date','iso': '"+new BmobDate(date).getDate()+"'}

2、Pointer类型:

  1. User user = BmobUser.getCurrentUser(this, User.class);
  2. String sql = select * from GameScore where player = pointer('_User', "+"'"+user.getObjectId()+"')

等价于:(以下是REST API文档中定义的创建Pointer类型的JSON对象)

  1. select * from GameScore where player = {'__type':'Pointer','className':'_User','objectId':'"+user.getObjectId()+"'}

3、GeoPoint类型

  1. select * from GameScore where gps = geopoint(112.934755,24.52065)

等价于:(以下是REST API文档中定义的创建GeoPoint类型的JSON对象)

  1. select * from GameScore where gps = {'__type':'GeoPoint','latitude':24.52065,'longitude':112.934755}

注:

1、如果不使用内置函数,你也使用 Rest API文档定义的 JSON 对象来创建特定类型,不过这样写相对来说比较繁琐。

2、占位符查询中如果要用到内置函数,其形式必须为上述列出的,最后的可变参数会自动替换类型中的?

性能和建议

BQL 最终还是转换成 Rest API文档 里查询部分提到的各种 where 条件,因为多了一层转换,理论上会比直接使用 where 查询慢一点。并且 BQL 对长度有所限制,要求在 4096 字节以内。

此外,我们推荐查询语句都采用占位符的方式,使用占位符的查询语句将有机会被缓存复用,避免重复解释的开销。