PostgreSQL中COPY FROM STDIN失效但指定路径正常,求故障原因
\copy通过cat管道STDIN导入失败,但指定文件路径成功 先还原你的场景:你创建了stock_data表,想用临时表stock_data_tmp中转导入多个CSV,计划用cat合并文件后管道传给SQL脚本,但执行cat /path/to/20140102.txt | psql -d my_db_name -f ~/path/to/script/update_stock_data.sql时报错ERROR: missing data for column "date",但直接在脚本里写\copy stock_data_tmp FROM '/path/to/20140102.txt' WITH csv;就成功。
为什么会出现这个问题?
这里有两个关键原因:
psql -f与STDIN管道的冲突
当你使用psql -f script.sql时,psql会把指定的脚本文件作为它的主要输入源,这时候你通过管道传入的CSV数据会被psql忽略——因为psql已经在专注读取脚本文件的内容了,不会再去读取管道过来的STDIN。所以当脚本执行到\copy stock_data_tmp FROM STDIN WITH CSV;时,实际上没有读到任何CSV数据,自然会报“缺少列数据”的错误。脚本里的小笔误(容易被忽略)
你的临时表列名是ddate,但在INSERT语句里写的是to_date(date, 'YYYYMMDD')——这里引用了不存在的date列。不过你直接指定文件路径时没报错?可能是你测试时刚好修正了这个点,或者PostgreSQL的错误提示被掩盖了,但这个问题必须修正,否则即使管道问题解决了,后续插入也会失败。
解决方案
针对管道导入的需求,有两种常用的解决方式:
方案1:用psql -c替代-f,将脚本嵌入命令行
把SQL脚本内容直接作为psql -c的参数,这样psql会先执行SQL命令,然后\copy会正确读取管道传入的STDIN数据:
cat /path/to/20140102.txt | psql -d my_db_name -c " CREATE TEMPORARY TABLE IF NOT EXISTS stock_data_tmp ( code varchar, ddate varchar, open decimal, high decimal, low decimal, close decimal, volume decimal, UNIQUE (code, ddate) ); \copy stock_data_tmp FROM STDIN WITH CSV; INSERT INTO stock_data SELECT code, to_date(ddate, 'YYYYMMDD'), open, high, low, close, volume FROM stock_data_tmp; DROP TABLE stock_data_tmp; "
注意这里已经把to_date(date, ...)修正为to_date(ddate, ...)了。
方案2:合并脚本与CSV内容后传入psql
如果你还是想保留独立的脚本文件,可以把脚本和CSV文件的内容合并后一起传给psql(不用-f参数):
cat ~/path/to/script/update_stock_data.sql /path/to/20140102.txt | psql -d my_db_name
但要确保你的脚本文件最后一行就是\copy stock_data_tmp FROM STDIN WITH CSV;,这样psql执行完脚本的前半部分后,会自动读取后续的CSV内容作为\copy的输入。如果要导入多个CSV文件,直接把所有文件都追加到cat后面即可:
cat ~/path/to/script/update_stock_data.sql /path/to/20140102.txt /path/to/20140103.txt | psql -d my_db_name
同样,记得先修正脚本里的date为ddate的笔误。
内容的提问来源于stack exchange,提问作者pragMATHiC




