rubyからsqlite3を扱いたいんです

もちろんkoboクローラ的な意味で。。

# coding: utf-8
require 'sqlite3'

begin
  db = SQLite3::Database.new('jkobo.db')

  db.execute(<<-DROP)
    drop table if exists jkobo;
  DROP

  db.execute(<<-CREATE)
    create table jkobo (
      id integer primary key autoincrement
      ,created_at text
      ,product_no text
      ,title text
      ,price integer
      ,publish_info text
      ,note text
    );
  CREATE
ensure
  db.close
end

このノリでselect/insert/update/deleteできる様子。
まあでもせっかくなのでActiveRecordでやった方がいいよね。

参考

mechanizeでgetしたhtmlが化けているっぽいんです

引き続きkoboをクローリングする文脈で、です。

irb(main):001:0> require 'mechanize'
=> true
irb(main):002:0> agent = Mechanize.new
=> #<Mechanize:0x410eda0 @agent=#<Mechanize::HTTP::Agent:0x410ed88 @allowed_error_codes=[], @conditional_requests=true, @context=#<Mechanize:0x410eda0 ...>, @content_encoding_hooks=[], @cookie_jar=#<Mechanize::CookieJar:0x410ed40 @store=#<HTTP::CookieJar::HashStore:0x410dbd0 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0x410dba0>, @logger=nil, @gc_threshold=150, @jar={}, @gc_index=0>>, @follow_meta_refresh=false, @follow_meta_refresh_self=false, @gzip_enabled=true, @history=[], @ignore_bad_chunking=false, @keep_alive=true, @max_file_buffer=100000, @open_timeout=nil, @post_connect_hooks=[], @pre_connect_hooks=[], @read_timeout=nil, @redirect_ok=true, @redirection_limit=20, @request_headers={}, @robots=false, @user_agent="Mechanize/2.7.2 Ruby/1.9.2p290 (http://github.com/sparklemotion/mechanize/)", @webrobots=nil, @auth_store=#<Mechanize::HTTP::AuthStore:0x410da98 @auth_accounts={}, @default_auth=nil>, @authenticate_parser=#<Mechanize::HTTP::WWWAuthenticateParser:0x410da38 @scanner=nil>, @authenticate_methods={}, @digest_auth=#<Net::HTTP::DigestAuth:0x410d9d8 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0x410d9c0>, @nonce_count=-1>, @digest_challenges={}, @pass=nil, @scheme_handlers={"http"=>#<Proc:0x410d948@D:/RailsInstaller/Ruby1.9.2/lib/ruby/gems/1.9.1/gems/mechanize-2.7.2/lib/mechanize/http/agent.rb:172 (lambda)>, "https"=>#<Proc:0x410d948@D:/RailsInstaller/Ruby1.9.2/lib/ruby/gems/1.9.1/gems/mechanize-2.7.2/lib/mechanize/http/agent.rb:172 (lambda)>, "relative"=>#<Proc:0x410d948@D:/RailsInstaller/Ruby1.9.2/lib/ruby/gems/1.9.1/gems/mechanize-2.7.2/lib/mechanize/http/agent.rb:172 (lambda)>, "file"=>#<Proc:0x410d948@D:/RailsInstaller/Ruby1.9.2/lib/ruby/gems/1.9.1/gems/mechanize-2.7.2/lib/mechanize/http/agent.rb:172 (lambda)>}, @http=#<Net::HTTP::Persistent:0x410d828 @name="mechanize", @debug_output=nil, @proxy_uri=nil, @no_proxy=[], @headers={}, @override_headers={}, @http_versions={}, @keep_alive=300, @open_timeout=nil, @read_timeout=nil, @idle_timeout=5, @max_requests=nil, @socket_options=[[6, 1, 1]], @generation_key=:net_http_persistent_mechanize_generations, @ssl_generation_key=:net_http_persistent_mechanize_ssl_generations, @request_key=:net_http_persistent_mechanize_requests, @timeout_key=:net_http_persistent_mechanize_timeouts, @certificate=nil, @ca_file=nil, @private_key=nil, @ssl_version=nil, @verify_callback=nil, @verify_mode=1, @cert_store=nil, @generation=1, @ssl_generation=1, @reuse_ssl_sessions=true, @retry_change_requests=false, @ruby_1=true, @retried_on_ruby_2=false>>, @log=nil, @watch_for_set=nil, @history_added=nil, @pluggable_parser=#<Mechanize::PluggableParser:0x410d510 @parsers={"text/html"=>Mechanize::Page, "application/xhtml+xml"=>Mechanize::Page, "application/vnd.wap.xhtml+xml"=>Mechanize::Page, "image"=>Mechanize::Image, "text/xml"=>Mechanize::XmlFile, "application/xml"=>Mechanize::XmlFile}, @default=Mechanize::File>, @keep_alive_time=0, @proxy_addr=nil, @proxy_port=nil, @proxy_user=nil, @proxy_pass=nil, @html_parse
r=Nokogiri::HTML, @default_encoding=nil, @force_default_encoding=false>
irb(main):003:0> agent.user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36'
=> "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36"
irb(main):004:0> agent.get('http://rakuten.kobobooks.com/search/search.html?q=&t=all&f=keyword&s=publicationdatedesc&g=both&c=5wgoE9EyJkuWj2DMT8OIZg&l=ja&p=1')
=> #<Mechanize::Page
 {url
  #<URI::HTTP:0x3eba688 URL:http://rakuten.kobobooks.com/search/search.html?q=&t=all&f=keyword&s=publicationdatedesc&g=both&c=5wgoE9EyJkuWj2DMT8OIZg&l=ja&p=1>}
:
:

ときて

irb(main):013:0* puts agent.page.body
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" ><html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
:
:
        <div class="browserNotification">
            縺薙・繧ヲ繧ァ繝悶し繧、繝医・縲∫樟蝨

なんでや。。

irb(main):015:0> puts agent.page.body.encode(Encoding::Windows_31J)
Encoding::UndefinedConversionError: "\xE3" to UTF-8 in conversion from ASCII-8BIT to UTF-8 to Windows-31J
        from (irb):15:in `encode'
        from (irb):15
        from D:/RailsInstaller/Ruby1.9.2/bin/irb:12:in `<main>'
irb(main):016:0> puts agent.page.body.encode(Encoding::Windows_31J, Encoding::UTF_8)
Encoding::UndefinedConversionError: U+00BB from UTF-8 to Windows-31J
        from (irb):16:in `encode'
        from (irb):16
        from D:/RailsInstaller/Ruby1.9.2/bin/irb:12:in `<main>'

これではだめ。

irb(main):017:0> puts agent.page.body.encode(Encoding::Windows_31J, Encoding::UTF_8, undef: :replace)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" ><html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
:
:
        <div class="browserNotification">
            このウェブサイトは、現在お客様がお使いのInternet Explorer 6 あるいは

(;^ω^)やっとか

参考

Rubyのエンコーディング - @tmtms のメモ

楽天koboの検索結果をクローリングしたいんです

1日1回。だって新着がわからないんですもん。「新しい順」でソートすると紙版の出版年月でソートしよるし。
ウェブAPIないし、だったら毎日クローリングして差分を取るしかないじゃないですか。。

mechanizeを使うよ

gem install mechanize

これでおk。

ざっくりした使い方はたぶんこんな感じ。

require 'mechanize'

begin
  agent = Mechanize.new
  agent.user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36'

  url = 'http://rakuten.kobobooks.com/search/search.html?q=&t=all&f=keyword&s=publicationdatedesc&g=both&c=&l=ja&p='
  agent.get("#{url}1")

  total_count = agent.page.at('dl.SCSearch span.SCNumResTotalCount').inner_text.strip.to_i
  total_page = (total_count / 10) + 1

  (1..total_page).each do |i|
    agent.get("#{url}#{i}")
    # 欲しい情報getだぜ!
  end
ensure
  agent.shutdown unless agent.nil?
end

参考

RubyのMechanizeを解説 for 1.0.0 - きたももんががきたん。

Oracleのパフォーマンスを気にしなきゃいけないけど何も知識ないんです

わたしのことなんですけどね。。今まで関係なかった(というかDBチームにおまかせ)のでほとんど気にしませんでしたが、そろそろ化けの皮が。。

1~2時間で最低限の中の最低限の基礎知識がほしいならこれでしょうか。

読んでおけばきっと周りの会話がチンプンカンプンという状態にはならないんじゃないかな、と思います。今まで職場でなんとなく聞こえてきた会話を思い出すと、ここに登場しない単語については「なんすかそれ?」と質問してもそこまで恥ずかしい気持ちにはならずに済むでしょう。。

書籍となると、読んだことあるものはありませんがたぶんこのへんでしょうか。

新・門外不出のOracle現場ワザ

新・門外不出のOracle現場ワザ
著者:小田圭二
価格:2,814円(税込、送料込)
楽天ブックスで詳細を見る

新・門外不出のOracle現場ワザ エキスパートが明かす運用・管理の極意 (DB Selection)
小田 圭二 大塚 信男 五十嵐 建平 谷 敦雄 宮崎 博之 神田 達成 村方 仁
翔泳社
売り上げランキング: 101,720
Oracle SQLチューニング

Oracle SQLチューニング
著者:加藤祥平
価格:2,730円(税込、送料込)
楽天ブックスで詳細を見る

即戦力のOracle管理術

即戦力のOracle管理術
著者:内村友亮
価格:3,780円(税込、送料込)
楽天ブックスで詳細を見る

即戦力のOracle管理術 ~仕組みからわかる効率的管理のノウハウ
内村 友亮 近藤 聖 近藤 良 武吉 佑祐 瀬沼 裕樹
技術評論社
売り上げランキング: 233,418

あるいはOracle Master Expertのパフォーマンスチューニングのやつの参考書とか?
Oracle Master Bronzeレベルの基礎知識も最低限ないといけないかなと。。

脱線ですがkindle版「達人に学ぶ SQL徹底指南書」は

目次がないのでとても読みにくいです。「目次から各章へのリンクがない」のではなく「目次ページがない」です。
サンプル版もそうでしたが「まあいっか」と思って買いました。。今は深く後悔しています。さらに全文検索もできない。悪夢ですね。サンプル版と本当に同じでした。
こういう場合はkoboでも同じですがしおりを付けまくって目次ジャンプ代わりにするしかありません。

ヒント句で表結合アルゴリズムを固定したいんです

ヒント句で表結合の仕方を固定したいんです

このSQLに対して実験。

select * from employees e, jobs j where e.job_id = j.job_id;

ヒント句なし

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |   107 | 10914 |     6  (17)| 00:00:01 |
|   1 |  MERGE JOIN                  |           |   107 | 10914 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| JOBS      |    19 |   627 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | JOB_ID_PK |    19 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |           |   107 |  7383 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | EMPLOYEES |   107 |  7383 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

ネステッドループ結合にする

USE_NLに内部表を指定する形で強制できる。

フルスキャンする外部表にEMPLOYEESを、インデックススキャンする内部表にJOBSを指定すると

SQL> select /*+ ORDERED USE_NL(j) */ * from employees e, jobs j where e.job_id = j.job_id;

108行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 1536616604

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |   107 | 10914 |   110   (0)| 00:00:02 |
|   1 |  NESTED LOOPS                |           |       |       |            |          |
|   2 |   NESTED LOOPS               |           |   107 | 10914 |   110   (0)| 00:00:02 |
|   3 |    TABLE ACCESS FULL         | EMPLOYEES |   107 |  7383 |     3   (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | JOB_ID_PK |     1 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| JOBS      |     1 |    33 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

その逆は

SQL> select /*+ ORDERED USE_NL(e) */ * from jobs j, employees e where e.job_id = j.job_id;

108行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 980169617

-------------------------------------------------------------------------------------------
| Id  | Operation                    | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |            |   107 | 10914 |    22   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |            |       |       |            |          |
|   2 |   NESTED LOOPS               |            |   107 | 10914 |    22   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL         | JOBS       |    19 |   627 |     3   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN          | EMP_JOB_IX |     6 |       |     0   (0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID| EMPLOYEES  |     6 |   414 |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

この例では後者のほうがコストが低い。EMPLOYEESの方がレコード数が多いので、外部表としてフルスキャンしている前者の方が単純に遅くなったと考えられる。
後者であっても、EMP_JOB_IXの効率が悪ければ前者より遅くなることはあるだろう。

ポイントは結合するレコード数が少ない方を外部表に、効率的な索引を使える方を内部表にすること。

ソートマージ結合にする

もとからソートマージでしたが……

SQL> select /*+ USE_MERGE(e, j) */ * from employees e, jobs j where e.job_id = j.job_id;

108行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 303035560

------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |   107 | 10914 |     6  (17)| 00:00:01 |
|   1 |  MERGE JOIN                  |           |   107 | 10914 |     6  (17)| 00:00:01 |
|   2 |   TABLE ACCESS BY INDEX ROWID| JOBS      |    19 |   627 |     2   (0)| 00:00:01 |
|   3 |    INDEX FULL SCAN           | JOB_ID_PK |    19 |       |     1   (0)| 00:00:01 |
|*  4 |   SORT JOIN                  |           |   107 |  7383 |     4  (25)| 00:00:01 |
|   5 |    TABLE ACCESS FULL         | EMPLOYEES |   107 |  7383 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

ハッシュ結合にする

SQL> select /*+ USE_HASH(e, j) */ * from employees e, jobs j where e.job_id = j.job_id;

108行が選択されました。


実行計画
----------------------------------------------------------
Plan hash value: 1300016118

--------------------------------------------------------------------------------
| Id  | Operation          | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |   107 | 10914 |     7  (15)| 00:00:01 |
|*  1 |  HASH JOIN         |           |   107 | 10914 |     7  (15)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| JOBS      |    19 |   627 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| EMPLOYEES |   107 |  7383 |     3   (0)| 00:00:01 |
--------------------------------------------------------------------------------

ORDEREDについて少し

ちなみにANSI準拠のSQLでも変わらずORDEREDは必要だった。
SQLの書きっぷりが結合順を表していそうに見えなくもないから勝手に解釈してくれるかと思ったけど、さすがにそんなことはなかった。

あとUSE_NL以外でも効いた。でもネステッドループ結合以外ではあまり使い出がないかも。。

参考

Oracle SQLチューニング講座(10):表の結合を極めるチューニング・テクニック (1/4) - @IT

いいところだけ引用します

ネステッド・ループ結合 ソート/マージ結合 ハッシュ結合
件数の多い表同士を結合し、全レコード出力する 不向き 結果を結合列でソートして出力する場合に有効。双方の結合列にNOT NULL制約が指定されており、索引が存在する場合、非常に効率的 システム・リソースに余裕がある場合には最適
一方の表に絞り込み条件を指定して表を結合し、少数のレコードを出力する 目安として索引を使用して表の15%以内の絞り込みであれば最適 不向き 目安として索引を使用して表の15%以上の絞り込みで、なおかつ等価条件があれば使用を検討

sqlplusでスキーマの持つインデックスの一覧を見たいんです

USER_INDEXESとUSER_IND_COLUMNSから引っ張ってくる。

SQL> conn hr/hr@xe
SQL> col table_name  format a16 trunc
SQL> col index_name  format a24
SQL> col uniqueness  format a4  trunc
SQL> col column_name format a24
SQL> select i.table_name, i.index_name, i.uniqueness, c.column_name
  2  from user_indexes i, user_ind_columns c
  3  where i.index_name = c.index_name
  4  order by index_name,table_name,column_position
  5  ;

TABLE_NAME       INDEX_NAME               UNIQ COLUMN_NAME
---------------- ------------------------ ---- ------------------------
COUNTRIES        COUNTRY_C_ID_PK          UNIQ COUNTRY_ID
DEPARTMENTS      DEPT_ID_PK               UNIQ DEPARTMENT_ID
DEPARTMENTS      DEPT_LOCATION_IX         NONU LOCATION_ID
EMPLOYEES        EMP_DEPARTMENT_IX        NONU DEPARTMENT_ID
EMPLOYEES        EMP_EMAIL_UK             UNIQ EMAIL
EMPLOYEES        EMP_EMP_ID_PK            UNIQ EMPLOYEE_ID
EMPLOYEES        EMP_JOB_IX               NONU JOB_ID
EMPLOYEES        EMP_MANAGER_IX           NONU MANAGER_ID
EMPLOYEES        EMP_NAME_IX              NONU LAST_NAME
EMPLOYEES        EMP_NAME_IX              NONU FIRST_NAME
JOB_HISTORY      JHIST_DEPARTMENT_IX      NONU DEPARTMENT_ID

TABLE_NAME       INDEX_NAME               UNIQ COLUMN_NAME
---------------- ------------------------ ---- ------------------------
JOB_HISTORY      JHIST_EMPLOYEE_IX        NONU EMPLOYEE_ID
JOB_HISTORY      JHIST_EMP_ID_ST_DATE_PK  UNIQ EMPLOYEE_ID
JOB_HISTORY      JHIST_EMP_ID_ST_DATE_PK  UNIQ START_DATE
JOB_HISTORY      JHIST_JOB_IX             NONU JOB_ID
JOBS             JOB_ID_PK                UNIQ JOB_ID
LOCATIONS        LOC_CITY_IX              NONU CITY
LOCATIONS        LOC_COUNTRY_IX           NONU COUNTRY_ID
LOCATIONS        LOC_ID_PK                UNIQ LOCATION_ID
LOCATIONS        LOC_STATE_PROVINCE_IX    NONU STATE_PROVINCE
REGIONS          REG_ID_PK                UNIQ REGION_ID

21行が選択されました。

参考

BDDってなんですか

いくつか記事を読んだ。読みやすかった順に並べると……

でもこれでは正直よくわからない。テストの戦略にTDDやBDDはどういう意図・位置づけで組み込まれるのかを知りたい。

これでもまだよくわからない。

これを読むとようやく他のページと内容がリンクしてくる。
まるで仕様書や設計書であるかのごとくテスト結果がアウトプットされることによって、設計のブラッシュアップに大きく貢献する。それがBDD。
アウトプットされた文章がなんだかおかしい場合は、そのクラスにあるべきでない処理になっている可能性がある、とか。

でもこの記事は途中から要件定義/受け入れテストの話に変わっちゃうんだよね。。まあでもそれは、JBehaveを分析行程に応用できるのではないかと気がついたからそうなったのであって、つまりBDD支援ツールたるこのJBehaveは当初「進化したJUnit」のような感じでスタートして受け入れテストに着地したということらしく。。
だから4つめに紹介したページのように「TDDでありATDDである」ということになる。

別に要件定義や受け入れテストの文脈でBDDを調べたのではないので。。とりあえず単体テスト的な部分でまとめ。
BDDはTDDの壮大な言い換え。テストコードにBDD特有の語彙(shouldとか?)を使うことで設計のブラッシュアップに大きく寄与する。

ここから(たぶん)私見。細かいデータのレベルではTDDもBDDもテストで確認することは結局同じ、と言う意味でTDD=BDDといっても間違いではない。ただ、TDDは正にテスト項目を挙げることから全てが始まるイメージ*1なのに対し、BDDは振る舞い(シナリオ)を挙げることから全てが始まるイメージ。
このレベルの話だとどこまでいってもニュアンスの問題になる感じで腑に落ちてこない。だからrspecとか具体的なBDDツールを例示して「ほらこんな感じで設計にも役立つんだよ」って話にした方が理解するのもしてもらうのも省エネな気がした。

それでそれで。今私のいるプロジェクトはScrum+BDDで単体テストがあまりないんだけど、BDDを調べてみてもやっぱりScrum+BDDだから単体テストは緩くていいなんて話は全然出てこなかった。。だいたいBDDとしても不完全でrspecのコードはかなり不足しているし。
プロジェクトが始まる前に「単体テストやりすぎると生産性が上がらない」と言われたので単体テストがなおざりなんだけど、今のプロジェクトって品質担保についてどう考えているんだ。。ScrumというかAgileの方を勉強すれば考え方が見えてくるのかな? そういえば読んでないなScrumの本……

*1:Kent BeckのTDD本は読んでいないので正直自信ないです