2013-11-08 2 views
0

У меня есть две модели ActiveRecord: Post и Vote. Я хочу сделать простой запрос:Подзапрос Rails уменьшает количество необработанного SQL

SELECT *, 
    (SELECT COUNT(*) 
    FROM votes 
    WHERE votes.id = posts.id) AS vote_count 
FROM posts 

Мне интересно, что это лучший способ сделать это в ActiveRecord DSL. Моя цель - свести к минимуму количество SQL, которое я должен написать.

я могу сделать Post.select("COUNT(*) from votes where votes.id = posts.id as vote_count")

Две проблемы с этим:

  1. Сырье SQL. В любом случае, чтобы написать это в DSL?
  2. Это возвращает только атрибут vote_count, а не «*» + vote_count. Я могу добавить .select("*"), но я буду повторять это каждый раз. Есть ли гораздо лучший/суровый способ сделать это?

Благодаря

+0

Вам действительно нужен этот подзапрос? Он выполняется один раз для каждой строки и вызывает огромную производительность. Разве вы не предпочитаете JOIN? 'SELECT posts. *, COUNT (votes.id) FROM posts LEFT JOIN голосов ON posts.id = votes.id GROUP BY posts.id, posts.title' – skalee

+0

@skalee Если вы не используете старую версию SQL db из 90-х годов, он достаточно умен, чтобы распознавать и оптимизировать запрос. В моих тестах, делая это через JOIN или подзапрос, вы принимаете равное количество времени, за исключением того, что подзапрос является более понятным/читаемым (но это только мое мнение). Несмотря на это, я считаю, что при использовании подзапроса нет проблем с производительностью. – 0xSina

+0

IMO в этом конкретном случае соединение более читаемо, однако я понимаю, что ваш вопрос более общий и применим и к другим запросам. – skalee

ответ

2

Ну, если вы хотите уменьшить количество SQL, вы можете разбить этот запрос на меньшие два конца, выполнить их отдельно. Так, например, подсчет голоса части могут быть извлечены для запроса:

SELECT votes.id, COUNT(*) FROM votes GROUP BY votes.id; 

, которые вы можете написать с методами ActiveRecord как:

Vote.group(:id).count 

Вы можете сохранить результат для последующего использования и доступа к нему непосредственно из Разместить объявление модель, например, вы можете определить #votes_count как метод:

class Post 
    def votes_count 
    @@votes_count_cache ||= Vote.group(:id).count 
    @@votes_count_cache[id] || 0 
    end 
end 

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

Но я настоятельно рекомендую вам рассмотреть еще один подход.

Я считаю, что писать сложные запросы, подобные вашим, с помощью методов ActiveRecord - даже если это было возможно - или расщепление запросов на два, как я предлагал ранее, являются плохими идеями. Они приводят к чрезвычайно загроможденному коду, гораздо менее понятному, чем исходный SQL. Вместо этого я предлагаю ввести объекты запроса. IMO нет ничего плохого в использовании сырого, сложного SQL, когда он скрыт за хорошим интерфейсом. См .: P of EAA М. Фоулера и сообщение Брынари по телефону Code Climate Blog.

0

Как насчет делать это без какого-либо дополнительного SQL на всех - рассмотреть возможность использования рельсов counter_cache функции.

Если добавить столбец целое votes_count к posts таблице, вы можете получить Rails автоматически увеличивать и декремента, что счетчик за счет изменения belongs_to декларации в Голосуйте на:

belongs_to :post, counter_cache: true 

Rails будет держать каждое сообщение обновляется с количеством голосов, которое у него есть. Таким образом, счет уже рассчитан, и никакой дополнительный запрос не требуется.

+0

Спасибо, я знаю об этом, однако я не могу изменить схему базы данных (к сожалению). Даже если бы я мог, есть некоторые ограничения, которые я хочу применить в предложении where, в зависимости от запроса, чтобы это не сработало. (Я не указывал в вопросе, чтобы это было просто) – 0xSina

0

Возможно, вы можете создать представление mysql и просто сопоставить его с новой моделью AR. Он работает аналогично таблице, вам просто нужно указать с помощью set_table_name «your_view_name» .... возможно, на уровне БД он будет работать быстрее и будет автоматически пересчитываться.

0

Просто наткнулся на postgres_ext драгоценный камень, который adds support для общих табличных выражений в Arel и ActiveRecord, который именно вы просили. Gem не для SQLite, но, возможно, некоторые части могут быть извлечены или служить примерами.

Смежные вопросы