我有2个型号 - 餐厅和特色。它们通过has_and_belongs_to_many关系连接。它的要点是你有餐厅有许多功能,如送货,比萨饼,三明治,沙拉吧,素食选项,...所以现在,当用户想要过滤餐厅,并让我说他检查披萨和交付,我想显示所有有两个特色的餐厅;披萨,送货,也许还有更多,但它必须有披萨和送货。
如果我做一个简单的.where('features IN (?)', params[:features])
我(当然)会让餐馆有 - 所以或比萨饼或送货或两者兼而有之 - 这根本不是我想要的。
我的SQL / Rails知识有点受限,因为我是新手,但我问了一位朋友,现在我有了这个完成工作的huuuge SQL:
Restaurant.find_by_sql(['SELECT restaurant_id FROM (
SELECT features_restaurants.*, ROW_NUMBER() OVER(PARTITION BY restaurants.id ORDER BY features.id) AS rn FROM restaurants
JOIN features_restaurants ON restaurants.id = features_restaurants.restaurant_id
JOIN features ON features_restaurants.feature_id = features.id
WHERE features.id in (?)
) t
WHERE rn = ?', params[:features], params[:features].count])
所以我的问题是:是否有更好的 - 甚至更多的Rails - 这样做的方式?你会怎么做?
哦,顺便说一句,我在Heroku上使用Rails 4,所以它是一个Postgres数据库。
答案 0 :(得分:3)
这是set-iwthin-sets查询的示例。我主张用group by
和having
解决这些问题,因为这提供了一个通用框架。
以下是您的情况:
select fr.restaurant_id
from features_restaurants fr join
features f
on fr.feature_id = f.feature_id
group by fr.restaurant_id
having sum(case when f.feature_name = 'pizza' then 1 else 0 end) > 0 and
sum(case when f.feature_name = 'delivery' then 1 else 0 end) > 0
having
子句中的每个条件都计算其中一个特征 - “披萨”和“交付”。如果两个功能都存在,那么您将获得restaurant_id。
答案 1 :(得分:1)
这不是“复制和粘贴”解决方案,但如果您考虑以下步骤,您将获得快速查询。
feature_name
列(我假设列feature_id
已在两个表上编入索引)将每个feature_name
参数放入exists()
:
select fr.restaurant_id
from
features_restaurants fr
where
exists(select true from features f where fr.feature_id = f.feature_id and f.feature_name = 'pizza')
and
exists(select true from features f where fr.feature_id = f.feature_id and f.feature_name = 'delivery')
group by
fr.restaurant_id
答案 2 :(得分:1)
也许你正在向后看?
也许尝试合并每个功能返回的餐厅。
简化为:
pizza_restaurants = Feature.find_by_name('pizza').restaurants
delivery_restaurants = Feature.find_by_name('delivery').restaurants
pizza_delivery_restaurants = pizza_restaurants & delivery_restaurants
显然,这是一个单一实例解决方案。但它说明了这个想法。
<强>更新
这是一种在不编写SQL的情况下引入所有过滤器的动态方法(即“Railsy”方式)
def get_restaurants_by_feature_names(features)
# accepts an array of feature names
restaurants = Restaurant.all
features.each do |f|
feature_restaurants = Feature.find_by_name(f).restaurants
restaurants = feature_restaurants & restaurants
end
return restaurants
end
答案 3 :(得分:1)
features
表中有多少数据?它只是一个ids和名称表吗?
如果是这样,并且您愿意进行一些非规范化,您可以通过在restaurant
上将这些功能编码为文本数组来更轻松地完成此操作。
使用此方案,您的查询将归结为
select * from restaurants where restaurants.features @> ARRAY['pizza', 'delivery']
如果您想维护您的功能表,因为它包含有用的数据,您可以在餐厅存储功能ID数组,并执行以下查询:
select * from restaurants where restaurants.feature_ids @> ARRAY[5, 17]
如果您事先不知道ID,并希望在一个查询中完成所有操作,那么您应该可以按照以下方式执行操作:
select * from restaurants where restaurants.feature_ids @> (
select id from features where name in ('pizza', 'delivery')
) as matched_features
最后一个查询可能需要更多考虑......
无论如何,如果你想了解更多细节,我实际上已经写了一篇关于Tagging in Postgres and ActiveRecord的非常详细的文章。
答案 4 :(得分:0)
因为它是一个AND条件(OR条件与AREL有关)。我重读了你陈述的问题并忽略了SQL。我想这就是你想要的。
# in Restaurant
has_many :features
# in Feature
has_many :restaurants
# this is a contrived example. you may be doing something like
# where(name: 'pizza'). I'm just making this condition up. You
# could also make this more DRY by just passing in the name if
# that's what you're doing.
def self.pizza
where(pizza: true)
end
def self.delivery
where(delivery: true)
end
# query
Restaurant.features.pizza.delivery
基本上,您使用“.features”调用关联,然后使用在功能上定义的自我方法。希望我没有误解原来的问题。
干杯!
答案 5 :(得分:0)
Restaurant
.joins(:features)
.where(features: {name: ['pizza','delivery']})
.group(:id)
.having('count(features.name) = ?', 2)
这似乎对我有用。我尝试过SQLite。