将 CSV 文件复制到具有动态列数的临时表?

Bmo*_*moe 5 postgresql dbms temporary-tables csv

我想知道是否有办法将 csv 文件复制到临时表中,其中 csv 文件中的列数未知。我使用的数据库软件是 PgAdmin III。我发现如果我知道列数,那么我可以创建一个包含该列数的临时表,然后像这样复制 csv 文件:

   CREATE TEMPORARY TABLE temp
   (
      col1 VARCHAR(80),
      col2 VARCHAR(80),
       ....
      coln VARCHAR(80)
   );

COPY temp FROM 'C:/Users/postgres/Boost.txt' CSV HEADER DELIMITER E'    '
Run Code Online (Sandbox Code Playgroud)

但是,如果我尝试简单地将 csv 文件复制到临时表中没有列的临时表,Postgresql(8.4 版)会抱怨我正在使用一个列数少于 csv 文件的表。我一直在研究,但似乎在 Postgresql 文档中找不到任何关于此的内容。有谁知道在 Postgresql 中是否可以将 csv 文件复制到具有在运行时决定的任意数量的列的临时表中?一旦临时表加载了 csv 文件,我计划在它被销毁之前用临时表与其他表进行一些比较。csv 文件中的第一行也包含标题。

Erw*_*ter 6

基本:

\n\n

全自动化

\n

该函数完全动态地复制任何表结构:

\n
CREATE OR REPLACE FUNCTION f_dynamic_copy(_file    text\n                                        , _tbl     text = \'tmp1\'\n                                        , _delim   text = E\'\\t\'\n                                        , _nodelim text = chr(127)) -- see below!\n  RETURNS text\n  LANGUAGE plpgsql AS\n$func$\nDECLARE\n   row_ct int;\nBEGIN\n   -- create staging table for 1st row as  single text column \n   CREATE TEMP TABLE tmp0(cols text) ON COMMIT DROP;\n\n   -- fetch 1st row\n   EXECUTE format($$COPY tmp0 FROM PROGRAM \'head -n1 %I\' WITH (DELIMITER %L)$$  -- impossible delimiter\n                , _file, _nodelim);\n\n   -- create actual temp table with all columns text\n   EXECUTE (\n      SELECT format(\'CREATE TEMP TABLE %I(\', _tbl)\n          || string_agg(quote_ident(col) || \' text\', \',\')\n          || \')\'\n      FROM  (SELECT cols FROM tmp0 LIMIT 1) t\n           , unnest(string_to_array(t.cols, E\'\\t\')) col\n      );\n\n   -- Import data\n   EXECUTE format($$COPY %I FROM %L WITH (FORMAT csv, HEADER, NULL \'\\N\', DELIMITER %L)$$\n                , _tbl, _file, _delim);\n\n   GET DIAGNOSTICS row_ct = ROW_COUNT;\n   RETURN format(\'Created table %I with %s rows.\', _tbl, row_ct);\nEND\n$func$;\n
Run Code Online (Sandbox Code Playgroud)\n

调用变体:

\n
SELECT f_dynamic_copy(\'/path/to/file.csv\');\nSELECT f_dynamic_copy(\'/path/to/file2.csv\', \'tmp_file2\');\nSELECT f_dynamic_copy(_file  => \'/path/to/file2.csv\'\n                    , _tbl   => \'tmp_file2\');\n                    , _delim => E\'\\t\'); -- using assignment operator since pg 9.5\n
Run Code Online (Sandbox Code Playgroud)\n

回答:

\n
CREATE OR REPLACE FUNCTION f_dynamic_copy(_file    text\n                                        , _tbl     text = \'tmp1\'\n                                        , _delim   text = E\'\\t\'\n                                        , _nodelim text = chr(127)) -- see below!\n  RETURNS text\n  LANGUAGE plpgsql AS\n$func$\nDECLARE\n   row_ct int;\nBEGIN\n   -- create staging table for 1st row as  single text column \n   CREATE TEMP TABLE tmp0(cols text) ON COMMIT DROP;\n\n   -- fetch 1st row\n   EXECUTE format($$COPY tmp0 FROM PROGRAM \'head -n1 %I\' WITH (DELIMITER %L)$$  -- impossible delimiter\n                , _file, _nodelim);\n\n   -- create actual temp table with all columns text\n   EXECUTE (\n      SELECT format(\'CREATE TEMP TABLE %I(\', _tbl)\n          || string_agg(quote_ident(col) || \' text\', \',\')\n          || \')\'\n      FROM  (SELECT cols FROM tmp0 LIMIT 1) t\n           , unnest(string_to_array(t.cols, E\'\\t\')) col\n      );\n\n   -- Import data\n   EXECUTE format($$COPY %I FROM %L WITH (FORMAT csv, HEADER, NULL \'\\N\', DELIMITER %L)$$\n                , _tbl, _file, _delim);\n\n   GET DIAGNOSTICS row_ct = ROW_COUNT;\n   RETURN format(\'Created table %I with %s rows.\', _tbl, row_ct);\nEND\n$func$;\n
Run Code Online (Sandbox Code Playgroud)\n

在 main 之前COPY,运行初步操作COPY ... TO tmp0以获取带有列名称的第一行,这些列名称预计是不带引号、区分大小写的字符串,就像COPY ... TO ... (FORMAT csv, HEADER)导出它们一样。

\n

实际目标表的结构源自它,所有列的数据类型text为。结果表的默认名称是tmp1- 或提供您自己的名称作为第二个函数参数

\n

然后就COPY被执行了。默认分隔符是制表符 - 或者提供分隔符作为第三个函数参数

\n

_nodelim使用未出现在 CSV 文件第一行中的任何单字节字符作为非分隔符。我任意选择控制字符“Delete”(ASCII 127)。该角色将在此处被吞噬,因此我改为生成chr(127),这也是有效的。假设字符不会弹出 - 或提供非分隔符作为第四个函数参数

\n

该函数返回表名称和导入的行数。
\n请记住,临时表会随着会话的结束而消失。

\n

手册:

\n
\n

执行命令PROGRAM可能会受到操作系统的访问控制机制(例如 SELinux)的限制。

\n
\n

SO的相关答案:

\n\n

Postgres 8.4

\n

Postgres 8.4 太旧了,无法向后移植。一些提示:

\n
    \n
  • GET DIAGNOSTICS是一个可选功能。您可以将其保留或替换为桌子上的完整计数

    \n
  • \n
  • PROGRAM第 9.3 页中的子句的一个原始(昂贵)替代方案COPY是导入完整的表:

    \n
      EXECUTE format($$COPY tmp0 FROM %L WITH (DELIMITER %L)$$, _file, _delim);\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
\n

或者您准备第二个输入文件,或者您可以通过从 shell 进行管道传输来使其工作:COPY tablename FROM STDIN在第 8.4 页中可用。

\n\n