Rails необроблений приклад SQL


239

Як я можу конвертувати цей код в необроблений sql та використовувати в рейках? Тому що, коли я розгортаю цей код у heroku, виникає помилка timeout timeout. Я думаю, що це буде швидше, якщо я використовую 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 (проти вашого db)
baash05

Відповіді:


427

Ви можете зробити це:

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 Що ви маєте на увазі під "на користь доступу до нього через клас". - це розповідає про те, як отримати доступ до таблиці без моделі ariond її
Йо Людке

1
Насправді не згадується про те, щоб не використовувати модель в ОП. Тільки як виконати raw sql. Працює PaymentDetail.execute (sql).
baash05

20
Мені подобається ActiveRecord::Base.connection.exec_queryбільше, тому що він повертає ActiveRecords::Resultоб'єкт, який має зручні методи, такі як .columnsта .rowsотримати доступ до заголовків та значень. Масив хешів від .executeможе бути важким для вирішення та дав мені зайві результати, коли я працював із пунктом SUM GROUP BY.
Francio Rodrigues

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 - Findinig від SQL .
  2. Ruby on Rails - активний результат запису .

9
#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 Mahakale

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, підготовкою та 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.create_at та для Payment_errors.create_at.

Якщо багато записів і не всі записи потрібно відобразити одразу, спробуйте скористатись сторінкою:

Якщо вам потрібно застосувати сторінку, розгляньте можливість створення представлення даних у БД, який спочатку називається Payment_records, який поєднує таблиці Payment_details та Payment_errors, а потім створіть модель для перегляду (який буде доступний лише для читання). Деякі БД підтримують матеріалізовані подання, що може бути хорошою ідеєю для продуктивності.

Також врахуйте технічні або технічні характеристики VM на сервері Rails та сервері БД, конфігурації, дисковому просторі, швидкості / затримці мережі / тощо., Близькості і т.д. .


Оскільки запитувач сортує за датою, я думаю, що сторінки не допоможуть? Я не найглибший у SQL, але, як я розумію, запит у початковій публікації повинен отримати всю таблицю, щоб сортувати її - лише тоді це могло б обмежити результати. Я підозрюю, що більша частина плану запитів знаходиться в пункті порядку.
Джим Врубель

@JimWrubel уточнив абзац про пагинацію - формулювання трохи не було.
Gary S. Weaver

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.