1NF
Прежде всего, я думаю, что ваш стол неправильный, потому что он не соответствует 1NF .Каждое поле должно содержать только атомарные атрибуты, но это не так.Почему не таблица типа:
CREATE TABLE my_table (
id,
ip inet,
port int
)
Где id
- номер вашей строки в исходном файле и ip
/ port
один из адресов в этой строке?Пример данных:
id | ip | port
-----------------------
1 | 10.10.10.1 | 80
1 | 10.10.10.2 | 443
2 | 10.10.10.3 | 8080
2 | 10.10.10.4 | 4040
...
Следовательно, вы сможете запрашивать вашу базу данных по одному адресу (найти все связанные адреса, вернуть true, если два адреса находятся на одной строке, что бы вы ни хотели)..).
Загрузить данные
Но давайте предположим, что вы знаете, что делаете.Основная проблема заключается в том, что ваш файл входных данных находится в специальном формате.Это может быть CSV-файл с одним столбцом, но это будет очень вырожденный CSV-файл.В любом случае, вы должны преобразовать строки, прежде чем вставлять их в базу данных.У вас есть два варианта:
- вы читаете каждую строку входного файла и делаете
INSERT
(это может занять некоторое время); - вы конвертируете входной файл втекстовый файл с ожидаемым форматом и использовать
COPY
.
Вставить один за другим
Первые варианты кажутся простыми: для первой строки файла CSV, {(10.10.10.1,80),(10.10.10.2,443)}
,вам нужно выполнить запрос:
INSERT INTO my_table VALUES (ARRAY[('10.10.10.1',80),('10.10.10.2',443)]::address[], 4)
. Для этого вам просто нужно создать новую строку:
String value = row.replaceAll("\\{", "ARRAY[")
.replaceAll("\\}", "]::address[]")
.replaceAll("\\(([0-9.]+),", "'$1'");
String sql = String.format("INSERT INTO my_table VALUES (%s)", value);
и выполнить запрос для каждой строки входного файла (или для большей безопасности используйте подготовленный оператор ).
Вставьте с COPY
Я подробно остановлюсь на втором варианте.Вы должны использовать в коде Java:
copyManager.copyIn(sql, from);
, где запрос на копирование является оператором COPY FROM STDIN
, а from
- читателем.Оператор будет выглядеть следующим образом:
COPY my_table (addresses) FROM STDIN WITH (FORMAT text);
Для подачи в менеджер копий необходимы данные типа (обратите внимание на кавычки):
{"(10.10.10.1,80)","(10.10.10.2,443)"}
{"(10.10.10.3,8080)","(10.10.10.4,4040)"}
С временным файлом
Более простой способ получить данные в правильном формате - создать временный файл.Вы читаете каждую строку входного файла и заменяете (
на "(
и )
на )"
.Запишите эту обработанную строку во временный файл.Затем передайте средство чтения этого файла в менеджер копий.
На лету
С двумя потоками Вы можете использовать два потока:
поток 1 читает входной файл, обрабатывает строки одну за другой и записывает их в PipedWriter
.
поток 2 передает PipedReader
подключен к предыдущему PipedWriter
к диспетчеру копирования.
Основная трудность заключается в синхронизации потоков таким образом, что поток 2 начинает читать PipedReader
перед потоком 1начинает записывать данные в PipedWriter
.См. этот мой проект для примера.
С пользовательским считывателем Считыватель from
может быть примером чего-то вроде (Наивная версия):
class DataReader extends Reader {
PushbackReader csvFileReader;
private boolean wasParenthese;
public DataReader(Reader csvFileReader) {
this.csvFileReader = new PushbackReader(csvFileReader, 1);
wasParenthese = false;
}
@Override
public void close() throws IOException {
this.csvFileReader.close();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
// rely on read()
for (int i = off; i < off + len; i++) {
int c = this.read();
if (c == -1) {
return i-off > 0 ? i-off : -1;
}
cbuf[i] = (char) c;
}
return len;
}
@Override
public int read() throws IOException {
final int c = this.csvFileReader.read();
if (c == '(' && !this.wasParenthese) {
this.wasParenthese = true;
this.csvFileReader.unread('(');
return '"'; // add " before (
} else {
this.wasParenthese = false;
if (c == ')') {
this.csvFileReader.unread('"');
return ')'; // add " after )
} else {
return c;
}
}
}
}
(Это наивная версия, потому что правильный способ сделать это - переопределить только public int read(char[] cbuf, int off, int len)
. Но вам нужно обработать cbuf
, чтобы добавить кавычки и сохранитьдополнительные символы сдвинуты вправо: это немного утомительно).Теперь, если r
считыватель для файла:
{(10.10.10.1,80),(10.10.10.2,443)}
{(10.10.10.3,8080),(10.10.10.4,4040)}
Просто используйте:
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager
.getConnection("jdbc:postgresql://db_host:5432/db_base", "user", "passwd");
CopyManager copyManager = connection.unwrap(PGConnection.class).getCopyAPI();
copyManager.copyIn("COPY my_table FROM STDIN WITH (FORMAT text)", new DataReader(r));
При массовой загрузке
Если вы загружаете огромное количестводанных, не забудьте основные советы : отключить автокоммит, удалить индексы и ограничения и использовать TRUNCATE
и ANALYZE
следующим образом:
TRUNCATE my_table;
COPY ...;
ANALYZE my_table;
Это ускоритзагрузка.