Запись результатов из SQL-запроса в CSV и избежание дополнительных разрывов строк
мне нужно извлечь данные из нескольких различных ядер баз данных. После экспорта этих данных я отправляю данные в AWS S3 и копирую их в Redshift с помощью команды COPY. Некоторые таблицы содержат много текста с разрывами строк и другими символами, присутствующими в полях столбцов. Когда я запускаю следующий код:
cursor.execute('''SELECT * FROM some_schema.some_message_log''')
rows = cursor.fetchall()
with open('data.csv', 'w', newline='') as fp:
a = csv.writer(fp, delimiter='|', quoting=csv.QUOTE_ALL, quotechar='"', doublequote=True, lineterminator='n')
a.writerows(rows)
некоторые столбцы, которые имеют возврат каретки / linebreaks создаст новые строки:
"2017-01-05 17:06:32.802700"|"SampleJob"|""|"Date"|"error"|"Job.py"|"syntax error at or near ""from"" LINE 34: select *, SYSDATE, from staging_tops.tkabsences;
^
-<class 'psycopg2.ProgrammingError'>"
что вызывает процесс импорта неудача. Я могу обойти это, жестко кодируя исключения:
cursor.execute('''SELECT * FROM some_schema.some_message_log''')
rows = cursor.fetchall()
with open('data.csv', 'w', newline='') as fp:
a = csv.writer(fp, delimiter='|', quoting=csv.QUOTE_ALL, quotechar='"', doublequote=True, lineterminator='n')
for row in rows:
list_of_rows = []
for c in row:
if isinstance(c, str):
c = c.replace("n", "n")
c = c.replace("|", "|")
c = c.replace("", "\")
list_of_rows.append(c)
else:
list_of_rows.append(c)
a.writerow([x.encode('utf-8') if isinstance(x, str) else x for x in list_of_rows])
но это занимает много времени для обработки больших файлов и кажется плохой практикой в целом. Есть ли более быстрый способ экспортировать данные из курсора SQL в CSV, который не будет нарушаться при столкновении с текстовыми столбцами, содержащими возврат каретки/разрывы строк?
4 ответов
Я подозреваю, что проблема так же проста, как убедиться, что библиотека экспорта Python CSV и импорт копии Redshift говорят общий интерфейс. Короче говоря, проверьте разделители и символы цитирования и убедитесь, что выходные данные Python и команда Redshift COPY согласны.
С немного более подробной информацией: драйверы БД уже проделали тяжелую работу по получению Python в хорошо понятной форме. То есть каждая строка из БД список (или кортеж, генератор, и т. д.), и каждый ячейка доступна индивидуально. И в тот момент, когда у вас есть структура, подобная списку, экспортер CSV Python может выполнить остальную часть работы, и-что важно-Redshift сможет скопировать с выхода, встроенных новых строк и все. в частности, вам не нужно делать никаких ручных побегов;.writerow()
или .writerows()
функции должны быть все, что вам нужно делать.
реализация копирования Redshift понимает наиболее распространенный диалект CSV по умолчанию, который является к
- разделите ячейки запятой (
,
), - кавычки с двойными кавычками (
"
), - и избежать любых встроенных двойных кавычек путем удвоения (
"
→""
).
поднимешь документации от Redshift FORMAT AS CSV
:
... Символ кавычки по умолчанию-двойная кавычка ("). Когда символ цитаты используется в поле, уберите его с дополнительным характером цитаты. ...
однако ваш код экспорта Python CSV использует канал (|
) как delimiter
и определяет quotechar
в двойные кавычки ("
). Это тоже может сработать, но зачем уходить от по умолчанию? Предложите использовать тезку CSV и упростить свой код в процессе:
cursor.execute('''SELECT * FROM some_schema.some_message_log''')
rows = cursor.fetchall()
with open('data.csv', 'w') as fp:
csvw = csv.writer( fp )
csvw.writerows(rows)
оттуда, скажите COPY использовать формат CSV (опять же, без необходимости не по умолчанию технические характеристики):
COPY your_table FROM your_csv_file auth_code FORMAT AS CSV;
это должно сделать это.
если вы делаете SELECT * FROM table
без WHERE
предложение, вы можете использовать COPY table TO STDOUT
вместо этого, с правильными параметрами:
copy_command = """COPY some_schema.some_message_log TO STDOUT
CSV QUOTE '"' DELIMITER '|' FORCE QUOTE *"""
with open('data.csv', 'w', newline='') as fp:
cursor.copy_expert(copy_command)
это, в моем тестировании, приводит к буквальному "\n " вместо фактических новых строк, где запись через CSV writer дает сломанные строки.
Если вам нужен WHERE
предложение в производстве вы можете создать временную таблицу и скопировать ее вместо этого:
cursor.execute("""CREATE TEMPORARY TABLE copy_me AS
SELECT this, that, the_other FROM table_name WHERE conditions""")
(edit) глядя на ваш вопрос снова, я вижу, что вы упоминаете "когда-либо все разные процессор базы данных." Вышеизложенное работает с psyopg2 и postgresql, но, вероятно, может быть адаптировано для других баз данных или библиотек.
зачем писать в базу данных после каждой строки?
cursor.execute('''SELECT * FROM some_schema.some_message_log''')
rows = cursor.fetchall()
with open('data.csv', 'w', newline='') as fp:
a = csv.writer(fp, delimiter='|', quoting=csv.QUOTE_ALL, quotechar='"', doublequote=True, lineterminator='\n')
list_of_rows = []
for row in rows:
for c in row:
if isinstance(c, basestring):
c = c.replace("\n", "\n")
c = c.replace("|", "\|")
c = c.replace("\", "\\")
list_of_rows.append(row)
a.writerows([x.encode('utf-8') if isinstance(x, str) else x for x in list_of_rows])
проблема в том, что вы используете Redshift COPY
команда с параметрами по умолчанию, которые используют канал в качестве разделителя (см. здесь и здесь) и требуют экранирования новых строк и каналов в текстовых полях (см. здесь и здесь). Тем не менее, Python csv writer знает только, как сделать стандартную вещь со встроенными новыми строками, которая должна оставить их как есть, внутри цитируемой строки.
к счастью, Красное смещение COPY
команда также может использовать стандартный формат CSV. Добавление на COPY
команда дает вам такое поведение:
позволяет использовать формат CSV во входных данных. Чтобы автоматически избежать разделителей, символов новой строки и возвращений каретки, заключите поле в символ, указанный параметром QUOTE. Символ кавычки по умолчанию-двойная кавычка ("). Когда символ предложения используется в поле, escape символ с дополнительным символом цитаты."
это именно тот подход, который используется Python CSV writer, поэтому он должен позаботиться о ваших проблемах. Поэтому мой совет-создать стандартный csv-файл, используя такой код:
cursor.execute('''SELECT * FROM some_schema.some_message_log''')
rows = cursor.fetchall()
with open('data.csv', 'w', newline='') as fp:
a = csv.writer(fp) # no need for special settings
a.writerows(rows)
затем в Redshift, измените свой что-то такой (заметьте, добавил CSV
tag):
COPY logdata
FROM 's3://mybucket/data/data.csv'
iam_role 'arn:aws:iam::0123456789012:role/MyRedshiftRole'
CSV;
кроме того, вы можете продолжить преобразование вручную поля, соответствующие настройкам по умолчанию для команды копирования Redshift. В Python csv.writer
не будет делать это для вас самостоятельно, но вы можете немного ускорить свой код, особенно для больших файлов, например:
cursor.execute('''SELECT * FROM some_schema.some_message_log''')
rows = cursor.fetchall()
with open('data.csv', 'w', newline='') as fp:
a = csv.writer(
fp,
delimiter='|', quoting=csv.QUOTE_ALL,
quotechar='"', doublequote=True, lineterminator='\n'
)
a.writerows(
c.replace("\", "\\").replace("\n", "\\n").replace("|", "\|").encode('utf-8')
if isinstance(c, str)
else c
for row in rows
for c in row
)
в качестве другой альтернативы вы можете поэкспериментировать с импортом данных запроса в pandas
DataFrame с .from_sql
, выполняя замены в фрейме данных (по целому столбцу за раз), а затем записывая таблицу с помощью .to_csv
. Pandas имеет невероятно быстрый код csv, так что это может дать вам значительное ускорение.
обновление: я просто заметил, что в конце концов я в основном дублировал ответ @hunteke. Ключевой момент (который я пропустил в первый раз) заключается в том, что вы, вероятно, не использовали CSV
аргумент в текущем Redshift COPY
command; если вы добавите это, это должно быть легко.