【问题标题】:postgresql: nested insertpostgresql:嵌套插入
【发布时间】:2011-05-11 16:21:02
【问题描述】:

我有两张桌子。让我们说 tblA 和 tblB。
我需要在 tblA 中插入一行并将返回的 id 作为值插入到 tblB 中的列之一。

我尝试在文档中找到它,但无法得到它。那么,有没有可能写一个语句(打算在准备中使用)像

INSERT INTO tblB VALUES 
(DEFAULT, (INSERT INTO tblA (DEFAULT, 'x') RETURNING id), 'y')

就像我们对 SELECT 所做的那样?

或者我应该通过创建存储过程来做到这一点?我不确定是否可以从存储过程中创建准备好的语句。

请指教。

问候,
马扬克

【问题讨论】:

    标签: postgresql stored-procedures prepared-statement


    【解决方案1】:

    为此,您需要等待 PostgreSQL 9.1:

    with
    ids as (
    insert ...
    returning id
    )
    insert ...
    from ids;
    

    同时,您需要在应用程序中使用 plpgsql、临时表或一些额外的逻辑...

    【讨论】:

      【解决方案2】:

      这对于 9.0 和用于匿名块的新 DO 是可能的:

      do $$
      declare 
        new_id integer;
      begin
        insert into foo1 (id) values (default) returning id into new_id;
        insert into foo2 (id) values (new_id);
      end$$;
      

      这可以作为单个语句执行。不过,我还没有尝试过创建 PreparedStatement 。

      编辑

      另一种方法是简单地分两步完成,首先使用返回子句将插入运行到 tableA 中,通过 JDBC 获取生成的值,然后触发第二次插入,如下所示:

      PreparedStatement stmt_1 = con.prepareStatement("INSERT INTO tblA VALUES (DEFAULT, ?) returning id");
      stmt_1.setString(1, "x");
      stmt_1.execute(); // important! Do not use executeUpdate()!
      ResultSet rs = stmt_1.getResult();
      long newId = -1;
      if (rs.next()) {
         newId = rs.getLong(1);
      }
      PreparedStatement stmt_2 = con.prepareStatement("INSERT INTO tblB VALUES (default,?,?)");
      stmt_2.setLong(1, newId);
      stmt_2.setString(2, "y");
      stmt_2.executeUpdate();
      

      【讨论】:

        【解决方案3】:

        您可以在两次插入中执行此操作,使用currval() 检索外键(假设键是serial):

        create temporary table tb1a (id serial primary key, t text);
        create temporary table tb1b (id serial primary key,
                                     tb1a_id int references tb1a(id),
                                     t text);
        begin;
        insert into tb1a values (DEFAULT, 'x');
        insert into tb1b values (DEFAULT, currval('tb1a_id_seq'), 'y');
        commit;
        

        结果:

        select * from tb1a;
         id | t
        ----+---
          3 | x
        (1 row)
        
        select * from tb1b;
         id | tb1a_id | t
        ----+---------+---
          2 |       3 | y
        (1 row)
        

        以这种方式使用 currval 是安全的,无论是在事务内部还是事务外部。来自Postgresql 8.4 documentation

        曲线

        返回最近的值 由 nextval 为这个序列获得 在当前会话中。 (错误是 如果 nextval 从未被报告过 在这个中要求这个序列 会话。)因为这是返回一个 会话本地值,它给出了一个 可预测的答案是否 其他会话已执行 nextval 从当前会话开始。

        【讨论】:

        • 也许我是偏执狂,但是依赖currval并不是最好的主意。首先,它假设 id 是基于序列的。其次,除非明确使用事务,否则键的值可能在插入之间发生变化,这会破坏完整性。第三,不可重复使用。但是,这只是我的意见,你不需要同意它:)
        • @Sorrow,我已经编辑了答案,以解决您对密钥更改值的担忧。对于其他指控,我恳求 nolo 竞争者
        • @Waynce,您的编辑澄清了这个问题。因此,我的评论现在应该说明“如果 id 与序列相关联,则依赖 currval 是一个不错的选择”。但是,第三个问题仍然存在 - 但这是一个未包含在问题中的设计问题,可能会引发无休止的辩论,所以最好在此停止。
        • @Sorrow,我同意,它完全不便携。无论如何,感谢您帮助使答案变得更好。
        【解决方案4】:

        您可能希望为此使用AFTER INSERT 触发器。大致如下:

        create function dostuff() returns trigger as $$
        begin
         insert into table_b(field_1, field_2) values ('foo', NEW.id);
         return new; --values returned by after triggers are ignored, anyway
        end;
        $$ language 'plpgsql';
        
        create trigger trdostuff after insert on table_name for each row execute procedure dostuff();
        

        after insert 是必需的,因为您需要有 id 才能引用它。希望这会有所帮助。

        编辑

        触发器将在与触发它的命令相同的“块”中被调用,即使不使用事务 - 换句话说,它在某种程度上成为该命令的部分。因此,有不存在更改插入之间引用的 id 的风险。

        【讨论】:

          猜你喜欢
          • 2022-08-19
          • 2014-02-18
          • 1970-01-01
          • 1970-01-01
          • 2018-08-21
          • 1970-01-01
          • 2017-09-15
          • 2020-05-28
          • 2013-01-20
          相关资源
          最近更新 更多