Пример Rails raw SQL

239

Как я могу преобразовать этот код в raw sql и использовать в rails? Потому что, когда я внедряю этот код в heroku, возникает ошибка тайм-аута запроса. Я думаю, что это будет быстрее, если я использую 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
Как вы думаете, почему сырой SQL будет быстрее? Как вы знаете, это проблема SQL?
Джон Нэгл
Не видя план запроса, я бы догадался, что проблема, с которой вы столкнулись, это упорядочение с помощью create_at Вероятно, вы выполняете последовательную проверку всех этих таблиц (и вы тоже вносите таблицу проектов). Выполнив два из них в одном методе контроллера для большой таблицы и базы данных с недостаточным энергопотреблением (базы данных heroku настраиваются в общем и относительно недостаточно для потраченных вами $$), вы, скорее всего, получите тайм-ауты. Если вы не производите много вставок в эти таблицы, вы можете сделать отсортированный индекс: devcenter.heroku.com/articles/postgresql-indexes#sorted-indexes
Джим Врубель,
1
Ну, это всегда будет быстрее, чтобы вручную вырезать SQL (если вы знаете, что вы делаете). Rails делает некоторые действительно неприятные sql. Это работает хорошо, но чтобы быть общим, оно должно быть ... общим. Тем не менее, Джим, вероятно, прав ... создание индексации было бы лучше. Попробуйте выполнить свой запрос в pgadmin (против вашей
базы данных

Ответы:

428

Ты можешь сделать это:

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

records_array будет тогда результатом вашего sql-запроса в массиве, который вы можете перебрать.

Зуй
источник
52
примечание стороны: records_arrayбудет разных типов для разных адаптеров базы данных. Если вы используете PG, это будет экземпляр PG::Result, а не Array.
tybro0103
58
и тогда вам нужно будет вызвать valuesэтот PG::Resultобъект, чтобы получить массив результатов
jobwat
3
@ baash05 Что вы подразумеваете под "в пользу доступа к нему через класс". - это говорит о том, как получить доступ к столу без его модели
Йо Людке
1
На самом деле нет упоминания о неиспользовании модели в ОП. Как выполнить сырой sql. PaymentDetail.execute (sql) работает.
baash05
20
Мне нравится ActiveRecord::Base.connection.exec_queryбольше, потому что он возвращает ActiveRecords::Resultобъект, который имеет удобные методы, такие как .columnsи .rowsдля доступа к заголовкам и значениям. Массив хэшей от .executeможет быть проблематичным, и он дал мне избыточные результаты, когда я запустил предложение SUM GROUP BY.
Францио Родригес
181

Я знаю, что это старый ... Но у меня была та же проблема сегодня и нашел решение:

Model.find_by_sql

Если вы хотите создать экземпляр результатов:

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

Если вы просто хотите хэш значений:

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

Объект результата:

select_allвозвращает resultобъект. Вы можете делать магические вещи с ним.

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

Источники:

  1. ActiveRecord - поиск по SQL .
  2. Ruby on Rails - результат активной записи .
Жоао Пауло Мотта
источник
10
#find_by_sqlэто именно то, что я хотел, большое спасибо.
Шелваку
#find_by_sql это так !!
Стивен Л.
28

Вы можете выполнить необработанный запрос, используя ActiveRecord. И я предложу пойти с блоком 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)
Дипак Махакале
источник
Привет .. есть ли способ передать параметры в этот запрос?
Акшай
@AkshayGoyal вы можете использовать обычную интерполяцию рубиновой строки в блоке. SELECT * FROM users WHERE users.id = #{ user.id }
Натан Бек
2
В то время как вы можете сделать это, это может подвергнуть вас возможности внедрения SQL
Deepak
26

Вы можете сделать прямой SQL, чтобы иметь один запрос для обеих таблиц. Я приведу пример очищенного запроса, который, как мы надеемся, не позволит людям помещать переменные непосредственно в саму строку (опасность внедрения SQL), хотя в этом примере не указана необходимость в этом:

@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

Редактировать : как сказал Хай, простой способ ActiveRecord::Base.connection.execute("..."). Другой способ есть ActiveRecord::Base.connection.exec_query('...').rows. И вы можете использовать собственные подготовленные операторы, например, если вы используете postgres, подготовленные операторы можно сделать с помощью raw_connection, prepare и exec_prepared, как описано в https://stackoverflow.com/a/13806512/178651.

Вы также можете помещать необработанные фрагменты SQL в реляционные запросы ActiveRecord: http://guides.rubyonrails.org/active_record_querying.html, а также в ассоциации, области и т. Д. Скорее всего, вы можете создать тот же SQL с помощью реляционных запросов ActiveRecord и делать крутые вещи с ARel, как упоминает Эрни в http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/ . И, конечно же, есть другие ORM, драгоценные камни и т. Д.

Если это будет использоваться часто, и добавление индексов не вызовет других проблем с производительностью / ресурсами, рассмотрите возможность добавления индекса в БД для payment_details.created_at и для payment_errors.created_at.

Если нужно показать сразу много записей, а не все записи, рассмотрите возможность использования нумерации страниц:

Если вам нужно разбить на страницы, рассмотрите возможность создания представления в БД, которое сначала называется payment_records, которое объединяет таблицы payment_details и payment_errors, а затем создайте модель для представления (которое будет доступно только для чтения). Некоторые БД поддерживают материализованные представления, что может быть хорошей идеей для производительности.

Также рассмотрите спецификации оборудования или ВМ на Rails-сервере и БД-сервере, конфигурацию, дисковое пространство, скорость / задержку в сети и т. Д., Близость и т. Д. И рассмотрите возможность размещения БД на другом сервере / ВМ, чем приложение Rails, если у вас его нет, и т. Д. ,

Гари С. Уивер
источник
Поскольку спрашивающий сортирует по дате, я думаю, что разбиение на страницы не поможет? Я не очень разбираюсь в SQL, но я понимаю, что запрос в исходном посте должен был бы извлечь всю таблицу, чтобы отсортировать ее - только тогда он может ограничить результаты. Я подозреваю, что основная часть плана запроса находится в разделе заказа.
Джим Врубель
@JimWrubel разъяснил пункт о нумерации страниц - формулировка была немного не в своем роде.
Гэри С. Уивер
2

Я хочу работать с exec_queryэтим ActiveRecordклассом, потому что он возвращает отображение запроса, преобразующегося в объект, поэтому становится очень практичным и продуктивным выполнять итерации с объектами, когда объектом является Raw SQL.

Пример:

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

и вернуть этот полный запрос:

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

Получить только список значений

p values.rows

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

Получить только поля столбцов

p values.columns

["id", "name"]
Дарлан Дитрих
источник
0

Вы также можете смешивать сырой SQL с условиями ActiveRecord, например, если вы хотите вызвать функцию в условии:

my_instances = MyModel.where.not(attribute_a: nil) \
  .where('crc32(attribute_b) = ?', slot) \
  .select(:id)
tsauerwein
источник