複数のテーブルのどれかに紐付いているデータを出力する(速度改善)

MySQLで速度改善の事例が見つかったのでメモ

テーブル構成

  • users
    • ユーザー
  • clubs
    • 部活
  • councils
    • 生徒会
mysql> DESC users
+------------------+------------------+------+-----+---------+----------------+
| Field            | Type             | Null | Key | Default | Extra          |
+------------------+------------------+------+-----+---------+----------------+
| id               | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name             | varchar(200)     | NO   |     |         |                |
+------------------+------------------+------+-----+---------+----------------+

mysql> DESC club_member_settings
+------------------+------------------+------+-----+---------+----------------+
| Field            | Type             | Null | Key | Default | Extra          |
+------------------+------------------+------+-----+---------+----------------+
| user_id          | int(10) unsigned | NO   |     |         |                |
| club_id          | int(10) unsigned | NO   |     |         |                |
+------------------+------------------+------+-----+---------+----------------+

mysql> DESC council_member_settings
+------------------+------------------+------+-----+---------+----------------+
| Field            | Type             | Null | Key | Default | Extra          |
+------------------+------------------+------+-----+---------+----------------+
| user_id          | int(10) unsigned | NO   |     |         |                |
| council_id       | int(10) unsigned | NO   |     |         |                |
+------------------+------------------+------+-----+---------+----------------+

やりたいコト

部活か生徒会に所属しているユーザーを取得したい

安直な方法

SELECT users.*
FROM users
  LEFT JOIN club_member_settings ON users.id = club_member_settings.user_id
  LEFT JOIN council_member_settings ON users.id = council_member_settings.user_id
WHERE
  club_member_settings.id IS NOT NULL OR
  council_member_settings.id IS NOT NULL
GROUP BY users.id
問題点

club_member_settings/council_member_settingsの数が多い場合、速度の劣化が激しい
おそらく、JOINの段階では絞れず、OR条件なので計算数が多くなるから?かなと思っていますが、詳しい方がいらっしゃいましたら、ご教示頂けますと幸いです。

速度改善が見込める方法

SELECT users.*
FROM users
  INNER JOIN club_member_settings ON users.id = club_member_settings.user_id
UNION SELECT users.*
FROM users
  INNER JOIN council_member_settings ON users.id = council_member_settings.user_id
ポイント

INNER JOINを行っている段階で、usersが絞り込まれる
また、UNIONすることで検索条件のORを満たすことができ、かつユニークにもなる


ご参考になれば幸いです。
最後までお読み頂きありがとうございました。

配列の要素の全組み合わせを出力する

配列の要素の全組み合わせを出力したいということが発生したのでメモ

array = %w[a b c d  e]
all_combi=[]
array.count.times{|i| all_combi += array.combination(i+1).to_a}
all_combi
#=>  [["a"], ["b"], ["c"], ["d"], ["e"], ["a", "b"], ["a", "c"], ["a", "d"], ["a", "e"], ["b", "c"], ["b", "d"], ["b", "e"], ["c", "d"], ["c", "e"], ["d", "e"], ["a", "b", "c"], ["a", "b", "d"], ["a", "b", "e"], ["a", "c", "d"], ["a", "c", "e"], ["a", "d", "e"], ["b", "c", "d"], ["b", "c", "e"], ["b", "d", "e"], ["c", "d", "e"], ["a", "b", "c", "d"], ["a", "b", "c", "e"], ["a", "b", "d", "e"], ["a", "c", "d", "e"], ["b", "c", "d", "e"], ["a", "b", "c", "d", "e"]]


参考:http://ref.xaio.jp/ruby/classes/array/combination

Rubyのinjectが便利な件

injectが便利なので、使い方をまとめてみた

使い方

  • 引数で初期値を指定
  • ブロックの返り値が、ブロックの第一引数に代入

使用例(Array)

今までだと、

hoge = [1, 2, 3, 4, 5]
sum = 0
hoge.each{|i| sum += i}
sum
#=> 15

と書かなければならなかったものが、

hoge = [1, 2, 3, 4, 5]
hoge.inject(0){|sum, i| sum += i}
#=> 15

使用例(Hash)

ほとんどArrayに同じ

hoge = {:a => 1, :b => 2, :c => 3, :d => 4, :e => 5}
hoge.inject(0){|sum, pair| sum += pair[1]}
#=> 15

# この場合、pairに[:a, 1]などが代入される

まとめ

ブロックの返り値が第一ブロック変数に代入されるところがミソかなと個人的には思っています。
こんな感じで、コード量が少なくて済むので便利です。


最後までお読みいただきありがとうございました。

instance_of?とis_a?の違い

instance_of?とis_a?の違いを、メモ。


以下のものを用いて実験

module Foo; end
module Bar; end

class Hoge
  include Foo
end

class Fuga < Hoge
  include Bar
end

fuga = Fuga.new

instance_of?

fuga.instance_of? Fuga #=> true
fuga.instance_of? Hoge #=> false
fuga.instance_of? Object #=> false
fuga.instance_of? Foo #=> false
fuga.instance_of? Bar #=> false
  • selfが、引数で指定されたクラスの直接のインスタンスの場合、trueを返す

is_a?メソッド

fuga.is_a? Fuga #=> true
fuga.is_a? Hoge #=> true
fuga.is_a? Object #=> true
fuga.is_a? Foo #=> true
fuga.is_a? Bar #=> true
  • selfが、引数で指定したクラスの直接のインスタンス、もしくは、そのサブクラスのインスタンスであれば、trueを返す
  • また、モジュールをインクルードしたクラス、もしくは、そのサブクラスのインスタンスであれば、trueを返す

kind_of?メソッド

# aliasを張っているだけなので、is_a?メソッドと同じ
# Hoge === fugaも同様

まとめ

  • instance_of?メソッドはその名の通り、そのクラスのインスタンスかどうかを判定


参考


以上です。
最後までお読みいただきありがとうございました。

&&とか||の返り値

&&と||の返り値に関して調べたので、メモ。

1 || 2
#=> 1

1 && 2
#=> 2


このようになるらしい。


というのも、

1 || 2
# この評価の時に、まずは、1をture/falseで評価するので、trueと評価され1が返る
# && の評価も同様のロジック


直感的に、以下のように記述したくなることがあるらしいので、要注意。。。

if hoge == (1||2)
  p "if文を通過"
end

上記の場合、hoge #=> 2のときは、falseを返すことになる。


条件分岐するときは、条件式を二つ書くか、case使えばいいと言う事になる。

if hoge == 1 || hoge == 2
  p "if文を通過"
end

case hoge
when 1, 2
  p "if文を通過"
end


最後までお読みいただきありがとうございました。

vimの機能を充実させてみた

そろそろ開発環境をカスタマイズしようと思い、vimのpluginを入れてみたのでメモ

neocomplcache.vim


参考
http://masterka.seesaa.net/article/161781923.html


以下、設定ファイル

" neocomplcache.vim
let g:neocomplcache_enable_at_startup = 1 " 起動時に有効化

imap <expr><TAB> neocomplcache#sources#snippets_complete#expandable() ? "\<Plug>(neocomplcache_snippets_expand)" : pumvisible() ? "\<C-n>" : "\<TAB>"


高速にTab補完が出来て重宝しています。

rails.vim

rubytest.vim

参考
http://d.hatena.ne.jp/apo-co/20110215/p1

smartchr.vim

参考
http://project-p.jp/halt/anubis/blog_show/1068


設定ファイル

" smartchr.vim
inoremap <expr> { smartchr#one_of('{', '#{')


私の場合、"#{}"の時の「#{」を打つのがめんどくさいので、「{{」で「#{」が出力されるようにしています。


他に便利なプラグインとか設定があれば教えてください。


最期までお読みいただきありがとうございました。


----- 追記 -----
sugilogさんからご指摘があって、neocomplcacheの設定をShougo氏のドキュメントに合わせました。
ありがとうございましたm(_ _)m

ARのsaveとかupdate_attributeとか

ActiveRecordのsaveやupdate_attribute(s)がどのような仕組みになっているのかのメモ

環境

  • rails1.2.3

ソースコード

# activerecord/lib/active_record/base.rb
1545       def save
1546         create_or_update
1547       end

# 中略

1584       def update_attribute(name, value)
1585         send(name.to_s + '=', value)
1586         save
1587       end

# 中略

1591       def update_attributes(attributes)
1592         self.attributes = attributes
1593         save
1594       end

# 中略

1787     private
1788       def create_or_update
1789         raise ReadOnlyRecord if readonly?
1790         result = new_record? ? create : update
1791         result != false
1792       end
1793       
1794       # Updates the associated record with values matching those of the instance attributes.
1795       # Returns the number of affected rows.
1796       def update
1797         connection.update(
1798           "UPDATE #{self.class.table_name} " +
1799           "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
1800           "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
1801           "#{self.class.name} Update"
1802         )
1803       end
1804 
1805       # Creates a record with values matching those of the instance attributes
1806       # and returns its id.
1807       def create
1808         if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
1809           self.id = connection.next_sequence_value(self.class.sequence_name)
1810         end
1811       
1812         self.id = connection.insert(
1813           "INSERT INTO #{self.class.table_name} " +
1814           "(#{quoted_column_names.join(', ')}) " +
1815           "VALUES(#{attributes_with_quotes.values.join(', ')})",
1816           "#{self.class.name} Create",
1817           self.class.primary_key, self.id, self.class.sequence_name
1818         )
1819 
1820         @new_record = false
1821         id
1822       end


結局は、saveメソッドもupdate_atteribute(s)も全ては、createメソッドもしくは、updateメソッドを呼び出している
それらのメソッドの内容をもとに、SQLが走る


では、validation周りはどのように制御されているのか?

validationのソースコード

# activerecord/lib/active_record/validations.rb
# module Validations
218     def self.included(base) # :nodoc:
219       base.extend ClassMethods
220       base.class_eval do
221         alias_method_chain :save, :validation
222         alias_method_chain :save!, :validation
223         alias_method_chain :update_attribute, :validation_skipping
224       end
225     end


saveメソッドやupdate_attributeメソッドがoverrideされている。
その内容は?

# activerecord/lib/active_record/validations.rb
# module Validations
733       private

750     def save_with_validation(perform_validation = true)
751       if perform_validation && valid? || !perform_validation
752         save_without_validation
753       else
754         false
755       end
756     end


単にDBに突っ込む前にvalid?かどうかを見ている。
valid? #=> trueであればAR::Base#saveが実行される


さて、perform_validationは何をしているのか?
save(false)として呼び出したときに、validationを通さずに、DBに保存するようにしている。

# activerecord/lib/active_record/validations.rb
# module Validations
733       private

771     def update_attribute_with_validation_skipping(name, value)
772       send(name.to_s + '=', value)
773       save(false)
774     end


update_attributeメソッドの場合、save(false)として呼び出しており、
validationを通らないので、誤った情報がDBに書き加えられることになる可能性があるので、要注意



最後までお読みいただきありがとうございました。