Exemple Rails SQL brut

239

Comment puis-je convertir ce code en SQL brut et l'utiliser dans des rails? Parce que lorsque je déploie ce code dans Heroku, il y a une erreur de délai d'expiration de la demande. Je pense que ce sera plus rapide si j'utilise SQL brut.

@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
la source
3
Pourquoi pensez-vous que le SQL brut sera plus rapide? Comment savez-vous que c'est un problème SQL?
John Naegle
Sans voir le plan de requête, je suppose que le problème que vous rencontrez est la commande par created_at. Vous effectuez probablement une analyse séquentielle sur l'ensemble de ces tables (oh et apportez également la table de projet). En faisant deux de ces méthodes en une seule fois sur une grande table et une base de données sous-alimentée (les bases de données Heroku sont réglées de manière générique et relativement sous-alimentées pour les $$ que vous dépensez), vous risquez d'obtenir des délais d'attente. Si vous ne faites pas beaucoup d'insertions dans ces tableaux, vous pouvez faire un index trié: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel
1
Eh bien, il serait toujours plus rapide de couper le sql à la main (si vous savez ce que vous faites). Rails fait un sql vraiment méchant. Cela fonctionne bien, mais pour être général, il faut ... être général. Cela dit, Jim a probablement raison ... créer une indexation serait mieux. Essayez d'exécuter votre requête dans pgadmin (par rapport à votre base de données)
baash05

Réponses:

428

Tu peux le faire:

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

records_array serait alors le résultat de votre requête SQL dans un tableau que vous pouvez parcourir.

Huy
la source
52
note latérale: records_arraysera de différents types pour différents adaptateurs de base de données. Si vous utilisez PG, ce sera une instance de PG::Result, non Array.
tybro0103
58
et ensuite vous devrez appeler valuescet PG::Resultobjet pour obtenir le tableau de résultats
jobwat
3
@ baash05 Qu'entendez-vous par "en faveur de l'accès via la classe". - ceci explique comment accéder à la table sans modèle et
Yo Ludke
1
En fait, il n'est pas fait mention de ne pas utiliser de modèle dans le PO. Juste comment exécuter SQL brut. PaymentDetail.execute (sql) fonctionne.
baash05
20
J'aime ActiveRecord::Base.connection.exec_querymieux car il retourne un ActiveRecords::Resultobjet qui a des méthodes pratiques comme .columnset .rowspour accéder aux en-têtes et aux valeurs. Le tableau de hachages .executepeut être difficile à gérer et m'a donné des résultats redondants lorsque j'ai exécuté une clause SUM GROUP BY.
Francio Rodrigues
181

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 souhaitez 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 de résultat:

select_allrenvoie un resultobjet. Vous pouvez faire des choses magiques avec.

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 - Findinig par SQL .
  2. Ruby on Rails - Résultat d'enregistrement actif .
João Paulo Motta
la source
10
#find_by_sqlc'est exactement ce que je voulais, merci beaucoup.
Shelvacu
#find_by_sql est-ce !!
Steven L.
28

Vous pouvez exécuter une requête brute à l'aide de ActiveRecord. Et je proposerai d'aller avec 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)
Deepak Mahakale
la source
Salut .. est-il possible de passer des paramètres à cette requête?
Akshay Goyal
@AkshayGoyal, vous pouvez utiliser une interpolation de chaîne rubis normale dans le bloc. SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck
2
Bien que vous puissiez le faire, cela peut vous exposer à la possibilité d'une injection SQL
Deepak Mahakale
26

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, espérons-le, empêcher les gens de placer des variables directement dans la chaîne elle-même (danger d'injection SQL), même si cet exemple n'en spécifie pas la nécessité:

@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 : comme l'a dit Huy, un moyen simple est ActiveRecord::Base.connection.execute("..."). Une autre façon 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 effectuée avec raw_connection, prepare et exec_prepared comme décrit dans https://stackoverflow.com/a/13806512/178651

Vous pouvez également placer des fragments SQL bruts dans des requêtes relationnelles ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html et dans des associations, des étendues, etc. Vous pourriez probablement construire le même SQL avec des requêtes relationnelles 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 cela va être beaucoup utilisé et que l'ajout d'index ne causera pas d'autres problèmes de performances / 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 et pas tous les enregistrements doivent apparaître simultanément, envisagez d'utiliser la pagination:

Si vous devez paginer, envisagez de créer une vue dans la base de données appelée d'abord payment_records qui combine les tables payment_details et payment_errors, puis disposez d'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.

Tenez également compte des spécifications du matériel ou de la machine virtuelle sur le serveur Rails et le serveur DB, la configuration, l'espace disque, la vitesse / latence du réseau / etc., la proximité, etc. .

Gary S. Weaver
la source
Étant donné que le demandeur trie par date, je pense que la pagination n'aiderait pas? Je ne suis pas le plus expert en SQL, mais je crois comprendre que la requête dans le message d'origine aurait besoin de récupérer la table entière pour la trier - alors seulement cela pourrait-il limiter les résultats. Je soupçonne que la majeure partie du plan de requête se trouve dans la clause de commande.
Jim Wrubel
@JimWrubel a clarifié le paragraphe sur la pagination - la formulation était un peu désordonnée.
Gary S. Weaver
2

Je veux travailler avec exec_queryde la ActiveRecordclasse, car elle renvoie le mappage de la requête se transformant en objet, il devient donc très pratique et productif d'itérer avec les objets lorsque le sujet est du SQL brut.

Exemple:

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

et renvoyez cette requête complète:

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

Pour obtenir uniquement la liste des valeurs

p values.rows

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

Pour obtenir uniquement les colonnes de champs

p values.columns

["id", "name"]
Darlan Dieterich
la source
0

Vous pouvez également mélanger du SQL brut avec des conditions ActiveRecord, par exemple si vous souhaitez appeler une fonction dans une condition:

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