使用 pl/sql 函数插入多个表会导致外键冲突

Posted

技术标签:

【中文标题】使用 pl/sql 函数插入多个表会导致外键冲突【英文标题】:Insert into multiple tables utilizing pl/sql function results in foreign key violation 【发布时间】:2012-08-09 15:25:37 【问题描述】:
DECLARE    
 cursor curs is select * from starting;  
 appleId number;      
 bananaId number;    
BEGIN  
    for foo in curs  
    LOOP     
        insert into apple (id, weight)  
        values(1,1)  
        returning id into appleId;  
        insert into banana(id,weight)  
        values(1,3)  
        returning id into bananaId;  
        insert into apple_banana_lookup  
        values(1,appleId,bananaId);  
    END LOOP;  
    COMMIT;   
END;  

以上代码导致外键约束违反。声称Apple 中的ID 字段尚不存在。我的问题是如何使上面的代码功能并使 apple_banana_lookup 表成功保留 appleIdbananaId 中引用的键。作为补充,我希望避免在每次插入 applebanana 后都必须提交,因为给定游标中将有大约 2 亿条记录。

更新

架构声明:

    create table apple  
    ( 
        id number(20,0) not null, 
        weight number (20,0)   
    );

    create table banana  
    (   
         id number(20,0) not null, 
         weight number(20,0)  
    )  ;

    create table apple_banana_lookup  
( 
      id number(20,0) not null,
      appleId number(20,0) not null,
      bananaId number(20,0) not null   
      CONSTRAINT "apple_fk" foreign key ("appleId")  
      REFERENCES "apple" ("id"),  
     CONSTRAINT "banana_fk" foreign key ("bananaId")  
      REFERENCES "banana" ("id"),
);  

错误信息:

ORA-02291: integrity constraint  
parent key not found

【问题讨论】:

你能在第二次插入后试试commit @Annjawn 我已经尝试过了,它会导致同样的错误 id in apple_banana_lookup 不应该是必要的,因为人们会假设元组 [appleId, bananaId] 是独一无二的......而且 2 亿行似乎很多在一个事务中处理 - 根据许多其他因素,您的系统可能只是锁定表。另外,为什么是游标?当 SQL 真正意味着处理集合时,它们往往表示命令式思维。您可以只使用一组标准的INSERTs 吗? @X-Zero 如果我这样做,我最终会从starting 表中读取三个表。因为我必须取 applestartingbananastarting 之间的析取,然后从我的查找表中取析取 好的,我明白你的意思了。性能节省有那么大吗?我原以为使用游标会将其变成 RBAR(逐行痛苦)情况。 【参考方案1】:

您的解释中似乎遗漏了某些内容——正确创建表后,您的代码似乎可以正常工作

我假设这就是 applebananaapple_banana_lookup 表的定义方式(请注意,由于您没有在插入 apple_banana_lookup 时指定列列表,所以我假设表中的列是按照您的 PL/SQL 块所期望的那样排序的)。

SQL> create table apple(
  2    id number primary key,
  3    weight number
  4  );

Table created.

SQL> create table banana(
  2    id number primary key,
  3    weight number
  4  );

Table created.

SQL> create table apple_banana_lookup (
  2    id number primary key,
  3    appleID number references apple(id),
  4    bananaID number references banana(id)
  5  );

Table created.

为了避免对您的代码进行任何更改,我创建了一个有 1 行的 starting

SQL> create table starting( id number );

Table created.

SQL> insert into starting values( 1 );

1 row created.

现在我完全按照您发布的方式运行您的代码。不产生错误,每个表中插入一行。

SQL> DECLARE
  2   cursor curs is select * from starting;
  3   appleId number;
  4   bananaId number;
  5  BEGIN
  6      for foo in curs
  7      LOOP
  8          insert into apple (id, weight)
  9          values(1,1)
 10          returning id into appleId;
 11          insert into banana(id,weight)
 12          values(1,3)
 13          returning id into bananaId;
 14          insert into apple_banana_lookup
 15          values(1,appleId,bananaId);
 16      END LOOP;
 17      COMMIT;
 18  END;
 19  /

PL/SQL procedure successfully completed.

SQL> select * from apple_banana_lookup;

        ID    APPLEID   BANANAID
---------- ---------- ----------
         1          1          1

【讨论】:

嗯,我可能错过了一些转录隐藏名字的东西,让我检查一下。【参考方案2】:
create table apple  
( 
    id number(20,0) not null, 
    weight number (20,0)   
);  --No primary keys here

create table banana  
(   
     id number(20,0) not null, 
     weight number(20,0)  
)  ;  -- No primary keys here


create table apple_banana_lookup  
( 
      id number(20,0) not null,
      appleId number(20,0) not null,
      bananaId number(20,0) not null,   
      CONSTRAINT apple_fk foreign key (appleId)  
      REFERENCES apple(id),   --this wont work
     CONSTRAINT banana_fk foreign key (bananaId)  
      REFERENCES banana(id)  --this wont work
);  

看起来上面创建apple_banana_lookup 的语句不起作用。表apple_banana_lookup 上的引用完整性需要引用引用表上的唯一键。应该是这样的

create table apple  
( 
  id number(20,0) not null, 
  weight number (20,0) ,
  CONSTRAINT apple_pk PRIMARY KEY (id)
);

create table banana  
(   
  id number(20,0) not null, 
  weight number(20,0) , 
  CONSTRAINT banana_pk PRIMARY KEY (id)
)  ;

create table apple_banana_lookup  
( 
      id number(20,0) not null,
      appleId number(20,0) not null,
      bananaId number(20,0) not null,  
      CONSTRAINT app_ban_pk PRIMARY KEY (appleId, bananaId),
      CONSTRAINT apple_fk foreign key (appleId)  
      REFERENCES apple(id),  
     CONSTRAINT banana_fk foreign key (bananaId)  
      REFERENCES banana(id)
); 

所以有了这个,下面的工作就像一个魅力(除了循环之外和你的一样)-

SQL> DECLARE    
 appleId number;      
 bananaId number;    
BEGIN    
        insert into apple (id, weight)  
        values(1,1)  
        returning id into appleId;  
        insert into banana(id,weight)  
        values(1,3)  
        returning id into bananaId;  
        insert into apple_banana_lookup  
        values(1,appleId,bananaId);      
    COMMIT;   
END; 
/

PL/SQL block completed successfully.


SQL> select * from apple;
                  ID               WEIGHT
-------------------- --------------------
                   1                    1

SQL> select * from banana;
                  ID               WEIGHT
-------------------- --------------------
                   1                    3 

SQL> select * from apple_banana_lookup;
                  ID              APPLEID             BANANAID
-------------------- -------------------- --------------------
                   1                    1                    1 

【讨论】:

我不确定你的意思是不正确的。我用改名写了我的 oracle DDL 中的内容 如果你在外键子句中引用apple(id),那么appleid列应该是唯一的主键,同样适用于banana(id)【参考方案3】:

AFAIR 数据在事务中始终可见,因此您应该能够引用在之前的 INSERT 语句中插入的数据。 我怀疑该错误是指字段名称,而不是字段值。如果您向我们提供了有关错误的更多详细信息,那么我们将能够提供更好的帮助。 还要检查你的约束源。

【讨论】:

以上是关于使用 pl/sql 函数插入多个表会导致外键冲突的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL - 连接多个表会导致重复行

如何使用 PL/SQL 中的过程在表中插入多个值?

PL/SQL - 仅当记录不存在时才插入记录

ORACLE PL/SQL 使用单个 insert 语句插入多个对象

PL/SQL 在插入语句的子查询中使用别名和函数

ibatis 插入 DB2 表会导致锁定阻止触发器工作