3

問題は、条件に一致するものが にRestaurantないMenuItems場合、ActiveRecord は が見つからないと言うことRestaurantです。関連するコードは次のとおりです。

class Restaurant < ActiveRecord::Base
  has_many :menu_items, dependent: :destroy
  has_many :meals, through: :menu_items

  def self.with_meals_of_the_week
    includes({menu_items: :meal}).where(:'menu_items.date' => Time.now.beginning_of_week..Time.now.end_of_week)
  end
end

そして生成されたSQLコード:

Restaurant Load (0.0ms)←[0m  ←[1mSELECT DISTINCT "restaurants".id FROM "restaurants"
LEFT OUTER JOIN "menu_items" ON "menu_items"."restaurant_id" = "restaurants"."id"
LEFT OUTER JOIN "meals" ON "meals"."id" = "menu_items"."meal_id" WHERE
"restaurants"."id" = ? AND ("menu_items"."date" BETWEEN '2012-10-14 23:00:00.000000'
AND '2012-10-21 22:59:59.999999') LIMIT 1←[0m  [["id", "1"]]

ただし、 Rails Guidesのこの部分によると、これは発生しないはずです。

Post.includes(:comments).where("comments.visible", true)

この includes クエリの場合、どの投稿にもコメントがなかった場合でも、すべての投稿が読み込まれます。

4

3 に答える 3

2

生成された SQL は、クエリの正しい翻訳です。しかし、SQL レベルだけを見てください (少し短くしました)。

SELECT *
FROM 
  "restaurants"
LEFT OUTER JOIN 
  "menu_items" ON "menu_items"."restaurant_id" = "restaurants"."id"
LEFT OUTER JOIN 
  "meals" ON "meals"."id" = "menu_items"."meal_id" 
WHERE 
  "restaurants"."id" = ? 
AND 
  ("menu_items"."date" BETWEEN '2012-10-14' AND '2012-10-21') 

左外部結合は、期待どおりに機能します。レストランは、menu_items と食事と結合されます。レストランと一緒に行く menu_item がない場合、レストランは引き続き結果に保持され、欠落している部分 (menu_items.id、menu_items.date、...) はすべて NULL で埋められます。

ここで、where の 2 番目の部分を見てください。ここで、食事のないすべてのレストランを除外します。

そのため、null 日付を使用できるようにクエリを変更する必要があります。

Ruby に戻ると、次のように書くことができます。

  def self.with_meals_of_the_week
    includes({menu_items: :meal})
       .where('menu_items.date is NULL or menu_items.date between ? and ?', 
            Time.now.beginning_of_week, 
            Time.now.end_of_week
        )
  end

結果のSQLは現在

.... WHERE (menu_items.date is NULL or menu_items.date between '2012-10-21' and '2012-10-28')

食事のないレストランはそのままです。

于 2012-10-24T21:10:48.060 に答える
0

Rails Guide で述べられているように、クエリ内のすべての投稿は、「includes」で「where」句を使用しない場合にのみ返されます。「where」句を使用すると、右側の外部テーブルで WHERE を使用して DB への OUTER JOIN 要求が生成されるため、 DB は何も返しません。

このような実装は、いくつかのオブジェクト (すべて、またはそれらの一部 - 基本モデルで where を使用) が必要な場合に非常に役立ちます。関連するモデルがある場合はそれらすべてを取得しますが、そうでない場合は基本モデルのリストを取得するだけです。

一方、テーブルを含める条件を使用しようとする場合、ほとんどの場合、この条件でのみオブジェクトを選択する必要があります。つまり、meal_items を持つレストランのみを選択する必要があるということです。

したがって、あなたの場合、(N+1 ではなく) 2 つのクエリのみを使用したい場合は、おそらく次のようにします。

class Restaurant < ActiveRecord::Base
  has_many :menu_items, dependent: :destroy
  has_many :meals, through: :menu_items

  cattr_accessor :meals_of_the_week

  def self.with_meals_of_the_week
    restaurants = Restaurant.all
    meals_of_the_week = {}
    MenuItems.includes(:meal).where(date: Time.now.beginning_of_week..Time.now.end_of_week, restaurant_id => restaurants).each do |menu_item|
      meals_of_the_week[menu_item.restaurant_id] = menu_item  
    end
    restaurants.each { |r| r.meals_of_the_week = meals_of_the_week[r.id] }

    restaurants
  end
end

更新: Rails 4 では、単にモデルに対して条件を実行しようとすると、非推奨の警告が発生します

タイプミスの可能性があり申し訳ありません。

于 2012-10-18T06:15:19.070 に答える
0

これについては誤解があると思います

where 条件がない場合、通常の 2 つのクエリのセットが生成されます。

この includes クエリの場合、どの投稿にもコメントがなかった場合でも、すべての投稿が読み込まれます。結合 (INNER JOIN) を使用する場合、結合条件が一致する必要があります。一致しない場合、レコードは返されません。【ガイドより

Post.includes(:comments).where("comments.visible", true) このステートメントは例を参照していないと思いますが、ステートメントのないものを参照していwhereますPost.includes(:comments)

だから、すべてがうまくいく!これが LEFT OUTER JOIN の仕組みです。


だから...あなたは書いた:「このincludesクエリの場合、どの投稿にもコメントがなかったとしても、すべての投稿が読み込まれます。」Ok!ただし、これは NOwhere句がない場合にのみ当てはまります。フレーズの文脈を見逃しました。

于 2012-10-24T18:54:06.073 に答える