QA@IT
«回答へ戻る

SQLのインデントを上下で合わせてみた

887
 
 ```SQL
 SELECT "movies".*
-FROM "movies"
-WHERE "movies"."id" IN (
-  SELECT movie_id FROM "movie_categorizations"
-   INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
-   WHERE "categories"."name" = 'Fantasy'
+  FROM "movies"
+ WHERE "movies"."id" IN (
+     SELECT movie_id
+       FROM "movie_categorizations"
+      INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
+      WHERE "categories"."name" = 'Fantasy'
 ) AND "movies"."id" IN (
-  SELECT movie_id FROM "movie_categorizations"
-   INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
-   WHERE "categories"."name" = 'Middle-earth'
+     SELECT movie_id
+       FROM "movie_categorizations"
+      INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
+      WHERE "categories"."name" = 'Middle-earth'
 )
 ```

タイトルにあるJOINではありませんが、サブクエリを使って同様のことが出来そうです。サブクエリですと、別名を付けなくてすみますから、多少はシンプルになりそうです。

where(id: ...)のキー部分にActiveRecord::Relationを渡すと、IN ()を使ったサブクエリに展開されます。これで外部への影響がないWHERE句が作れますので、あとはそれをたたみ込めばよいのではないでしょうか。

%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: Category.where(name: category).joins('INNER JOIN categories_movies cm ON cm.category_id = categories.id').select('cm.movie_id'))
}
=> [#<Movie id: 1, title: "Hobbit", released_on: nil, imdb_url: nil, created_at: "2013-04-02 14:51:10", updated_at: "2013-04-02 14:51:10">]

発行されるSQLは以下のようになります。冒頭に書いたようにJOINは使っていないものの、これはこれで理屈としてはおかしくないSQLのように見えます。いかがでしょうか。

SELECT "movies".* 
  FROM "movies"
 WHERE "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Fantasy'
  ) AND "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Middle-earth'
  )

ただ、hbtmだと交差テーブルをアプリケーションコードから扱いづらいため、けっこうモリっとSQL"文字列"を書いてしまう感じですね。

コレがmovie_categorizationsを交差テーブルにしたhas_many :throughですと、交差テーブルもモデルとして遇せるために、もうちょっとキレイになります。

%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: MovieCategorization.select(:movie_id).joins(:category).where(categories: {name: category}))
}

SQLはこんなかんじです。

SELECT "movies".*
  FROM "movies"
 WHERE "movies"."id" IN (
     SELECT movie_id
       FROM "movie_categorizations"
      INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
      WHERE "categories"."name" = 'Fantasy'
) AND "movies"."id" IN (
     SELECT movie_id
       FROM "movie_categorizations"
      INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
      WHERE "categories"."name" = 'Middle-earth'
)
タイトルにあるJOINではありませんが、サブクエリを使って同様のことが出来そうです。サブクエリですと、別名を付けなくてすみますから、多少はシンプルになりそうです。

`where(id: ...)`のキー部分にActiveRecord::Relationを渡すと、`IN ()`を使ったサブクエリに展開されます。これで外部への影響がないWHERE句が作れますので、あとはそれをたたみ込めばよいのではないでしょうか。

```ruby
%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: Category.where(name: category).joins('INNER JOIN categories_movies cm ON cm.category_id = categories.id').select('cm.movie_id'))
}
=> [#<Movie id: 1, title: "Hobbit", released_on: nil, imdb_url: nil, created_at: "2013-04-02 14:51:10", updated_at: "2013-04-02 14:51:10">]
```

発行されるSQLは以下のようになります。冒頭に書いたようにJOINは使っていないものの、これはこれで理屈としてはおかしくないSQLのように見えます。いかがでしょうか。

```SQL
SELECT "movies".* 
  FROM "movies"
 WHERE "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Fantasy'
  ) AND "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Middle-earth'
  )
```

ただ、hbtmだと交差テーブルをアプリケーションコードから扱いづらいため、けっこうモリっとSQL"文字列"を書いてしまう感じですね。

コレが`movie_categorizations`を交差テーブルにした`has_many :through`ですと、交差テーブルもモデルとして遇せるために、もうちょっとキレイになります。

```ruby
%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: MovieCategorization.select(:movie_id).joins(:category).where(categories: {name: category}))
}
```

SQLはこんなかんじです。

```SQL
SELECT "movies".*
  FROM "movies"
 WHERE "movies"."id" IN (
     SELECT movie_id
       FROM "movie_categorizations"
      INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
      WHERE "categories"."name" = 'Fantasy'
) AND "movies"."id" IN (
     SELECT movie_id
       FROM "movie_categorizations"
      INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
      WHERE "categories"."name" = 'Middle-earth'
)
```

回答を投稿

タイトルにあるJOINではありませんが、サブクエリを使って同様のことが出来そうです。サブクエリですと、別名を付けなくてすみますから、多少はシンプルになりそうです。

where(id: ...)のキー部分にActiveRecord::Relationを渡すと、IN ()を使ったサブクエリに展開されます。これで外部への影響がないWHERE句が作れますので、あとはそれをたたみ込めばよいのではないでしょうか。

%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: Category.where(name: category).joins('INNER JOIN categories_movies cm ON cm.category_id = categories.id').select('cm.movie_id'))
}
=> [#<Movie id: 1, title: "Hobbit", released_on: nil, imdb_url: nil, created_at: "2013-04-02 14:51:10", updated_at: "2013-04-02 14:51:10">]

発行されるSQLは以下のようになります。冒頭に書いたようにJOINは使っていないものの、これはこれで理屈としてはおかしくないSQLのように見えます。いかがでしょうか。

SELECT "movies".* 
  FROM "movies"
 WHERE "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Fantasy'
  ) AND "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Middle-earth'
  )

ただ、hbtmだと交差テーブルをアプリケーションコードから扱いづらいため、けっこうモリっとSQL"文字列"を書いてしまう感じですね。

コレがmovie_categorizationsを交差テーブルにしたhas_many :throughですと、交差テーブルもモデルとして遇せるために、もうちょっとキレイになります。

%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: MovieCategorization.select(:movie_id).joins(:category).where(categories: {name: category}))
}

SQLはこんなかんじです。

SELECT "movies".*
FROM "movies"
WHERE "movies"."id" IN (
  SELECT movie_id FROM "movie_categorizations"
   INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
   WHERE "categories"."name" = 'Fantasy'
) AND "movies"."id" IN (
  SELECT movie_id FROM "movie_categorizations"
   INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
   WHERE "categories"."name" = 'Middle-earth'
)
タイトルにあるJOINではありませんが、サブクエリを使って同様のことが出来そうです。サブクエリですと、別名を付けなくてすみますから、多少はシンプルになりそうです。

`where(id: ...)`のキー部分にActiveRecord::Relationを渡すと、`IN ()`を使ったサブクエリに展開されます。これで外部への影響がないWHERE句が作れますので、あとはそれをたたみ込めばよいのではないでしょうか。

```ruby
%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: Category.where(name: category).joins('INNER JOIN categories_movies cm ON cm.category_id = categories.id').select('cm.movie_id'))
}
=> [#<Movie id: 1, title: "Hobbit", released_on: nil, imdb_url: nil, created_at: "2013-04-02 14:51:10", updated_at: "2013-04-02 14:51:10">]
```

発行されるSQLは以下のようになります。冒頭に書いたようにJOINは使っていないものの、これはこれで理屈としてはおかしくないSQLのように見えます。いかがでしょうか。

```SQL
SELECT "movies".* 
  FROM "movies"
 WHERE "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Fantasy'
  ) AND "movies"."id" IN (
     SELECT cm.movie_id FROM "categories"
     INNER JOIN categories_movies cm ON cm.category_id = categories.id
     WHERE "categories"."name" = 'Middle-earth'
  )
```

ただ、hbtmだと交差テーブルをアプリケーションコードから扱いづらいため、けっこうモリっとSQL"文字列"を書いてしまう感じですね。

コレが`movie_categorizations`を交差テーブルにした`has_many :through`ですと、交差テーブルもモデルとして遇せるために、もうちょっとキレイになります。

```ruby
%w[Fantasy Middle-earth].inject(Movie.scoped) {|rel, category|
  rel.where(id: MovieCategorization.select(:movie_id).joins(:category).where(categories: {name: category}))
}
```

SQLはこんなかんじです。

```SQL
SELECT "movies".*
FROM "movies"
WHERE "movies"."id" IN (
  SELECT movie_id FROM "movie_categorizations"
   INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
   WHERE "categories"."name" = 'Fantasy'
) AND "movies"."id" IN (
  SELECT movie_id FROM "movie_categorizations"
   INNER JOIN "categories" ON "categories"."id" = "movie_categorizations"."category_id"
   WHERE "categories"."name" = 'Middle-earth'
)
```