注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

网易杭州 QA Team

务实 专注 分享 做有态度的QA

 
 
 
 
 

日志

 
 

Rails中多列模糊搜索实现  

来自杨澍瑞   2016-04-06 20:44:09|  分类: 语言类 |举报 |字号 订阅

  下载LOFTER 我的照片书  |

Rails Query Interface: where

Rails提供了一套非常便利而又强大的查询接口(Query Interface)。例如我们经常同其打交道的where方法,可以帮我们解决大多数查询条件(Conditions)相关的问题:

# Equality Conditions
Device.where('is_root' => true)

# Range Conditions
Device.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)

# Subset Conditions
Device.where(os_type: [1,3,5])

# NOT Conditions
Device.where.not(brand: brand)

# OR Conditions(Rails5 Only)
# see https://github.com/rails/rails/pull/16052/
Device.where('id = 1').or(Device.where('id = 2'))

问题背景

今天我遇到了这么一个的查询问题:

假设数据库中存在一张设备表(devices),表中有多个字段:设备品牌(brand)、系统类型(os type)、分辨率(resolution)、序列号(serial number)、内存(memory)等十多个字段。我们已经实现了一个index页面,负责展示所有的设备,现在希望在页面上实现一个搜索框,根据搜索框中输入的文字,针对设备表中的设备品牌、分辨率、序列号这三个字段进行模糊搜索。

大体上长这个样子: 

模糊搜索示例

解决思路

根据需求我实现了一个search方法,看起来基本可以满足需求了:

# models/devices.rb
def self.search(search)
if search
search.strip!
where("device_brand like ? OR serial_number like ? OR resolution like ?", "%#{search}%", "%#{search}%", "%#{search}%")
else
all
end
end

然而深入思考一下,如果有一天需求发生了变更:不再仅仅只针对三个字段进行模糊搜索,而是将模糊搜索的范围扩大到更多字段,那是不是需要拼一个非常长的where查询子句:

where("aaa like ? OR bbb like ? OR ccc like ? OR ddd like ? OR eee like ? OR fff like ?", "%#{param}%", "%#{param}%", "%#{param}%", "%#{param}%", "%#{param}%", "%#{param}%")

有没有更优雅一些的实现方案呢?

Arel

Arel是Rails里用来管理生成AST(Abstract Syntax Tree 抽象语法树)的组件,负责将Rails里一些SQL查询的DSL转化为底层的SQL语句。

我们可以使用Arel灵活地实现一些复杂的查询。

Rails提供了一个叫做arel_table的方法,用来访问底层Arel的相关接口:

Device.arel_table  # => #<Arel::Table:0x00000004a03e18>

这里返回了一个Arel::Table对象,我们可以把它理解成一个包含了数据库表中每一列的hash对象,可以通过正常的访问hash元素的方式来访问这些列。

Arel提供了一系列的方法作用于这些列上,用于构造SQL语句。下面是一段简单的示例:

devices = Device.arel_table
devices.where(devices[:brand].eq('HUAWEI'))
# SELECT * FROM devices WHERE devices.brand = 'HUAWEI'

回到我们的需求上,我们可以用Arel将原先的代码重构下:

# 使用Arel实现多字段模糊查询
def self.search(search)
if search
search.strip!
search_fields = %w(device_brand serial_number resolution)
# 每个待查询字段都会有一个对应的 matches 匹配条件,最后这些条件之间用or运算合并,语义即“device_brand matches xxx or serial_number matches xxx or ...”
condition = search_fields.map do |field|
arel_table[field].matches("%#{search}%")
end.inject(:or)
# 使用 Arel 构造的查询子句可以直接用于更高层级的 Query Method,也就是where方法
where(condition)
else
all
end
end

这样即使模糊搜索的范围扩大到了更多的字段,我们只需要将相应的字段名赋值给search_fields就可以了,其它代码不需要做任何修改。

Any way else?

需要注意到的一点是,使用like '%keyword%'这种查询方式是无法使用索引的,如果数据库表中的数据非常多,那么必然会成为性能上的瓶颈。

也可以考虑使用一些搜索引擎来实现模糊查询,例如ElasticSearch。

参考

Active Record Query Interface

Using Arel to compose SQL queries

Rails 源码分析之 Arel


  评论这张
 
阅读(260)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016