将数据插入多个表Postgres

时间:2016-03-28 11:06:09

标签: sql postgresql sql-insert

我目前有一个具有以下架构的MongoDB数据库:

Image: { name: String, src: String, category: String, tags: [String] }

我想把它迁移到Postgres,为此我有4个表

image (id, src, name, category_id)
tag (id, name)
image_tag (image_id, tag_id)
category (id, name)

每个tag插入可能会有新的image,因此在使用CTE时我需要选择所有标记(如果不存在则只插入新标记)。我正在考虑使用缓存(redis)来存储已经插入的标签(所以我不需要从数据库中选择它们)。

所以我的问题是我应该使用insert into tags.. where not exists语句或CTE + redis与CTE一起使用,只在缓存中找不到标签时插入标签?

1 个答案:

答案 0 :(得分:0)

所以这里是 small 语句,用于将带有类别和多个标签的图像插入到postgres数据库的多个表中。以下表达式假定表类别和标记中的名称已定义唯一约束。为了完成,我还创建了一个没有该约束的语句(参见示例部分)。

Postgres声明

WITH image_values(image_name, src, category) AS (
  VALUES 
  ('Goldkraut', 'goldkraut.jpg', 'logo')
),
tag_values(tag_name) AS (
  VALUES
  ('music'), ('band')
),
category_select AS (
  SELECT id, name FROM category
  WHERE name IN (SELECT category FROM image_values) 
),
category_insert AS (
  INSERT INTO category(name) 
  SELECT category FROM image_values
  ON CONFLICT (name) DO NOTHING 
  RETURNING id, name
),
category_created AS (
  SELECT id, name FROM category_select
  UNION ALL
  SELECT id, name FROM category_insert
),
tag_select AS (
  SELECT id, name FROM tag
  WHERE name IN (SELECT tag_name FROM tag_values) 
),
tag_insert AS (
  INSERT INTO tag(name) 
  SELECT tag_name FROM tag_values
  ON CONFLICT (name) DO NOTHING 
  RETURNING id, name
),
tag_created AS (
  SELECT id, name FROM tag_select
  UNION ALL
  SELECT id, name FROM tag_insert
),
image_insert AS (
  INSERT INTO image(src, name, category_id)
  SELECT src, image_name, category_created.id 
  FROM image_values
  LEFT JOIN category_created ON(image_values.category=category_created.name)
  RETURNING id, src, name, category_id
),
image_tag_insert AS (
  INSERT INTO image_tag(image_id, tag_id)
  SELECT image_insert.id, tag_created.id FROM image_insert
  CROSS JOIN tag_created
  RETURNING image_id, tag_id
)
SELECT image_insert.*, category_created.name as category_name, image_tag_insert.*, tag_created.name as "tag.name"
  FROM image_tag_insert 
  LEFT JOIN image_insert ON (image_id = image_insert.id) 
  LEFT JOIN category_created ON (category_created.id = image_insert.category_id) 
  LEFT JOIN tag_created ON (tag_created.id = tag_id)

声明

的说明

在第一个公用表表达式(CTE)image_values中,您将定义具有1:1关系的图像的所有值。在下一个表达式tag_values中,定义了该图像的所有标记名称。

现在让我们从类别开始。要知道名称的类别是否已存在,请在category_select中查询类别条目。在表达式category_insert中,如果尚未退出,您将为该类别创建一个新条目(而不是再次从数据库中查询,我们使用cte category_select来查明我们是否已经有一个具有此名称的类别)。要在图像表中存储类别ID,我们需要类别条目,无论现有(来自category_select)还是插入(来自category_insert),所以我们将这两个表达式合并到category_created中。

现在我们对标签使用相同的模式。查询现有标记tag_select,插入标记(如果不存在)tag_insert并将此条目合并到tag_created中。

接下来,我们将图片插入image_insert。因此,我们从表达式image_values中选择值并加入表达式category_created以获取类别的ID。要将关系图像插入标记,我们将需要插入图像的id,因此我们将返回此值。其他返回值不是必需的,但我们将使用它们在最终查询中获得更好的结果集。

现在我们有了插入图像的主键,我们可以将图像的关联存储到标签中。在表达式image_tag_insert中,我们选择插入图像的id,并将其与我们选择或插入的每个标记ID交叉连接。

对于最终语句,只需执行SELECT * FROM image_tag_insert即可执行所有表达式。但是为了概述存储在数据库中的内容,我加入了所有的关系。所以结果将如下所示:

加入结果

| id |           src |      name | category_id | category_name | image_id | tag_id | tag.name |
|----|---------------|-----------|-------------|---------------|----------|--------|----------|
|  1 | goldkraut.jpg | Goldkraut |           2 |          logo |        1 |      3 |     band |
|  1 | goldkraut.jpg | Goldkraut |           2 |          logo |        1 |      1 |    music |

实施例

在这个sqlfiddle上,您将看到给定的查询。在另一个sqlfiddle中,我在最后一个语句中添加了一些额外内容,以将所有插入的标签格式化为列表。如果您尚未在表格标签和类别中的名称列中添加唯一约束,则可以使用此example