2

関連するテーブルの最初の行にのみアクセスしようとしています。これを行うための最良かつ最速の方法は何ですか?(理想的にはSQLステートメント)

アソシエーション:

class User
  has_many :addresses


class Address
  belongs_to :user

私が理解できる最速の方法は次のとおりです。

User.includes(:addresses).find(:all).map {|m| m.addresses.first.area_code}

しかし、この操作は私の要件には遅すぎます(200 Users平均して20 Adresses~4000 Adresses全体的に)持っていると想像してください)

これは私がやろうとしていることのほんの一例です。

これはSQLJOINステートメントで表現できると思いましたが、すべてのユーザーの最初のアドレスのテーブルを受け取る方法がわかりません。

期待される出力の例:

12345 #=>User.find(1).addresses.first.area_code

56789 #=>User.find(2).addresses.first.area_code 

23456 #=>User.find(3).addresses.first.area_code 

.....
4

2 に答える 2

0

これにはいくつかの方法があります。4000 レコードというのは全体像としてはそれほど多くはありませんが、(質問で示唆したように) 複数のクエリでアクセスする必要がなくなるのは「良いこと」です。

解決策の鍵は、見たいアドレスをどのように選択するかです。「最初」は相対的であり、修飾子 (order by またはその他の属性) なしでは非決定論的です。

シンプルなアプローチ - has_one 関連付け

簡単なアプローチの 1 つは、has_one 関係を定義することです。

class User
  has_many :addresses
  has_one :primary_address, :class_name => 'Address', :conditions => ["primary = ?", true]
end

これは、ディサイダーを使用できる Address に属性があることを前提としています。条件を指定しない場合は、has_one が基になるクエリに SQL LIMIT 1 を適用するため、"最初に" 来るアドレス エントリが取得されます。

これにより、ユーザーと単一のアドレスの詳細を簡単に収集できます。

# iterating over users
User.scoped.each { |user| puts user.primary_address.area_code }
# or collect area_codes
area_codes = User.scoped.collect{ |user| user.primary_address.area_code }

ただし、これはまだ各行にアクセスしているため、すべてのユーザーのアドレスに対して余分なクエリ (および ruby​​ 配列の処理) が必要になります。

はるかに良い - アレル

ユーザーが持っている可能性のある複数のアドレスから決定基準を選択したら、これを 1 つのクエリにまとめることができます。

min(id) で住所を選択するだけだと仮定すると (Rails 3 を使用していると仮定すると)、個別の area_codes をクエリするための Arel のアプローチは次のとおりです。

address = Address.arel_table
primary_addresses = Address.where(
  address[:id].in(
    address.group(address[:user_id]).project(address[:id].minimum)
  )
)

primary_addresses は、ユーザーの「プライマリ」アドレスの完全なコレクションを提供するスコープです。その後、必要に応じて処理/リスト/収集できます。

area_codes = primary_addresses.collect(&:area_code)

クエリは実際に次のような SQL を生成します。

SELECT "addresses".* 
FROM "addresses" 
WHERE "addresses"."id" IN (
  SELECT MAX("addresses"."id") AS max_id FROM "addresses" GROUP BY "addresses"."user_id"
)

ご覧のとおり、サブセレクトは、アドレス ID のリストを返しています。ユーザーごとに 1 つずつです。使用するアドレスを選択するために別の基準を使用する場合は、サブクエリを変更できます (たとえば、制限 1 で primary=true を選択するようにします)。

于 2011-10-04T22:07:32.207 に答える
0
area_codes = 
  Address.find_by_sql(
    "SELECT DISTINCT user_id, area_code FROM addresses"
  ).index_by(&:user_id)
User.find(:all).map{ |u| [u, area_codes[u.id]] }

それらすべてを 1 つにまとめたい場合。

于 2011-06-27T17:33:18.120 に答える