Rails Raw-SQL-Beispiel

239

Wie kann ich diesen Code in unformatiertes SQL konvertieren und in Rails verwenden? Denn wenn ich diesen Code in Heroku bereitstelle, tritt ein Fehler beim Anforderungszeitlimit auf. Ich denke, dies wird schneller, wenn ich Raw SQL verwende.

@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)
Johnny Cash
quelle
3
Warum wird Raw SQL Ihrer Meinung nach schneller sein? Woher wissen Sie, dass es sich um ein SQL-Problem handelt?
John Naegle
Ohne den Abfrageplan zu sehen, würde ich vermuten, dass das Problem, das Sie haben, darin besteht, nach created_at zu bestellen. Sie führen wahrscheinlich einen seq-Scan über diese gesamten Tabellen durch (oh, und bringen auch die Projekttabelle ein). Wenn Sie zwei dieser Methoden auf einmal für einen großen Tisch und eine unterlastete Datenbank ausführen (Heroku-Datenbanken sind generisch optimiert und für die von Ihnen ausgegebenen US-Dollar relativ schwach), treten wahrscheinlich Zeitüberschreitungen auf. Wenn Sie nicht viele Einfügungen in diese Tabellen vornehmen, können
Jim Wrubel
1
Nun, es wäre immer schneller, die SQL von Hand zu schneiden (wenn Sie wissen, was Sie tun). Rails macht einige wirklich böse SQL. Es funktioniert gut, aber um allgemein zu sein, muss es ... allgemein sein. Das heißt, Jim hat wahrscheinlich Recht. Eine Indexierung zu erstellen wäre besser. Versuchen Sie, Ihre Abfrage in pgadmin (gegen Ihre
Datenbank

Antworten:

427

Du kannst das:

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

records_array wäre dann das Ergebnis Ihrer SQL-Abfrage in einem Array, das Sie durchlaufen können.

Huy
quelle
52
Randnotiz: Es records_arraygibt verschiedene Typen für verschiedene Datenbankadapter. Wenn Sie PG verwenden, ist dies eine Instanz von PG::Result, nicht Array.
Tybro0103
58
und dann müssten Sie valuesdieses PG::ResultObjekt aufrufen , um das Ergebnisarray zu erhalten
jobwat
3
@ baash05 Was meinst du mit "für den Zugriff über die Klasse". - Hier geht es darum, wie man ohne Model auf den Tisch zugreifen kann
Yo Ludke
1
Eigentlich wird nicht erwähnt, dass im OP kein Modell verwendet wird. Nur wie man Raw SQL ausführt. PaymentDetail.execute (sql) funktioniert.
Baash05
20
Ich mag es ActiveRecord::Base.connection.exec_querybesser, weil es ein ActiveRecords::ResultObjekt zurückgibt, das praktische Methoden wie .columnsund .rowsfür den Zugriff auf Header und Werte hat. Das Array von Hashes von .executekann schwierig zu handhaben sein und hat mir redundante Ergebnisse geliefert, wenn ich mit einer SUM GROUP BY-Klausel ausgeführt habe.
Francio Rodrigues
181

Ich weiß, das ist alt ... Aber ich hatte heute das gleiche Problem und fand eine Lösung:

Model.find_by_sql

Wenn Sie die Ergebnisse instanziieren möchten:

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

Wenn Sie nur einen Hash von Werten wollen:

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"}
]

Ergebnisobjekt:

select_allgibt ein resultObjekt zurück. Sie können damit magische Dinge tun.

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

Quellen:

  1. ActiveRecord - Suchen mit SQL .
  2. Ruby on Rails - Ergebnis des aktiven Datensatzes .
João Paulo Motta
quelle
9
#find_by_sqlist genau das, was ich wollte, vielen Dank.
Shelvacu
#find_by_sql ist es !!
Steven L.
28

Sie können eine Rohabfrage mit ausführen ActiveRecord. Und ich werde vorschlagen, mit SQL-Block zu gehen

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)
Deepak Mahakale
quelle
Hallo .. gibt es eine Möglichkeit, Parameter an diese Abfrage zu übergeben?
Akshay Goyal
@AkshayGoyal Sie können die normale Ruby-String-Interpolation innerhalb des Blocks verwenden. SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck
2
Während Sie das tun können, kann es Sie der Möglichkeit der SQL-Injektion aussetzen
Deepak Mahakale
26

Sie können Direct SQL ausführen, um eine einzige Abfrage für beide Tabellen zu erhalten. Ich werde ein Beispiel für eine bereinigte Abfrage bereitstellen, um zu verhindern, dass Benutzer Variablen direkt in die Zeichenfolge selbst einfügen (SQL-Injection-Gefahr), obwohl in diesem Beispiel nicht angegeben wurde, dass dies erforderlich ist:

@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

Edit : wie Huy sagte, ist ein einfacher Weg ActiveRecord::Base.connection.execute("..."). Ein anderer Weg ist ActiveRecord::Base.connection.exec_query('...').rows. Sie können native vorbereitete Anweisungen verwenden, z. B. wenn Sie postgres verwenden. Die vorbereitete Anweisung kann mit raw_connection, prepare und exec_prepared ausgeführt werden, wie unter https://stackoverflow.com/a/13806512/178651 beschrieben

Sie können auch rohe SQL-Fragmente in relationale ActiveRecord-Abfragen einfügen : http://guides.rubyonrails.org/active_record_querying.html und in Zuordnungen, Bereichen usw. Sie könnten wahrscheinlich dasselbe SQL mit relationalen ActiveRecord-Abfragen erstellen und coole Dinge damit tun ARel als Ernie erwähnt in http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . Und natürlich gibt es auch andere ORMs, Edelsteine ​​usw.

Wenn dies häufig verwendet wird und das Hinzufügen von Indizes keine anderen Leistungs- / Ressourcenprobleme verursacht, sollten Sie einen Index in der Datenbank für payment_details.created_at und für success_errors.created_at hinzufügen.

Wenn viele Datensätze und nicht alle Datensätze gleichzeitig angezeigt werden müssen, sollten Sie die Paginierung verwenden:

Wenn Sie paginieren müssen, sollten Sie in der Datenbank zunächst eine Ansicht mit dem Namen "payment_records" erstellen, die die Tabellen "pay_details" und "payment_errors" kombiniert, und dann ein Modell für die Ansicht erstellen (das schreibgeschützt ist). Einige DBs unterstützen materialisierte Ansichten, was für die Leistung eine gute Idee sein könnte.

Berücksichtigen Sie auch Hardware- oder VM-Spezifikationen auf dem Rails-Server und dem DB-Server, die Konfiguration, den Speicherplatz, die Netzwerkgeschwindigkeit / -latenz usw., die Nähe usw. und erwägen Sie, die DB auf einem anderen Server / einer anderen VM als der Rails-App zu installieren, falls dies nicht der Fall ist .

Gary S. Weaver
quelle
Da der Fragesteller nach Datum sortiert, würde Paginierung meiner Meinung nach nicht helfen? Ich bin nicht der tiefste in SQL, aber ich verstehe, dass die Abfrage im ursprünglichen Beitrag die gesamte Tabelle abrufen müsste, um sie zu sortieren - nur dann könnte sie die Ergebnisse einschränken. Ich vermute, dass der Großteil des Abfrageplans in der Bestellklausel enthalten ist.
Jim Wrubel
@ JimWrubel hat den Absatz über die Paginierung klargestellt - der Wortlaut war ein wenig falsch.
Gary S. Weaver
2

Ich möchte mit exec_queryder ActiveRecordKlasse arbeiten, da sie die Zuordnung der Abfrage zurückgibt, die in ein Objekt umgewandelt wird. Daher wird es sehr praktisch und produktiv, mit den Objekten zu iterieren, wenn das Subjekt Raw SQL ist.

Beispiel:

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

und geben Sie diese vollständige Abfrage zurück:

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

Um nur eine Liste von Werten zu erhalten

p values.rows

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

Um nur Feldspalten zu erhalten

p values.columns

["id", "name"]
Darlan Dieterich
quelle
0

Sie können auch unformatiertes SQL mit ActiveRecord-Bedingungen mischen, z. B. wenn Sie eine Funktion in einer Bedingung aufrufen möchten:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
Tsauerwein
quelle