6

次のように構築されたPL/SQLクエリがあります。

DECLARE
a NUMBER;
B NUMBER;
CURSOR cursor
IS
 ( SOME SELECT QUERY);
BEGIN
  OPEN cursor;
    LOOP
    SOME STUFF;
    END LOOP;
  CLOSE cursor;
END

jdbc を使用して Java コードからこのクエリを実行し、結果セットを取得するにはどうすればよいですか? カーソルを使用せずにクエリを実行しようとしましたが、正しく実行されています。Javaコードでこれを行う方法がわかりませんでした。Oracle クライアントで直接クエリを実行すると、問題なく動作します。したがって、クエリに問題はありません。

PSコードをストアドプロシージャとして保存し、いくつかの制約のためにそれを呼び出したくありません。

4

4 に答える 4

5

これは不可能です。無名PL/SQLブロックから結果セットを返すことはできません(したがって、JDBCから結果セットを取得する方法はありません)。

JDBC から直接選択を実行する必要があります。

唯一の、本当に醜い回避策は、使用dbms_output.put_line()して後でそれを読むことです。しかし、これは非常に厄介なハックであり、SELECT クエリの結果を JDBC で直接処理する方がはるかに優れています。


編集 1

dbms_output を使用した小さな例を次に示します。

Connection con = ....;

// turn on support for dbms_output
CallableStatement cstmt = con.prepareCall("{call dbms_output.enable(32000) }");
cstmt.execute();

// run your PL/SQL block
Statement stmt = con.createStatement();
String sql =
    "declare  \n" +
    " a number;  \n" +
    " cursor c1 is select id from foo;  \n" +
    "begin  \n" +
    "  open c1; \n" +
    "  loop \n" +
    "    fetch c1 into a;  \n" +
    "    exit when c1%notfound;  \n" +
    "    dbms_output.put_line('ID: '||to_char(a)); \n" +
    "  end loop; \n" +
    "end;";
stmt.execute(sql);

// retrieve the messages written with dbms_output
cstmt = con.prepareCall("{call dbms_output.get_line(?,?)}");
cstmt.registerOutParameter(1,java.sql.Types.VARCHAR);
cstmt.registerOutParameter(2,java.sql.Types.NUMERIC);

int status = 0;
while (status == 0)
{
    cstmt.execute();
    String line = cstmt.getString(1);
    status = cstmt.getInt(2);
    if (line != null && status == 0)
    {
        System.out.println(line);
    }
}

編集 2 (これはコメントには長すぎます)

データを取得するためにループをネストすることは、ほとんどの場合、悪い考えです。あなたがこのようなことをしているのを見つけたら:

begin
  for data_1 in (select id from foo_1) loop
    dbms_output.put_line(to_char(data_1.id));

    for data_2 in (select f2.col1, f2.col2 from foo_2 f2 where f2.id = data_1.id) loop
        ... do something else
    end loop;

  end loop;
end;
/

次のようにすると、はるかに効率的になります。

begin
  for data_1 in (select f2.col1, f2.col2 from foo_2 f2
                 where f2.id in (select f1.id from foo_1 f1)) loop

     ... do something

  end loop;
end;
/

これは、次のようなものを使用して、JDBC で過剰なメモリなしで処理できます。

String sql = "select f2.col1, f2.col2 from foo_2 f2 where f2.id in (select f1.id from foo_1 f1)";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next())
{
   String col1_value = rs.getString(1);
   int    col2_value = rs.getInt(2);
   ... do something
}

上記のコードは、数十億行を処理する場合でも、メモリ内に 1 行しか保持しません。正確に言うと、JDBC ドライバーは実際には複数の行をプリフェッチします。デフォルトは 10 で、変更できます。それでも、過度のメモリ使用量はありません。

于 2013-04-16T21:18:26.573 に答える
5

ここでの他の答えは非常に複雑に思えます。

使用するSYS_REFCURSOR

ずっと前SYS_REFCURSORから、JDBC から型を非常に簡単に取得できました。

DECLARE
  cur SYS_REFCURSOR;
BEGIN
  OPEN cur FOR SELECT ...;
  ? := cur;
END;

上記を Java から次のように実行します。

try (CallableStatement c = con.prepareCall(sql)) {
    c.registerOutParameter(1, OracleTypes.CURSOR); // -10
    c.execute();

    try (ResultSet rs = (ResultSet) c.getObject(1)) {
        ...
    }
}

もちろん、pmr's answerで提案されているように、パッケージで独自のカーソルを宣言することもできますが、JDBC から匿名ブロックを実行している場合、なぜそうするのでしょうか?

Oracle 12c の暗黙的な結果セットの使用

Oracle 12c では、これらの場合に便利な新機能が追加されました。これは、SQL Server / Sybase および MySQL が結果を返すプロシージャ / バッチについて考える方法に似ています。DBMS_SQL.RETURN_RESULT任意のカーソルでプロシージャを使用できるようになり、 「魔法によって」返されます。

DECLARE
  cur SYS_REFCURSOR;
BEGIN
  OPEN cur FOR SELECT ...;
  DBMS_SQL.RETURN_RESULT(cur);
END;

Oracle JDBC ドライバーのバグ (または「機能」) により、JDBCからそのカーソルを正しく取得するのは少し難しいですが、この記事で示したように確実に実行できます。これは、無名の PL/SQL ブロックやプロシージャ、トリガーなどから任意の数の暗黙カーソルを検出する方法です。

try (PreparedStatement s = cn.prepareStatement(sql)) {
    // Use good old three-valued boolean logic
    Boolean result = s.execute();

    fetchLoop:
    for (int i = 0;; i++) {

        // Check for more results if not already done in this iteration
        if (i > 0 && result == null)
            result = s.getMoreResults();
        System.out.println(result);

        if (result) {
            result = null;

            try (ResultSet rs = s.getResultSet()) {
                System.out.println("Fetching result " + i);
            }
            catch (SQLException e) {
                // Ignore ORA-17283: No resultset available
                if (e.getErrorCode() == 17283)
                    continue fetchLoop;
                else
                    throw e;
            }
        }
        else if (s.getUpdateCount() == -1)
            // Ignore -1 value if there is one more result!
            if (result = s.getMoreResults())
                continue fetchLoop;
            else
                break fetchLoop;
    }
}
于 2017-12-19T15:22:34.943 に答える
0

java.sql.PreparedStatement.execute() のシグネチャは「Boolean execute()」ではなく「boolean execute()」であるため、s.execute( )、したがって、テスト「i>0 && result==null」は「result==null」になる可能性があります

于 2018-01-05T16:14:28.623 に答える