Самый последний внутренний запрос в activerecord - PullRequest
4 голосов
/ 27 мая 2020

У меня есть таблица users:

id  first_name
--------------
1   Bill
2   Denise

кто читает несколько books:

id  book                       user_id  read_at
---------------------------------------------------
1   Garry Potter               1        2020-1-1
2   Lord of the wrist watch    2        2020-1-1
3   90 Shades of navy          2        2020-1-2

Я хочу создать область видимости в моей модели book, которая получает мне последняя книга для каждого пользователя. Есть много примеров того, как сделать это с чистым SQL, проблема, с которой я сталкиваюсь, заключается в создании гибкой области действия, которую можно использовать с подсчетом, внутренним запросом или любым другим способом, которым вы обычно используете область.

Пока что в моей модели book есть:

def self.most_recent
  inner_query = select('DISTINCT ON (user_id) *').order(:user_id, read_at: :desc)
  select('*').from(inner_query, :inner_query).order('inner_query.id')
end

Что очень близко к тому, что я хочу. Он работает со счетом, но не в более сложной ситуации.

Например, если я хочу получить список пользователей, где их последняя книга называется «Гарри Поттер», я пробую что-то вроде этого:

User.where(id: Book.most_recent.where(book: 'Garry Potter').select(:user_id))

Активная запись сбивается с толку и генерирует это SQL:

SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT "user_id", * FROM (SELECT "books"."user_id", DISTINCT ON (user_id) * FROM "books" ORDER BY "books"."user_id" ASC, "books"."read_at" DESC) inner_query WHERE "books"."book" = "Garry Potter" ORDER BY inner_query.id)

Что дает следующую ошибку:

ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR:  syntax error at or near "DISTINCT"

Есть ли элегантный способ добиться этого?

1 Ответ

1 голос
/ 28 мая 2020

Вы можете попробовать изменить метод most_recent, вернув запрос с where:

def self.most_recent
  # Select only the ids of the most recent books
  inner_query = select('DISTINCT ON (user_id) books.id').order(:user_id, read_at: :desc)

  # Return a where query like Book.where(id: <ids in the result set of the query above>)
  where(id: inner_query)
end

# You should now be able to perform the following query
User.where(
  id: Book.most_recent.where(book: 'Garry Potter').select(:user_id)
)
...