将TXT加载到postgres数据库中,将非当前列填充为null

时间:2016-02-09 01:48:17

标签: python database postgresql psycopg2

我是一个python新手,所以如果有一个我没有看到的简单解决方案,请原谅我。我有大量的txt文件(~280)我需要读入postgres数据库中的表。该表的结构使其包含txt文件中存在的所有可能的列/变量。考虑下面的例子......

file1.txt

ID | Var_A | Var_B
1  | Apple | Dog
2  | Peach | Bird

file2.txt

ID | Var_A | Var_C
5  | Grape | Cat      
3  | Apple | Dog   

file3.txt

ID | Var_B | Var_C
7  | Apple | Cat
6  | Peach | Bird

理想情况下,我希望我的表能够包含txt文件中的所有数据,其中txt文件中不存在的列/变量将被记录为空白或空。 (为了示范和清晰起见,我在下面将它们写为空。)

postgres_table:

ID | Var_A | Var_B | Var_C
1  | Apple | Dog   | NULL
2  | Peach | Bird  | NULL
3  | Apple | NULL  | Dog
5  | Grape | NULL  | Cat
6  | NULL  | Peach | Bird
7  | NULL  | Apple | Cat

鉴于文件数量很大,此过程需要自动化。我一直在python中使用psycopg2执行此任务,并且已经做了许多先前的尝试,但没有一个成功。我相信COPY查询效率最高(有数百万行数据),但在尝试复制时,我在txt文件中的非现有列有困难。

cursor = db.cursor()
query = "COPY postgres_table FROM '{}'(DELIMITER('|'));".format(~/file1.txt)
cursor.execute(query)
db.commit()

此查询产生以下错误:

psycopg2.DataError: missing data for column "Var_C"

也许有人可以看到我不知道的。我也对这项任务的其他潜在解决方案感兴趣。

2 个答案:

答案 0 :(得分:0)

一种选择是将文本文件作为字符串读取,然后修改字符串以有效地添加空值,然后将其传递给格式方法而不是文件。

因此,您可以读取文件的第一行,查看哪些字段缺失(将其与数据库表中的字段进行比较)并添加“|”在字符串的每一行中都有一个缺少的字段。

答案 1 :(得分:0)

您可以使用临时表将所有文件中的数据加载到其中,然后使用plpgsql甚至plpython执行您想要的操作。

例如:

-- Destination table
create table if not exists dest(
  id int,
  var_a varchar,
  var_b varchar,
  var_c varchar);

begin; -- Start transaction

-- Temporary table to load data from all files
create temporary table tt(
  tt_order serial,
  tt_id varchar,
  tt_var_1 varchar,
  tt_var_2 varchar,
  tt_file varchar)
on commit drop; -- It will be drooped at the end of transaction

-- Load data, header will be included
copy tt(tt_id, tt_var_1, tt_var_2) from '~/file1.txt' (delimiter '|', header false);
update tt set tt_file = '~/file1.txt' where tt_file is null;

copy tt(tt_id, tt_var_1, tt_var_2) from '~/file2.txt' (delimiter '|', header false);
update tt set tt_file = '~/file2.txt' where tt_file is null;

copy tt(tt_id, tt_var_1, tt_var_2) from '~/file3.txt' (delimiter '|', header false);
update tt set tt_file = '~/file3.txt' where tt_file is null;

-- Remove spaces
update tt set
  tt_id = trim(tt_id),
  tt_var_1 = trim(tt_var_1),
  tt_var_2 = trim(tt_var_2);

select * from tt;

-- Result is
/*
 tt_order | tt_id | tt_var_1 | tt_var_2 |   tt_file   
----------+-------+----------+----------+-------------
        1 | ID    | Var_A    | Var_B    | ~/file1.txt
        2 | 1     | Apple    | Dog      | ~/file1.txt
        3 | 2     | Peach    | Bird     | ~/file1.txt
        4 | ID    | Var_A    | Var_C    | ~/file2.txt
        5 | 5     | Grape    | Cat      | ~/file2.txt
        6 | 3     | Apple    | Dog      | ~/file2.txt
        7 | ID    | Var_B    | Var_C    | ~/file3.txt
        8 | 7     | Apple    | Cat      | ~/file3.txt
        9 | 6     | Peach    | Bird     | ~/file3.txt
(9 rows)
*/

-- Move data to destination table
do language plpgsql $$
declare
  r record;
  q text;
begin
  for r in select * from tt order by tt_order loop
    if lower(r.tt_id) = 'id' then -- If header from next file
      -- Build insert query
      q := 'insert into dest (id, ' || lower(r.tt_var_1) || ', ' || lower(r.tt_var_2) || ') values ($1::int, $2, $3)';
    else
      -- Insert data record
      execute q using r.tt_id, r.tt_var_1, r.tt_var_2;
    end if;
  end loop;
end; $$;

commit;

select * from dest;

-- Result is
/*
 id | var_a | var_b | var_c 
----+-------+-------+-------
  1 | Apple | Dog   | 
  2 | Peach | Bird  | 
  5 | Grape |       | Cat
  3 | Apple |       | Dog
  7 |       | Apple | Cat
  6 |       | Peach | Bird
(6 rows)
*/