postgresql 在PSQLCLI中使用\set变量在普通查询中工作但在\copy中不工作/展开

cgvd09ve  于 2023-08-04  发布在  PostgreSQL
关注(0)|答案(2)|浏览(117)

如标题中所述,问题是使用\set设置的psql变量对我有效,除非在psql客户端提供的\copy函数中使用
引用\copy中的psql变量是否需要一些特殊的语法?还是我运气不好如果是的话,有任何记录吗?
我在StackOverflow或任何地方都找不到这个问题。我看了20篇文章,但什么也没找到。我还检查了the documentation on \copy for PostgreSQL 11(CLI的版本),没有看到关于这一点的警告-我在页面上搜索了“变量”,没有发现任何与此相关的内容。我还搜索了“扩展”和“扩展”,什么也没找到。所以现在我在这里寻求帮助...
PostgreSQL客户端的版本是11.10,不管Debian应用了什么下游补丁:

psql (PostgreSQL) 11.10 (Debian 11.10-1.pgdg100+1)

字符串
我很确定服务器版本几乎没有相关性,但为了彻底起见,服务器是Ubuntu发布的版本10.13:

psql (PostgreSQL) 10.13 (Ubuntu 10.13-1.pgdg16.04+1)

  • 正在复制 *

我知道\copyCOPY之间的区别(一个是作为psql客户机中的功能实现的,另一个是在服务器进程的上下文中执行的服务器功能),对于这个任务,我需要使用的肯定是\copy
显示我正确设置和引用变量的标准查询:

[local:/tmp]:5432 dbuser@dbdev# \set var_tname ag_test
[local:/tmp]:5432 dbuser@dbdev# \set var_cname fname
[local:/tmp]:5432 dbuser@dbdev# SELECT * from :var_tname WHERE :var_cname = 'TestVal' LIMIT 1;
fname|lname|score|nonce
TestVal|C|100|b
(1 row)
Time: 88.786 ms


失败的案例,似乎是失败的,因为变量是在\copy内部引用的-我看不出这和工作示例之间有任何其他区别:

[local:/tmp]:5432 dbuser@dbdev# \set var_tname ag_test
[local:/tmp]:5432 dbuser@dbdev# \set var_cname fname
[local:/tmp]:5432 dbuser@dbdev# \copy (SELECT * from :var_tname WHERE :var_cname = 'TestVal' LIMIT 1) TO 'testvar.csv';
ERROR:  syntax error at or near ":"
LINE 1: COPY  ( SELECT * from :var_tname WHERE :var_cname = 'TestVal...
                              ^
Time: 193.322 ms


显然,根据该错误,扩展没有发生,查询正在尝试引用文本名称为:var_tname的表
我并不指望引用来帮助解决这个问题,但为了以防万一,我还是尝试了一下--谁知道呢,可能是一个奇怪的例外,对吧?毫无疑问,这也无济于事:

[local:/tmp]:5432 dbuser@dbdev# \copy (SELECT * from :'var_tname' WHERE :var_cname = 'TestVal' LIMIT 1) TO 'testvar.csv';
ERROR:  syntax error at or near ":"
LINE 1: COPY  ( SELECT * from : 'var_tname' WHERE :var_cname = 'Test...
                              ^
Time: 152.407 ms
[local:/tmp]:5432 dbuser@dbdev# \set var_tname 'ag_test'
[local:/tmp]:5432 dbuser@dbdev# \copy (SELECT * from :var_tname WHERE :var_cname = 'TestVal' LIMIT 1) TO 'testvar.csv';
ERROR:  syntax error at or near ":"
LINE 1: COPY  ( SELECT * from :var_tname WHERE :var_cname = 'TestVal...
                              ^
Time: 153.001 ms
[local:/tmp]:5432 dbuser@dbdev# \copy (SELECT * from :'var_tname' WHERE :var_cname = 'TestVal' LIMIT 1) TO 'testvar.csv';
ERROR:  syntax error at or near ":"
LINE 1: COPY  ( SELECT * from : 'var_tname' WHERE :var_cname = 'Test...
                              ^
Time: 153.459 ms


我也试着用单引号设置变量(这可能是最好的做法),但这没有什么区别:

[local:/tmp]:5432 dbuser@dbdev# \set var_tname 'ag_test'
[local:/tmp]:5432 dbuser@dbdev# \set var_cname 'fname'
... <same behavior as above> ...


\copy内部不支持变量扩展吗?如果是这样的话,这似乎是一个非常糟糕的限制,而且似乎没有记录在案
最后一点需要补充的是,我希望有人会问--我没有将这些实现为函数或存储过程的原因。首先,我的PostgreSQL版本根本不支持存储过程。它也不支持函数中的事务。即使这样,我希望这些查询在应用程序存储库中的psql文件中的真实的原因是,它们非常易于阅读以进行代码审查,易于维护以进行开发,并作为文档使用

  • 除非您也有此问题并希望找到解决方法,否则没有必要阅读此内容

除此之外,我还记录了一堆我能很快想到的解决方法--这个问题可以用1001种不同的方法来解决。但是,如果有一个解决方案可以让我坚持下去,我宁愿知道它,而不是应用任何变通方法。我还在下面添加了用例信息,因为响应沿着于“您为什么要这样做?只是不要使用xyz功能,问题解决了!“。我希望不要收到这些回复:>
感谢任何愿意帮助的人!

解决方案选项

我有很多变通方法,但我真的很想了解为什么这不起作用,如果它在某个地方被记录下来,或者如果可能有一些特殊的方法导致在\copy中使用时发生扩展,以避免需要更改这一点-原因我将在下面的 * 用例**部分解释

  • 这是我想出的解决办法 *
  • 使用变量SELECT到临时表中,\复制固定名称表 *
SELECT * INTO tmp_table FROM :var_tname WHERE :var_cname = 'TestVal' LIMIT 1;
\copy (SELECT * FROM tmp_table) TO 'testvar.csv'


这是可行的,但它有点笨重,似乎不应该是不必要的

  • 使用\pset fieldsetp生成TSV并将stdout重定向到文件(笨重,可能有转义问题)*

另一个选项是不使用\copy,并在将分隔符设置为tab后将stdout管道到文件:

[local:/tmp]:5432 dbuser@dbdev# \set var_tname ag_test
[local:/tmp]:5432 dbuser@dbdev# \pset format unaligned
Output format is unaligned.
[local:/tmp]:5432 dbuser@dbdev# \pset fieldsep '\t'
Field separator is "    ".
[local:/tmp]:5432 dbuser@dbdev# SELECT * from :var_tname LIMIT 1;
fname   lname   score   nonce
TestVal G   500 a
(1 row)
Time: 91.596 ms
[local:/tmp]:5432 dbuser@dbdev#


这可以通过psql -f query.psql > /the/output/path.tsv调用。我还没有检查,但我假设 * 应该 * 生成一个有效的TSV文件。我不确定的一件事是,它是否会正确地转义或引用包含制表符的列值,如\copyCOPY

  • 在shell脚本中进行扩展并写入临时psql文件,使用psql -f tmp.psql*

最后的解决方法是在shell脚本中设置变量,然后使用psql -c "$shellvar"调用,或者将shell扩展查询写入临时.psql文件,然后使用psql -f调用,然后删除临时文件

  • 用例(以及为什么我不特别喜欢一些解决方案)*

我可能应该提到用例...我有几个独立的(但相关的)Python应用程序,它们收集、解析和处理数据,然后使用psycopg 2将它们加载到数据库中。一旦原始数据进入数据库,我就将一堆较重的逻辑委托给psql文件,以提高可读性并减少需要维护的代码量
psql文件在应用程序完成时使用如下方式调用:

for psql_file in glob.glob(os.path.expandvars("$VIRTUAL_ENV/etc/<appname>/psql-post.d/*.psql:
    subprocess.call([which('psql'), '-f', psql_file])


我想对表名(和一些列名)使用变量的原因之一是因为数据库当前正在重构/重建,所以表名和一些列名将随着时间的推移而重命名。因为一些.psql脚本非常广泛,表名在其中被引用了很多次-所以使用\set在顶部设置它们一次更有意义,这样当数据库中的每个表被更改时,每个psql文件中只需要一次更改。将来可能会有一些小的变化,使这种方法比需要搜索和替换10-15个不同列名或表名的示例的方法更好

  • 最后一个我不想使用的解决方法:从Python模板化psql文件 *

我意识到我也可以使用一些自己开发的模板或Jinja 2直接从Python代码中动态地从模板生成PSQL文件。但我更喜欢在文件中使用纯psql,因为它使项目更具可读性和可编辑性,对于那些可能需要执行代码审查或在未来接管项目维护的人来说。对我来说也更容易工作。显然,一旦我们开始讨论在Python中通过psycopg 2使用查询来实现这一点,就有大量的变通方法可供选择-但是将.psql文件放在每个项目存储库的同一相对目录中会起到非常有用的作用

bt1cpqcv

bt1cpqcv1#

这似乎是\copy的解析问题。更新:实际上是一种记录在案的行为:https://www.postgresql.org/docs/current/app-psql.html
\copy...与大多数其他元命令不同的是,该行的其余部分始终被视为\copy的参数,并且变量插值和反引号扩展都不会在参数中执行。
尖端
另一种获得与“复制”相同结果的方法。。使用SQL > COPY... TO STDOUT命令并以\g filename或\g >终止它|与\copy不同,此方法允许命令跨越多个>行;还可以使用变量插值和反引号扩展。

\set var_tname 'cell_per'

\copy (select * from :var_tname) to stdout WITH (FORMAT CSV, HEADER);
ERROR:  syntax error at or near ":"
LINE 1: COPY  ( select * from :var_tname ) TO STDOUT WITH (FORMAT CS...

 \copy (select * from :"var_tname") to stdout WITH (FORMAT CSV, HEADER);
ERROR:  syntax error at or near ":"
LINE 1: COPY  ( select * from : "var_tname" ) TO STDOUT WITH (FORMAT...

--Note the added space when using the suggested method of including a variable as 
--table name. 

copy (select * from :var_tname) to stdout WITH (FORMAT CSV, HEADER);
copy (select * from :"var_tname") to stdout WITH (FORMAT CSV, HEADER);

--Using COPY directly works.

--So:

\o cp.csv
copy (select * from :var_tname) to stdout WITH (FORMAT CSV, HEADER);
\o

--This opens file cp.csv  COPYs  to it and then closes file. 

-- Or per docs example and UPDATE:

copy (select * from :var_tname) to stdout WITH (FORMAT CSV, HEADER) \g cp.csv

cat cp.csv  

line_id,category,cell_per,ts_insert,ts_update,user_insert,user_update,plant_type,season,short_category
5,H PREM 3.5,18,,06/02/2004 15:11:26,,postgres,herb,none,HP3
7,HERB G,1,,06/02/2004 15:11:26,,postgres,herb,none,HG
9,HERB TOP,1,,06/02/2004 15:11:26,,postgres,herb,none,HT
10,VEGGIES,1,,06/02/2004 15:11:26,,postgres,herb,annual,VG

字符串

a0zr77ik

a0zr77ik2#

我的解决方案是使用shell脚本重命名目标文件为预定名称,然后启动引用该名称的psql脚本。

infile=$(find downloads -name '38*.CSV' | head -1)
rm -f importfile
ln -s $infile importfile
psql 'host=myHost ' -f copy_importfile.psql

字符串

相关问题