小编典典

将数据库API游标与JDBC和SQLServer结合使用以选择批处理结果

sql

已解决(请参见下面的答案。)

我没有在适当的背景下理解我的问题。真正的问题是我的查询返回了多个ResultSet对象,而我之前从未遇到过。我在下面发布了解决问题的代码。


问题

我有一个包含数千行的SQL
Server数据库表。我的目标是从源数据库中拉回数据并将其写入第二个数据库。由于应用程序内存的限制,我将无法一次全部拉回数据。另外,由于该特定表的模式(我无法控制),因此我没有一种使用某种ID列来剔除行的好方法。

数据库管理员StackExchange的一位绅士通过组合一个称为数据库API游标的东西帮助了我,并基本上编写了这个复杂的查询,我只需要将语句放入其中即可。当我在SQL
Management Studio(SSMS)中运行查询时,它的效果很好。我取回所有数据,一次一千行。

不幸的是,当我尝试将其转换为JDBC代码时,我只获得了前几千行。

问题

是否可以使用JDBC检索数据库API游标,从中拉出第一行,允许游标前进,然后一次拉出后一组?(在这种情况下,一次执行一千行。)

SQL代码

这变得很复杂,因此我将对其进行分解。

实际查询可以是简单的也可以是复杂的。没关系
我在实验过程中尝试了几种不同的查询,它们都可以工作。您只需将其放入适当位置的SQL代码中即可。因此,让我们将此简单语句作为查询:

SELECT MyColumn FROM MyTable;

实际的SQL数据库API游标要复杂得多。我将在下面打印出来。您可以看到上面的查询隐藏在其中:

-- http://dba.stackexchange.com/a/82806
DECLARE @cur INTEGER
    ,
    -- FAST_FORWARD | AUTO_FETCH | AUTO_CLOSE
    @scrollopt INTEGER = 16 | 8192 | 16384
    ,
    -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE
    @ccopt INTEGER = 1 | 32768 | 65536
    ,@rowcount INTEGER = 1000
    ,@rc INTEGER;

-- Open the cursor and return the first 1,000 rows
EXECUTE @rc = sys.sp_cursoropen @cur OUTPUT
    ,'SELECT MyColumn FROM MyTable'
    ,@scrollopt OUTPUT
    ,@ccopt OUTPUT
    ,@rowcount OUTPUT;

IF @rc <> 16 -- FastForward cursor automatically closed
BEGIN
    -- Name the cursor so we can use CURSOR_STATUS
    EXECUTE sys.sp_cursoroption @cur
        ,2
        ,'MyCursorName';

    -- Until the cursor auto-closes
    WHILE CURSOR_STATUS('global', 'MyCursorName') = 1
    BEGIN
        EXECUTE sys.sp_cursorfetch @cur
            ,2
            ,0
            ,1000;
    END;
END;

就像我说过的那样,上面的代码在数据库中创建了一个游标,并要求数据库执行该语句,(内部)跟踪返回的数据,并一次返回一千行。效果很好。

JDBC代码

这就是我遇到的问题。我的Java代码没有编译问题或运行时问题。我遇到的问题是它仅返回前一千行。我不明白如何正确利用数据库游标。我已经尝试了Java基础的各种变化:

// Hoping to get all of the data, but I only get the first thousand.
ResultSet rs = stmt.executeQuery(fq.getQuery());
while (rs.next()) {
    System.out.println(rs.getString("MyColumn"));
}

我对结果并不感到惊讶,但是我尝试过的所有变体都会产生相同的结果。

从我的研究看来,当数据库为Oracle时,JDBC似乎对数据库游标做了一些操作,但是您必须将结果集中返回的数据类型设置为Oracle游标对象。我猜想SQL
Server有一些类似的东西,但是我还找不到任何东西。

有人知道吗?

我完整地包含了示例Java代码(这很丑陋)。

// FancyQuery.java

import java.sql.*;

public class FancyQuery {

    // Adapted from http://dba.stackexchange.com/a/82806
    String query = "DECLARE @cur INTEGER\n"
                 + "    ,\n"
                 + "    -- FAST_FORWARD | AUTO_FETCH | AUTO_CLOSE\n"
                 + "    @scrollopt INTEGER = 16 | 8192 | 16384\n"
                 + "    ,\n"
                 + "    -- READ_ONLY, CHECK_ACCEPTED_OPTS, READ_ONLY_ACCEPTABLE\n"
                 + "    @ccopt INTEGER = 1 | 32768 | 65536\n"
                 + "    ,@rowcount INTEGER = 1000\n"
                 + "    ,@rc INTEGER;\n"
                 + "\n"
                 + "-- Open the cursor and return the first 1,000 rows\n"
                 + "EXECUTE @rc = sys.sp_cursoropen @cur OUTPUT\n"
                 + "    ,'SELECT MyColumn FROM MyTable;'\n"
                 + "    ,@scrollopt OUTPUT\n"
                 + "    ,@ccopt OUTPUT\n"
                 + "    ,@rowcount OUTPUT;\n"
                 + "    \n"
                 + "IF @rc <> 16 -- FastForward cursor automatically closed\n"
                 + "BEGIN\n"
                 + "    -- Name the cursor so we can use CURSOR_STATUS\n"
                 + "    EXECUTE sys.sp_cursoroption @cur\n"
                 + "        ,2\n"
                 + "        ,'MyCursorName';\n"
                 + "\n"
                 + "    -- Until the cursor auto-closes\n"
                 + "    WHILE CURSOR_STATUS('global', 'MyCursorName') = 1\n"
                 + "    BEGIN\n"
                 + "        EXECUTE sys.sp_cursorfetch @cur\n"
                 + "            ,2\n"
                 + "            ,0\n"
                 + "            ,1000;\n"
                 + "    END;\n"
                 + "END;\n";

    public String getQuery() {
        return this.query;
    }

    public static void main(String[ ] args) throws Exception {

        String dbUrl = "jdbc:sqlserver://tc-sqlserver:1433;database=MyBigDatabase";
        String user = "mario";
        String password = "p@ssw0rd";
        String driver = "com.microsoft.sqlserver.jdbc.SQLServerDriver";

        FancyQuery fq = new FancyQuery();

        Class.forName(driver);

        Connection conn = DriverManager.getConnection(dbUrl, user, password);
        Statement stmt = conn.createStatement();

        // We expect to get 1,000 rows at a time.
        ResultSet rs = stmt.executeQuery(fq.getQuery());
        while (rs.next()) {
            System.out.println(rs.getString("MyColumn"));
        }

        // Alas, we've only gotten 1,000 rows, total.

        rs.close();
        stmt.close();
        conn.close();
    }
}

阅读 240

收藏
2021-05-16

共1个答案

小编典典

我想到了。

stmt.execute(fq.getQuery());

ResultSet rs = null;

for (;;) {
    rs = stmt.getResultSet();
    while (rs.next()) {
        System.out.println(rs.getString("MyColumn"));
    }
    if ((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1)) {
        break;
    }
}

if (rs != null) {
    rs.close();
}

经过更多的谷歌搜索之后,我发现了一些早在2004年发布的代码:

http://www.coderanch.com/t/300865/JDBC/databases/SQL-Server-JDBC-Registering-
cursor

张贴了我发现有用的代码片段的绅士(朱利安·肯尼迪)建议:“阅读Javadoc的getUpdateCount()和getMoreResults()可以清楚地理解。”
这样我就可以将其拼凑起来。

基本上,我认为我从一开始就不太了解我的问题,无法正确地表达它。归结为,我的查询将在多个ResultSet实例中返回数据。我需要的是一种不仅遍历ResultSet中的每一行,而且遍历整个ResultSet集的方法。那就是上面的代码所做的。

2021-05-16