掲題の願望を満たすために、調べていましたら、学びがありましたので、「結論」と、
その**「過程からの学んだ4つのTIPS」**をお届けしたいと思います。
結論
explain
使う。なので、もう興味を失った方はもう以下お読み頂く必要はないかと思います。。
SQLをスッキリみたいというささやかな願望
rais c
使います。Active Record 使います。発行されるSQLを見たいです。
irb(main):001:0> User.first
User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> #<User id: 1, name: "name1", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08">
「見れてるやん。」 => SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
...いや、もっと長いやつ。
irb(main):028:0> Post.all
Post Load (0.3ms) SELECT "posts".* FROM "posts"
=> #<ActiveRecord::Relation [#<Post id: 1, title: "title1_of_user1", created_at: "2016-10-08 11:20:08",updated_at: "2016-10-08 11:20:08", user_id: 1>,
#<Post id: 2, title: "title2_of_user1", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 1>,
#<Post id: 3, title: "title3_of_user1", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 1>,
#<Post id: 4, title: "title4_of_user1", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 1>,
#<Post id: 5, title: "title5_of_user1", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 1>,
#<Post id: 6, title: "title1_of_user2", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 2>,
#<Post id: 7, title: "title2_of_user2", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 2>,
#<Post id: 8, title: "title3_of_user2", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 2>,
#<Post id: 9, title: "title4_of_user2", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 2>,
#<Post id: 10, title: "title5_of_user2", created_at: "2016-10-08 11:20:08", updated_at: "2016-10-08 11:20:08", user_id: 2>, ...]>
irb(main):029:0>
「見れてるやん。。」 => SELECT "posts".* FROM "posts"
....いや、そうですけど、ちょっと見づらいです。このサンプルは、テーブルのカラム数もデータ数も少ないからいいですけど、大きくなってくるととても見づらいんです。いちいちターミナルでスクロールして上に行って調べるのも面倒くさいなぁと思いまして。
ということで、いろいろ調べてみました。
その1: 最初はセミコロンとの出会いでした
irb(main):034:0> User.first; nil
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> nil
このように末尾にセミコロンをつけると、出力を操作することができます。これで、出力結果を nil とかにしちゃえば、大量に出力されなくてスッキリみれるんじゃないか!
ところが、
irb(main):035:0> User.all; nil
=> nil
おい、どうした! (よくわからないけど、出てこない....) ということで、駄目でした。
その2: 次に出会ったのは to_sql
irb(main):047:0* User.all.to_sql
=> "SELECT \"users\".* FROM \"users\""
出た! ...けど、バックスラッシュが、とても邪魔。。。。
そこで、puts を使う。
irb(main):048:0> puts User.all.to_sql
SELECT "users".* FROM "users"
=> nil
いいじゃないですか!
ところが、
irb(main):050:0> puts Post.preload(:user).to_sql
SELECT "posts".* FROM "posts"
=> nil
あれ....。本当は以下のように2つのSQLが発行されているはずなのに。。。。
irb(main):051:0> Post.preload(:user)
Post Load (0.4ms) SELECT "posts".* FROM "posts"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
=> #略
to_sql は何故かひとつめのSQLしか出力してくれないため、これも駄目。
その3: explain に出逢う
irb(main):052:0> Post.preload(:user).explain
Post Load (0.3ms) SELECT "posts".* FROM "posts"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
=> EXPLAIN for: SELECT "posts".* FROM "posts"
0|0|0|SCAN TABLE posts
EXPLAIN for: SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?)
0|0|0|EXECUTE LIST SUBQUERY 1
いいじゃないですか! セミコロンと組み合わせれば、よりスッキリするじゃないですか!
irb(main):053:0> Post.preload(:user).explain; nil
Post Load (0.3ms) SELECT "posts".* FROM "posts"
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
=> nil
でも、first とかに explain はエラーになる。
irb(main):054:0> Post.preload(:user).first.explain
Post Load (0.2ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT 1
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1)
NoMethodError: undefined method `explain' for #<Post:0x007fba75e24dc0>
from /Users/mochizuki/Developer/appStudy/vendor/bundle/gems/activemodel-4.2.3/lib/active_model/attribute_methods.rb:433:in `method_missing'
from (irb):54
from /Users/mochizuki/Developer/appStudy/vendor/bundle/gems/railties-4.2.3/lib/rails/commands/console.rb:110:in `start'
from /Users/mochizuki/Developer/appStudy/vendor/bundle/gems/railties-4.2.3/lib/rails/commands/console.rb:9:in `start'
from /Users/mochizuki/Developer/appStudy/vendor/bundle/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:68:in `console'
from /Users/mochizuki/Developer/appStudy/vendor/bundle/gems/railties-4.2.3/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
from /Users/mochizuki/Developer/appStudy/vendor/bundle/gems/railties-4.2.3/lib/rails/commands.rb:17:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
その4: オブジェクトのクラスが違うことを知る
irb(main):065:0* User.first.class.name
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> "User"
irb(main):066:0> User.all.class.name
=> "ActiveRecord::Relation"
このように .first
を使ったときと、.all
を使ったときでは、返されるオブジェクトのクラスは異なります。今まで意識をしたことはありませんでした。
そして、 to_sql や explain は ActiveRecord::Relation に定義されているメソッドになります。
# こっちにはメソッドが存在
irb(main):071:0> User.all.methods.grep /\Aexplain\z/
=> [:explain]
irb(main):072:0> User.all.methods.grep /\Ato_sql\z/
=> [:to_sql]
# こっちには存在しない
irb(main):073:0> User.first.methods.grep /\Ato_sql\z/
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> []
irb(main):074:0> User.first.methods.grep /\Aexplain\z/
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> []
結論(再び)
explain
を使えば、だいたい満たされます。 .first
とかの場合は、explain も to_sql も使えないので、セミコロンとかでお茶を濁せばよいかと思います。(そもそも first しつつSQLを直視したいかは、わかりませんが。)
例
includes してジョインする方のテーブルの where 句で条件絞りこみするけど、references を使わなくても LEFT OUTER JOIN されていることを確認してみるサンプル。
irb(main):096:0> Post.includes(:user).where(users: {id: 5..Float::INFINITY}).explain; 'Thanks!'
SQL (0.3ms)
SELECT
"posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."created_at" AS t0_r2, "posts"."updated_at" AS t0_r3, "posts"."user_id" AS t0_r4, "users"."id" AS t1_r0, "users"."name" AS t1_r1, "users"."created_at" AS t1_r2, "users"."updated_at" AS t1_r3 FROM "posts"
LEFT OUTER JOIN
"users" ON "users"."id" = "posts"."user_id"
WHERE
("users"."id" >= 5)
=> "Thanks!"
以上、ありがとうございました。