4

我有 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 DB。

4

6 回答 6

3

这是一个 set-iwthin-sets 查询的示例。我提倡用group byand解决这些问题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。

于 2013-05-01T19:26:03.843 回答
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
    
于 2013-05-09T10:59:51.487 回答
1

你的表中有多少数据features?它只是一个ID和名称表吗?

如果是这样,并且您愿意进行一些非规范化,则可以通过将特征编码为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

最后一个查询可能需要更多考虑......

无论如何,如果你想了解更多细节,我实际上已经写了一篇关于Postgres 和 ActiveRecord 中的标记的非常详细的文章。

于 2013-05-15T05:08:25.913 回答
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
于 2013-05-09T14:23:49.200 回答
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”调用关联,然后使用在功能上定义的 self 方法。希望我没有误解原来的问题。

干杯!

于 2013-05-09T13:56:49.433 回答
0
Restaurant
  .joins(:features)
  .where(features: {name: ['pizza','delivery']})
  .group(:id)
  .having('count(features.name) = ?', 2)

这似乎对我有用。不过我用 SQLite 试过了。

于 2013-05-09T14:30:35.037 回答