Использование курсора API базы данных с JDBC и SQLServer для выбора результатов пакета

1

РЕШЕНИЕ (см. Ответ ниже).

Я не понял свою проблему в правильном контексте. Реальная проблема заключалась в том, что мой запрос возвращал несколько объектов ResultSet, и я никогда раньше не сталкивался с этим. Я опубликовал код ниже, который решает проблему.


ПРОБЛЕМА

У меня есть таблица базы данных SQL Server со многими тысячами строк. Моя цель - вытащить данные из исходной базы данных и записать их во вторую базу данных. Из-за ограничений памяти приложения я не смогу сразу вернуть данные. Кроме того, из-за этой конкретной схемы таблиц (над которой я не контролирую), мне не удастся пометить строки, используя какой-то столбец идентификатора.

Джентльмен в администраторах баз данных StackExchange помог мне, собрав что-то, называемое курсором API базы данных, и в основном написал этот сложный запрос, в который мне нужно только отбросить мое утверждение. Когда я запускаю запрос в SQL Management Studio (SSMS), он отлично работает. Я возвращаю все данные за тысячу строк за раз.

К сожалению, когда я пытаюсь перевести это в JDBC-код, я возвращаю только первую тысячу строк.

ВОПРОС

Можно ли использовать JDBC для извлечения курсора API базы данных, вытащить из него первый набор строк, позволить курсору продвигаться, а затем вытаскивать последующие наборы по одному за раз? (В этом случае тысяча строк за раз).

SQL CODE

Это осложняется, поэтому я собираюсь разбить его.

Фактический запрос может быть простым или сложным. Это не имеет значения. Я пробовал несколько разных запросов во время моих экспериментов, и все они работают. Вы просто в основном бросаете его в код SQL в нужном месте. Итак, допустим, что этот простой оператор является нашим запросом:

SELECT MyColumn FROM MyTable; 

Фактический курсор API базы данных SQL намного сложнее. Я распечатаю его ниже. Вы можете видеть, что приведенный выше запрос похож на него:

-- 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"));
}

Я не удивлен результатами, но все варианты, которые я пробовал, дают одинаковые результаты.

Из моих исследований кажется, что JDBC делает что-то с курсорами базы данных, когда база данных является Oracle, но вы должны установить тип данных, возвращаемый в результирующем наборе, как объект курсора 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();
    }
}
  • 1
    Если вы тянете из одной таблицы, почему бы не использовать «select * from tableName» в вашем JDBC. Затем извлеките каждую строку и вставьте ее в целевую базу данных, используя пакетные обновления JDBC. Каждую тысячу строк или около того очищайте выходной пакет. Но не нужно ничего делать для ввода: просто выберите * Может быть, я не понимаю какой-то аспект вашей проблемы?
  • 0
    Я нашел следующее: tutorials.jenkov.com/jdbc/batchupdate.html . Он гласит: «Вы можете пакетно вставлять и обновлять SQL, и обновлять, и удалять. Нет смысла группировать операторы select». Я полагаю, что пакетные обновления предполагают, что вы отправляете данные партиями. Я хочу, чтобы база данных отправляла мне данные партиями. Если я не ошибаюсь, я считаю, что это то, что в мире баз данных называется «подкачкой страниц». (Ссылка на то место, откуда исходит курсор-запрос, который я использую, объясняет проблему более подробно.) Я считаю, что проблема решена. Благодарю.
Теги:
sql-server
jdbc
cursor

2 ответа

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();
}

После некоторого дополнительного поиска в Google я нашел немного кода, опубликованного еще в 2004 году:

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

Джентльмен, который опубликовал фрагмент, который я нашел полезным (Джулиан Кеннеди), предложил: "Прочтите Javadoc для getUpdateCount() и getMoreResults() для четкого понимания". Мне удалось собрать это вместе.

В принципе, я не думаю, что я правильно понял свою проблему с самого начала, чтобы правильно ее сформулировать. Дело в том, что мой запрос будет возвращать данные в нескольких экземплярах ResultSet. Мне нужен был способ не просто перебирать каждую строку в ResultSet, а скорее перебирать весь набор ResultSets. Это то, что делает код выше.

1

Если вам нужны все записи из таблицы, просто выберите "Выбрать * из таблицы".

Единственная причина для извлечения в кусках - если есть какое-то промежуточное место для данных: например, если вы показываете его на экране или сохраняете в памяти.

Если вы просто читаете с одного и вставляете в другое, просто читайте все, начиная с первого. Вы не получите никакой лучшей производительности, пытаясь получить в партиях. Если есть разница, это будет отрицательным. Разорвите свой запрос таким образом, чтобы вернуть все. Программное обеспечение JDBC будет обрабатывать все остальные расцепители и восстановление, которые вам нужны.

Тем не менее, вы должны выполнить пакет обновления/вставки.

Настройка создаст два оператора для двух соединений:

Statement stmt = null;
ResultSet rs = null;
PreparedStatement insStmt = null;

stmt = conDb1.createStatement();
insStmt = conDb2.prepareStament("insert into tgt_db2_table (?,?,?,?,?......etc. ?,?) ");
rs = stmt.executeQuery("select * from src_db1_table");

Затем зациклируйте над выбором как обычно, но используйте доработку на цели.

    int batchedRecordCount = 0;
    while (rs.next()) {
        System.out.println(rs.getString("MyColumn"));

        //Here you read values from the cursor and set them to the insStmt ...
        String field1 = rs.getString(1);
        String field2 = rs.getString(2);
        int field3 = rs.getInt(3);
        //--- etc. 

        insStmt.setString(1, field1);
        insStmt.setString(2, field2);
        insStmt.setInt(3, field3);

        //----- etc. for all the fields

        batchedRecordCount++;
        insStmt.addBatch();
        if (batchRecordCount > 1000) {
          insStmt.executeBatch();
        }
    }
    if (batchRecordCount > 0) {
       //Finish of the final (partial) set of records
       insStmt.executeBatch();
    }

    //Close resources...
  • 0
    Я понимаю, что мое первоначальное описание было немного многословным, но в нем я отметил, что проблема не в производительности, а в «ограничениях памяти приложения». Некоторые базы данных могут содержать десятки миллионов строк в этой конкретной таблице, из которой я извлекаю. Заказчик попросил приложение работать с «нормальной» памятью и использованием сети. Таким образом, я должен извлекать партиями. Однако, спасибо.
  • 0
    Вы лучше знаете свою среду, но если вы просто вытягиваете и вставляете, я не вижу, где используется память, даже если у вас есть миллиарды строк. Вы имеете в виду, что сам процесс базы данных будет использовать слишком много памяти с запросом, который возвращает все обратно? Или вы имеете в виду вашу программу?
Показать ещё 8 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню