这是一个 Rails 解决方案,可为 AND 案例创建自引用连接,并为 OR 案例创建简单的 SQL 包含。该解决方案假定一个名为 TopicTag 的模型和一个名为 topic_tags 的表。
类方法 Search 需要 2 个参数,一个标签数组和一个包含“and”或“or”的字符串
class TopicTag < ActiveRecord::Base
def self.search(tags, andor)
# Ensure tags are unique or you will get duplicate table names in the SQL
tags.uniq!
if andor.downcase == "and"
first = true
sql = ""
tags.each do |tag|
if first
sql = "SELECT DISTINCT topic_tags.topic_id FROM topic_tags "
first = false
else
sql += " JOIN topic_tags as tag_#{tag} ON tag_#{tag}.topic_id = \
topic_tags.topic_id AND tag_#{tag}.tag = '#{tag}'"
end
end
sql += " WHERE topic_tags.tag = '#{tags[0]}'"
TopicTag.find_by_sql(sql)
else
TopicTag.find(:all, :select => 'DISTINCT topic_id',
:conditions => { :tag => tags})
end
end
end
为了获得更多的测试覆盖率,数据被扩展为包含一个额外的国际象棋记录。使用以下代码为数据库播种
[1,2].each {|i| TopicTag.create(:topic_id => i, :tag => 'football')}
[1,3].each {|i| TopicTag.create(:topic_id => i, :tag => 'cricket')}
[2,3,4].each {|i| TopicTag.create(:topic_id => i, :tag => 'basketball')}
[4,5].each {|i| TopicTag.create(:topic_id => i, :tag => 'chess')}
下面的测试代码产生了显示的结果
tests = [
%w[football cricket],
%w[chess],
%w[chess cricket basketball]
]
tests.each do |test|
%w[and or].each do |op|
puts test.join(" #{op} ") + " = " +
(TopicTag.search(test, op).map(&:topic_id)).join(', ')
end
end
足球和板球 = 1
足球或板球 = 1、2、3
国际象棋 = 4, 5
国际象棋 = 4, 5
国际象棋、板球和篮球=
国际象棋、板球或篮球 = 1、2、3、4、5
使用 SqlLite 在 Rails 2.3.8 上测试
编辑
如果你想使用 like 那么OR 的情况也会稍微复杂一些。您还应该注意,如果您正在搜索的表大小不一,使用带有前导 '%' 的 LIKE 可能会对性能产生重大影响。
以下版本的模型对这两种情况都使用 LIKE。
class TopicTag < ActiveRecord::Base
def self.search(tags, andor)
tags.uniq!
if andor.downcase == "and"
first = true
first_name = ""
sql = ""
tags.each do |tag|
if first
sql = "SELECT DISTINCT topic_tags.topic_id FROM topic_tags "
first = false
else
sql += " JOIN topic_tags as tag_#{tag} ON tag_#{tag}.topic_id = \
topic_tags.topic_id AND tag_#{tag}.tag like '%#{tag}%'"
end
end
sql += " WHERE topic_tags.tag like '%#{tags[0]}%'"
TopicTag.find_by_sql(sql)
else
first = true
tag_sql = ""
tags.each do |tag|
if first
tag_sql = " tag like '%#{tag}%'"
first = false
else
tag_sql += " OR tag like '%#{tag}%'"
end
end
TopicTag.find(:all, :select => 'DISTINCT topic_id',
:conditions => tag_sql)
end
end
end
tests = [
%w[football cricket],
%w[chess],
%w[chess cricket basketball],
%w[chess ll],
%w[ll]
]
tests.each do |test|
%w[and or].each do |op|
result = TopicTag.search(test, op).map(&:topic_id)
puts ( test.size == 1 ? "#{test}(#{op})" : test.join(" #{op} ") ) +
" = " + result.join(', ')
end
end
足球和板球 = 1
足球或板球 = 1、2、3
国际象棋(和)= 4, 5
国际象棋(或)= 4, 5
国际象棋、板球和篮球=
国际象棋、板球或篮球 = 1、2、3、4、5
国际象棋和 ll = 4
国际象棋或 ll = 1、2、3、4、5
ll(and) = 1, 2, 3, 4
ll(或) = 1, 2, 3, 4