如何插入包含外键的行?

Sté*_*ane 77 postgresql foreign-key insert postgresql-9.1

使用 PostgreSQL v9.1。我有以下表格:

CREATE TABLE foo
(
    id BIGSERIAL     NOT NULL UNIQUE PRIMARY KEY,
    type VARCHAR(60) NOT NULL UNIQUE
);

CREATE TABLE bar
(
    id BIGSERIAL NOT NULL UNIQUE PRIMARY KEY,
    description VARCHAR(40) NOT NULL UNIQUE,
    foo_id BIGINT NOT NULL REFERENCES foo ON DELETE RESTRICT
);
Run Code Online (Sandbox Code Playgroud)

假设第一个表foo是这样填充的:

INSERT INTO foo (type) VALUES
    ( 'red' ),
    ( 'green' ),
    ( 'blue' );
Run Code Online (Sandbox Code Playgroud)

有没有办法bar通过引用foo表轻松插入行?或者我必须分两步完成,首先查找foo我想要的类型,然后在其中插入一个新行bar

这是一个伪代码示例,显示了我希望可以完成的工作:

INSERT INTO bar (description, foo_id) VALUES
    ( 'testing',     SELECT id from foo WHERE type='blue' ),
    ( 'another row', SELECT id from foo WHERE type='red'  );
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 97

你的语法几乎很好,需要在子查询周围加上一些括号,它会起作用:

INSERT INTO bar (description, foo_id) VALUES
    ( 'testing',     (SELECT id from foo WHERE type='blue') ),
    ( 'another row', (SELECT id from foo WHERE type='red' ) );
Run Code Online (Sandbox Code Playgroud)

测试于 DB-Fiddle

另一种方法,如果您要插入很多值,则使用更短的语法:

WITH ins (description, type) AS
( VALUES
    ( 'more testing',   'blue') ,
    ( 'yet another row', 'green' )
)  
INSERT INTO bar
   (description, foo_id) 
SELECT 
    ins.description, foo.id
FROM 
  foo JOIN ins
    ON ins.type = foo.type ;
Run Code Online (Sandbox Code Playgroud)

  • 读了几次,但我现在明白你提供的第二个解决方案。我喜欢。当系统第一次启动时,现在使用它用一些已知值引导我的数据库。 (3认同)

Erw*_*ter 50

清楚的 INSERT

INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM  (
   VALUES
      (text 'testing', text 'blue')  -- explicit type declaration; see below
    , ('another row' , 'red' )
    , ('new row1'    , 'purple')      -- purple does not exist in foo, yet
    , ('new row2'    , 'purple')
   ) val (description, type)
LEFT   JOIN foo f USING (type);
Run Code Online (Sandbox Code Playgroud)
  • LEFT [OUTER] JOIN代替[INNER] JOIN意味着val 保留所有行中的行,即使在foo. 而是NULL输入 for foo_id(如果已定义列,则会引发异常NOT NULL)。

  • VALUES子查询表达式不一样@ ypercube的CTE。Common Table Expressions提供了额外的特性并且在大查询中更容易阅读,但它们也构成了优化障碍(直到 Postgres 12)。当不需要上述任何一项时,子查询通常会快一点。

  • 您可能需要显式类型转换。由于VALUES表达式不直接附加到表(如 in INSERT ... VALUES ...),除非明确键入,否则无法派生类型并使用默认数据类型。这可能不适用于所有情况。第一排做就够了,剩下的排成一行。

INSERT 同时丢失 FK 行

要在运行中创建丢失的条目foo,在单个 SQL 语句中,CTE 是有用的:

INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM  (
   VALUES
      (text 'testing', text 'blue')  -- explicit type declaration; see below
    , ('another row' , 'red' )
    , ('new row1'    , 'purple')      -- purple does not exist in foo, yet
    , ('new row2'    , 'purple')
   ) val (description, type)
LEFT   JOIN foo f USING (type);
Run Code Online (Sandbox Code Playgroud)

Postgres 9.6 的旧sqlfiddle - 在 9.1 中工作相同。另请参阅下面的新小提琴!

注意要插入的另外两行。两者都是紫色,在 中尚不存在foo行说明需要DISTINCT在第一个INSERT语句中。

分步说明

  1. 第一个 CTEsel提供多行输入数据。val带有VALUES表达式的子查询可以替换为表或子查询作为源。马上LEFT JOINfoo到追加foo_id的预先存在的type行。所有其他行都是foo_id IS NULL这样。

  2. 第二个 CTEins不同的新类型 ( foo_id IS NULL) 插入到 中foo,并返回新生成的foo_id- 与type连接回插入行。

  3. 最后的外部INSERT现在可以foo_id为每一行插入一个:类型预先存在,或者它是在步骤 2 中插入的。

严格来说,两个插入“并行”发生,但由于这是一个单一的语句,默认FOREIGN KEY约束不会抱怨。默认情况下,在语句末尾强制执行参照完整性。

如果您同时运行多个这些查询,则会出现微小的竞争条件。看:

真的只发生在并发负载很重的情况下,如果有的话。与另一个答案中宣传的缓存解决方案相比,机会非常

重复使用功能

创建一个 SQL 函数,该函数将复合类型的数组作为参数并unnest(param)代替VALUES表达式使用。

或者,如果此类数组的语法看起来过于混乱,请使用逗号分隔的字符串作为参数_param。以表格为例:

'description1,type1;description2,type2;description3,type3'
Run Code Online (Sandbox Code Playgroud)

然后用它来替换VALUES上面语句中的表达式:

SELECT split_part(x, ',', 1) AS description
       split_part(x, ',', 2) AS type
FROM   unnest(string_to_array(_param, ';')) x;
Run Code Online (Sandbox Code Playgroud)

在 Postgres 9.5 或更高版本中使用 UPSERT 的功能

创建用于参数传递的自定义行类型。我们可以没有它,但它更简单:

CREATE TYPE foobar AS (description text, type text);
Run Code Online (Sandbox Code Playgroud)

功能:

WITH sel AS (
   SELECT val.description, val.type, f.id AS foo_id
   FROM  (
      VALUES
         (text 'testing', text 'blue')
       , ('another row', 'red'   )
       , ('new row1'   , 'purple')
       , ('new row2'   , 'purple')
      ) val (description, type)
   LEFT   JOIN foo f USING (type)
   )
, ins AS ( 
   INSERT INTO foo (type)
   SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
   RETURNING id AS foo_id, type
   )
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM   sel
LEFT   JOIN ins USING (type);
Run Code Online (Sandbox Code Playgroud)

称呼:

'description1,type1;description2,type2;description3,type3'
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

对于具有并发事务的环境,速度快且坚如磐石。

除了上面的查询,这个函数...

有关的:


Tom*_*Tom 6

抬头。您基本上需要 foo id 才能将它们插入到 bar 中。

不是 postgres 特定的,顺便说一句。(并且您没有像那样标记它) - 这通常是 SQL 的工作方式。这里没有捷径。

但是,在应用程序方面,您可能在内存中缓存了 foo 项。我的表通常最多有 3 个唯一字段:

  • 作为表级主键的 ID(整数或其他类型)。
  • 标识符,这是一个 GUID,用作稳定的 ID 应用程序级别(并且可能在 URL 等中向客户公开)
  • 代码 - 一个可能存在的字符串,如果存在则必须是唯一的(sql server:非空过滤的唯一索引)。那是一个客户集标识符。

例子:

  • 帐户(在交易应用程序中)-> Id 是用于外键的 int。-> 标识符是一个 Guid,用于门户网站等。 - 总是被接受。-> 代码是手动设置的。规则:一旦设置就不会改变。

显然,当您想将某些内容链接到帐户时 - 从技术上讲,您首先必须获得 Id - 但鉴于标识符和代码一旦存在就永远不会改变,内存中的正缓存可以阻止大多数查找访问数据库。

  • 不变的元素?最贵的元素?许可费用(对于 PostgreSQL)?ORM 定义什么是理智的?不,我不知道这一切。 (15认同)
  • 您是否知道可以让 RDBMS 在单个 SQL 语句中为您进行查找,从而避免容易出错的缓存? (10认同)