Esempio SQL raw di rotaie


239

Come posso convertire questo codice in sql grezzo e usarlo nelle rotaie? Perché quando distribuisco questo codice in heroku, c'è un errore di timeout della richiesta. Penso che questo sarà più veloce se uso sql grezzo.

@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
Perché pensi che SQL raw sarà più veloce? Come fai a sapere che è un problema SQL?
John Naegle,

Senza vedere il piano di query, immagino che il problema che stai riscontrando sia l'ordinamento di Created_at. Probabilmente stai eseguendo una scansione seq su quelle intere tabelle (oh e portando anche la tabella del progetto). Facendo due di questi in un unico metodo di controller su una grande tabella e un database sottodimensionato (i database heroku sono sintonizzati genericamente e relativamente sottodimensionati per i $ che spendi) è probabile che tu abbia timeout. Se non stai inserendo molti inserimenti in queste tabelle, potresti fare un indice ordinato: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel,

1
Beh, sarebbe sempre più veloce tagliare a mano il sql (se sai cosa stai facendo). Rails rende davvero sgradevole sql. Funziona bene ma per essere generale deve ... essere generale. Detto questo, probabilmente Jim ha ragione ... creare indicizzazione sarebbe meglio. Prova a eseguire la tua query in pgadmin (contro il tuo db)
baash05 il

Risposte:


427

Puoi farlo:

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

records_array sarebbe quindi il risultato della tua query sql in un array attraverso il quale puoi scorrere.


52
nota a margine: records_arraysaranno di diversi tipi per adattatori di database diversi. Se stai usando PG, sarà un'istanza di PG::Resultno Array.
tybro0103,

58
e quindi dovrai chiamare valuesquesto PG::Resultoggetto per ottenere l'array dei risultati
jobwat,

3
@ baash05 Cosa intendi con "a favore di accedervi tramite la classe". - questo parla di come accedere al tavolo senza un modello e lo appende
Yo Ludke,

1
In realtà non si parla di non usare un modello nell'OP. Proprio come eseguire sql grezzo. PaymentDetail.execute (sql) funziona.
baash05,

20
Mi piace di ActiveRecord::Base.connection.exec_querypiù perché restituisce un ActiveRecords::Resultoggetto con metodi utili come .columnse .rowsper accedere a intestazioni e valori. L'array di hash da .executepuò essere problematico da gestire e mi ha dato risultati ridondanti quando ho corso con una clausola SUM GROUP BY.
Francio Rodrigues,

181

So che questo è vecchio ... Ma stavo avendo lo stesso problema oggi e ho trovato una soluzione:

Model.find_by_sql

Se si desidera creare un'istanza dei risultati:

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

Se vuoi solo un hash di valori:

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

Oggetto risultato:

select_allrestituisce un resultoggetto. Puoi fare cose magiche con esso.

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

fonti:

  1. ActiveRecord - Findinig by SQL .
  2. Ruby on Rails - Risultato record attivo .

9
#find_by_sqlè esattamente quello che volevo, grazie mille.
Shelvacu,

#find_by_sql è !!
Steven L.,

28

È possibile eseguire query non elaborate utilizzando ActiveRecord. E suggerirò di andare con il blocco 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)

Ciao .. c'è un modo per passare i parametri a questa query?
Akshay Goyal,

@AkshayGoyal puoi usare la normale interpolazione di stringhe di rubini all'interno del blocco. SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck,

2
Mentre puoi farlo potrebbe
esporti

26

È possibile eseguire l'SQL diretto per avere una singola query per entrambe le tabelle. Fornirò un esempio di query igienizzato per impedire alle persone di inserire le variabili direttamente nella stringa stessa (pericolo di iniezione SQL), anche se questo esempio non ne specifica la necessità:

@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

Modifica : come ha detto Huy, un modo semplice è ActiveRecord::Base.connection.execute("..."). Un altro modo è ActiveRecord::Base.connection.exec_query('...').rows. E puoi usare istruzioni preparate native, ad es. Se usi postgres, le istruzioni preparate possono essere fatte con raw_connection, preparazione ed exec_prepared come descritto in https://stackoverflow.com/a/13806512/178651

Puoi anche inserire frammenti SQL grezzi nelle query relazionali di ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html e in associazioni, ambiti, ecc. Probabilmente potresti costruire lo stesso SQL con le query relazionali di ActiveRecord e fare cose interessanti con ARel come menziona Ernie in http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . E, naturalmente, ci sono altri ORM, gemme, ecc.

Se questo verrà utilizzato molto e l'aggiunta di indici non causerà altri problemi di prestazioni / risorse, prendere in considerazione l'aggiunta di un indice nel DB per payment_details.created_at e per payment_errors.created_at.

Se molti record e non tutti i record devono essere visualizzati contemporaneamente, considera l'utilizzo dell'impaginazione:

Se è necessario impaginare, prendere in considerazione la creazione di una vista nel DB prima chiamata payment_records che combina le tabelle payment_details e payment_errors, quindi disporre di un modello per la vista (che sarà di sola lettura). Alcuni DB supportano viste materializzate, che potrebbero essere una buona idea per le prestazioni.

Considera anche le specifiche hardware o VM sul server Rails e sul server DB, sulla configurazione, sullo spazio su disco, sulla velocità / latenza / ecc. Della rete, sulla vicinanza, ecc. .


Dal momento che il richiedente sta ordinando per data penso che l'impaginazione non sarebbe di aiuto? Non sono il più approfondito su SQL ma la mia comprensione è che la query nel post originale avrebbe bisogno di recuperare l'intera tabella per ordinarla - solo allora potrebbe limitare i risultati. Sospetto che la maggior parte del piano di query sia nella clausola dell'ordine.
Jim Wrubel,

@JimWrubel ha chiarito il paragrafo sull'impaginazione: il testo era un po 'fuori posto.
Gary S. Weaver,

2

Voglio lavorare con exec_queryla ActiveRecordclasse, perché restituisce la mappatura della query che si trasforma in oggetto, quindi diventa molto pratico e produttivo iterare con gli oggetti quando il soggetto è Raw SQL.

Esempio:

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

e restituisci questa query completa:

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

Per ottenere solo un elenco di valori

p values.rows

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

Per ottenere solo colonne di campi

p values.columns

["id", "name"]

0

È inoltre possibile combinare SQL grezzo con condizioni ActiveRecord, ad esempio se si desidera chiamare una funzione in una condizione:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.