複数のテーブルのどれかに紐付いているデータを出力する(速度改善)
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"]]
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
kind_of?メソッド
# aliasを張っているだけなので、is_a?メソッドと同じ # Hoge === fugaも同様
&&とか||の返り値
&&と||の返り値に関して調べたので、メモ。
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
- vimの補完プラグイン
- autocomplpop.vimよりも圧倒的にはやい
- ソース:GitHub - Shougo/neocomplcache.vim: Ultimate auto-completion system for 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補完が出来て重宝しています。
neco-look
rails.vim
- railsをやるなら必須なプラグイン
- Rとかでcontrollerとview間の異動が一瞬で行える素晴らしいもの
- generateとかmigrationとかrakeもvim上から実行出来る
- 超便利!!
- ソース:rails.vim - Ruby on Rails: easy file navigation, enhanced syntax highlighting, and more : vim online
rubytest.vim
- vim上からRubyテストが実行出来るプラグイン
- ソース:GitHub - janx/vim-rubytest: Run ruby test in vim
- \tとか\Tでvim上からテストが実行できて便利
smartchr.vim
- 地味に超便利なプラグイン
- 同じ文字を連続で打ったときの出力を変更できるプラグイン
- 個人的には、超おすすめ
- ソース:smartchr - Insert several candidates with a single key : vim online
参考
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に書き加えられることになる可能性があるので、要注意
最後までお読みいただきありがとうございました。