5

特定の順序でソートされたモデルがあります。私の目標は、以前のすべてのレコードの特定の列の合計が特定の数に等しいモデルからレコードを見つけることです。次の例では、必要なものが得られますが、特にテーブルがかなり大きい場合は非常に遅くなります。以前のすべての製品のポイントの合計 = 100000 である product.id を解決するより速い方法はありますか?

 total_points = 0
 find_point_level = 100000
 @products = Product.order("id").all
 @products.each do |product|
    total_points = product.points + total_points
    @find_product = product.id
    break if total_points >= find_point_level
 end

アップデート

以下に、いくつかの解決策を示します。これは約60,000件のレコードを通過しています。時間は ActiveRecord のものです。

元の例 (上):
2685.0ms
1238.8ms
1428.0ms

find_each を使用した元の例:
799.6ms
799.4ms
797.8ms

合計で新しい列を作成します:
181.3ms
170.7ms
172.2ms

4

4 に答える 4

6

データベースを非正規化し、部分的な合計を直接productsテーブルに保持してみることができます。を使用した単純なクエリでwherelimitすぐに適切な答えが返されます。

追加のフィルターを作成する必要があります。これにより、製品が追加されるたびに単一のレコードが更新され、製品が削除されるかそのpointsフィールドが変更されるたびにすべての製品が更新されます。

于 2012-11-17T12:33:11.800 に答える
1

実は、SQLでこれを行う方法が実際にあります。まず、いくつかのテスト環境を設定しましょう。

rails new foobar
cd foobar
rails g model Product name:string points:integer
rake db:migrate
rails console

Railsコンソールで、DBにいくつかのレコードをフィードします。

Product.new(name: 'Foo',  points: 1).save!
Product.new(name: 'Bar',  points: 2).save!
Product.new(name: 'Baz',  points: 3).save!
Product.new(name: 'Baf',  points: 4).save!
Product.new(name: 'Quux', points: 5).save!

今、私はここのこの投稿でSQLで現在の合計を取得する方法を見つけました。それはこのように動作します:

query = <<-SQL
  SELECT *, (
    SELECT SUM(points)
    FROM products
    WHERE id <= p.id
  ) AS total_points
  FROM products p
SQL

テストDBに対してこのクエリを実行すると、次のようになります。

Product.find_by_sql(query).each do |p|
  puts p.name.ljust(5) + p.points.to_s.rjust(2) + p.total_points.to_s.rjust(3)
end

# Foo   1  1
# Bar   2  3
# Baz   3  6
# Baf   4 10
# Quux  5 15

HAVINGこれで、句を使用して(GROUP BYこれはに必要であるため)、条件と結果の数が1にHAVING一致する製品のみをフェッチできます。LIMIT

query = <<-SQL
  SELECT *, (
    SELECT SUM(points)
    FROM products
    WHERE id <= p.id
  ) AS total_points
  FROM products p
  GROUP BY p.id
  HAVING total_points >= #{find_point_level}
  LIMIT 1
SQL

多くのレコードがある環境でこれがどのように機能するのか、私は本当に興味があります。それを試してみて、あなたが好きなら、それがあなたのために働くかどうか教えてください。

于 2012-11-17T13:03:32.140 に答える
0
  • これは実際には問題を解決しませんが、すべてのテーブルをロードする代わりに製品をバッチでロードするfind_each代わりに使用できます。ガイドeachを見る

編集は次を無視します。ウィンドウ関数ではWHERE句とHAVING句が許可されていないことを忘れていました

  • データベースに依存しないソリューションを使用したい場合は、これを使用できます(テストされていません):

    query = <<-SQL
      SELECT id, SUM(points) OVER (ORDER BY id) AS total_points
      FROM products
      HAVING total_points >= 100000
      LIMIT 1
    SQL
    
    @product = Product.find_all_by_sql( query )
    

これは、すべての RDBMS でサポートされていないウィンドウ関数を使用します (Postgresql はサポートしています)。を取得する@productと、次の 2 つの属性のみがアクセス可能な読み取り専用レコードになることに注意idしてください。total_points

于 2012-11-17T13:18:16.460 に答える
-2

テーブルが非常に大きい場合は、プレーンSQLクエリを使用できます。

find_point_level = 100000
Product.find_all_by_sql("SELECT SUM(points) FROM (SELECT points FROM products ORDER BY id LIMIT #{find_point_level}) AS subquery")

また、列インデックスの場合、データベースにインデックスが存在する必要があります。

于 2012-11-17T12:52:51.000 に答える