Postgres: .sql 文件中的 \copy 语法错误

Postgres: \copy syntax error in .sql file

我正在尝试编写一个脚本,将数据从交叉表查询复制到 Postgres 8.4 中的 .csv 文件。我可以在 psql 命令行中 运行 命令,但是当我将命令放入文件中并使用 -f 选项 运行 时,出现语法错误。

这是我正在查看的示例(来自 this 很好的答案):

CREATE TEMP TABLE t (
  section   text
 ,status    text
 ,ct        integer 
);

INSERT INTO t VALUES
 ('A', 'Active', 1), ('A', 'Inactive', 2)
,('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7);

\copy (
SELECT * FROM crosstab(
       'SELECT section, status, ct
        FROM   t
        ORDER  BY 1,2' 
       ,$$VALUES ('Active'::text), ('Inactive')$$)
AS ct ("Section" text, "Active" int, "Inactive" int)
) TO 'test.csv' HEADER CSV

然后我 运行 得到以下语法错误:

$ psql [system specific] -f copy_test.sql
CREATE TABLE
INSERT 0 5
psql:copy_test.sql:12: \copy: parse error at end of line
psql:copy_test.sql:19: ERROR:  syntax error at or near ")"
LINE 7: ) TO 'test.csv' HEADER CSV
        ^

一个类似的练习只做一个没有交叉表的简单查询就可以正常工作。

是什么导致语法错误?如何使用脚本文件将此 table 复制到 csv 文件?

根据 psql documentation:

-f filename

--file filename

Use the file filename as the source of commands instead of reading commands interactively. After the file is processed, psql terminates. This is in many ways equivalent to the internal command \i.

If filename is - (hyphen), then standard input is read.

Using this option is subtly different from writing psql < filename. In general, both will do what you expect, but using -f enables some nice features such as error messages with line numbers. There is also a slight chance that using this option will reduce the start-up overhead. On the other hand, the variant using the shell's input redirection is (in theory) guaranteed to yield exactly the same output that you would have gotten had you entered everything by hand.

这可能是 -f 选项以不同于命令行的方式处理您的输入的情况之一。删除换行符有效,将原始文件重定向到 psql 的标准输入也可能有效。

psql 认为您的第一个命令只是 \copy ( 并且下面的行来自另一个不相关的语句。元命令不会散布在多行中,因为换行符是它们的终止符。

来自 psql manpage 的相关摘录,并添加了一些重点:

Meta-Commands

Anything you enter in psql that begins with an unquoted backslash is a psql meta-command that is processed by psql itself. These commands make psql more useful for administration or scripting. Meta-commands are often called slash or backslash commands.
....
....(skipped)

Parsing for arguments stops at the end of the line, or when another unquoted backslash is found. An unquoted backslash is taken as the beginning of a new meta-command. The special sequence \ (two backslashes) marks the end of arguments and continues parsing SQL commands, if any. That way SQL and psql commands can be freely mixed on a line. But in any case, the arguments of a meta-command cannot continue beyond the end of the line.

所以第一个错误是 \copy ( 失败,然后下面的行被解释为一个独立的 SELECT 它看起来很好,直到第 7 行有一个虚假的右括号。

如评论中所述,解决方法是将整个元命令塞进一行。

此处列出的答案非常清楚地解释了推理。这是一个小技巧,可以让您的 sql 包含多行并使用 psql.

# Using a file
psql -f <(tr -d '\n' < ~/s/test.sql )
# or
psql < <(tr -d '\n' < ~/s/test.sql )

# Putting the SQL using a HEREDOC
cat <<SQL | tr -d '\n'  | \psql mydatabase
\COPY (
  SELECT
    provider_id,
    provider_name,
    ...
) TO './out.tsv' WITH( DELIMITER E'\t', NULL '', )
SQL

一样,使用 single-line \copy 命令创建 multi-line VIEW,例如:

CREATE TEMP TABLE t (
  section   text
 ,status    text
 ,ct        integer 
);

INSERT INTO t VALUES
 ('A', 'Active', 1), ('A', 'Inactive', 2)
,('B', 'Active', 4), ('B', 'Inactive', 5)
                   , ('C', 'Inactive', 7);
CREATE TEMP VIEW v1 AS
  SELECT * FROM crosstab(
         'SELECT section, status, ct
          FROM   t
          ORDER  BY 1,2' 
         ,$$VALUES ('Active'::text), ('Inactive')$$)
  AS ct ("Section" text, "Active" int, "Inactive" int);

\copy (SELECT * FROM v1) TO 'test.csv' HEADER CSV

-- optional
DROP VIEW v1;