LoginSignup
5
1

More than 5 years have passed since last update.

[Help募集]RailsでのLEFT OUTER JOINの記法

Last updated at Posted at 2016-03-05

概要

Railsで実装している際LEFT OUTER JOINを色々試したので、まとめました。
(※2016/03/07 その6追加しました)

ただ、やりたい事自体は実現できていません。
有識者の方、コメントお願いします。

やりたいこと

Facebookのような投稿に対しユーザーが「いいね」を押すようなWEBアプリを作成しています。

テーブルとmodelは以下の3つで構成しています。

  • Post id[PK], user_id, text
  • User id[PK], name
  • Good id[PK], post_id, user_id

Facebookみたいに、POSTの一覧ページにて、自分が「いいね」を押しているか判るようにしたいので、

以下のようなクエリを発行したい。
かつ、
eager_loadで実行することで、クエリ発行数を減らしたい。

という条件があります。

SELECT * FROM posts
LEFT OUTER JOIN goods
ON goods.post_id = posts.id AND goods.user_id = 3

※goods.user_id = 3 のuser_idの指定は任意の値を与えたい。

その1 .joins()を利用する。

user_id = 3
@posts = Post
.joins("LEFT OUTER JOIN goods ON posts.id = goods.post_id AND goods.user_id = #{user_id}")

一応、クエリ自体は発行できました。
ただ、キャッシュされていないので、N+1問題が残ります。

その2 .joins() + arel を利用する。

user_id = 3
good = Good.arel_table
good_condition = post
  .join(good, Arel::Nodes::OuterJoin)
  .on(post[:id].eq(good[:post_id]), good[:user_id].eq(user_id))
  .join_sources
@posts = Post.joins(good_condition)

その1の改良版で、ArelというActiveRecordの基盤?を使っているので、DBの方言に起因する実行問題がなくなります。
ただ、こちらもキャッシュされないので、N+1問題が残ります。

その3 eager_load + where を利用する。

user_id = 3
@posts = Post.eager_load(:goods)
.where(good[:user_id].eq(user_id))

eager_loadを使っているので、クエリは1回だけです。
ただ発行されるクエリが

SELECT * FROM posts
LEFT OUTER JOIN goods
ON goods.post_id = posts.id
WHERE goods.user_id = 3

と、絞り込まれてINNER JOINしたみたいになってます。

その4 eager_load + active_record の アソシエーションでのラムダ式 を利用する。

今までは、単純なhas_manyやbelong_toしか利用してませんでしたが、
アソシエーションでラムダ式を利用してみました。

@posts = Post.eager_load(:goods)
has_many :goods, -> { where(user_id: 3) }

とりあえず、user_idは3でべた書き。

SELECT * FROM posts
LEFT OUTER JOIN goods
ON goods.post_id = posts.id
AND goods.user_id = 3

期待通りのクエリが発行されて、かつeager_loadになっている。
ただし、user_idは固定なのでダメ

その5 eager_load + active_record の アソシエーションでのラムダ式 + 変数 を利用する。

基本的にやってる事はその4と一緒です。

@posts = Post.eager_load(:goods)
has_many :goods, ->(post_user_id) { where(user_id: post_user_id) }

で、これどうやって、post_user_idを渡すの?
やり方全然判らないんですけど…

ちなみに実行すると

SELECT * FROM posts
LEFT OUTER JOIN goods
ON goods.post_id = posts.id
AND goods.user_id = NULL

上記のクエリが発行されます。

その6 eager_load + joinsのstring型を利用する。

@posts = Post.eager_load(:goods).joins("AND goods.user_id = #{user_id}")

このやり方だと、プレイスホルダは使えてませんが、一応要件は満たしてます。
ただ、やり方が裏技っぽい気がするので、将来的には少し心配です。

あと、joinsが1テーブルしか使えません。
以下のように複数テーブルに対して使うと、うまく動きません。

@posts = Post.eager_load(:goods).joins("AND goods.user_id = #{user_id}")
       .eager_load(:goods2).joins("AND goods2.user_id = #{user_id}")

HELP募集中

というわけで、色々試しましたが、どれも要件を満たしません。
どなたか、良い解決方法がありましたら、ご教示お願いします。
preloadとかの方が良いのかなぁ。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1