我将客户记录插入表中,如果已经存在同名记录,我会为新插入的记录分配相同的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;
但是我不确定它是否会起作用并且对性能而言似乎是多余的/坏的。如果有一种首选方式,请告诉我。
答案 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行。这些数字将来可能会发生变化,随着表格的增长,执行计划也可能会发生变化 - 当它发生时,我会处理问题,而不是试图解决不存在的问题。
每一段代码都没问题,直到它没有。虽然知识和经验可以帮助代码保持更长的时间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程序员,因此语法可能不正确。