Exemplo de SQL bruto do Rails

239

Como posso converter esse código em sql bruto e usar em trilhos? Como quando implanto esse código no heroku, há um erro de tempo limite da solicitação. Acho que isso será mais rápido se eu usar o sql bruto.

@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
fonte
3
Por que você acha que o SQL bruto será mais rápido? Como você sabe que é um problema de SQL?
John Naegle
Sem ver o plano de consulta, acho que o problema que você está enfrentando é pedir pelo created_at. Você provavelmente está fazendo uma verificação seq nessas tabelas inteiras (ah, e trazendo a tabela do projeto também). Ao executar dois desses métodos de controle único em uma tabela grande e com um banco de dados com pouca potência (os bancos de dados heroku são ajustados genericamente e relativamente com pouca energia para o valor gasto), é provável que você tenha tempos limite. Se você não está fazendo um monte de inserções nessas tabelas que você poderia fazer um índice classificadas: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel
1
Bem, sempre seria mais rápido cortar manualmente o sql (se você souber o que está fazendo). O Rails cria um sql realmente desagradável. Funciona bem, mas para ser geral, tem que ... ser geral. Dito isto, Jim provavelmente está certo. Criar indexação seria melhor. Tente executar a consulta no pgadmin (contra a sua db)
baash05

Respostas:

427

Você consegue fazer isso:

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

records_array seria o resultado da sua consulta sql em uma matriz pela qual você pode iterar.

Huy
fonte
52
Nota: records_arrayserá de tipos diferentes para diferentes adaptadores de banco de dados. Se você estiver usando PG, será uma instância PG::Result, não Array.
precisa saber é o seguinte
58
e então você precisaria chamar valuesesse PG::Resultobjeto para obter a matriz de resultados
jobwat
3
@ baash05 O que você quer dizer com "a favor de acessá-lo através da classe". - este fala sobre como acessar a tabela sem um modelo ariond-lo
Yo Ludke
1
Na verdade, não há menção de não usar um modelo no OP. Apenas como executar sql bruto. PaymentDetail.execute (sql) funciona.
baash05
20
Eu gosto ActiveRecord::Base.connection.exec_querymais porque retorna um ActiveRecords::Resultobjeto que possui métodos úteis, como .columnse .rowspara acessar cabeçalhos e valores. A matriz de hashes de .executepode ser problemática de lidar e me deu resultados redundantes quando executei uma cláusula SUM GROUP BY.
Francio Rodrigues
181

Eu sei que isso é antigo ... Mas eu estava tendo o mesmo problema hoje e encontrei uma solução:

Model.find_by_sql

Se você deseja instanciar os 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

Se você quer apenas um 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 de resultado:

select_allretorna um resultobjeto. Você pode fazer coisas mágicas com isso.

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

Fontes:

  1. ActiveRecord - Pesquisa por SQL .
  2. Ruby on Rails - resultado do registro ativo .
João Paulo Motta
fonte
9
#find_by_sqlé exatamente o que eu queria, muito obrigado.
Shelvacu
#find_by_sql é isso !!
Steven L.
28

Você pode executar uma consulta bruta usando ActiveRecord. E eu vou sugerir ir com bloco 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
fonte
Oi .. existe alguma maneira de passar parâmetros para esta consulta?
Akshay Goyal
@AkshayGoyal, você pode usar a interpolação normal de cadeias de rubi dentro do bloco. SELECT * FROM users WHERE users.id = #{ user.id }
18719 Nathan Nathan Beck
2
Enquanto você pode fazer que pode expô-lo à possibilidade de injeção de SQL
Deepak Mahakale
26

Você pode fazer o SQL direto ter uma única consulta para ambas as tabelas. Fornecerei um exemplo de consulta higienizada para evitar que as pessoas insiram variáveis ​​diretamente na própria string (perigo de injeção de SQL), mesmo que este exemplo não tenha especificado a necessidade:

@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 : como Huy disse, é uma maneira simples ActiveRecord::Base.connection.execute("..."). Outra maneira é ActiveRecord::Base.connection.exec_query('...').rows. E você pode usar instruções preparadas nativas, por exemplo, se estiver usando o postgres, a instrução preparada poderá ser feita com raw_connection, prepare e exec_prepared, conforme descrito em https://stackoverflow.com/a/13806512/178651

Você também pode colocar fragmentos de SQL brutos nas consultas relacionais do ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html e em associações, escopos etc. Você provavelmente pode construir o mesmo SQL com as consultas relacionais do ActiveRecord e pode fazer coisas interessantes ARel como Ernie menciona em http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . E, claro, existem outras ORMs, gemas, etc.

Se isso for usado muito e a adição de índices não causará outros problemas de desempenho / recursos, considere adicionar um índice no banco de dados para payment_details.created_at e para payment_errors.created_at.

Se muitos registros e nem todos os registros precisarem aparecer ao mesmo tempo, considere usar a paginação:

Se você precisar paginar, considere criar uma visualização no banco de dados chamada payment_records que combine as tabelas payment_details e payment_errors e, em seguida, tenha um modelo para a visualização (que será somente leitura). Alguns bancos de dados oferecem suporte a visualizações materializadas, o que pode ser uma boa idéia para desempenho.

Considere também as especificações de hardware ou VM no servidor Rails e no servidor DB, configuração, espaço em disco, velocidade / latência da rede / etc., Proximidade etc. E considere colocar o DB em um servidor / VM diferente do aplicativo Rails, se você não tiver, etc. .

Gary S. Weaver
fonte
Como o autor da pergunta está classificando por data, acho que a paginação não ajudaria? Eu não sou o mais profundo em SQL, mas meu entendimento é que a consulta na postagem original precisaria buscar a tabela inteira para classificá-la - somente então isso poderia limitar os resultados. Suspeito que a maior parte do plano de consulta esteja na cláusula order.
perfil completo de Jim Wrubel
@ JimWrubel esclareceu o parágrafo sobre paginação - o texto estava um pouco fora de espécie.
Gary S. Weaver
2

Eu quero trabalhar com exec_querya ActiveRecordclasse, porque ela retorna o mapeamento da consulta transformada em objeto, para que seja muito prático e produtivo interagir com os objetos quando o assunto for Raw SQL.

Exemplo:

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

e retorne esta consulta completa:

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

Para obter apenas lista de valores

p values.rows

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

Para obter apenas colunas de campos

p values.columns

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

Você também pode misturar SQL bruto com condições do ActiveRecord, por exemplo, se desejar chamar uma função em uma condição:

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