2013-02-12 16 views
145

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

@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) 
+1

Perché pensi che SQL raw sarà più veloce? Come sai che è un problema SQL? –

+0

Senza vedere il piano di query, suppongo che il problema che stai riscontrando sia l'ordine di created_at. Probabilmente stai facendo una scansione seq su tutti questi tavoli interi (oh e inserendo anche la tabella del progetto). Effettuando due di questi in un unico metodo di controllo su un grande tavolo e DB sottodimensionato (i database di heroku sono ottimizzati in modo generico e relativamente sottodimensionati per i $$ spesi) è probabile che si verifichino timeout. Se non stai facendo molti inserti in queste tabelle potresti fare un indice ordinato: https://devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes –

+1

Beh, sarebbe sempre più veloce tagliare a mano il sql (se sai cosa stai facendo). Rails rende alcuni sql davvero sgradevoli. Funziona bene, ma per essere generale deve ... essere generale. Detto questo, Jim probabilmente ha ragione ... la creazione dell'indicizzazione sarebbe stata migliore. Prova a eseguire la query in pgadmin (contro il tuo db) – baash05

risposta

281

È possibile farlo:

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

records_array sarebbe allora il risultato della query SQL in una matrice che è possibile scorrere.

+38

nota a margine: 'records_array' sarà di diversi tipi per diversi adattatori di database. Se stai usando PG, sarà un'istanza di 'PG :: Result', non' Array'. – tybro0103

+42

e quindi è necessario chiamare 'values' su questo oggetto' PG :: Result' per ottenere l'array di risultati. – jobwat

+3

AVVISO DI DEPRECAZIONE: #connection è deprecato a favore dell'accesso tramite la classe. – baash05

24

È possibile eseguire direttamente SQL per avere una singola query per entrambe le tabelle. Vi fornirò un esempio di query sterilizzata per tenere la gente spera di mettere le variabili direttamente nella stringa stessa (SQL iniezione pericolo), anche se questo esempio non ha specificato la necessità di esso:

@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: come ha detto Huy, un modo semplice è ActiveRecord::Base.connection.execute("..."). Un altro modo è ActiveRecord::Base.connection.exec_query('...').rows. Ed è possibile utilizzare istruzioni preparate native, ad es. se si utilizza Postgres, dichiarazione preparata può essere fatto con raw_connection, preparare e exec_prepared come descritto nel https://stackoverflow.com/a/13806512/178651

Si può anche mettere frammenti di SQL prime in ActiveRecord query relazionali: http://guides.rubyonrails.org/active_record_querying.html e nelle associazioni, oscilloscopi, ecc Si potrebbe probabilmente costruire lo stesso SQL con le query relazionali ActiveRecord e può 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, si consideri l'aggiunta di un indice nel DB per payment_details.created_at e per payment_errors.created_at.

Se un sacco di dischi e non tutti i record devono presentarsi in una sola volta, considerare l'utilizzo di impaginazione:

Se avete bisogno di impaginare, considerare la creazione di una vista nel DB prima ha chiamato payment_records che combina le tabelle payment_details e payment_errors, quindi ha un modello per la vista (che sarà di sola lettura). Alcuni DB supportano visualizzazioni materializzate, che potrebbero essere una buona idea per le prestazioni.

Considerare anche le specifiche hardware o VM su server Rails e server DB, config, spazio su disco, velocità/latenza di rete/ecc., Prossimità, ecc. E prendere in considerazione l'inserimento di DB su diversi server/VM rispetto all'app Rails 't, ecc

+0

Dato che il richiedente sta selezionando per data, penso che la paginazione non sarebbe di aiuto? Non sono il più profondo 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 di ordine. –

+0

@JimWrubel ha chiarito il paragrafo sull'impaginazione - la formulazione era un po 'fuori tema. –

+0

Grande modifica. Mi piace anche l'idea di un modello payment_records. –

107

So che questo è vecchio ...Ma ho avuto lo stesso problema di oggi e trovare 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 si desidera solo 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"} 
] 

Risultato oggetto:

select_all restituisce un oggetto result. 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 - Active Record Result .
+4

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

+0

Estremamente utile! Grazie. – BrunoFacca

+0

'find_by_sql' funziona! dolce! – Mustafa

11

È 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)