Postgresql:从巨大的 csv 文件插入,收集 ID 并遵守唯一约束

Postgresql: Insert from huge csv file, collect the ids and respect unique constraints

在 postgresql 数据库中:

class Persons(models.Model):
    person_name = models.CharField(max_length=10, unique=True)

persons.csv 文件,包含 100 万个名字。

$cat persons.csv
Name-1
Name-2
...
Name-1000000

我想:

  1. 创建尚不存在的名称
  2. 查询数据库并获取 csv 文件中包含的每个名称的 ID。

我的做法:

  1. 使用实现它的COPY command or the django-postgres-copy应用程序。

    同时利用新的 Postgresql-9.5+ upsert feature.

  2. 现在,csv 文件中的所有名称也在数据库中。

    我需要以一种有效的方式从数据库中获取他们的 ID,无论是在内存中还是在另一个 csv 文件中:

    • 使用 Q 个对象

      list_of_million_q = <iterate csv and append Qs>
      million_names = Names.objects.filter(list_of_million_q)
      

    • 使用__in根据姓名列表进行过滤:

      list_of_million_names = <iterate csv and append strings>
      million_names = Names.objects.filter(
          person_name__in=[list_of_million_names]
      )
      

      ?

我觉得以上任何一种获取 id 的方法都不是有效的。

更新

还有第三个选项,按照 的思路,它应该是一个很好的解决方案,结合了上述所有内容。

类似于:

SELECT * FROM persons;

根据从数据库收到的名字创建一个 name: id 字典:

db_dict = {'Harry': 1, 'Bob': 2, ...}

查询字典:

ids = []
for name in list_of_million_names:
    if name in db_dict:
        ids.append(db_dict[name])

与较慢的 if x in list 方法相反,您使用的是快速字典索引。

但真正确定的唯一方法是对这 3 种方法进行基准测试。

描述了如何将 RETURNINGON CONFLICT 一起使用,因此在将 csv 文件的内容插入数据库时​​,id 将保存在另一个 table 中当插入成功时,或者当 - 由于唯一约束 - 插入被省略时。

我已经在 sqlfiddle where I used that resembles the one used for the COPY 命令中测试了它,该命令直接从 csv 文件插入到数据库中,尊重唯一约束。

架构:

CREATE TABLE IF NOT EXISTS label (
  id serial PRIMARY KEY,
  label_name varchar(200) NOT NULL UNIQUE
  );
INSERT INTO label (label_name) VALUES
  ('Name-1'),
  ('Name-2');

CREATE TABLE IF NOT EXISTS ids (
  id serial PRIMARY KEY,
  label_ids varchar(12) NOT NULL
  );

剧本:

CREATE TEMP TABLE tmp_table
(LIKE label INCLUDING DEFAULTS)
ON COMMIT DROP;

INSERT INTO tmp_table (label_name) VALUES
  ('Name-2'),
  ('Name-3');

WITH ins AS(
  INSERT INTO label 
  SELECT *
  FROM tmp_table
  ON CONFLICT (label_name) DO NOTHING
  RETURNING id
)
INSERT INTO ids (label_ids)
SELECT
  id FROM ins
UNION ALL
SELECT
  l.id FROM tmp_table
JOIN label l USING(label_name);

输出:

SELECT * FROM ids;
SELECT * FROM label;