web-dev-qa-db-ja.com

SQL ResultSetをScala Streamのように扱う

データベースにクエリを送信して(転送のみ、読み取り専用)ResultSetを受け取ると、ResultSetはデータベース行のリストのように機能します。

このResultSetをScala Streamとして扱う方法を見つけようとしています。これにより、filtermap、など、大量のRAMを消費しません。

個々の項目を抽出するために末尾再帰メソッドを実装しましたが、これにはすべての項目が同時にメモリ内にある必要があり、ResultSetが非常に大きい場合に問題が発生します。

// Iterate through the result set and gather all of the String values into a list
// then return that list
@tailrec
def loop(resultSet: ResultSet,
         accumulator: List[String] = List()): List[String] = {
  if (!resultSet.next) accumulator.reverse
  else {
    val value = resultSet.getString(1)
    loop(resultSet, value +: accumulator)
  }
}
44
Ralph

私はそれをテストしませんでした、しかしそれはなぜうまくいかないのですか?

new Iterator[String] {
  def hasNext = resultSet.next()
  def next() = resultSet.getString(1)
}.toStream
75
elbowich

@elbowichの回答のユーティリティ関数:

def results[T](resultSet: ResultSet)(f: ResultSet => T) = {
  new Iterator[T] {
    def hasNext = resultSet.next()
    def next() = f(resultSet)
  }
}

型推論を使用できます。例えば。:

stmt.execute("SELECT mystr, myint FROM mytable")

// Example 1:
val it = results(stmt.resultSet) {
  case rs => rs.getString(1) -> 100 * rs.getInt(2)
}
val m = it.toMap // Map[String, Int]

// Example 2:
val it = results(stmt.resultSet)(_.getString(1))
11
hraban

これは暗黙のクラスにとって絶好の機会のように思えます。まず、暗黙のクラスをどこかに定義します。

import Java.sql.ResultSet

object Implicits {

    implicit class ResultSetStream(resultSet: ResultSet) {

        def toStream: Stream[ResultSet] = {
            new Iterator[ResultSet] {
                def hasNext = resultSet.next()

                def next() = resultSet
            }.toStream
        }
    }
}

次に、クエリを実行してResultSetオブジェクトを定義した場所に、この暗黙のクラスをインポートします。

import com.company.Implicits._

最後に、toStreamメソッドを使用してデータを取得します。たとえば、次のようにすべてのIDを取得します。

val allIds = resultSet.toStream.map(result => result.getInt("id"))
8
Jeroen Minnaert

私は似たようなものが必要でした。エルボウィッチの非常に優れた答えに基づいて、私はそれを少しラップし、文字列の代わりに結果を返します(したがって、任意の列を取得できます)

def resultSetItr(resultSet: ResultSet): Stream[ResultSet] = {
    new Iterator[ResultSet] {
      def hasNext = resultSet.next()
      def next() = resultSet
    }.toStream
  }

テーブルのメタデータにアクセスする必要がありましたが、これはテーブルの行に対して機能します(md.getColumnsの代わりにstmt.executeQuery(sql)を実行できます)。

 val md = connection.getMetaData()
 val columnItr = resultSetItr( md.getColumns(null, null, "MyTable", null))
      val columns = columnItr.map(col => {
        val columnType = col.getString("TYPE_NAME")
        val columnName = col.getString("COLUMN_NAME")
        val columnSize = col.getString("COLUMN_SIZE")
        new Column(columnName, columnType, columnSize.toInt, false)
      })
3
Greg

ResultSetはnextによってナビゲートされる単なる可変オブジェクトなので、次の行の独自の概念を定義する必要があります。次のように入力関数を使用してこれを行うことができます。

class ResultSetIterator[T](rs: ResultSet, nextRowFunc: ResultSet => T) 
extends Iterator[T] {

  private var nextVal: Option[T] = None

  override def hasNext: Boolean = {
    val ret = rs.next()
    if(ret) {
      nextVal = Some(nextRowFunc(rs))
    } else {
      nextVal = None
    }
    ret
  }

  override def next(): T = nextVal.getOrElse { 
    hasNext 
    nextVal.getOrElse( throw new ResultSetIteratorOutOfBoundsException 
  )}

  class ResultSetIteratorOutOfBoundsException extends Exception("ResultSetIterator reached end of list and next can no longer be called. hasNext should return false.")
}

編集:上記のようにストリームまたは何か他のものに翻訳します。

2
Brendan

Iterator契約を尊重してhasNextに副作用がないソリューションが必要な場合のための、Sergey Alaevおよびthoredgeのソリューションと同様の代替案を次に示します。

関数を想定するとf: ResultSet => T

Iterator.unfold(resultSet.next()) { hasNext =>
  Option.when(hasNext)(f(resultSet), resultSet.next())
}

mapResultSet "拡張メソッド"を含めると便利です。

implicit class ResultSetOps(resultSet: ResultSet) {
    def map[T](f: ResultSet => T): Iterator[T] = {
      Iterator.unfold(resultSet.next()) { hasNext =>
        Option.when(hasNext)(f(resultSet), resultSet.next())
      }
    }
  }
0
henko

この実装は、より長くて扱いにくいですが、ResultSetコントラクトとの対応が優れています。副作用はhasNext(...)から削除され、next()に移動されました。

new Iterator[String] {
  private var available = resultSet.next()
  override def hasNext: Boolean = available
  override def next(): String = {
    val string = resultSet.getString(1)
    available = resultSet.next()
    string
  }
}
0
thoredge

上記の実装のほとんどには、非決定的なhasNextメソッドがあると思います。 2回呼び出すと、カーソルが2行目に移動します。私はそのようなものを使うことを勧めます:

  new Iterator[ResultSet] {
    def hasNext = {
      !resultSet.isLast
    }
    def next() = {
      resultSet.next()
      resultSet
    }
  }
0
Matzz
Iterator.continually(rs.next())
  .takeWhile(identity)
  .map(_ => Model(
      id = rs.getInt("id"),
      text = rs.getString("text")
   ))
0
Sergey Alaev