S-JIS[2011-01-16] 変更履歴

Scala SELECT文(dbc)

ScaladbcのDatabase.executeStatementメソッド(SELECT文)のメモ。


SELECT 〜 FROM テーブル

テーブルの特定項目を全レコード取得する例。

import scala.dbc._
import scala.dbc.syntax.DataTypeUtil._ //DBのデータ型
import scala.dbc.syntax.Statement._    //select等のステートメント
import java.net.URI
    val db = new Database(new Oracle(new URI("jdbc:oracle:thin:@localhost:1521:ora92"), "scott", "tiger"))
    try {
      //●SQLの構築
      val st = select fields {
          ("EMPNO" of numeric(4)) and
          ("ENAME" of characterVarying(10))
        } from "EMP"

      //●SQLの実行
      val rows = db.executeStatement(st)

      //●結果の表示
      for (row <- rows) {
        for (f <- row.fields) {
          println(f.metadata.name + "=" + f.content.sqlString)
        }
      }

    } finally {
      db.close
    }

SQLの構築

ぱっと見て分かる通り、普通のSQLのSELECT文とよく似ている。

select EMPNO, ENAME from EMP;
      val st = select fields {
          ("EMPNO" of numeric(4)) and
          ("ENAME" of characterVarying(10))
        } from "EMP"

fieldsの中に取得したい項目(カラム)を列挙する。複数項目あるときはandでつなぐ。
項目名の他に、データ型も指定する。桁数は、実際の値が入り切る大きさであれば良いようだ。

仕組みはどうなっているかと言うと、まず、selectは「scala.dbc.Statement.select」というオブジェクト。
最初に「import scala.dbc.Statement._」でStatementオブジェクト内に定義されているオブジェクトを暗黙にインポートしているので、selectオブジェクトが使える。

次の「fields{ 〜 }」は、selectオブジェクトのfields()メソッド呼び出し。Scalaでは引数が1つの時は丸括弧以外に波括弧が使えるので、それを利用している。
また、Scalaではメソッド呼び出し時のピリオドが省略できるので、selectとfieldsの間のピリオドが無い。
「of」はSelectDerivedFieldというクラスのメソッド。文字列からSelectDerivedFieldへの暗黙変換がStatementオブジェクト内に定義されているので、項目名(String)からof()メソッドが呼び出せる。ついでに、Scalaでは引数が1つの時は括弧を省略できるので、of()の括弧は省略されている。
「numeric」や「characterVarying」はDataTypeUtilオブジェクト内で定義されているメソッドで、これも最初にインポートしている。
つまり「"EMPNO" of numeric(4)」は、「Statement.stringToSelectDerivedField("EMPNO").of(DataTypeUtil.numeric(4))」と等しい。

「from "EMP"」は「from("EMP")」と同じ。
また、from()はSelectOfというクラスのメソッドであり、fields()メソッドの戻り値がSelectOfになっている。


ここまで通常のSQL文に似せるなら、複数項目を列挙するのも、andで結ぶのではなくカンマ区切りにして欲しかったところだ(笑)
今のfields()メソッドは引数が1つだが、可変長引数を使って複数の引数を取れるようにすれば出来ると思うのだが…。

また、fields()メソッドを使うのではなく、selectオブジェクトにapply()メソッドを用意するか、
いっそのことselectオブジェクトをやめてselectメソッドにしてしまえば、fields抜きでselectの直後に項目を列挙できたんじゃないかなぁ?


実行結果の取得

DatabaseクラスのexecuteStatement()メソッドを呼び出すと、実行結果が返ってくる。

      for (row <- rows) {
        for (f <- row.fields) {
          println(f.metadata.name + "=" + f.content.sqlString)
        }
      }

rowsは実行結果の全体、つまり複数行を表す。
rowはその内の1行(1レコード)。
fは行(レコード)内の各フィールドとなる。型はscala.dbc.result.Fieldクラス。
Fieldクラスのmetadataメソッドで文字通りメタデータ(項目名やデータ型を保持している)が取得でき、contentメソッドでデータ(値)が取得できる。
また、Fieldオブジェクトに暗黙変換メソッドが定義されているので、「f.content.sqlString」は「f.sqlString」と書くことも出来る。

ただ、sqlStringメソッドは文字データではシングルクォーテーションで囲まれた値が返ってくる
(きっと、Scalaのプログラム内で使う為のデータを取得する目的ではなく、SQL文を生成するための文字列を返すメソッドなのだろう)
contentもしくはvalueメソッドで返るValueクラスのnativeValueフィールドで値を取得し、さらに目的の型にキャストしてやれば通常のScalaで使える型になる。
暗黙変換があるので、Fieldに直接nativeValueを適用することも出来る)

ちなみに項目毎にデータ型は異なるので、row.fieldsで一括して扱うわけにはいかない。
row(scala.dbc.result.Tupleクラス)にはapply()メソッドがあるので、項目名(もしくはインデックス番号)からフィールドを取得できる。

      for (row <- rows) {
//      val f = row.apply("EMPNO")
//      val f = row.apply(0)
//      val f = row("EMPNO")
//      val f = row(0)

//      val empno = row("EMPNO").content.nativeValue.asInstanceOf[Int]
//      val empno = row("EMPNO").value.  nativeValue.asInstanceOf[Int]
        val empno = row("EMPNO").        nativeValue.asInstanceOf[Int]

//      val ename = row("ENAME").content.nativeValue.asInstanceOf[String]
//      val ename = row("ENAME").value.  nativeValue.asInstanceOf[String]
        val ename = row("ENAME").        nativeValue.asInstanceOf[String]

        println(empno + ":" + ename)
      }

使用できるデータ型

Scala2.8ではdbcは未完成なので、使えるデータ型は限られている。

DBのデータ型 dbcのDataTypeUtil 備考
number(n) numeric(n) 数値
number(n,m) numeric(n,m) 数値(Oracle9iだと駄目)
char(n) character(n) 固定長文字列
varchar2(n) characterVarying(n) 可変長文字列
date
time
timestamp
  使えない。

JDBCのデータ型からScalaのdbcの型への変換は、scala.dbc.datatype.Factoryオブジェクトが行っている。
対応していないデータ型を扱おうとした場合、この中で例外が発生する。

↓DATE型のデータをSELECTしようとした時のエラー

Exception in thread "main" java.lang.RuntimeException: I don't support date and time data types yet.
	at scala.Predef$.error(Predef.scala:58)
	at scala.dbc.datatype.Factory$.create(Factory.scala:245)
	at scala.dbc.result.Relation$$anonfun$metadata$1$$anon$2.<init>(Relation.scala:34)
	at scala.dbc.result.Relation$$anonfun$metadata$1.apply(Relation.scala:31)
	at scala.dbc.result.Relation$$anonfun$metadata$1.apply(Relation.scala:30)
	at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:206)
	at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:206)
	at scala.collection.LinearSeqOptimized$class.foreach(LinearSeqOptimized.scala:61)
	at scala.collection.immutable.List.foreach(List.scala:45)
	at scala.collection.TraversableLike$class.map(TraversableLike.scala:206)
	at scala.collection.immutable.List.map(List.scala:45)
	at scala.dbc.result.Relation.metadata(Relation.scala:30)
	at scala.dbc.statement.Relation.typeCheck(Relation.scala:23)
	at scala.dbc.Database$$anon$1.<init>(Database.scala:117)
	at scala.dbc.Database.executeStatement(Database.scala:111)
	at scala.dbc.Database.executeStatement(Database.scala:101)

WHERE条件

WHERE条件の指定方法はちょっと残念なことになってる気がする。

select EMPNO, ENAME from EMP
where SAL >= 13000
;
import scala.dbc.syntax.StatementExpression._

      val st = select fields {
          ("EMPNO" of numeric(4)) and
          ("ENAME" of characterVarying(10))
        } from "EMP" where {
          stringToStatementField("SAL") >= "13000"
        }

from()メソッドが返すSelectBeyondクラスにwhere()メソッドがある。
where()メソッドの引数はStatementExpressionクラスで、そこには「>=」だの「==」だの「like」だのといった、WHERE条件に使える演算子が定義されている。
項目名(String)からStatementField(StatementExpressionを継承している)を生成するにはstringToStatementField()メソッドを使う。

stringToStatementField()メソッドは実は暗黙変換用のメソッドなのだが、残念なことに、Stringから変換可能な「>=」メソッドが使えるクラスにはStringOpsも存在する。
暗黙変換の候補が2つ以上ある時はコンパイルエラーとなるので、ここでは暗黙変換が使えない。
なので、自分で明示的にstringToStatementField()を呼び出すしかない。

逆に言えば、StringOpsで定義されていない(他の暗黙変換候補のクラスで定義されていない)メソッドであれば、暗黙変換が使用できる。

        } from "EMP" where {
          "ENAME" like "'K%'"
        }

GROUP BY

WHERE条件と同様にGROUP BY句も記述できる。

select DEPTNO, sum(SAL)
from EMP
group by DEPTNO
;
  val st = select fields {
      ("DEPTNO" of numeric(2)) and
      ("sum(SAL)" of numeric(7,2))
    } from "EMP" groupBy {
      "DEPTNO"
    }

これでコンパイルは通るからOKだと思うのだが、Oracle9iではnumeric(7,2)がちゃんと変換できないらしくて、実行時に例外が発生する(苦笑)


Scala dbcへ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま