已解决(请参见下面的答案。)
我没有在适当的背景下理解我的问题。真正的问题是我的查询返回了多个ResultSet对象,而我之前从未遇到过。我在下面发布了解决问题的代码。
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(); } }
我想到了。
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集的方法。那就是上面的代码所做的。