一から勉強させてください( ̄ω ̄;)

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです。

RubyとSQLiteを使って、郵便番号検索アプリをつくってみた

今回はRubySQLiteを使って、郵便番号から住所を検索できるアプリケーションを作ってみました。


ソースはすべてここに置いてあります。
https://github.com/danimal141/search_zipcode


やりたいことは非常にシンプルで

「郵便番号から該当する住所を検索する」

というだけのものなのですが、簡単なDB連携などもあって良い勉強になると思ったのでつくりました。(まあ、本にのってたサンプルを参考にしただけなのですが…)


ちなみに郵便番号のCSVファイルは

http://www.post.japanpost.jp/zipcode/dl/oogaki.html

からダウンロードできます。


もちろんCSVファイルから該当箇所を抽出して取り出す方法もあるのですが、
それだと処理速度がかなり遅いので、今回はDBにデータを登録しておいてそこから検索をかける方法でつくりたいと思います。(githubのコードはsearch_from_db.rbです)


それでは早速ソースのほうを。これ↓

require "sqlite3"

class JZipCode
  COL_ZIP = 2
  COL_PREF = 6
  COL_CITY = 7
  COL_ADDR = 8

  def initialize(dbfile)
    @dbfile = dbfile
  end

  # DB作成用メソッド(すでに作成済みの場合は何もしない)
  def make_db(zipfile)
    return if File.exists?(@dbfile)

    SQLite3::Database.new(@dbfile) do |db|
      db.execute <<-SQL
        CREATE TABLE IF NOT EXISTS zips
        (code TEXT, pref TEXT, city TEXT, addr TEXT, alladdr TEXT)
      SQL

      File.open(zipfile, "r:shift_jis") do |zip|
        db.execute "BEGIN TRANSACTION"
        zip.each_line do |line|
          columns = line.split(/,/).map{|col| col.delete('"')}
          code = columns[COL_ZIP]
          pref = columns[COL_PREF]
          city = columns[COL_CITY]
          addr = columns[COL_ADDR]
          all_addr = pref + city + addr
         db.execute "INSERT INTO zips VALUES (?,?,?,?,?)",
         [code, pref, city, addr, all_addr]
        end
        db.execute "COMMIT TRANSACTION"
      end
    end
  end

  # 郵便番号から検索するためのメソッド
  def find_by_code(code)
    sql = "SELECT * FROM zips WHERE code = ?"
    str = ""
    SQLite3::Database.new(@dbfile) do |db|
      db.execute(sql, code) do |row|
        str << sprintf("%s %s", row[0], row[4]) << "\n"
      end
    end
    str
  end

  # 住所から検索するためのメソッド
  def find_by_address(addr)
    sql = "SELECT * FROM zips WHERE alladdr LIKE ?"
    str = ""
    SQLite3::Database.new(@dbfile) do |db|
      db.execute(sql, "%#{addr}%") do |row| #like %#{addr}%とすることで、addrに完全に合致するものだけマッチする
        str << sprintf("%s %s", row[0], row[4]) << "\n"
      end
    end
    str
  end
end

if __FILE__ == $0
  jzipcode = JZipCode.new("zip.db")
  jzipcode.make_db("KEN_ALL.CSV")

  # 使用例
  puts jzipcode.find_by_code("1500001")
  puts jzipcode.find_by_address("東京都新宿区西新宿")
end


では順番に見ていきます。


1、SQLiteのインストール
まず今回はDBにSQLiteを使用するため、まずはそちらをインストールする必要がありました。
でもめっちゃ簡単でコマンドラインから

gem install sqlite3

でOK。これで最新バージョンのsqlite3のインストールが完了したと思います。

sqlite3のインストールが完了したら次に進んでいきます。


今回は、郵便番号検索用のクラス「JZipCode」を用意してそのインスタンス「jzipcode」から検索を行うようにしたいと思います。それではJZipCodeクラスを作っていきます。


2、JZipCodeクラスの作成(DB作成メソッド定義)
まず事前に入手しておいたCSVからDBを作成しなければならないので、そのメソッドを定義していきます。

はじめにreturn ~の部分ですが、これは既にDB作成済みの場合は何もする必要がないので、その時用の処理です。

次に、SQLite3::Database.new~の部分で新規のSQLiteオブジェクトを作成、引数にDBファイル名を指定します(指定したDBが存在しない場合は新規で作成します)。


ちなみに余談ですが、この「SQLite3::Database」ってSQLite3クラスの中にDatabaseクラスがあって、「クラス名は定数だから :: でアクセスできる」という認識でOKすかね…??


SQL文を実行するにはexecuteメソッドを使用します。

今回はヒアドキュメントの形式でcreate文を実行し、code, pref, city, addr, alladdrを持つテーブルzipsを作成しました。

そして次にDBの中身となるファイル(今回はCSVファイル)を読み込んで1行ずつデータを挿入していきます。


ここで BEGIN TRANSACTION, COMMIT TRANSACTION というものがありますが、トランザクションとは、


「関連のあるデータベースの更新処理を一つにまとめたもの」


のことで、BEGINからCOMMITまでを処理のひとかたまりにしている感じです。
間違ってたらすんません。


で、zipsにいれたいデータはCSVファイルの各行の2、6、7、8番目のデータと住所(6、7、8番目のデータを足したもの)の計5つなので、それらをinsert文で挿入してこのメソッドの役目は完了です。

ちなみにvaluesにある「?」には対応する値が順に入ってくれるっぽいです。プレースホルダらしいです。便利。


3、JZipCodeクラスの作成(郵便番号から検索する用のメソッド定義)
次に検索用のメソッドです。まずは郵便番号から該当する住所を検索するメソッドを定義します。

まず今回検索したい値はzipsでいうcodeに当たるので、select文は「where code = ?」としました。
あとはstringを格納する変数strを用意しておいて、該当するものをstrに追加していけば完了です。

ちなみに<<はarrayのpushメソッドと同じで末尾にどんどん追加していくメソッドです。今回は、sprintfを使い、stringの形式でrow[0](郵便番号)、row[4](住所)を追加していってます。最後に\nで改行ですね。


で、最後にそのstrを返して役目は完了です。


4、JZipCodeクラスの作成(住所から検索する用のメソッド定義)
最後に実際に住所を適当に入力しても検索できるようなメソッドをつくりました。
こちらはあいまい検索になるのでlikeを使って、「where alladdr like ?」としました。

そして上記の3のときのように該当するものをstrに格納していくのですが、ここではexecuteの第二引数を「"%#{addr}%"」としています。

#{}という書き方はダブルクオーテーション内で変数の中身を展開する書き方です。んでそれを挟んでる%はsql文のlikeの書き方で完全にaddrの中身を含んでいるものにのみ合致します。


こちらで詳しく書いてくださっていたので、参考にしました。ありがとうございます。
http://www.1keydata.com/jp/sql/sql-like.php


で、あとは普通にstrを返して完了です。


5、実際に検索してみる
ここでif __FILE__ == $0という書き方をしてみたのですが、これは

requireされた時は実行しないけど、直接実行された時は実行する

っていう方法らしいです。

こちら↓参考にさせていただきました!!


http://d.hatena.ne.jp/gan2/20070520/1179649635


__FILE__ が実行中のプログラムのファイル名を表し、$0 は実行するときに指定したプログラムのファイル名を表すらしいです。


で、あとはただの使用例です。

まず、jzipcode = JZipCode.new("zip.db") のとこでJZipCodeのインスタンスを作成し、
jzipcode.make_db("KEN_ALL.CSV") のとこでCSVファイルを読んでzipsテーブル生成
(初回のみ、テーブル作成のためちょっと時間かかります)

そして、ためしに郵便番号「1500001」と住所「東京都新宿区西新宿」を検索してみました。

ruby search_from_db.rbするとちゃんと表示されましたね。表示結果は長いので割愛しますが。。


こんな感じで今回は簡単なアプリケーションをつくることができました。DB連携とか良い勉強になるし、普段クライアントサイドのJSばかり書いてる僕としては新鮮で面白かったですねー。
今後はもっとバックエンド寄りなことにも挑戦していきたいです。


あ、ちなみに今回参考にした本はこれです。めっちゃ良い本だと思います!!


小さなことからコツコツと。