2021 年度 DB 技術 : SQL の利用 (1)

はじめに

本演習では, Ruby スクリプトから MySQL サーバにアクセスし, テーブルの情報を Web ブラウザから閲覧するための HTML ファイルを作成する.

今回の演習で利用する Ruby のライブラリ.

サンプルファイル

以下で述べる 1st Step から 6th Step までのサンプルスクリプトをサーバ上に 用意している.それを活用して構わない.

mysql2 のサンプル

  $ ls ~sugiyama/sample1

    db1-1.rb  db1-2.rb  db1-3.rb  db1-4.rb  db1-5.rb  db1-6.rb  db_info.yml 

ActiveRecord のサンプル

  $ ls ~sugiyama/sample2

    db2-1.rb  db2-2.rb  db2-3.rb  db2-4.rb  db2-5.rb  db2-6.rb  db_info.yml 

例えば以下のようにして,自分のディレクトリにコピーして使うことができる.

mysql2 の 1st Step のサンプルを使う場合:

  $ cp ~sugiyama/sample1/db1-1.rb .

また,各ステップでの差分は,diff コマンドで調べることができる.-c オプションを付けて diff コマンドを実行した場合には,互いに異なる行の先頭に "!" が付く.

$ diff -c db1-1.rb db1-2.rb 

   2020-07-26 23:05:38.398766808 +0900
   2020-07-26 23:05:13.938921563 +0900
   ***************
   *** 12,16 ****

     sql = "SELECT * FROM 商品"
     client.query( sql ).each do |item|
  !    p item     # 各レコードが `{ key => value}` 形式のオブジェクトになっている
     end
   --- 12,17 ----

    sql = "SELECT * FROM 商品"
    client.query( sql ).each do |item|
  !   # p item
  !   puts "#{item["商品番号"]}\t#{item["商品名"]}\t#{item["価格"]}"
    end
  --- db1-2.rb*** db1-1.rb

mysql2 ライブラリを利用する場合

作業ディレクトリ (今回は,sample1) を作成し,そこで作業することにする.

$ mkdir ~/sample1

$ cd ~/sample1

1st Step

テーブル「商品」に含まれるデータを表示する ruby プログラムを作成する. 以下の内容を db1-1.rb としてホームディレクトリ以下に保存する. なお, ユーザ名とパスワードは適宜修正すること.

$ vi db1-1.rb    (もしくは, $ cp ~sugiyama/sample1/db1-1.rb . ) 

  #!/usr/bin/env ruby
  # coding: utf-8
  require 'mysql2'

  # データベースへの接続
  client = Mysql2::Client.new(
    :host     => "localhost",
    :username => "******",    # <= 自分のユーザ名
    :password => "******",    # <= 自分のパスワード
    :database => "******"     # <= 自分のデータベース名 (学生番号)
  )

  sql = "SELECT * FROM 商品"
  client.query( sql ).each do |item|
    p item     # 各レコードが `{ key => value}` 形式のオブジェクトになっている
  end

このプログラムにはパスワードが書かれているので, 最低限の作法として, 他人に読めないようパーミッションを設定する必要がある.

$ chmod 600 db1-1.rb

db1-1.rb を実行すると以下のような出力を得る. テーブルの 1 行 1 行がハッシュとして保存されていることがわかる.

$ ruby db1-1.rb

  {"商品番号"=>"A01", "商品名"=>"オフィス用紙 A4", "価格"=>2000}
  {"商品番号"=>"A02", "商品名"=>"オフィス用紙 A3", "価格"=>4000}
  {"商品番号"=>"A03", "商品名"=>"オフィス用紙 B5", "価格"=>1500}
  {"商品番号"=>"B01", "商品名"=>"トナーカートリッジ黒", "価格"=>25000}
  {"商品番号"=>"C01", "商品名"=>"ホワイトボード", "価格"=>14000}
  {"商品番号"=>"X00", "商品名"=>"ノート", "価格"=>120}
  {"商品番号"=>"X01", "商品名"=>"テープ", "価格"=>100}
  {"商品番号"=>"Y01", "商品名"=>"はさみ", "価格"=>100}
  {"商品番号"=>"A04", "商品名"=>"紙", "価格"=>300}

2nd Step

1st Step で, 取り出したデータがハッシュの形で保管されていることがわかったので, 次にハッシュを使って db1-1.rb の表示部分を書き直す.

$ cp db1-1.rb db1-2.rb  (もしくは, $ cp ~sugiyama/sample1/db1-2.rb . ) 

$ chmod 600 db1-2.rb

$ vi db1-2.rb

  ...(略)...

  sql = "SELECT * FROM 商品"
  client.query( sql ).each do |item|
    # p item
    puts "#{item["商品番号"]}\t#{item["商品名"]}\t#{item["価格"]}"
  end

db1-2.rb を実行すると以下のような出力を得る. ハッシュのキーとしてカラム名(属性名)を与えることで, その値が得られることがわかる.

$ ruby db1-2.rb

  A01   オフィス用紙 A4   2000
  A02   オフィス用紙 A3   4000
  A03   オフィス用紙 B5   1500
  B01   トナーカートリッジ黒   25000
  C01   ホワイトボード    14000
  X00   ノート   120
  X01   テープ   100
  Y01   はさみ   100
  A04   紙       300

3rd Step

puts 関数を用いてテーブル「商品」を HTML 形式で出力してみる. db1-2.rb を db1-3.rb としてコピーし, 以下のように修正する.

$ cp db1-2.rb db1-3.rb   (もしくは, $ cp ~sugiyama/sample1/db1-3.rb . ) 

$ chmod 600 db1-3.rb

$ vi db1-3.rb

  ...(略)...
  sql = "SELECT * FROM 商品"

  puts "<html>"
  puts "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"
  puts "<body>"
  puts "<table><tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>"
  client.query( sql ).each do |item|
    puts "<tr><td>#{item["商品番号"]}</td><td>#{item["商品名"]}</td><td>#{item["価格"]}</td></tr>"
  end
  puts "</table>"
  puts "</body></html>"

db1-3.rb を実行すると HTML のソースが表示される.

$ ruby db1-3.rb

  <html>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <body>
  <table><tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>
  <tr><td>A01</td><td>オフィス用紙 A4</td><td>2000</td></tr>
  <tr><td>A02</td><td>オフィス用紙 A3</td><td>4000</td></tr>
  <tr><td>A03</td><td>オフィス用紙 B5</td><td>1500</td></tr>
  <tr><td>B01</td><td>トナーカートリッジ黒</td><td>25000</td></tr>
  <tr><td>C01</td><td>ホワイトボード</td><td>14000</td></tr>
  <tr><td>X00</td><td>ノート</td><td>120</td></tr>
  <tr><td>X01</td><td>テープ</td><td>100</td></tr>
  <tr><td>Y01</td><td>はさみ</td><td>100</td></tr>
  <tr><td>A04</td><td>紙</td><td>300</td></tr>
  </table>
  </body></html>

4th Step

3rd Step の段階では, puts 命令が多すぎてプログラムが読みにくい. このような場合には ERB を用いてヒアドキュメントに Ruby スクリプトを埋め込むと良い. ERB の説明は例えば, <URL:https://magazine.rubyist.net/articles/0017/0017-BundledLibraries.html> を参照すると良い.

ERB では埋め込む際に以下のタグを用いる.

<% … %>
Ruby スクリプト片をその場で実行

<%= … %>
式を評価した結果をその場に挿入

db1-3.rb を db1-4.rb としてコピーし, db1-4.rb を ERB を用いて以下のように書き直す. ヒアドキュメント内の HTML に <%...%>, <%=...%> の形で ruby スクリプトが埋め込まれているのがわかると思う. なお, ヒアドキュメントでは << を使う. << の後ろにヒアドキュメントの始まりと終わりを示す文字列の識別子を書く (<< と識別子の間に空白を入れない). EOS(End Of String)とEOL(End Of Line)が使われている例が多いが, 統一されていれば何を使っても問題ない.

$ cp db1-3.rb db1-4.rb   (もしくは, $ cp ~sugiyama/sample1/db1-4.rb . ) 

$ chmod 600 db1-4.rb

$ vi db1-4.rb

  require 'erb'
  require 'mysql2'

  # データベースへの接続  
  ...(省略. 1st Step 参照)...

  #SQL の実行と埋め込み
  sql = "SELECT * FROM 商品"

  contents = <<EOS
  <html>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <body>
  <table><tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>
  <% client.query( sql ).each do |item| %>
  <tr> 
    <td><%= item["商品番号"] %></td><td><%= item["商品名"] %></td><td><%= item["価格"] %></td>
  </tr>
  <% end %>
  </table>
  </body></html>
  EOS

  # おまじない
  erb = ERB.new(contents)
  puts erb.result(binding)

db1-4.rb を実行すると 3rd Step と同様の結果が得られる.

$ ruby db1-4.rb 

  ... 出力は省略 ...

5th Step

アカウント・パスワードの情報は, ファイル内に書いておくのではなく, 別ファイルに分けて外部から参照・実行できない場所に保存するのが良い. ここでは db_info.yml に以下のような YAML 形式でアカウント・パスワードの情報を保管する.

$ vi db_info.yml

  SERV: "localhost"
  USER: "******"
  PASS: "******"
  DBNM: "******"

このファイルにはパスワードが書かれているので, 最低限の作法として, 他人に読めないようパーミッションを設定する必要がある.

$ chmod 600 db_info.yml

次に db1-4.rb を db1-5.rb としてコピーし, db1-5.rb を以下のように変更する.

$ cp db1-4.rb db1-5.rb    (もしくは, $ cp ~sugiyama/sample1/db1-5.rb . ) 

$ vi db1-5.rb

  require 'erb'
  require 'mysql2'   
  require 'yaml'

    ...(略)...

  # 設定ファイルの読み込み
  mydb = YAML.load_file( "db_info.yml" )

  # データベースへの接続
  client = Mysql2::Client.new(
    :host => "#{mydb["SERV"]}",
    :username => "#{mydb["USER"]}",
    :password => "#{mydb["PASS"]}",
    :database => "#{mydb["DBNM"]}"
  )    

  ...(略)...

db1-5.rb を実行すると 3rd Step と同様の結果が得られる.

$ ruby db1-5.rb 

  ... 出力は省略 ...

6th Step

これまでは全て標準出力に出力してきたが, このステップではHTML ファイルに出力することにする. まずはじめに, HTML ファイルを格納するためのディレクトリを作る.

$ mkdir ~/public_html

db1-5.rb を db1-6.rb としてコピーし, db1-6.rb を編集して出力を HTML ファイルとして書き出すようにする. なお, ファイルパスの HOGEHOGE の部分は自分のユーザアカウントに修正すること.

$ cp db1-5.rb  db1-6.rb   (もしくは, $ cp ~sugiyama/sample1/db1-6.rb . ) 

$ vi db1-6.rb

  ...(略)...

  #ファイルオープン
  # HOGEHOGE の部分を自分のユーザ名に書き換えること 
  fp = open("/home/HOGEHOGE/public_html/db1.htm", "w")

  # おまじない 
  erb = ERB.new(contents)
  fp.puts erb.result(binding)

  #ファイルクローズ
  fp.close

db1-6.rb を実行すると, ~/public_html 以下に新たに db1.htm というファイルが出来ていることがわかる.

$ ruby db1-6.rb 

$ ls ~/public_html

  db1.htm

確認のためにブラウザで db1.htm を閲覧してみよ. https://pluto.epi.it.matsue-ct.jp/~HOGEHOGE/db1.htm の HOGEHOGE を自分のアカウントに変えてアクセスしてみよ.

Active Record の利用

ActiveRecord は Ruby on Rails 標準の O/R (object/Relational) マッパーである. SQL を意識せずに, オブジェクト指向的にデータベースを扱うことができる. Active Record 単体でも利用することができる.

作業ディレクトリ (今回は,sample2) を作成し,そこで作業することにする.

$ mkdir ~/sample2

$ cd ~/sample2

1st Step

ActiveRecord を用いた例として, 以下の内容を db2-1.rb として保存する. 但し, ユーザ名, パスワード, データベース名を自分の環境に合わせて適宜修正すること.

$ vi db2-1.rb    (もしくは, $ cp ~sugiyama/sample2/db2-1.rb . ) 

  #!/usr/bin/env ruby
  # coding: utf-8
  require 'active_record'

  # DB接続設定
  ActiveRecord::Base.establish_connection(
    adapter:  "mysql2",
    host:     "localhost",
    username: "xxxxxx",
    password: "xxxxxx",
    database: "xxxxxx",
  )

  # テーブルにアクセスするためのクラスを宣言
  class User < ActiveRecord::Base
    self.table_name = '商品'
  end

  # レコード取得
  User.all.each do |item|
    p item
  end

  # 検索 (射影)
  User.select('商品番号').each do |item|
    p item
  end

  # 検索 (選択)
  User.where(商品番号: 'A01').each do |item|
    p item
  end

このプログラムにはパスワードが書かれているので, 最低限の作法として, 他人に読めないようパーミッションを設定する必要がある.

$ chmod 600 db2-1.rb

db3.rb を実行すると以下のようになる. SQL 文を直接書かなくてもデータベース操作ができていることがわかる.

$ ruby db2-1.rb

  #<User 商品番号: "A01", 商品名: "オフィス用紙 A4", 価格: 2000>
  #<User 商品番号: "A02", 商品名: "オフィス用紙 A3", 価格: 4000>
  #<User 商品番号: "A03", 商品名: "オフィス用紙 B5", 価格: 1500>
  #<User 商品番号: "B01", 商品名: "トナーカートリッジ黒", 価格: 25000>
  #<Usear 商品番号: "C01", 商品名: "ホワイトボード", 価格: 14000>
  #<User 商品番号: "X00", 商品名: "ノート", 価格: 120>
  #<User 商品番号: "X01", 商品名: "テープ", 価格: 100>
  #<User 商品番号: "Y01", 商品名: "はさみ", 価格: 100>
  #<User 商品番号: "A04", 商品名: "紙", 価格: 300>
  #<User 商品番号: "A01">
  #<User 商品番号: "A02">
  #<User 商品番号: "A03">
  #<User 商品番号: "B01">
  #<User 商品番号: "C01">
  #<User 商品番号: "X00">
  #<User 商品番号: "X01">
  #<User 商品番号: "Y01">
  #<User 商品番号: "A04">
  #<User 商品番号: "A01", 商品名: "オフィス用紙 A4", 価格: 2000>

2nd Step

1st Step で, 取り出したデータがハッシュの形で保管されていることがわかったので, 次にハッシュを使って db2-1.rb の表示部分を書き直す.

$ cp db2-1.rb db2-2.rb   (もしくは, $ cp ~sugiyama/sample2/db2-2.rb . ) 

$ chmod 600 db2-2.rb

$ vi db2-2.rb

  ...(略)...

  User.all.each do |item|
     # puts item
     puts "#{item["商品番号"]}\t#{item["商品名"]}\t#{item["価格"]}"
  end

  ## 検索 (射影)
  #User.select('商品番号').each do |item|
  #  p item
  #end

  ## 検索 (選択)
  #User.where(商品番号: 'A01').each do |item|
  #  p item
  #end

db2-2.rb を実行すると以下のような出力が得られる. ハッシュのキーとしてカラム名(属性名)を与えることで, その値が得られることがわかる.

$ ruby db2-2.rb

  A01   オフィス用紙 A4   2000
  A02   オフィス用紙 A3   4000
  A03   オフィス用紙 B5   1500
  B01   トナーカートリッジ黒   25000
  C01   ホワイトボード    14000
  X00   ノート   120
  X01   テープ   100
  Y01   はさみ   100
  A04   紙       300

3rd Step

前節と同様に puts 関数を使って HTML のソースを出力できるように db2-2.rb を改良する.

$ cp db2-2.rb db2-3.rb    (もしくは, $ cp ~sugiyama/sample2/db2-3.rb . ) 

$ chmod 600 db2-3.rb

$ vi db2-3.rb

  ...(省略)...

  puts "<html>"
  puts "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">"
  puts "<body>"
  puts "<table><tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>"
  User.all.each do |item|
    puts "<tr><td>#{item["商品番号"]}</td><td>#{item["商品名"]}</td><td>#{item["価格"]}</td></tr>"
  end
  puts "</table>"
  puts "</body></html>"

db2-3.rb を実行すると HTML のソースが表示される.

$ ruby db2-3.rb

  <html>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <body>
  <table><tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>
  <tr><td>A01</td><td>オフィス用紙 A4</td><td>2000</td></tr>
  <tr><td>A02</td><td>オフィス用紙 A3</td><td>4000</td></tr>
  <tr><td>A03</td><td>オフィス用紙 B5</td><td>1500</td></tr>
  <tr><td>B01</td><td>トナーカートリッジ黒</td><td>25000</td></tr>
  <tr><td>C01</td><td>ホワイトボード</td><td>14000</td></tr>
  <tr><td>X00</td><td>ノート</td><td>120</td></tr>
  <tr><td>X01</td><td>テープ</td><td>100</td></tr>
  <tr><td>Y01</td><td>はさみ</td><td>100</td></tr>
  <tr><td>A04</td><td>紙</td><td>300</td></tr>
  </table>
  </body></html>

4th Step

ここでは前節にならって ERB を用いるように db2-3.rb を書き換える. プログラムの先頭で require 'erb' をするのを忘れないこと.

$ cp db2-3.rb  db2-4.rb    (もしくは, $ cp ~sugiyama/sample2/db2-4.rb . ) 

$ chmod 600 db2-4.rb

$ vi db2-4.rb

  require 'active_record'
  require 'erb'

  # データベースへの接続  
  ...(省略. 1st Step 参照)...

  contents = <<EOS
  <html>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <body>
  <table><tr><th>商品番号</th><th>商品名</th><th>価格</th></tr>
  <% User.all.each do |item| %>   
  <tr> 
    <td><%= item["商品番号"] %></td><td><%= item["商品名"] %></td><td><%= item["価格"] %></td>
  </tr>
  <% end %>
  </table>
  </body></html>
  EOS

  # おまじない
  erb = ERB.new(contents)
  puts erb.result(binding)

db2-4.rb を実行すると 3rd Step と同様の結果が得られる.

$ ruby db2-4.rb

  ...(出力は省略)...

5th Step

前節と同様に, アカウント・パスワードの情報を別ファイルに置く. db_info.yml にアダプタの情報 (ADPT) を追加する.

$ vi db_info.yml

  SERV: "localhost"
  USER: "******"
  PASS: "******"
  DBNM: "******"
  ADPT: "mysql2"

このファイルにはパスワードが書かれているので, 最低限の作法として, 他人に読めないようパーミッションを設定する必要がある.

$ chmod 600 db_info.yml

また, db2-4.rb を以下のように変更する.

$ cp db2-4.rb db2-5.rb    (もしくは, $ cp ~sugiyama/sample2/db2-5.rb . ) 

$ vi db2-5.rb

  require 'active_record'
  require 'erb'
  require 'yaml'

  # 設定ファイルの読み込み
  mydb = YAML.load_file( "db_info.yml" )

  # DB接続設定
  ActiveRecord::Base.establish_connection(
    adapter:  mydb["ADPT"],
    host:     mydb["SERV"],
    username: mydb["USER"],
    password: mydb["PASS"],
    database: mydb["DBNM"]
  )

  ...(略)...

db2-5.rb を実行すると 3rd Step と同様の結果が得られる.

$ ruby db2-5.rb

  ...(出力は省略)...

6th Step

db2-5.rb を編集して, 出力を HTML ファイルとして書き出すようにする. まず始めに, HTML ファイルを格納するディレクトリが存在するか確認し, もし存在しない場合は以下のように public_html ディレクトリを作成する.

$ mkdir ~/public_html

db2-5.rb を編集する. なお, ファイルパスの hogehoge の部分は自分のユーザアカウントに修正すること. 出力ファイル名は db2.htm にすること.

$ cp db2-5.rb db2-6.rb     (もしくは, $ cp ~sugiyama/sample2/db2-6.rb . ) 

$ vi db2-6.rb

  ...(略)...

  #ファイルオープン
  fp = open("/home/hogehoge/public_html/db2.htm", "w")

  # おまじない 
  erb = ERB.new(contents)
  fp.puts erb.result(binding)

  #ファイルクローズ
  fp.close

db2-6.rb を実行すると, ~/public_html 以下に新たに db2.htm というファイルが出来ていることがわかる.

$ ruby db2-6.rb 

$ ls ~/public_html

  db1.htm db2.htm  helloworld.htm

ブラウザで db2.htm を閲覧してみよ. https://pluto.epi.it.matsue-ct.jp/~HOGEHOGE/db2.htm の HOGEHOGE を自分のアカウントに変えてアクセスしてみよ.

課題

  • Ruby スクリプト内で, (1) テーブルへのデータの挿入, (2) テーブル中の全データの表示, (3) テーブル中の一部データの表示 (選択 or 射影) を行いなさい. (2), (3) の操作の結果を 1 つの HTML ファイルとして出力すること. なお, HTML のタグや css を積極的に利用して, 人が読みやすい Web となるように出力を工夫しなさい.
    • mysql2 か Active Record のどちらか好きな方を使うこと.
    • 利用するテーブルは任意とする. これまで作ったテーブルの中から選ぶと良い.
    • 挿入する行数は 5 行以上とすること.
    • 提出物:作成した Ruby スクリプト. HTML ファイルをブラウザで表示したもの (スクリーンショット). 但し,スクリーンショットには必ずブラウザのアドレスバー (http://pluto.epi.it.matsue-ct.jp/~jXXXX/YYY.htm といった文字列が表示されている部分) を含めること.