Przykład surowego kodu SQL

239

Jak mogę przekonwertować ten kod na raw sql i używać go w szynach? Ponieważ kiedy wdrażam ten kod w heroku, pojawia się błąd przekroczenia limitu czasu żądania. Myślę, że będzie to szybsze, jeśli użyję 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)
Johnny Cash
źródło
3
Jak myślisz, dlaczego surowy SQL będzie szybszy? Skąd wiesz, że jest to problem SQL?
John Naegle
Nie widząc planu zapytań, zgaduję, że twoim problemem jest zamawianie przez create_at. Prawdopodobnie wykonujesz skanowanie sekwencyjne na tych całych tabelach (no i przynosząc również tabelę projektu). Wykonując dwie z tych metod jednorazowego kontrolera na dużym stole i słabej DB (bazy danych heroku są ogólnie dostrojone i względnie słabe w stosunku do wydawanych $$), prawdopodobnie dostaniesz limity czasu. Jeśli nie robisz dużo wstawek do tych tabel, możesz zrobić posortowany indeks: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Jim Wrubel
1
Cóż, zawsze byłoby szybciej ręcznie wyciąć sql (jeśli wiesz, co robisz). Szyny tworzą naprawdę paskudne sql. Działa dobrze, ale aby być ogólnym, musi być ... ogólny. To powiedziawszy, Jim prawdopodobnie ma rację. Lepiej byłoby stworzyć indeksowanie. Spróbuj uruchomić zapytanie w pgadmin (przeciwko db)
baash05,

Odpowiedzi:

427

Możesz to zrobić:

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

records_array byłby wówczas wynikiem zapytania SQL w tablicy, którą można iterować.

Huy
źródło
52
uwaga dodatkowa: records_arraybędą różnych typów dla różnych adapterów bazy danych. Jeśli używasz PG, będzie to instancja PG::Result, a nie Array.
tybro0103
58
a następnie musisz wywołać valuesten PG::Resultobiekt, aby uzyskać tablicę wyników
jobwat
3
@ baash05 Co rozumiesz przez „na rzecz dostępu do niego za pośrednictwem klasy”. - mówi o tym, jak uzyskać dostęp do stołu bez modelu.
Yo Ludke
1
Właściwie nie ma wzmianki o nieużywaniu modelu w OP. Tylko jak wykonać raw sql. PaymentDetail.execute (sql) działa.
baash05
20
Lubię ActiveRecord::Base.connection.exec_querybardziej, ponieważ zwraca ActiveRecords::Resultobiekt, który ma przydatne metody, takie jak .columnsi .rowsdostęp do nagłówków i wartości. Szereg skrótów z .executemoże być kłopotliwy i dać mi zbędne wyniki, gdy uruchomiłem klauzulę SUM GROUP BY.
Francio Rodrigues,
181

Wiem, że to stare ... Ale miałem dzisiaj ten sam problem i znalazłem rozwiązanie:

Model.find_by_sql

Jeśli chcesz utworzyć instancję wyników:

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

Jeśli chcesz tylko skrótu wartości:

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

Obiekt wynikowy:

select_allzwraca resultobiekt. Możesz z tym robić magiczne rzeczy.

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

Źródła:

  1. ActiveRecord - Findinig by SQL .
  2. Ruby on Rails - wynik aktywnego rekordu .
João Paulo Motta
źródło
9
#find_by_sqljest dokładnie tym, czego chciałem, dziękuję bardzo.
Shelvacu,
#find_by_sql jest to !!
Steven L.
28

Możesz wykonać surowe zapytanie używając ActiveRecord. I zasugeruję, aby przejść do bloku 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
źródło
Cześć .. czy jest jakiś sposób na przekazanie parametrów do tego zapytania?
Akshay Goyal
@AkshayGoyal możesz użyć normalnej interpolacji sznurka rubinu w bloku. SELECT * FROM users WHERE users.id = #{ user.id }
Nathan Beck
2
Chociaż możesz to zrobić, może narazić Cię na możliwość wstrzyknięcia SQL
Deepak Mahakale
26

Możesz wykonać bezpośredni SQL, aby mieć jedno zapytanie dla obu tabel. Podam przykład zdezynfekowanego zapytania, aby, mam nadzieję, powstrzymać ludzi przed wprowadzaniem zmiennych bezpośrednio do samego łańcucha (niebezpieczeństwo wstrzyknięcia SQL), nawet jeśli ten przykład nie określa potrzeby:

@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

Edycja : jak powiedział Huy, prosty sposób jest ActiveRecord::Base.connection.execute("..."). Innym sposobem jest ActiveRecord::Base.connection.exec_query('...').rows. I możesz używać natywnych przygotowanych instrukcji, np. Jeśli używasz postgres, przygotowaną instrukcję można wykonać za pomocą raw_connection, przygotuj i exec_prepared zgodnie z opisem w https://stackoverflow.com/a/13806512/178651

Możesz również umieszczać surowe fragmenty SQL w zapytaniach relacyjnych ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html oraz w powiązaniach, zakresach itp. Prawdopodobnie możesz zbudować ten sam SQL za pomocą zapytań relacyjnych ActiveRecord i możesz robić fajne rzeczy z ARel, jak wspomina Ernie w http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . I oczywiście są inne ORM, klejnoty itp.

Jeśli będzie to często używane, a dodanie indeksów nie spowoduje innych problemów z wydajnością / zasobami, rozważ dodanie indeksu w DB dla payment_details.created_at i for payment_errors.created_at.

Jeśli wiele rekordów, a nie wszystkie, muszą pojawić się na raz, rozważ użycie podziału na strony:

Jeśli chcesz dokonać paginacji, rozważ utworzenie w DB najpierw widoku o nazwie payment_records, który łączy tabele payment_details i payment_errors, a następnie przygotuj model widoku (który będzie tylko do odczytu). Niektóre bazy danych obsługują zmaterializowane widoki, co może być dobrym pomysłem na wydajność.

Weź również pod uwagę specyfikację sprzętu lub maszyny wirtualnej na serwerze Rails i serwerze DB, konfiguracji, przestrzeni dyskowej, szybkości / opóźnieniu sieci / itp., Bliskości itp. I rozważ umieszczenie DB na innym serwerze / maszynie wirtualnej niż aplikacja Rails, jeśli nie masz, itp. .

Gary S. Weaver
źródło
Ponieważ pytający sortuje według daty, myślę, że paginacja nie pomogłaby? Nie jestem najgłębszy w SQL, ale rozumiem, że zapytanie w oryginalnym poście musiałoby pobrać całą tabelę, aby ją posortować - tylko wtedy mogłoby to ograniczyć wyniki. Podejrzewam, że większość planu zapytań znajduje się w klauzuli kolejności.
Jim Wrubel
@JimWrubel wyjaśnił paragraf dotyczący paginacji - sformułowanie było trochę nietypowe.
Gary S. Weaver
2

Chcę pracować z exec_queryz ActiveRecordklasy, ponieważ zwraca mapowanie zapytania przekształcania obiektu, więc robi się bardzo praktyczne i wydajne iteracyjne z obiektami, gdy obiekt jest Raw SQL.

Przykład:

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

i zwróć to pełne zapytanie:

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

Aby uzyskać tylko listę wartości

p values.rows

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

Aby uzyskać tylko kolumny pól

p values.columns

["id", "name"]
Darlan Dieterich
źródło
0

Możesz także mieszać surowy SQL z warunkami ActiveRecord, na przykład jeśli chcesz wywołać funkcję w stanie:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
tsauerwein
źródło