我正在使用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中使用无引号的空字符串 格式。在某些情况下,即使文本格式也可能需要一个空字符串 您不想将空值与空字符串区分开的地方。这个 使用二进制格式时不允许使用此选项。
答案:
为我解决问题的是将报价更改为csv.QUOTE_MINIMAL。
csv.QUOTE_MINIMAL指示编写器对象仅引用那些字段 其中包含特殊字符,例如定界符,quotechar或任何其他字符 行尾符中的字符。
答案 0 :(得分:7)
您在这里有两个选择:更改Python中的csv.writing
引用选项,或告诉PostgreSQL接受带引号的字符串作为可能的NULL(需要PostgreSQL 9.4或更高版本)
csv.writer()
和引号在Python方面,您正在告诉csv.writer()
对象添加引号,因为您已将其配置为使用csv.QUOTE_NONNUMERIC
:
指示
writer
对象引用所有非数字字段。
None
的值是非数字的,因此导致写入""
。
切换为使用csv.QUOTE_MINIMAL
或csv.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
,
COPY FROM
,NULL
值和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端使用了什么引用选项都不再重要了。
如果您已经在查询数据库以将数据整理到PostgreSQL中,请考虑将直接插入Postgres 。如果数据来自其他来源,则可以使用foreign data wrapper (fdw) module切掉中间人,直接从其他来源将数据拉入PostgreSQL。
可以通过binary COPY FROM
更有效地插入大量数据;链接的答案使用所需的额外元数据和字节顺序扩充了一个numpy结构化数组,然后有效地创建数据的二进制副本,并使用COPY FROM STDIN WITH BINARY
和psycopg2.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')