Запись результатов из 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; если вы добавите это, это должно быть легко.