База данных Rails / Postgres — не удается получить правильные записи из БД

#sql #ruby-on-rails #rails-activerecord #postgresql-9.1

#sql #ruby-on-rails #rails-activerecord #postgresql-9.1

Вопрос:

Мне нужна помощь в устранении неполадок с запросом к базе данных для получения правильных результатов. Это приложение для фитнеса, разработанное с Rails 4.2.6, Ruby 2.2.4 и базой данных Postgres 1.9.0. Когда пользователь сохраняет новые измерения тела, я пытаюсь отобразить изменения (например, дюймы) с момента последнего измерения. Проблема заключается в том, что значения изменений для ранее сохраненных записей неправильно вычисляются в представлениях show.

Вот что я закодировал до сих пор:

контроллеры /fitness_measurements_controller.rb:

 before_action :get_second_latest_waist_measurement

def 

get_second_latest_waist_measurement     
  @waist_second_latest_measurement = @member.fitness_measurements.order(:created_at).offset(1).last.waist  || 0

end
  

fitness_measurements/show.html.erb:

 <p>
  <strong>Change in waist since last measurement:</strong>
  <%= (@fitness_measurement.waist - @waist_second_latest_measurement).round(2) %> in
</p>
  

С тестовыми данными, вот таблица, которая содержит результаты моего запроса к базе данных. Обратите внимание на значения в «Calc. Изменить столбец».

  ------------ ----------- ------------------ -------------------- 
|    Date    | Waist(in) | Calc. Change(in) | Correct Change(in) |
 ------------ ----------- ------------------ -------------------- 
| 2016-10-01 |    37.5   |        1.00      |         1.00       |
 ------------ ----------- ------------------ -------------------- 
| 2016-09-01 |    36.5   |       0.00       |         3.00       |
 ------------ ----------- ------------------ -------------------- 
| 2016-08-01 |    33.5   |       -3.00      |         0.05       |
 ------------ ----------- ------------------ -------------------- 
| 2016-07-01 |    33.0   |       -3.50      |        0.00        |
 ------------ ----------- ------------------ -------------------- 
  

Как вы видите, за исключением последней сохраненной записи (2016-10-01), значения в «Calc. Столбец «Изменить» неверен. Что-то не так с тем, как я разработал запрос.

В настоящее время мой запрос извлекает запись, которая на 1 ниже последнего сохраненного измерения, как «второе последнее измерение талии». В тестовом примере это запись, созданная в 2016-09-01. Это работает для ПОСЛЕДНЕЙ записи, сохраненной в базе данных (в данном случае 2016-10-01), но выдает неправильные результаты, когда я запрашиваю просмотр страниц ранее сохраненных записей. Например, запись, созданная в 2016-09-01, должна сравниваться с записью, созданной в 2016-08-01.

Использование «offset (1)» в запросе, по-видимому, является корнем моей проблемы, но я не знаю, как получить правильную запись. Есть ли решение для перебора записей? Я не понимаю, как это сделать в этой ситуации?

Как мне исправить запрос к базе данных, чтобы генерировать правильные значения изменений? Если есть лучший подход, пожалуйста, дайте мне знать. Спасибо!

Ответ №1:

Я не уверен в лучшем способе сделать это «рельсовым способом», но чтобы просто сделать это в Postgresql, вы можете использовать оконные функции:

 SELECT *, waist - LAG(waist) OVER (order by date) as change FROM fitness_measurements WHERE member_id = ?
  

Попробовал это на PostgreSQL 9.3.x, и это работает без проблем. Не пробовал это в Rails, но я думаю, вам нужно будет сделать что-то вроде:

 sql = "SELECT *, waist - ..."
FitnessMeasurement.find_by_sql(sql)
...
  

Комментарии:

1. спасибо за ваш ответ. Я посмотрю на это и посмотрю, смогу ли я понять, как применить это к моему проекту Rails. Нужно выяснить, как изменить код моего метода запроса.

2. необработанный SQL вызывает следующую ошибку: синтаксическая ошибка, неожиданный ‘,’ sql = SELECT *, задержка талии (талии) (ord…

3. Вы действительно оставили кавычки? Была ли это ошибка ruby или ошибка postgres? Я должен попробовать запустить SQL в «rails dbconsole», чтобы сначала заставить его работать.

4. Я запустил SQL без кавычек. Я решил принять решение @David Alridge… это чище / лучше, чем мой первоначальный подход. Тем не менее, я ценю вашу помощь.

5. Я не могу прокомментировать ответ Дэвида Олдриджа (недостаточно репутации), но я бы посмотрел на добавление .limit(1) перед . в противном случае, похоже, что он вернет все предыдущие fitness_measurements из базы данных вместо одного.

Ответ №2:

Контроллер является неправильным местом для этого кода — для более простой реализации добавьте в модель метод, который содержит сохраненные значения.

 def previous
  self.class.where(member: self.member).
             where("date < ?", self.date).
             order(date: :desc).
             take
end
  

Это дает вам предыдущий экземпляр для элемента, из которого вы затем можете считывать измерения, позволяя использовать такую логику, как:

 @measurement.waist - @measurement.previous.waist
  

Комментарии:

1. Спасибо за это решение! Этот подход более чистый и намного лучше, чем у меня был. Это отлично работает. Потрясающе.