2015-07-27 4 views
2

Я пытаюсь вставить несколько слов в базу данных и вернуть вновь вставленный идентификатор или существующий идентификатор, если это слово уже находится в базе данных.Как вставить (или существующие) идентификаторы с executeBatch()

Я нашел, что могу сделать это, используя PreparedStatement и включая Statement.RETURN_GENERATED_KEYS. Но PreparedStatement ужасно медленный. Мне нужно вставить как 5000 слов сразу. Другой способ, которым я мог бы добиться этого, выполнив отдельного запроса в течение цикла:

public ArrayList<Integer> addWords(ArrayList<String[]> allTermsForTag) { 
    ArrayList ids = new ArrayList<Integer>(); 
    ResultSet rs = null; 
    try{ 
     Statement st = connection.createStatement(); 
     for (String[] articleTerms: allTermsForTag) { 
      for(String term: articleTerms) { 
       String query = "WITH a AS (INSERT INTO tag (name) SELECT '"+term+"' WHERE NOT EXISTS (SELECT name FROM tag WHERE name = '"+term+"') " + 
         "RETURNING id) SELECT id FROM a UNION SELECT id FROM tag WHERE name = '"+term+"'"; 
       rs = st.executeQuery(query); 
       while (rs.next()) 
       { 
        int id = rs.getInt(1); 
        ids.add(id); 
        System.out.printf("id: "+id); 

       } 
      } 
     } 
     rs.close(); 
     st.close(); 
    }catch(SQLException e){ 
     System.out.println("SQL exception was raised while performing SELECT: "+e); 
    } 
    return ids; 


} 

Это делает то, что мне нужно красиво, но это слишком медленно, как хорошо.

Другой метод, который я написал использует executeBatch(), однако, он не возвращает идентификаторы:

public ArrayList<Integer> addWords(ArrayList<String[]> allTermsForTag){ 
     ResultSet rs = null; 
     ArrayList ids = new ArrayList<Integer>(); 

     try{ 
      Statement st = connection.createStatement(); 
      for (String[] articleTerms: allTermsForTag) { 
       for(String term: articleTerms) { 
        String query = "WITH a AS (INSERT INTO tag (name) SELECT '"+term+"' WHERE NOT EXISTS (SELECT name FROM tag WHERE name = '"+term+"') " + 
          "RETURNING id) SELECT id FROM a UNION SELECT id FROM tag WHERE name = '"+term+"'"; 
        st.addBatch(query); 
       } 
       st.executeBatch(); 
       rs = st.getGeneratedKeys(); 
       while (rs.next()) { 
        int id = rs.getInt(1); 
        ids.add(id); 
       } 
      } 
      st.close(); 
      return ids; 
     }catch (SQLException e){ 
      System.out.println("SQL exception was raised while performing batch INSERT: "+e.getNextException()); 
      System.out.println("dub"); 
     } 
     return null; 
    } 

Таким образом, вопрос - как получить идентификаторы при использовании executeBatch() или, если это невозможно, как подойти к этому проблема? Мне нужно, чтобы он работал как можно быстрее, потому что в INSERT будет много операций с большим объемом данных. Спасибо!

ответ

1

executeBatch может возвращать сгенерированные ключи в последних версиях PgJDBC. См. issue 195 и pull 204. Вы должны использовать the prepareStatement variant that takes a String[] of returned column names.

Однако ... сделайте шаг назад здесь. Решение - это не петли. Решение почти никогда не зацикливается.

В этом случае, вы должны почти наверняка использовать COPY через the PgJDBC CopyManager API к COPY данных в TEMPORARY таблицы. Затем сделайте INSERT INTO ... SELECT ... RETURNING ..., чтобы вставить содержимое таблицы temp в итоговую таблицу и вернуть все сгенерированные поля. Вы также можете сделать SELECT, чтобы присоединиться к таблице temp, чтобы вернуть уже существующие. Это в основном bulk upsert или тесно связанная массовая вставка-если-не существует.

Если по какой-либо причине вы не можете этого сделать, то лучший вариант, вероятно, является многозначным INSERT с большими VALUES списками, но для этого требуется некоторый уродливый динамический SQL. Поскольку вам нужны существующие значения, если строка уже существует, вам, вероятно, понадобится также записываемый CTE. Так что действительно, просто используйте COPY и запрос для слияния таблицы.

+0

Благодарим вас за ответ. Это некоторые продвинутые вещи, о которых вы говорите) Не знакомы с большинством из них, но попытаются выяснить. Итак, если у меня есть таблица с моими данными, я КОПИРУЮ ее в таблицу temp? Затем сделайте - INSERT INTO ... SELECT ... RETURNING ... введите временную таблицу, не так ли? И я полагаю, все это должно быть сделано в транзакции? – Sermilion

+0

@Sermilion Yep, почти все. В противном случае вы можете попробовать только последний драйвер jdbc и пакетную вставку с сгенерированными ключами. Это будет довольно немного медленнее, но, вероятно, достаточно быстро. –

+0

Ну, я добавил драйвер версии 9.4-1201-jdbc4. Это дает мне ту же ошибку: org.postgresql.util.PSQLException: результат был возвращен, когда никто не ожидал. Не могли бы вы посмотреть на код для executeBatch() и посмотреть, что все в порядке. Или, может быть, я добавил неправильную версию jdbc? Спасибо . – Sermilion