Во время работы над Best Tech Videos с Alexey Kovyrin, мы столкнулись с проблемой фильтрации видео по категории, с выбором категорий видео в том же запросе. Решилось быстро, но в коде появилась маленькая хитрость с запросом.
Предположим, мы разрабатываем приложение для блога. У нас есть модели Post и Category, при этом заметки (posts) могут иметь одну или несколько категорий (отношение многие-ко-многим):
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
Нам нужно отобразить заметки на странице вместе с категориями, к которым они относятся. Простейший способ сделать это — выполнить find с параметром :include:
И соответствующий хелпер:
post.categories.map(&:name).join(' ')
end
Но что если нам нужно отфильтровать заметки по категории? Вот пример:
:include => :categories,
:conditions => ['categories.id = ?', category_id]
Код работает, но есть одна небольшая неприятность: вы получаете не все категории! Можно исправить хелпер:
# перегружаем категории
post.categories(true).map(&:name).join(' ')
end
В этом случае для всех заметок их категории будут загружены в отдельных запросах. Это не слишком хорошо, потому я предлагаю использовать подзапрос:
: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]
Теперь заметки будут отфильтрованы по категории, и все категории заметок будут загружены в одном запросе без проблем. Есть другие идеи?
Русский
English
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, для простоты, именно то, что я назвал “ассемблером”)
$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:
@video = Video.find :first
# Загружаем категории отдельным запросом
@video.categories
# Здесь категории юзаются уже загруженные
# дополнительного запроса нет
@video.categories
# Здесь форсируем загрузку категорий
# повторый запрос к базе
@video.categories(true)
Ответил?
Мог бы просто ответить, что кеширование есть :). К чему какие подробности.