将SQL查询的结果写入CSV并避免额外的换行符

时间:2018-02-14 18:25:52

标签: python sql amazon-web-services csv cursor

我必须从几个不同的数据库引擎中提取数据。导出此数据后,我将数据发送到AWS S3并使用COPY命令将该数据复制到Redshift。某些表包含大量文本,列字段中包含换行符和其他字符。当我运行以下代码时:

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)

一些具有回车符/换行符的列将创建新行:

"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 个答案:

答案 0 :(得分:3)

如果您在没有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编写器写入会产生断行。

如果在生产中确实需要WHERE子句,您可以创建一个临时表并将其复制:

cursor.execute("""CREATE TEMPORARY TABLE copy_me AS
        SELECT this, that, the_other FROM table_name WHERE conditions""")

(编辑)再次看你的问题,我看到你提到“所有不同的数据库引擎”。上面的内容适用于psyopg2和postgresql,但可能适用于其他数据库或库。

答案 1 :(得分:2)

我怀疑问题就像确保Python CSV导出库和Redshift的COPY导入说一个通用界面一样简单。简而言之,检查分隔符并引用字符,并确保Python输出和Redshift COPY命令都一致。

稍微详细一点:数据库驱动程序已经完成了以易于理解的形式进入Python的艰苦工作。也就是说,来自DB的每一行是列表(或元组,生成器等),并且每个单元都是可单独访问的。而且,当你有一个类似列表的结构时,Python的CSV导出器可以完成其余的工作 - 至关重要的是 - Redshift将能够从输出,嵌入的换行符和所有内容中复制。 特别是,您不需要进行任何手动转义;您应该只需要.writerow().writerows()个功能。

Redshift的COPY实现默认了解最常见的CSV方言,即

  • 用逗号(,),
  • 分隔单元格
  • 引用带双引号("),
  • 的单元格
  • 并通过加倍(""")来转义任何嵌入的双引号。

使用Redshift FORMAT AS CSV的文档提供支持:

  

...默认引号字符是双引号(“)。当在字段中使用引号字符时,使用附加引号字符转义字符。...

但是,您的Python CSV导出代码使用管道(|)作为delimiter,并将quotechar设置为双引号(")。那也可以,但为什么偏离the defaults呢?建议使用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;

应该这样做。

答案 2 :(得分:0)

为什么要在每一行之后写入数据库?

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])

答案 3 :(得分:0)

问题在于您使用Redshift clearInterval(currentState.someInterval)命令及其默认参数,该参数使用管道作为分隔符(请参阅herehere)并要求转义换行符和文本字段中的管道(请参阅herehere)。但是,Python csv编写器只知道如何使用嵌入的换行符进行标准操作,即将它们保留为带引号的字符串。

幸运的是,Redshift COPY命令也可以使用标准的CSV格式。将COPY选项添加到CSV命令gives you this behavior

  

允许在输入数据中使用CSV格式。要自动转义分隔符,换行符和回车符,请将该字段括在QUOTE参数指定的字符中。默认引号字符是双引号(“)。当在字段中使用引号字符时,使用附加引号字符转义字符。”

这正是Python CSV编写器使用的方法,因此它应该处理您的问题。所以我的建议是使用如下代码创建一个标准的csv文件:

COPY

然后在Redshift中,将您的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) 命令更改为like this {注意添加的COPY标记:

CSV

或者,您可以继续手动转换字段以匹配Redshift的COPY命令的默认设置。 Python的COPY logdata FROM 's3://mybucket/data/data.csv' iam_role 'arn:aws:iam::0123456789012:role/MyRedshiftRole' CSV; 本身不会为你做这件事,但你可能会加速你的代码,特别是对于大文件,如:

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 ) 的{​​{1}} DataFrame,在DataFrame中进行替换(一次一行),然后将表格写出来与pandas。 Pandas拥有令人难以置信的快速csv代码,因此这可能会为您带来显着的加速。

更新:我刚注意到,最后我基本上重复了@ hunteke的回答。关键点(我第一次错过了)是你可能没有在当前的Redshift .from_sql命令中使用.to_csv参数;如果你添加它,这应该很容易。