Использование подзапросов во избежание лишних обращений к БД в Rails

Apr 12
2007 08:12 (Программирование, MySQL, Ruby on Rails) · English (6,146 views)

Во время работы над Best Tech Videos с Alexey Kovyrin, мы столкнулись с проблемой фильтрации видео по категории, с выбором категорий видео в том же запросе. Решилось быстро, но в коде появилась маленькая хитрость с запросом.

Предположим, мы разрабатываем приложение для блога. У нас есть модели Post и Category, при этом заметки (posts) могут иметь одну или несколько категорий (отношение многие-ко-многим):

class Post < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

class Category < ActiveRecord::Base
  has_and_belongs_to_many :posts
end

Нам нужно отобразить заметки на странице вместе с категориями, к которым они относятся. Простейший способ сделать это — выполнить find с параметром :include:

Post.find :all, :include => :categories

И соответствующий хелпер:

def show_categories(post)
  post.categories.map(&:name).join(' ')
end

Но что если нам нужно отфильтровать заметки по категории? Вот пример:

Post.find :all,
          :include => :categories,
          :conditions => ['categories.id = ?', category_id]

Код работает, но есть одна небольшая неприятность: вы получаете не все категории! Можно исправить хелпер:

def show_categories(post)
  # перегружаем категории
  post.categories(true).map(&:name).join(' ')
end

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

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]

Теперь заметки будут отфильтрованы по категории, и все категории заметок будут загружены в одном запросе без проблем. Есть другие идеи?

8 отзывов на 'Использование подзапросов во избежание лишних обращений к БД в Rails'

Подписаться на комментарии по RSS или TrackBack на 'Использование подзапросов во избежание лишних обращений к БД в Rails'.

1
Julik
сказал 13.04.2007 в 17.02

Sure thing.

http://bugs.mysql.com/bug.php?id=9021

2
Alex
сказал 16.05.2007 в 11.49

Вопрос чисто из любопытства :)
Какой язык используется в этом примере и почему нельзя было использовать классический SQL запрос с оператором JOIN?

3
сказал 16.05.2007 в 12.04

Этот таинственный язык — Ruby. Данная заметка посвящена использованию Ruby on Rails (фрейморка), в частности Active Record (ORM этого фреймворка).

Твой воспрос не особо корректен. Это то же самое, что спросить “Зачем использовать ПХП и почему нельзя использовать классический ассемблер?” (без обид, ничего личного) :-)

Active Record берет на себя все сложности (и рутину) составления типовых SQL-запросов, а также конструирует объекты (сущности) вашего приложения из результатов запроса. В общем много всяких вкусностей, настоятельно рекомендую хотя бы посмотреть (скринкаст можно найти на официальном сайте Ruby on Rails) или почитать что-нибудь об этом фреймворке, даже если не собираетесь его использовать.

4
Alex
сказал 16.05.2007 в 13.56

Обижаться конечно же не стану, не для того вопрос задал.
Обязательно попытаюсь узнать что такое Ruby и с чем эти вкусности едят :).
Ну а по поводу сложности и рутины - не зная как устроены эти самые классы (их ведь писали люди для упрощения каких-то своих задач) не сможешь понять как они работают. Я вот смотрю на эти запросы и со своим приверженством чтоли к языкам так-называемого “среднего уровня”, не понимаю чего хотел программист получить, без обид :).
Приятно конечно осознать, что SQL у кого-то ассоциируется с ассемблером, но все же SQL также написан для людей (не как ассемблер - словесное представление машинных кодов) и надеюсь, что Active Record (как всеже дополнительное звено) не является помехой для конструирования гибких запросов к базе данных.
Не сочти за критику, это лишь мое первое представление, обещаю ознакомиться с Active Record.

5
сказал 16.05.2007 в 14.18

Честно говоря, Вы можете не осознавать всю работу ORM фреймворка для того, чтобы работать с ним. К тому же вопрос о гибких запросов к базе данных должен подыматься только в случае необходимости. В большинстве случаем мы делаем большую ошибку, начиная думать об оптимизации.

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

Кстати, как с кешированием выборок в RoR?

6
сказал 16.05.2007 в 14.23

Ты все переставил местами :-) SQL ассоциируется с ассемблером, как Ruby ассоциируется с PHP. Эдакая пропорция. Может немного преувеличено, но смысл такой.

Я не вижу преимущества у следующего кода (PHP, для простоты, именно то, что я назвал “ассемблером”)

$rs = $db->fetch_all('SELECT v.id AS v_id, v.title AS v_title, c.id AS c_id, c.name AS c_name FROM videos v JOIN categories_videos cv ON v.id = cv.video_id JOIN categories c ON c.id = cv.category_id LIMIT 20')
$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']);
}

(код я не проверял, скорее всего не работает) перед кодом на руби с использованием рельсов:

Video.find :all, :include => :categories, :limit => 20

Да, не надо меня пинать на тему, что под пхп есть куча фреймворком и мой код не имеет смысла. Просто попробуйте руби и вы не сможете остановиться.

ЗЫ. Приведенный код имеет несколько проблем. Во-первых, я выбрал не все поля (у меня в таблице их десяток), во-вторых использовал LIMIT в SQL, что нельзя использовать в других СУБД, потому еще надо долепить условий. Ну и никаких WHERE тоже не приводил, потому что надоело писать.

7
сказал 16.05.2007 в 14.34

2Roman:

# Выбираем видео
@video = Video.find :first
# Загружаем категории отдельным запросом
@video.categories
# Здесь категории юзаются уже загруженные
# дополнительного запроса нет
@video.categories
# Здесь форсируем загрузку категорий
# повторый запрос к базе
@video.categories(true)

Ответил?

8
сказал 16.05.2007 в 14.43

Мог бы просто ответить, что кеширование есть :). К чему какие подробности.

Оставить отзыв

Вы можете использовать простые теги форматирования HTML (вроде <a>, <ul> and others). Чтобы вставить пример код, используйте <code lang="php">$a = "hello";</code> (поддерживаемые языки: ruby, php, yaml, html, csharp, javascript). Также Вы можете использовать <code>$a = "hello";</code>, синтаксис не будет подсвечен. Если вы не хотите использовать тег <code>, замените символ < на &lt;.

Отправить

 
Copyright © 2005 - 2008, Dmytro Shteflyuk