Ejemplo de SQL sin formato de Rails

239

¿Cómo puedo convertir este código a sql sin procesar y usarlo en rails? Porque cuando implemento este código en heroku, hay un error de tiempo de espera de solicitud. Creo que será más rápido si uso sql sin formato.

@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
fuente
3
¿Por qué crees que el SQL sin procesar será más rápido? ¿Cómo sabes que es un problema de SQL?
John Naegle
Sin ver el plan de consulta, supongo que el problema que está teniendo es ordenar por created_at. Probablemente esté haciendo una exploración secuencial a través de esas tablas enteras (ah, y trayendo también la tabla del proyecto). Al hacer dos de ellos en el método de controlador una vez en una mesa grande y una base de datos de baja potencia (las bases de datos de heroku se ajustan genéricamente y relativamente poco de potencia para el dinero que gasta), es probable que obtenga tiempos de espera. Si no está haciendo muchas inserciones en estas tablas, puede hacer un índice ordenado: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel
1
Bueno, siempre sería más rápido cortar a mano el sql (si sabes lo que estás haciendo). Rails hace algunos sql realmente desagradables. Funciona bien pero para ser general tiene que ... ser general. Dicho esto, Jim probablemente tenga razón ... crear indexación sería mejor. Intente ejecutar su consulta en pgadmin (contra su base de datos)
baash05 05 de

Respuestas:

427

Puedes hacerlo:

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

records_array entonces sería el resultado de su consulta sql en una matriz a través de la cual puede iterar.

Huy
fuente
52
nota al margen: records_arrayserá de diferentes tipos para diferentes adaptadores de bases de datos. Si está utilizando PG, será una instancia de PG::Result, no Array.
tybro0103
58
y luego necesitaría llamar valuesa este PG::Resultobjeto para obtener la matriz de resultados
jobwat
3
@ baash05 ¿Qué quiere decir con "a favor de acceder a través de la clase"? - esto habla sobre cómo acceder a la mesa sin un modelo ariond
Yo Ludke
1
En realidad no hay mención de no usar un modelo en el OP. Justo cómo ejecutar SQL sin procesar. PaymentDetail.execute (sql) funciona.
baash05
20
Me gusta ActiveRecord::Base.connection.exec_querymás porque devuelve un ActiveRecords::Resultobjeto que tiene métodos útiles como .columnsy .rowspara acceder a encabezados y valores. El conjunto de hashes de .executepuede ser problemático y me dio resultados redundantes cuando ejecuté una cláusula SUM GROUP BY.
Francio Rodrigues
181

Sé que esto es viejo ... Pero hoy tenía el mismo problema y encontré una solución:

Model.find_by_sql

Si quieres instanciar los resultados:

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

Si solo quieres un hash de valores:

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

Objeto resultante:

select_alldevuelve un resultobjeto Puedes hacer cosas mágicas con él.

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

Fuentes:

  1. ActiveRecord - Findinig por SQL .
  2. Ruby on Rails - Resultado de registro activo .
João Paulo Motta
fuente
99
#find_by_sqles exactamente lo que quería, muchas gracias.
Shelvacu
#find_by_sql es eso!
Steven L.
28

Puede ejecutar una consulta sin formato utilizando ActiveRecord. Y sugeriré ir con el bloque 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)
Deepak Mahakale
fuente
Hola ... ¿hay alguna forma de pasar parámetros a esta consulta?
Akshay Goyal el
@AkshayGoyal puede usar la interpolación de cadena de rubí normal dentro del bloque SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck
2
Si bien puede hacerlo, puede exponerlo a la posibilidad de inyección de SQL
Deepak Mahakale
26

Puede hacer que SQL directo tenga una única consulta para ambas tablas. Proporcionaré un ejemplo de consulta desinfectado para evitar que las personas pongan variables directamente en la cadena (peligro de inyección SQL), aunque este ejemplo no especificó la necesidad de ello:

@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

Editar : como dijo Huy, una forma simple es ActiveRecord::Base.connection.execute("..."). Otra forma es ActiveRecord::Base.connection.exec_query('...').rows. Y puede usar declaraciones preparadas nativas, por ejemplo, si usa postgres, la declaración preparada se puede hacer con raw_connection, prepare y exec_prepared como se describe en https://stackoverflow.com/a/13806512/178651

También puede colocar fragmentos SQL sin procesar en consultas relacionales de ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html y en asociaciones, ámbitos, etc. Probablemente podría construir el mismo SQL con consultas relacionales de ActiveRecord y puede hacer cosas interesantes con ARel como Ernie menciona en http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . Y, por supuesto, hay otros ORM, gemas, etc.

Si esto se va a usar mucho y agregar índices no causará otros problemas de rendimiento / recursos, considere agregar un índice en la base de datos para payment_details.created_at y para payment_errors.created_at.

Si es necesario mostrar muchos registros y no todos a la vez, considere usar la paginación:

Si necesita paginar, considere crear una vista en la base de datos primero llamada payment_records que combine las tablas payment_details y payment_errors, luego tenga un modelo para la vista (que será de solo lectura). Algunos DB admiten vistas materializadas, lo que podría ser una buena idea para el rendimiento.

Considere también las especificaciones de hardware o VM en el servidor Rails y el servidor DB, la configuración, el espacio en disco, la velocidad / latencia de la red / etc., la proximidad, etc. Y considere colocar DB en un servidor / VM diferente a la aplicación Rails si no lo ha hecho, etc. .

Gary S. Weaver
fuente
Dado que el autor de la pregunta está ordenando por fecha, ¿creo que la paginación no ayudaría? No soy el más profundo en SQL, pero entiendo que la consulta en la publicación original necesitaría obtener toda la tabla para ordenarla, solo entonces podría limitar los resultados. Sospecho que la mayor parte del plan de consulta está en la cláusula de orden.
Jim Wrubel
@JimWrubel aclaró el párrafo sobre paginación: la redacción estaba un poco fuera de lugar.
Gary S. Weaver
2

Quiero trabajar con exec_queryla ActiveRecordclase, porque devuelve el mapeo de la consulta que se transforma en objeto, por lo que se vuelve muy práctico y productivo iterar con los objetos cuando el sujeto es Raw SQL.

Ejemplo:

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

y devuelve esta consulta completa:

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

Para obtener solo una lista de valores

p values.rows

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

Para obtener solo columnas de campos

p values.columns

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

También puede mezclar SQL sin procesar con condiciones ActiveRecord, por ejemplo, si desea llamar a una función en una condición:

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