45
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

rails console の中で、ただSQLをスッキリと直視したいだけのお話。

Posted at

掲題の願望を満たすために、調べていましたら、学びがありましたので、「結論」と、
その**「過程からの学んだ4つのTIPS」**をお届けしたいと思います。 :grimacing:

結論

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!"

以上、ありがとうございました。

45
37
2

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
45
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?