Во время работы над Best Tech Videos с Alexey Kovyrin, мы столкнулись с проблемой фильтрации видео по категории, с выбором категорий видео в том же запросе. Решилось быстро, но в коде появилась маленькая хитрость с запросом.
Предположим, мы разрабатываем приложение для блога. У нас есть модели Post и Category, при этом заметки (posts) могут иметь одну или несколько категорий (отношение многие-ко-многим):
1 2 3 4 5 6 7 | class Post < ActiveRecord::Base has_and_belongs_to_many :categories end class Category < ActiveRecord::Base has_and_belongs_to_many :posts end |
Нам нужно отобразить заметки на странице вместе с категориями, к которым они относятся. Простейший способ сделать это — выполнить find с параметром :include:
1 | Post.find :all, :include => :categories |
И соответствующий хелпер:
1 2 3 | def show_categories(post) post.categories.map(&:name).join(' ') end |
Но что если нам нужно отфильтровать заметки по категории? Вот пример:
1 2 3 | Post.find :all, :include => :categories, :conditions => ['categories.id = ?', category_id] |
Код работает, но есть одна небольшая неприятность: вы получаете не все категории! Можно исправить хелпер:
1 2 3 4 | def show_categories(post) # перегружаем категории post.categories(true).map(&:name).join(' ') end |
В этом случае для всех заметок их категории будут загружены в отдельных запросах. Это не слишком хорошо, потому я предлагаю использовать подзапрос:
1 2 3 | Post.find :all, :include => :categories, :conditions => ['EXISTS (SELECT tmp_cp.category_id FROM categories_posts tmp_cp WHERE posts.id = tmp_cp.post_id AND tmp_cp.category_id = ?)', category_id] |
Теперь заметки будут отфильтрованы по категории, и все категории заметок будут загружены в одном запросе без проблем. Есть другие идеи?

Sure thing.
http://bugs.mysql.com/bug.php?id=9021
Вопрос чисто из любопытства :)
Какой язык используется в этом примере и почему нельзя было использовать классический SQL запрос с оператором JOIN?
Этот таинственный язык — Ruby. Данная заметка посвящена использованию Ruby on Rails (фрейморка), в частности Active Record (ORM этого фреймворка).
Твой воспрос не особо корректен. Это то же самое, что спросить “Зачем использовать ПХП и почему нельзя использовать классический ассемблер?” (без обид, ничего личного) :-)
Active Record берет на себя все сложности (и рутину) составления типовых SQL-запросов, а также конструирует объекты (сущности) вашего приложения из результатов запроса. В общем много всяких вкусностей, настоятельно рекомендую хотя бы посмотреть (скринкаст можно найти на официальном сайте Ruby on Rails) или почитать что-нибудь об этом фреймворке, даже если не собираетесь его использовать.
Обижаться конечно же не стану, не для того вопрос задал.
Обязательно попытаюсь узнать что такое Ruby и с чем эти вкусности едят :).
Ну а по поводу сложности и рутины – не зная как устроены эти самые классы (их ведь писали люди для упрощения каких-то своих задач) не сможешь понять как они работают. Я вот смотрю на эти запросы и со своим приверженством чтоли к языкам так-называемого “среднего уровня”, не понимаю чего хотел программист получить, без обид :).
Приятно конечно осознать, что SQL у кого-то ассоциируется с ассемблером, но все же SQL также написан для людей (не как ассемблер – словесное представление машинных кодов) и надеюсь, что Active Record (как всеже дополнительное звено) не является помехой для конструирования гибких запросов к базе данных.
Не сочти за критику, это лишь мое первое представление, обещаю ознакомиться с Active Record.
Честно говоря, Вы можете не осознавать всю работу ORM фреймворка для того, чтобы работать с ним. К тому же вопрос о гибких запросов к базе данных должен подыматься только в случае необходимости. В большинстве случаем мы делаем большую ошибку, начиная думать об оптимизации.
Только после нескольких предварительных релизов и более менее установлении архитектуры, можно думать об оптимизации SQL запросов. А так получается – еще ничего не сделали, зато уже хотим оптимизировать.
Кстати, как с кешированием выборок в RoR?
Ты все переставил местами :-) SQL ассоциируется с ассемблером, как Ruby ассоциируется с PHP. Эдакая пропорция. Может немного преувеличено, но смысл такой.
Я не вижу преимущества у следующего кода (PHP, для простоты, именно то, что я назвал “ассемблером”)
2
3
4
5
6
7
8
9
10
11
$old_post_id = 0;
$posts = array();
foreach ($rs as $row) {
if ($old_post_id != $row['v_id']) {
$post = array('id' => $row['v_id'], 'title' => $row['v_title'], 'categories' => array());
$posts[] = $post;
$old_post_id = $post['id'];
}
$post['categories'][] = array('id' => $row['c_id'], 'name' => $row['c_name']);
}
(код я не проверял, скорее всего не работает) перед кодом на руби с использованием рельсов:
Да, не надо меня пинать на тему, что под пхп есть куча фреймворком и мой код не имеет смысла. Просто попробуйте руби и вы не сможете остановиться.
ЗЫ. Приведенный код имеет несколько проблем. Во-первых, я выбрал не все поля (у меня в таблице их десяток), во-вторых использовал LIMIT в SQL, что нельзя использовать в других СУБД, потому еще надо долепить условий. Ну и никаких WHERE тоже не приводил, потому что надоело писать.
2Roman:
2
3
4
5
6
7
8
9
10
@video = Video.find :first
# Загружаем категории отдельным запросом
@video.categories
# Здесь категории юзаются уже загруженные
# дополнительного запроса нет
@video.categories
# Здесь форсируем загрузку категорий
# повторый запрос к базе
@video.categories(true)
Ответил?
Мог бы просто ответить, что кеширование есть :). К чему какие подробности.