查询以将相同的ID分配给正在插入的行(如果它已存在于表中)

时间:2014-07-31 17:31:04

标签: sql oracle plsql

我将客户记录插入表中,如果已经存在同名记录,我会为新插入的记录分配相同的ID。

假设表T有此记录:

ID | Name | Phone_Number | Date_Inserted
105| Sam  | 111111       | 04/03/2014
106| Rita | 222222       |04/03/2014

我从表A中插入这个:

Name| Phone_Number
Sam | 333333

然后在插入后,表T应该有:

ID | Name | Phone_Number | Date_Inserted
105| Sam  | 111111       | 04/03/2014
106| Rita | 222222       | 04/03/2014
105| Sam  | 333333       | 04/04/2014

如果没有上述更改,它将如下所示:

INSERT INTO T SELECT CustID.nextval,Name,Phone_Number,SYSDATE FROM A;

我正在考虑使用,

INSERT INTO T
  SELECT CASE 
           WHEN NOT EXISTS(select null from T WHERE T.Name=A.Name) THEN CustID.nextVal
           ELSE (select ID from T where T.Name=A.Name) 
         END, 
         Name,
         Phone_Number,
         SYSDATE 
   FROM A;

但是我不确定它是否会起作用并且对性能而言似乎是多余的/坏的。如果有一种首选方式,请告诉我。

8 个答案:

答案 0 :(得分:8)

如果您的架构没有尽头,我可能会重新配置它,以便有一个“人”表和一个单独的“人员电话号码”表。通过这种设置,您可以将多个电话号码与一个人相关联,并且您不会踩踏ID,也不会创建非主键的令人困惑的辅助ID列。

答案 1 :(得分:2)

如果表A很小:

insert into T (id, name, phone_number, date_inserted)
select 
    nvl((select max(id) from T where T.name=A.name ), custid.nextval), 
    a.name, a.phone_number, sysdate 
from A

如果表A很大:

insert into T (id, name, phone_number, date_inserted)
select nvl(DT.id,custid.nextval), a.name, a.phone_number, sysdate 
from A 
left outer join (
    select max(id) id, name from T 
    where name in (select distinct name from A)
    group by T.name
) DT 
on A.name=DT.name

如果要将行从表A“移动”到表T:

begin
  for row in (select name, phone_number, rowid rid from A) loop
     insert into T (id, name, phone_number, date_inserted) 
     select 
        nvl((select max(id) from T where T.name=row.name ), custid.nextval), 
        row.name, row.phone_number, sysdate
     from dual; 
     delete from A where rowid=row.rid;
  end loop;
end; 
/

答案 2 :(得分:1)

将任何事物描述为“坏”都是主观的。只要结果是正确的,如果花费太长时间或使用太多系统资源,某些东西只会“坏”。 定义“长”和“太多”。如果在可接受的时间内使用可接受的系统资源返回正确的结果,则无需更改。

但是,您可以查看许多内容(假设更改数据模型不是可接受的解决方案):

  • 当您在NAME, ID上选择并返回NAME时,您将需要ID上的索引。

  • 您的第二个相关子查询(select ID from T where T.Name=A.Name)正在返回多行,这将导致错误。您需要将结果集限制为单行,或者使用某些聚合函数。最好添加一个附加条件where rownum < 2来限制结果,因为添加聚合会强制Oracle对具有该名称的每一行执行范围扫描,而您只需要查找它是否存在。

  • CASE声称它进行短路评估; this isn't necessarily true when you get sequences involved.

  • 我认为它不会影响您的INSERT语句,但可能值得将DATE_INSERTED列更改为默认值;这意味着您不需要将它添加到每个查询中,您不能忘记这样做:

    alter table t modify date_inserted date default sysdate;
    

将这些(非常小的)更改放在一起,您的查询可能如下所示:

insert into t (id, name, phone_number)
select coalesce( select id from t where name = a.name and rownum < 2
               , custid.nextval
                 )
     , name
     , phone_number
  from a

只有你可以判断这是否可以接受。

我做了类似的事情 - 对于一个分析数据库,我必须维护一个旧的基于数据的主键。我能够让事情发挥作用的唯一方法是每分钟在后台作业中运行它,使用相关的子查询并明确地对潜在行的数量添加rownum限制。我知道在INSERT语句中维护它是“更好”,但执行时间是不可接受的。我知道代码每分钟最多只能处理10,000行,但这并不重要,因为我每分钟最多只能添加5,000行。这些数字将来可能会发生变化,随着表格的增长,执行计划也可能会发生变化 - 当它发生时,我会处理问题,而不是试图解决不存在的问题。

TL;博士

每一段代码都没问题,直到它没有。虽然知识和经验可以帮助代码保持更长的时间don't prematurely optimise if there's no need to optimise

答案 3 :(得分:1)

您的insert查询版本将为第三行和后续行生成错误。我同意@JeffN你应该修改架构,因为你显然有一个&#34; person&#34;实体和电话&#34;实体。但是,鉴于您不想这样做,您想要的查询是:

INSERT INTO T(id, name, phone_number, date_inserted)
   SELECT (CASE WHEN oldid is null THEN CustID.nextVal
                ELSE oldid 
           END) as Id, Name, Phone_Number, SYSDATE 
   FROM (select a.*, (select max(id) from T where T.Name = A.Name) as OldId
         from A
        ) a;

出于此查询的目的,您应该在T(Name, Id)

上创建索引
create index idx_t_name_id on t(name, id);

您也可以将其包装在before insert触发器中。我通常使用before insert触发器在旧版本的Oracle中自动递增列,而不是显式地放置序列值。

答案 4 :(得分:1)

我可能会使用内联视图来获取不同的id,name,然后使用外部联接。

INSERT INTO T
(   id
,   name
,   phone_number
,   date_inserted
)
SELECT  NVL( TVW.id, CustID.nextval )
    ,   A.name
    ,   A.phone_number
    ,   SYSDATE
FROM    A
    ,   (   SELECT DISTINCT id, name
            FROM   T
        ) TVW
WHERE   A.name = TVW.name (+)

答案 5 :(得分:1)

我会建议这种程序。

create or replace procedure insert_id(name_in     in varchar2,
                                  phone_in    in number,
                                  date_ins_in date default sysdate) is
 cursor names is
 select id, name from names;
 type id is table of names.id%type;
 type name is table of names.name%type;
 sql_text varchar2(4000);
 r_ct     pls_integer;
 l_id     id;
 l_name   name;

begin

   open names;
   fetch names bulk collect
   into l_id, l_name;
   close names;

r_ct := 0;

for i in l_id.first .. l_id.last

 loop

  if l_name(i) = name_in then
  sql_text :=    q'{insert into names values(}' || q'{'}' 
                                                || l_id(i) 
                                                ||q'{'}' 
                                                || ',' 
                                                || q'{'}'
                                                || name_in 
                                                || q'{'}' 
                                                || ',' 
                                                ||q'{'}' 
                                                || phone_in 
                                                || q'{'}'
                                                || ',' 
                                                || q'{'}' 
                                                ||date_ins_in 
                                                || q'{'}'
                                                || ')';

    execute immediate sql_text;
    r_ct := sql%rowcount;
    commit;
    exit;
   end if;
 end loop;

 if r_ct != 1 then

for i in l_id.first .. l_id.last 
 loop
  if l_name(i) != name_in then
    sql_text := 'insert into names values(' || q'{'}' 
                                            || CustID.nextval --this part may be wrong, i guess it will be easy to correct, if something's wrong
                                            || q'{'}' 
                                            || ',' 
                                            || q'{'}' 
                                            || name_in
                                            || q'{'}' 
                                            || ',' 
                                            || q'{'}' 
                                            || phone_in 
                                            || q'{'}' 
                                            || ',' 
                                            || q'{'}' 
                                            || date_ins_in 
                                            || q'{'}'
                                            || ')';

execute immediate sql_text;
commit;
exit;
end if;
end loop;
end if;
end;

答案 6 :(得分:1)

如果可以的话,我同意关于拆分桌子的建议。您的ID列看起来应该是一个外键,将一个人的记录加入多个电话号码。

假设您无法更改表格,表格A中是否可能存在重复的名称,这些名称尚未出现在表格T中?如果是这样,您需要编写一些PL / SQL并一次处理一个记录。 例如,如果A包含...

Name| Phone_Number
Sam | 333333
Tom | 444444
Tom | 555555

...您将无法在单个插入中处理记录,因为表T中将不提供Tom的ID .Tom最终会在表T中显示两个ID。

根据您提供的示例数据,您的插入将起作用。我的下面的版本将完全相同的东西,应该稍微更高效。请注意,将评估序列的nextval是否被使用,因此您将发现在重复使用表t中的ID的任何地方都会跳过序列号。如果这是一个问题,你可能会考虑编写一些PL / SQL。

insert into t 
            (id
            ,name
            ,phone_number
            ,date_inserted)
   select    nvl(t.id,CustID.nextval)
            ,a.name
            ,a.phone_number
            ,sysdate
   from a
   left join t on a.name = t.name;

答案 7 :(得分:0)

我主要使用mysql,所以不确定oracle语法,但逻辑上我们可以使用if语句和子查询来实现。像这样:

INSERT INTO T SELECT (CASE WHEN (SELECT COUNT(ID) FROM T WHERE Name=A.Name) > 0 THEN (SELECT ID FROM T WHERE Name=A.Name where ROWNUM <= 1) ELSE CustID.nextval END),Name,Phone_Number,SYSDATE FROM A;

同样,我不是Oracle程序员,因此语法可能不正确。