具有HABTM关系的过滤模型

时间:2013-05-01 19:17:12

标签: sql ruby-on-rails ruby postgresql has-and-belongs-to-many

我有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数据库。

6 个答案:

答案 0 :(得分:3)

这是set-iwthin-sets查询的示例。我主张用group byhaving解决这些问题,因为这提供了一个通用框架。

以下是您的情况:

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)

这不是“复制和粘贴”解决方案,但如果您考虑以下步骤,您将获得快速查询。

  • index 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。

相关问题