277 votes

Exemple de SQL brut dans Rails

Comment puis-je convertir ce code en sql brut et l'utiliser dans rails ? Parce que lorsque je déploie ce code dans Heroku, il y a une erreur de timeout de la demande. Je pense que ce sera plus rapide si j'utilise raw 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 votes

Pourquoi pensez-vous que le SQL brut sera plus rapide ? Comment savez-vous que c'est un problème de SQL ?

0 votes

Sans voir le plan de requête, je suppose que le problème que vous rencontrez est le classement par created_at. Vous êtes probablement en train de faire un seq scan sur l'ensemble de ces tables (en y ajoutant la table des projets). En faisant deux de ces requêtes en une seule fois avec la méthode du contrôleur sur une grande table et une base de données sous-puissante (les bases de données Heroku sont réglées de manière générique et relativement sous-puissantes pour les dollars que vous dépensez), vous risquez d'obtenir des délais d'attente. Si vous ne faites pas beaucoup d'insertions dans ces tables, vous pouvez utiliser un index trié : devcenter.heroku.com/articles/postgresql-index#sorted-index

1 votes

Il serait toujours plus rapide de couper le code SQL à la main (si vous savez ce que vous faites). Rails fait du sql vraiment méchant. Il fonctionne bien mais pour être général, il doit... être général. Ceci dit, Jim a probablement raison... créer une indexation serait mieux. Essayez d'exécuter votre requête dans pgadmin (contre votre base de données).

494voto

Huy Points 2289

Vous pouvez le faire :

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

records_array sera alors le résultat de votre requête sql dans un tableau que vous pourrez parcourir par itération.

62 votes

Note complémentaire : records_array seront de différents types pour différents adaptateurs de base de données. Si vous utilisez PG, il s'agira d'une instance de PG::Result pas Array .

69 votes

Et ensuite vous devrez appeler values sur ce PG::Result pour obtenir le tableau des résultats

3 votes

@baash05 Que voulez-vous dire par "en faveur de l'accès via la classe". - Ceci parle de la façon d'accéder à la table sans modèle associé.

214voto

João Paulo Motta Points 2946

Je sais que c'est vieux... Mais j'avais le même problème aujourd'hui et j'ai trouvé une solution :

Model.find_by_sql

Si vous voulez instancier les résultats :

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 vous voulez juste un hachage de valeurs :

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

Objet du résultat :

select_all renvoie un result objet. Vous pouvez faire des choses magiques avec lui.

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

Sources :

  1. ActiveRecord - Recherche par SQL .
  2. Ruby on Rails - Résultat de l'enregistrement actif .

37voto

Deepak Mahakale Points 13858

Vous pouvez exécuter une requête brute en utilisant ActiveRecord . Et je vous suggère d'opter pour le bloc 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)

30voto

Gary S. Weaver Points 4034

Vous pouvez faire du SQL direct pour avoir une seule requête pour les deux tables. Je vais fournir un exemple de requête aseptisée pour éviter que les gens ne mettent des variables directement dans la chaîne elle-même (danger d'injection SQL), même si cet exemple n'en a pas besoin :

@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

Modifier comme l'a dit Huy, un moyen simple est de ActiveRecord::Base.connection.execute("...") . Un autre moyen est ActiveRecord::Base.connection.exec_query('...').rows . Et vous pouvez utiliser des instructions préparées natives, par exemple si vous utilisez postgres, l'instruction préparée peut être faite avec raw_connection, prepare, et exec_prepared comme décrit dans https://stackoverflow.com/a/13806512/178651

Vous pouvez également mettre des fragments SQL bruts dans des requêtes relationnelles ActiveRecord : http://guides.rubyonrails.org/active_record_querying.html et dans les associations, les portées, etc. Vous pourriez probablement construire le même SQL avec les requêtes relationnelles d'ActiveRecord et faire des choses sympas avec ARel comme Ernie le mentionne dans http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . Et, bien sûr, il existe d'autres ORM, des gemmes, etc.

Si cette fonction est utilisée fréquemment et que l'ajout d'index ne pose pas de problèmes de performance ou de ressources, envisagez d'ajouter un index dans la base de données pour payment_details.created_at et pour payment_errors.created_at.

Si de nombreux enregistrements ne doivent pas tous s'afficher en même temps, pensez à utiliser la pagination :

Si vous avez besoin de paginer, envisagez de créer d'abord une vue dans la BD appelée payment_records qui combine les tables payment_details et payment_errors, puis créez un modèle pour la vue (qui sera en lecture seule). Certaines bases de données prennent en charge les vues matérialisées, ce qui peut être une bonne idée pour les performances.

Considérez également les spécifications du matériel ou de la VM sur le serveur Rails et le serveur de base de données, la configuration, l'espace disque, la vitesse/latence/etc. du réseau, la proximité, etc. Envisagez également de placer la base de données sur un serveur/VM différent de celui de l'application Rails si vous ne l'avez pas fait, etc.

0 votes

Étant donné que l'auteur de la demande classe les données par date, je pense que la pagination ne serait pas utile ? Je ne suis pas le plus grand spécialiste du SQL, mais je crois comprendre que la requête du message d'origine doit récupérer la totalité de la table pour la trier - ce n'est qu'ensuite qu'elle peut limiter les résultats. Je soupçonne que la majeure partie du plan de requête se trouve dans la clause d'ordre.

0 votes

@JimWrubel a clarifié le paragraphe sur la pagination - la formulation n'était pas très claire.

9voto

Darlan Dieterich Points 168

Je veux travailler avec exec_query de la ActiveRecord car elle renvoie le mappage de la transformation de la requête en objet. Il devient donc très pratique et productif d'itérer avec les objets lorsque le sujet est Raw SQL.

Exemple :

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

et renvoyer cette requête complète :

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

Pour obtenir uniquement une liste de valeurs

p values.rows

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

Pour obtenir uniquement les colonnes des champs

p values.columns

["id", "name"]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X