19

Rails 3 の Arel や Active Record で SELECT クエリをネストして、次の SQL ステートメントを生成しようとしています。

SELECT sorted.* FROM (SELECT * FROM points ORDER BY points.timestamp DESC) AS sorted GROUP BY sorted.client_id

サブクエリのエイリアスは、次のようにして作成できます

points = Table(:points)
sorted = points.order('timestamp DESC').alias

しかし、それを親クエリに渡す方法に行き詰まっています(#to_sqlかなり醜いように聞こえる の呼び出しを除いて)。

上記を達成するために、Arel (または Active Record) のサブクエリとして SELECT ステートメントをどのように使用しますか? ネストされたクエリを使用しない、このクエリを実行するまったく別の方法があるのではないでしょうか?

4

5 に答える 5

25

これが一時テーブルと Arel への私のアプローチです。Arel#to_sql で内部クエリを渡す Arel#from メソッドを使用します。

inner_query = YourModel.where(:stuff => "foo")
outer_query = YourModel.scoped  # cheating, need an ActiveRelation
outer_query = outer_query.from(Arel.sql("(#{inner_query.to_sql}) as results")).
                          select("*")

これで、outer_query、paginate、select、group などを使用していくつかの優れた操作を行うことができます...

inner_query ->

select * from your_models where stuff='foo'

外部クエリ ->

select * from (select * from your_models where stuff='foo') as results;
于 2011-10-12T04:51:05.917 に答える
8

問題は、なぜ「ネストされたクエリ」が必要なのかということです。「ネストされたクエリ」を使用する必要はありません。これは、リレーショナル代数ではなく SQL の考え方で考えています。関係代数では、関係を導出し、ある関係の出力を別の関係への入力として使用するため、次のことが当てはまります。

points = Table(:points, {:as => 'sorted'}) # rename in the options hash
final_points = points.order('timestamp DESC').group(:client_id, :timestamp).project(:client_id, :timestamp)

絶対に必要でない限り、名前の変更は arel に任せるのが最善です。

ここで、client_id とタイムスタンプの射影は非常に重要です。関係からすべてのドメインを射影することはできないからです (つまり、sorted.*)。リレーションのグループ化操作で使用されるすべてのドメインを具体的に射影する必要があります。その理由は、グループ化された client_id を明確に表す * の値がないためです。たとえば、次のテーブルがあるとします

client_id   |   score
----------------------
    4       |    27
    3       |    35
    2       |    22
    4       |    69

ここでグループ化すると、値が 27 または 69 になる可能性があるため、スコア ドメインで射影を実行できませんでしたが、合計 (スコア) を射影できます。

一意の値を持つドメイン属性のみをグループに射影できます (通常、合計、最大、最小などの集計関数です)。クエリでは、ポイントがタイムスタンプでソートされていても問題ありません。最終的には client_id でグループ化されるためです。グループ化を表すことができる単一のタイムスタンプがないため、タイムスタンプの順序は無関係です。

Arel についてどのようにお手伝いできるか教えてください。また、私は、人々が Arel を中心に使用するための学習シリーズに取り組んでいます。シリーズの第 1 弾はhttp://Innovative-Studios.com/#pilot にあります。ActiveRecord モデルの Point ではなく Table(:points) を使用したため、方法を理解し始めていることがわかります。

于 2010-05-24T13:02:32.050 に答える
7
Point.
 from(Point.order(Point.arel_table[:timestamp].desc).as("sorted")).
 select("sorted.*").
 group("sorted.client_id")
于 2012-07-20T03:29:28.717 に答える
7

Snuggsが言及したように、この問題にはネストされたクエリが必要だとは思いませんが。ネストされたクエリが必要な人向け。これは私がこれまで取り組んできたことです。素晴らしいものではありませんが、うまくいきます:

class App < ActiveRecord::Base   
  has_many :downloads

  def self.not_owned_by_users(user_ids)
    where(arel_table[:id].not_in( 
      Arel::SqlLiteral.new( Download.from_users(user_ids).select(:app_id).to_sql ) ) )
  end
end

class Download  < ActiveRecord::Base
  belongs_to :app
  belongs_to :user

  def self.from_users(user_ids)
    where( arel_table[:user_id].in user_ids )
  end

end

class User < ActiveRecord::Base
  has_many :downloads
end

App.not_owned_by_users([1,2,3]).to_sql #=>
# SELECT `apps`.* FROM `apps` 
# WHERE (`apps`.`id` NOT IN (
#   SELECT app_id FROM `downloads` WHERE (`downloads`.`user_id` IN (1, 2, 3))))
#
于 2010-12-30T13:06:20.337 に答える
1

「純粋な」Arelでこれを行うには、これでうまくいきました:

points = Arel::Table.new('points')
sorted = Arel::Table.new('points', as: 'sorted')
query = sorted.from(points.order('timestamp desc').project('*')).project(sorted[Arel.star]).group(sorted[:client_id])
query.to_sql

もちろん、あなたの場合、上のように製造されたのではなく、 Points と sorted が取得され、 Points モデルから調整されます。

于 2014-08-22T23:45:35.673 に答える