Railsのraw SQLの例


239

このコードを未加工のSQLに変換してRailsで使用するにはどうすればよいですか?このコードをherokuにデプロイすると、リクエストタイムアウトエラーが発生するため、生のSQLを使用した方が高速になると思います。

@payments = PaymentDetail.joins(:project).order('payment_details.created_at desc')
@payment_errors = PaymentError.joins(:project).order('payment_errors.created_at desc')

@all_payments = (@payments + @payment_errors)

3
生のSQLの方が速いと思うのはなぜですか?SQLの問題をどのようにして知っていますか?
John Naegle 2013

クエリプランが表示されない場合、発生している問題はcreated_atによる順序付けであると思います。あなたはおそらくそれらのテーブル全体にわたってシーケンススキャンを行っています(ああ、プロジェクトテーブルも持ち込んでいます)。大きなテーブルと能力の低いDB(herokuデータベースは総称的に調整され、費やした$$に対して比較的能力が低い)で一度に2つのコントローラーメソッドを実行すると、タイムアウトが発生する可能性があります。これらのテーブルに多くの挿入を行わない場合は、ソートされたインデックスを作成できます。devcenter.heroku.com
Jim

1
ええと、SQLを手動でカットする方が常に高速です(実行していることがわかっている場合)。Railsは本当に厄介なSQLを作ります。それはうまく機能しますが、一般的であるためには...一般的でなければなりません。ジムはおそらく正しいと言った..インデックスを作成する方が良いでしょう。pgadmin(dbに対して)でクエリを実行してみてください
baash05

回答:


427

あなたはこれを行うことができます:

sql = "Select * from ... your sql query here"
records_array = ActiveRecord::Base.connection.execute(sql)

records_array その後、反復可能な配列のSQLクエリの結果になります。


52
補足:records_arrayデータベースアダプターごとにタイプが異なります。PGを使用している場合、それはでPG::ResultはなくのインスタンスになりますArray
tybro0103 2014年

58
結果の配列を取得するにはvalues、このPG::Resultオブジェクトを呼び出す必要があります
jobwat

3
@ baash05「クラスを介してアクセスすることを支持して」とはどういう意味ですか。-これは、モデルを使用せずにテーブルにアクセスする方法について説明しています
Yo Ludke

1
実際には、OPでモデルを使用しないことについての言及はありません。生のSQLを実行する方法。PaymentDetail.execute(sql)が機能します。
baash05 14

20
私は好きActiveRecord::Base.connection.exec_queryそれが返すので、より良いActiveRecords::Resultような便利なメソッドを持つオブジェクト.columns.rowsアクセスのヘッダーと値にします。からのハッシュの配列は.execute、扱いが面倒で、SUM GROUP BY句を使用して実行すると冗長な結果が得られます。
ロドリゲス

181

私はこれが古いことを知っています...しかし、私は今日同じ問題を抱えていて解決策を見つけました:

Model.find_by_sql

結果をインスタンス化する場合:

Client.find_by_sql("
  SELECT * FROM clients
  INNER JOIN orders ON clients.id = orders.client_id
  ORDER BY clients.created_at desc
")
# => [<Client id: 1, first_name: "Lucas" >, <Client id: 2, first_name: "Jan">...]

Model.connection.select_all('sql').to_hash

値のハッシュだけが必要な場合:

Client.connection.select_all("SELECT first_name, created_at FROM clients
   WHERE id = '1'").to_hash
# => [
  {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
  {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
]

結果オブジェクト:

select_allresultオブジェクトを返します。魔法のようなことができます。

result = Post.connection.select_all('SELECT id, title, body FROM posts')
# Get the column names of the result:
result.columns
# => ["id", "title", "body"]

# Get the record values of the result:
result.rows
# => [[1, "title_1", "body_1"],
      [2, "title_2", "body_2"],
      ...
     ]

# Get an array of hashes representing the result (column => value):
result.to_hash
# => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
      {"id" => 2, "title" => "title_2", "body" => "body_2"},
      ...
     ]

# ActiveRecord::Result also includes Enumerable.
result.each do |row|
  puts row['title'] + " " + row['body']
end

出典:

  1. ActiveRecord-SQLによる検索
  2. Ruby on Rails-Active Record Result

9
#find_by_sqlまさに私が欲しかったものです、どうもありがとうございました。
シェルバク2016

#find_by_sql is it !!
Steven L.

28

を使用して生のクエリを実行できますActiveRecord。そして、私はSQLブロックに行くことをお勧めします

query = <<-SQL 
  SELECT * 
  FROM payment_details
  INNER JOIN projects 
          ON projects.id = payment_details.project_id
  ORDER BY payment_details.created_at DESC
SQL

result = ActiveRecord::Base.connection.execute(query)

こんにちは。このクエリにパラメータを渡す方法はありますか?
Akshay Goyal

@AkshayGoyalでは、ブロック内で通常のルビ文字列補間を使用できます。SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck、

2
それは可能ですが、SQLインジェクションの可能性にさらされる可能性があります
Deepak Mahakale

26

SQLを直接実行して、両方のテーブルに対して単一のクエリを実行できます。この例では必要性を指定していませんが、うまくいけば、人々が文字列自体に直接変数を入れないようにするサニタイズされたクエリの例を提供します(SQLインジェクションの危険)。

@results = []
ActiveRecord::Base.connection.select_all(
  ActiveRecord::Base.send(:sanitize_sql_array, 
   ["... your SQL query goes here and ?, ?, ? are replaced...;", a, b, c])
).each do |record|
  # instead of an array of hashes, you could put in a custom object with attributes
  @results << {col_a_name: record["col_a_name"], col_b_name: record["col_b_name"], ...}
end

編集:ホイが言ったように、簡単な方法はActiveRecord::Base.connection.execute("...")です。別の方法ですActiveRecord::Base.connection.exec_query('...').rows。また、ネイティブの準備済みステートメントを使用できます。たとえば、postgresを使用する場合、準備済みステートメントは、https: //stackoverflow.com/a/13806512/178651で説明されているようにraw_connection、prepare、およびexec_preparedで実行できます

生のSQLフラグメントをActiveRecordリレーショナルクエリに配置することもできます。http://guides.rubyonrails.org/active_record_querying.html や関連付け、スコープなどで。ActiveSQLリレーショナルクエリを使用して同じSQLを構築し、アーニーがhttp://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/で言及しているARel 。そしてもちろん、他のORM、宝石などもあります。

これが頻繁に使用され、インデックスを追加しても他のパフォーマンス/リソースの問題が発生しない場合は、payment_details.created_atおよびpayment_errors.created_atのDBにインデックスを追加することを検討してください。

多くのレコードがあり、すべてのレコードを一度に表示する必要がない場合は、ページネーションの使用を検討してください。

ページネーションを行う必要がある場合は、最初にpayment_recordsと呼ばれるDBにビューを作成し、payment_detailsテーブルとpayment_errorsテーブルを組み合わせてから、ビューのモデル(読み取り専用)を作成することを検討してください。一部のDBはマテリアライズドビューをサポートしていますが、これはパフォーマンスにとっては良いアイデアかもしれません。

また、RailsサーバーとDBサーバーのハードウェアまたはVMの仕様、構成、ディスク容量、ネットワーク速度/レイテンシーなど、近接性などを考慮します。また、DBをRailsアプリとは別のサーバー/ VMに配置することを検討してください。 。


質問者が日付でソートしているので、ページネーションは役に立たないと思いますか?私はSQLに精通していませんが、元の投稿のクエリではテーブルを並べ替えるためにテーブル全体をフェッチする必要があります-そうしないと結果を制限できません。クエリプランの大部分はorder句にあると思います。
Jim Wrubel 2013年

@JimWrubelがページネーションに関する段落を明確にしました-表現が少し不規則でした。
Gary S. Weaver

2

私は仕事をしたいexec_queryActiveRecordそれは被写体が生SQLの場合にオブジェクトを反復するために非常に実用的かつ生産的な取得しますので、それは、オブジェクトにクエリ変換のマッピングを返すので、クラス。

例:

values = ActiveRecord::Base.connection.exec_query("select * from clients")
p values

この完全なクエリを返します:

[{"id": 1, "name": "user 1"}, {"id": 2, "name": "user 2"}, {"id": 3, "name": "user 3"}]

値のリストのみを取得するには

p values.rows

[[1, "user 1"], [2, "user 2"], [3, "user 3"]]

フィールド列のみを取得するには

p values.columns

["id", "name"]

0

生のSQLとActiveRecord条件を混在させることもできます。たとえば、条件で関数を呼び出す場合は、次のようにします。

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.