![]() |
るびくる: |
![]() |
RB(あーるびー): |
![]() |
ハッピーニューイヤー! |
![]() |
|
![]() |
みなさんは、2012年の大みそかをすっきりした気持ちで迎えることができたでしょうか? |
![]() |
夜の闇よりもどんよりした気持ちで新年を迎えました。 |
![]() |
ど、どうしたのるびくる!? |
![]() |
RBが前回のパート1(概要編・前半)をすごい中途半端なところで終わらせるからだよ! |
![]() |
るびくるはもともと箱根駅伝に興味なかったんじゃなかったっけ? |
![]() |
わーわー聞こえなーい!!! |
![]() |
さて、それじゃパート1(概要編・前半)のおさらいとして |
# encoding: utf-8 require 'mysql' # 1. サーバー名やDB名を指定して、DBに接続する DB = Mysql.connect('mysqlserver.example.net', 'username', 'password', 'testdb', encoding: 'utf8') # 2. SQL文を実行して、色が「赤」であるデータを名前順に取得する sql = 'SELECT * FROM mascots WHERE color = ? ORDER BY name' rows = DB.query(sql, '赤') # 3. 取得したデータの内容(name)を表示する rows.each do |row| p row[:name] # => 'るびくる' end
![]() |
そして、こっちがSequelを使って、データベースから同じようにデータを取得するコードだね。 |
# encoding: utf-8 require 'sequel' # 1. サーバー名やDB名を指定して、DBに接続する DB = Sequel.connect('mysql://username:password@mysqlserver.example.net/testdb', encoding: 'utf8') # 2. SQL文を実行して、色が「赤」であるデータを名前順に取得する mascot_dataset = DB[:mascots] mascot_dataset = mascot_dataset.where(:color => '赤').order(:name) rows = mascot_dataset.all # 3. 取得したデータの内容(name)を表示する mascot_dataset.each do |row| p row[:name] # => 'るびくる' end
![]() |
パート1の最後でRBが言ってたのは、この2つのコードを順に比較して解説していってくれる、ってことだったよね。 |
![]() |
OK。それじゃ、順々にコードの中身を見て行こう! |
# encoding: utf-8 require 'mysql' # 1. サーバー名やDB名を指定して、DBに接続する DB = Mysql.connect('mysqlserver.example.net', 'username', 'password', 'testdb', encoding: 'utf8')
# encoding: utf-8 require 'sequel' # 1. サーバー名やDB名を指定して、DBに接続する DB = Sequel.connect('mysql://username:password@mysqlserver.example.net/testdb', encoding: 'utf8')
![]() |
まずは最初の、データベース(MySQL)への接続部分を見てみようか。 |
![]() |
必要なライブラリをrequireで読み込んで、サーバー名やDB名を指定して接続、だね。 |
![]() |
そうだね。ここまではSequelを使っても、使わなくてもほとんど同じだよ。 |
# encoding: utf-8 require 'sequel' # 1. データベースのパスを指定して、SQLite DBに接続する DB = Sequel.connect('sqlite://testdb.db')
![]() |
ADO.NETなんかと同じように、接続設定を書き変えて、それぞれのデータベース用のドライバを組み込むだけで |
![]() |
そういうこと。 |
![]() |
あとからMySQLを捨ててPostgreSQLに移行したいときも大丈夫! ってことだね。 |
![]() |
あのるびくるさん、一応これWeb上で公開されてる記事なんで、発言には少し気を使っていただけますか? |
# 2. SQL文を実行して、色が「赤」であるデータを名前順に取得する sql = 'SELECT * FROM mascots WHERE color = ? ORDER BY name' rows = DB.query(sql, '赤')
# 2. SQL文を実行して、色が「赤」であるデータを名前順に取得する mascot_dataset = DB[:mascots] mascot_dataset = mascot_dataset.where(:color => '赤').order(:name) rows = mascot_dataset.all
![]() |
さて、今回のメインはここだ。データベースの「mascots」テーブルからデータを取得するコード。 |
![]() |
Sequelを使わない側のコードは、普通にSQL文を実行してデータをとってくる、っていうよくあるコードだよね。 |
![]() |
そうだね。 |
![]() |
で、Sequelを使う側のコードが…… |
![]() |
ここでは、mascotsテーブルからデータを取得するために必要な |
![]() |
データセット? ……って何? |
![]() |
うん、それなんだけど、このデータセットは今回の話の中でもいちばん大きな話題になる。 |
![]() |
はーい。 |
# 3. 取得したデータの内容(name)を表示する rows.each do |row| p row[:name] # => 'るびくる' end
# 3. 取得したデータの内容(name)を表示する mascot_dataset.each do |row| p row[:name] # => 'るびくる' end
![]() |
最後に、さっきデータベースから取得したデータ全件の |
![]() |
この部分のコードは、Sequelを使っても使わなくても、まったく同じなんだね。 |
![]() |
うん、そうだね。 |
![]() |
とすると……Sequelを使った時の一番大きな違いは、さっきのデータセットを使うっていうことなの? |
![]() |
OK。それじゃ、これからSequelのデータセットがどんな役割を持っていて |
![]() |
まずデータセットとは何なのか、について改めて説明しようか。 |
![]() |
SQLクエリをオブジェクトとして表したもの……? |
![]() |
この言い方だと分かりにくいかもしれないけど |
# encoding: utf-8 # Sequelを使って、SQLiteデータベース「test.db」へ接続 require 'sequel' DB = Sequel.connect('sqlite://test.db') # 表示 p DB #=> #<Sequel::SQLite::Database: "sqlite://test.db"> # A. mascotsテーブルからデータを取得する、SQL文の文字列(Stringオブジェクト) mascot_sql = 'SELECT * FROM mascots' # B. mascotsテーブルからデータを取得する、データセット(Datasetオブジェクト) mascot_dataset = DB[:mascots] mascot_dataset = DB.from(:mascots) # DB[:mascots] の別記法 # 表示 p mascot_sql #=> "SELECT * FROM mascots" p mascot_dataset #=> #<Sequel::SQLite::Dataset: "SELECT * FROM `mascots`"> p mascot_dataset.sql #=> "SELECT * FROM `mascots`"
![]() |
Sequelでは、Sequel.connectメソッドを使ってデータベースに接続することで |
![]() |
ふむふむ、なるほど。 |
![]() |
そういうこと。 |
![]() |
で、実際にこのデータセットを元にして、データベースからデータをとってくるにはどうすればいいの? |
![]() |
最初のコードで出てた例と同じで、Dataset#allメソッドを使えばOKだよ。 |
# データセットを作る。まだこの時点ではSQL文を作ってるだけで、実際のデータは取得していない! mascot_dataset = DB[:mascots] # データセットを元に、データベースから実際のデータを取得する rows = mascot_dataset.all # 表示 p rows # 取得した全レコードの配列 (Array) p rows.first # 取得したレコードのうち、1行目の各列のデータを格納したHash puts rows.first[:id] #=> 1 (1行目レコード・id列の値) puts rows.first[:name] #=> るびくる (1行目レコード・name列の値)
![]() |
もしくは、その代わりにDataset#eachメソッドを使って、直接1レコードごとの処理をしてもいい。 |
# データセットを作る。まだこの時点ではSQL文を作ってるだけで、実際のデータは取得していない! mascot_dataset = DB[:mascots] # データセットを元に、データベースから実際のデータを1件ずつ取得しながら処理 mascot_dataset.each do |row| puts row[:id] #=> 1 (レコード・id列の値) puts row[:name] #=> るびくる (レコード・name列の値) end
![]() |
データセットは、作った時点では単なるSQLクエリを表すオブジェクトでしかないから、まだデータの取得はしない。 |
![]() |
へー、「データセット」っていうと、いかにも何かのデータが入ってそうな名前だけど |
![]() |
そうだね。データの入れ物があるだけ、みたいなイメージかな。 |
![]() |
あと、さっきの話を聞いてて思ったんだけど…… |
![]() |
もちろんできるよ。 |
# SQL文からデータセットを作る red = '赤' raw_sql_dataset = DB['SELECT * FROM mascots WHERE color = ?', red] rows = raw_sql_dataset.all
![]() |
もしくは、データセットを作らずに、直接SQLを実行することもできるよ。 |
# SQL文を直接実行してデータ取得 red = '赤' DB.fetch('SELECT * FROM mascots WHERE color = ?', red) do |row| puts row[:id] #=> 1 (レコード・id列の値) puts row[:name] #=> るびくる (レコード・name列の値) end # データを取得しないSQL文の場合は、fetchメソッドの代わりにrunメソッドを使う DB.run "CREATE TABLE users (name VARCHAR(255) NOT NULL, age INT(3) NOT NULL)"
![]() |
なるほど、普段はデータセットをメインで使いつつ |
![]() |
そういうことだね。 |
![]() |
でもさ、今の説明を聞いてて思ったんだけど…… |
![]() |
というと? |
![]() |
たとえばさ、こういう2つのオブジェクトがあるとするじゃない。 |
# A. mascotsテーブルからデータを取得する、SQL文の文字列(Stringオブジェクト) mascot_sql = 'SELECT * FROM mascots' # B. mascotsテーブルからデータを取得する、データセット(Datasetオブジェクト) mascot_dataset = DB[:mascots]
![]() |
RBの話だと、Aのmascot_sqlとBのmascot_datasetが |
![]() |
そうだね。「mascotsテーブルからデータを取得するためのオブジェクト」ってところは同じだね。 |
![]() |
そうすると、そのデータセット?を使ってみたところで |
![]() |
ああなるほど、SQL文をそのまま実行することができるSequelで |
![]() |
そうそう、そういうこと! |
![]() |
そうだね、るびくるの疑問ももっともだ。 |
![]() |
ほかにもまだ、データセットならではの利点があるってことだね? |
![]() |
その通り。具体的にはこの3つだ。
|
![]() |
お、O/Rマッパー……? って何? |
![]() |
Sequelが持ってる大きな機能の1つで、ざっくり言うと |
![]() |
わかりました先生。 |
![]() |
そんな週刊連載みたいな手法使わないよ! 前回パート1が中途半端なところで終わったの、まだ根に持ってるの!? |
![]() |
で、1つ目の利点の |
![]() |
そういうこと。 |
![]() |
データセットを使えば、もうそうしたSQLの種類の違いに悩まされる夜を送らなくてもいいってこと!? |
![]() |
いや、残念ながらそこまでじゃない。 |
![]() |
なーんだ、残念。 |
![]() |
……るびくる、いったいデータベースにどんな思い出があるの? |
![]() |
じゃあ、3つの利点のうち最後に残った利点は、2つ目の |
![]() |
なるほど、確かに今までに見てきたようなサンプルコードだと |
![]() |
さらりとわたしの体重に関する重大なヒントを漏らさないでくれる!? |
def red_girl_mascot_sql select_sql = <<SQL SELECT * FROM mascots WHERE color = '赤' AND sex = '女性' AND weight >= 50.0 ORDER BY name ASC LIMIT 3 SQL return select_sql end
![]() |
こんな感じのSQL文でいい? |
![]() |
ありがとう。 |
def red_girl_mascot_dataset ds = DB[:mascots] ds = ds.where(:color => '赤', :sex => '女性') ds = ds.where{ weight >= 50.0 } ds = ds.order(:name).limit(3) return ds end
![]() |
なるほど、このwhereっていうメソッドで、データセットのSQLに絞り込み条件を追加したり |
![]() |
とはいえ、この時点ではまだSQL文とデータセットの間に、大きな違いはないよね。 |
![]() |
わかった! |
def add_white_cloth_condition(mascot_sql_string) end
![]() |
あとはこのメソッドの、処理の中身を実装して…………あ!! |
![]() |
気がついた? |
![]() |
そっか、SQL文の文字列は、もうLIMIT句まで含めてできあがってるわけだから…… |
def add_white_cloth_condition(mascot_sql_string) cond_sql = "AND cloth_color = '白'" # mascot_sql_stringに対して、どうやれば上のcond_sqlを追加できる? end
![]() |
そう。SQLクエリを、単に文字列で組み立てていこうとすると |
![]() |
考えるだけで脳がショートしそうです。 |
![]() |
そこでデータセットの出番なんだ。 |
# encoding: utf-8 require 'sequel' # SQLiteデータベースをメモリ内に作成して、そのDBに接続 DB = Sequel.sqlite def red_girl_mascot_dataset ds = DB[:mascots] ds = ds.where(:color => '赤', :sex => '女性') ds = ds.where{ weight >= 50.0 } ds = ds.order(:name).limit(3) return ds end # 文字列ではなく、データセットを受け取るように変更 def add_white_cloth_condition(mascot_dataset) ds = mascot_dataset.where(:cloth_color => '白') return ds end # メソッドの実行サンプル ds = red_girl_mascot_dataset # マスコットを検索するデータセット取得 puts ds.sql #=> "SELECT * FROM `mascots` WHERE ((`color` = '赤') AND (`sex` = '女性') AND (`weight` >= 50.0)) ORDER BY `name` LIMIT 3" ds = add_white_cloth_condition(ds) # そのデータセットに対して、さらに条件を追加! puts ds.sql #=> "SELECT * FROM `mascots` WHERE ((`color` = '赤') AND (`sex` = '女性') AND (`weight` >= 50.0) AND (`cloth_color` = '白')) ORDER BY `name` LIMIT 3"
![]() |
すごい! スムーズに絞り込み条件を追加できてる! |
![]() |
その通り! これこそがデータセット、ひいてはSequelの大きな強みなんだ。 |
![]() |
さて、ここまでSequelの概要について説明してきたんだけど…… |
![]() |
はーい、お願いします! |
![]() |
まとめると、Sequelはこの4つの役割をしてくれるデータベース・ツールキット(ライブラリ)なんだ。
|
![]() |
あれ!? 役割4にさりげなく聞いたことのない単語が出てます! 先生! |
![]() |
これもO/Rマッパーと同じで、重要な機能なんだけど、やっぱり今回のうちに話せる規模じゃないのでパス。 |
![]() |
これも次回以降!? タイトルが「概要編」ってなってるのに、まだ4つある役割のうち2つまでしか説明されてな |
![]() |
それではみなさん、2013年もよろしくお願いします! |
![]() |
先生ーーー!! 「概要編」っていったいなんだったんですか先生ーーー!!! |
パート3:基本的な使い方編に続きます。