写入csv

时间:2019-02-21 21:02:27

标签: python python-3.x postgresql csv

我正在使用Python的csv模块将数据从sql server写入csv文件,然后使用copy命令将csv文件上传到postgres数据库。问题是Python的csv编写器自动将Nulls转换为空字符串“”,并且当列为int或float数据类型时,它使我的工作失败,并且当其应为None或null值时,它将尝试插入此“”。 >

  

使与模块的接口尽可能容易   实现DB API时,值None被写为空字符串。

     

https://docs.python.org/3.4/library/csv.html?highlight=csv#csv.writer

保持空值的最佳方法是什么?有没有更好的方法用Python编写csv?我愿意接受所有建议。

示例:

我有纬度和经度值:

42.313270000    -71.116240000
42.377010000    -71.064770000
NULL    NULL

写入csv时,会将null转换为“”:

with file_path.open(mode='w', newline='') as outfile:
    csv_writer = csv.writer(outfile, delimiter=',', quoting=csv.QUOTE_NONNUMERIC)
    if include_headers:
        csv_writer.writerow(col[0] for col in self.cursor.description)
    for row in self.cursor:
        csv_writer.writerow(row)

42.313270000,-71.116240000
42.377010000,-71.064770000
"",""
  

NULL

     

指定表示空值的字符串。默认值为\ N   (反斜杠-N)为文本格式,并在CSV中使用无引号的空字符串   格式。在某些情况下,即使文本格式也可能需要一个空字符串   您不想将空值与空字符串区分开的地方。这个   使用二进制格式时不允许使用此选项。

     

https://www.postgresql.org/docs/9.2/sql-copy.html

答案:

为我解决问题的是将报价更改为csv.QUOTE_MINIMAL。

  

csv.QUOTE_MINIMAL指示编写器对象仅引用那些字段   其中包含特殊字符,例如定界符,quotechar或任何其他字符   行尾符中的字符。

相关问题: -Postgresql COPY empty string as NULL not work

5 个答案:

答案 0 :(得分:7)

您在这里有两个选择:更改Python中的csv.writing引用选项,或告诉PostgreSQL接受带引号的字符串作为可能的NULL(需要PostgreSQL 9.4或更高版本)

Python csv.writer()和引号

在Python方面,您正在告诉csv.writer()对象添加引号,因为您已将其配置为使用csv.QUOTE_NONNUMERIC

  

指示writer对象引用所有非数字字段。

None的值是非数字的,因此导致写入""

切换为使用csv.QUOTE_MINIMALcsv.QUOTE_NONE

  

csv.QUOTE_MINIMAL
  指示writer对象仅引用那些包含特殊字符(例如定界符 quotechar lineterminator 中的任何字符)的字段。

     

csv.QUOTE_NONE
  指示writer对象不要引用字段。当前的定界符出现在输出数据中时,它前面是当前的 escapechar 字符。

由于您所编写的只是经度和纬度值,因此在这里不需要任何引号,因此数据中没有分隔符或引号字符。

使用任一选项,None值的CSV输出都是简单的空字符串:

>>> import csv
>>> from io import StringIO
>>> def test_csv_writing(rows, quoting):
...     outfile = StringIO()
...     csv_writer = csv.writer(outfile, delimiter=',', quoting=quoting)
...     csv_writer.writerows(rows)
...     return outfile.getvalue()
...
>>> rows = [
...     [42.313270000, -71.116240000],
...     [42.377010000, -71.064770000],
...     [None, None],
... ]
>>> print(test_csv_writing(rows, csv.QUOTE_NONNUMERIC))
42.31327,-71.11624
42.37701,-71.06477
"",""

>>> print(test_csv_writing(rows, csv.QUOTE_MINIMAL))
42.31327,-71.11624
42.37701,-71.06477
,

>>> print(test_csv_writing(rows, csv.QUOTE_NONE))
42.31327,-71.11624
42.37701,-71.06477
,

PostgreSQL 9.4 COPY FROMNULL值和FORCE_NULL

从PostgreSQL 9.4开始,当使用NULL选项时,还可以强制PostgreSQL将带引号的空字符串作为FORCE_NULL接受。来自COPY FROM documentation

  

FORCE_NULL

     

将指定列的值与空字符串匹配,即使已将其引号,并且将找到的匹配项也设置为NULL。在空字符串为空的默认情况下,这会将带引号的空字符串转换为NULL。仅在COPY FROM中以及使用CSV格式时才允许使用此选项。

通过在FORCE_NULL选项中命名列可以使PostgreSQL接受空列和""作为这些列的NULL值,例如:

COPY position (
    lon, 
    lat
) 
FROM "filename"
WITH (
    FORMAT csv,
    NULL '',
    DELIMITER ',',
    FORCE_NULL(lon, lat)
);

这时,您在Python端使用了什么引用选项都不再重要了。

要考虑的其他选项

对于来自其他数据库的简单数据转换任务,请不要使用Python

如果您已经在查询数据库以将数据整理到PostgreSQL中,请考虑将直接插入Postgres 。如果数据来自其他来源,则可以使用foreign data wrapper (fdw) module切掉中间人,直接从其他来源将数据拉入PostgreSQL。

数据是否为空?考虑直接从Python使用COPY FROM作为二进制文件

可以通过binary COPY FROM更有效地插入大量数据;链接的答案使用所需的额外元数据和字节顺序扩充了一个numpy结构化数组,然后有效地创建数据的二进制副本,并使用COPY FROM STDIN WITH BINARYpsycopg2.copy_expert() method将其插入PostgreSQL。这样可以避免数字->文本->数字转换。

要保留数据以处理管道中的大型数据集吗?

不要重新发明数据流水线。考虑使用诸如Apache Spark之类的现有项目,这些项目已经解决了效率问题。 Spark可让您treat data as a structured stream,并包含run data analysis steps in parallel的基础架构,并且您可以使用distributed, structured data as Pandas dataframes

另一种选择是查看Dask,以帮助在分布式任务之间共享数据集以处理大量数据。

即使将已经在运行的项目转换为Spark可能也太遥不可及,至少要考虑使用Apache Arrow,Spark会基于此构建数据交换平台。 pyarrow project可让您通过Parquet文件或exchange data over IPC交换数据。

Pandas和Numpy团队投入了大量资金来支持Arrow和Dask的需求(这些项目之间的核心成员有相当多的重叠),并且正在积极努力使Python数据交换尽可能高效,包括{{3 }}避免共享数据时不必要的内存复制。

答案 1 :(得分:2)

您的代码

for row in self.cursor:
    csv_writer.writerow(row)

按原样使用writer,但是您不必这样做。您可以使用生成器理解和三元表达式来过滤值以更改某些特定值

for row in self.cursor:
    csv_writer.writerow("null" if x is None else x for x in row)

答案 2 :(得分:2)

您要输入csv.QUOTE_NONNUMERIC。这会将非数字的所有内容转换为字符串。您应该考虑使用csv.QUOTE_MINIMAL,因为它可能会更多:

测试代码:

import csv

test_data = (None, 0, '', 'data')
for name, quotes in (('test1.csv', csv.QUOTE_NONNUMERIC),
                     ('test2.csv', csv.QUOTE_MINIMAL)):

    with open(name, mode='w') as outfile:
        csv_writer = csv.writer(outfile, delimiter=',', quoting=quotes)
        csv_writer.writerow(test_data))

结果:

test1.csv:

"",0,"","data"

test2.csv:

,0,,data

答案 3 :(得分:2)

  

我正在使用Python的csv模块将数据从sql server写入csv文件,然后使用copy命令将csv文件上传到postgres数据库。

我相信您的真正要求是您需要在文件系统中跳转数据行,并且正如上面的句子和问题标题所表明的那样,您当前正在使用csv文件进行此操作。 问题是csv格式不能很好地支持RDBMS NULL概念。 让我通过稍微更改问题为您解决问题。 我想向您介绍镶木地板格式。 给定内存中的一组表行,它可以非常快速地将它们持久保存到压缩的二进制文件中,并通过完整的元数据和NULL恢复它们,而不会引起文本引用麻烦。 这是一个使用pyarrow 0.12.1拼花引擎的示例:

import pandas as pd
import pyarrow


def round_trip(fspec='/tmp/locations.parquet'):
    rows = [
        dict(lat=42.313, lng=-71.116),
        dict(lat=42.377, lng=-71.065),
        dict(lat=None, lng=None),
    ]

    df = pd.DataFrame(rows)
    df.to_parquet(fspec)
    del(df)

    df2 = pd.read_parquet(fspec)
    print(df2)


if __name__ == '__main__':
    round_trip()

输出:

      lat     lng
0  42.313 -71.116
1  42.377 -71.065
2     NaN     NaN

一旦恢复了数据框中的行,您就可以自由调用df2.to_sql()或使用其他一些喜欢的技术将数字和NULL放入数据库表中。

编辑:

如果您可以在PG服务器或同一LAN上运行.to_sql(),请执行此操作。 否则,您最喜欢的技术可能涉及.copy_expert()。 为什么? 摘要是,使用psycopg2时,“批量INSERT速度很慢”。 诸如sqlalchemy和pandas之类的中间层,以及关心插入性能的编写良好的应用程序,将使用.executemany()。 这样做的想法是一次发送很多行,而不必等待单个结果状态,因为我们不担心唯一索引冲突。 因此,TCP获得了巨大的SQL文本缓冲区,并立即将其全部发送出去,从而饱和了端到端通道的带宽, 就像copy_expert向TCP发送一个大缓冲区以实现高带宽一样。

相反,psycopg2驱动程序缺乏对高性能执行的支持。 从2.7.4版本开始,它仅一次执行一项,通过WAN发送一个SQL命令,并在往返时间之前等待结果,然后再发送下一个命令。 ping您的服务器; 如果ping时间显示您每秒可以往返12次, 然后计划每秒仅插入十几行。 大部分时间都花在等待回复数据包上,而不是花在处理数据库行上。 如果将来某个时候psycopg2能够对此提供更好的支持,那将是很可爱的事情。

答案 4 :(得分:-1)

我会使用pandas,psycopg2和sqlalchemy。确保已安装。来自您当前的工作流程,避免写入csv

#no need to import psycopg2
import pandas as pd
from sqlalchemy import create_engine


#create connection to postgres
engine = create_engine('postgres://.....')

#get column names from cursor.description
columns = [col[0] for col in self.cursor.description]

#convert data into dataframe
df = pd.DataFrame(cursor.fetchall(),columns=columns)

#send dataframe to postgres
df.to_sql('name_of_table',engine,if_exists='append',index=False)

#if you still need to write to csv
df.to_csv('your_file.csv')