获取 ORA-01422: 调用过程时精确提取 returns 多于请求的行数

getting ORA-01422: exact fetch returns more than requested number of rows while calling a procedure

CREATE TABLE cmb_staging (
    e_id               NUMBER(10),
    e_name             VARCHAR2(30),
    e_loc              VARCHAR2(30),
    validation_status  VARCHAR2(30),
    validation_result  VARCHAR2(30)
);

insert into cmb_staging values(1,'A','AA',null,null);
insert into cmb_staging values(1,'B','BB',null,null);
insert into cmb_staging values(2,'C','CC',null,null);
insert into cmb_staging values(2,'D','DD',null,null);
insert into cmb_staging values(3,'A','AA',null,null);

CREATE TABLE cmb_target (
    e_id    NUMBER(10),
    e_name  VARCHAR2(30),
    e_loc   VARCHAR2(30)
);

CREATE TABLE cmb_reject (
    e_id               NUMBER(10),
    e_name             VARCHAR2(30),
    e_loc              VARCHAR2(30),
    validation_status  VARCHAR2(30),
    validation_result  VARCHAR2(30)
);

CREATE TABLE SUMMARY_TAB
   (    TOT_RECORDS NUMBER(10,0), 
    SUCCESS_RECORDS NUMBER(10,0), 
    FAILED_RECORDS NUMBER(10,0), 
    PROCESS_STATUS VARCHAR2(30)
   );

程序:

create or replace procedure sp_dup_rec(ov_err_msg OUT varchar2)
    is
      lv_succ_rec number(30);
      lv_fail_rec number(30);
      lv_count number(30);
    begin
      lv_succ_rec := 0;
      lv_fail_rec := 0;

      UPDATE cmb_staging
      SET   validation_status = 'Fail',
            validation_result    = CASE
                                WHEN e_id IS NULL
                                THEN 'Id is not present'
                                ELSE 'Id is longer than expected'
                                END
      WHERE e_id is null
      OR    LENGTH(e_id) > 4;

--If there are duplicates id then it should go into cmb_reject table
select e_id into lv_count from cmb_staging;
if lv_count < 1 then
      MERGE INTO cmb_target t
        USING (SELECT e_id,
             e_name,
             e_loc
      FROM   cmb_staging
      WHERE  validation_status IS NULL) S
        ON (t.e_id = S.e_id)

            WHEN MATCHED THEN UPDATE SET 
                t.e_name = s.e_name,
                t.e_loc = s.e_loc


            WHEN NOT MATCHED THEN INSERT (t.e_id,t.e_name,t.e_loc)
                VALUES (s.e_id,s.e_name,s.e_loc);

      lv_succ_rec := SQL%ROWCOUNT;
else
      insert into cmb_reject
      select s.*
      from   cmb_staging s
      WHERE  validation_status = 'Fail';

      lv_fail_rec := SQL%ROWCOUNT;
end if;

      dbms_output.put_line('Inserting into Summary table');
      insert into summary_tab(
        tot_records,
        success_records,
        failed_records
      ) values (
        lv_succ_rec + lv_fail_rec,
        lv_succ_rec,
        lv_fail_rec
      );

      COMMIT;
      ov_err_msg := 'Procedure completed succesfully';
    EXCEPTION
      WHEN OTHERS THEN
        ov_err_msg := 'Procedure end up with errors'|| sqlerrm;
        ROLLBACK;
    END sp_dup_rec;

调用程序:

set serveroutput on;
declare
    v_err_msg varchar2(100);
begin
    sp_dup_rec(v_err_msg);
    dbms_output.put_line(v_err_msg);
end;

您好团队, 调用过程时出现 ora-01422 错误。基本上,我想将重复记录插入 cmb_reject 选项卡,因为合并语句将失败,如果我只使用合并,我将得到 ora - 30296 错误。所以,我写了 if 条件,其中它将获取计数,如果计数更多,则将插入 cmb_reject tab

这是一个问题:

--If there are duplicates id then it should go into cmb_reject table
select e_id into lv_count from cmb_staging;

由于没有 WHERE 子句限制返回的行数,如果 cmb_staging 包含 2(或更多)行,查询将失败,因为您不能将那么多行放入标量 lv_count 变量。

看来你真的是想说

select count(*) into lv_count from cmb_staging;

正如下一行所说

if lv_count < 1 then

错误堆栈应该告诉您抛出错误的行。我的猜测是这条线是

select e_id 
  into lv_count 
  from cmb_staging;

因为该查询没有意义。如果 cmb_staging 中有多行,查询将抛出 ORA-01422 错误,因为您不能将多行放在一个标量变量中。您似乎极有可能希望您的查询至少执行 count。并且很可能带有某种谓词。像

select count(*)
  into lv_count 
  from cmb_staging;

会避免错误。但这可能没有意义,因为您在评论中说“它应该进入 cmb_reject table”,这意味着存在一个单一的对象。每当 cmb_staging 中有任何行时,此更改都会导致 lv_count > 0,并且您似乎不太可能希望始终拒绝行。

我的粗略猜测是,当您说“重复”时,您的意思是“两行具有相同 e_id”。如果是这样

select e_id, count(*)
  from cmb_staging
 group by e_id
having count(*) > 1

将显示重复的 e_id 值。然后你可以

  insert into cmb_reject
  select s.*
  from   cmb_staging s
  where  e_id in (select s2.e_id
                    from cmb_staging s2
                   group by s2.e_id
                  having count(*) > 1);

如果您并不真正关心性能(根据您提出的问题,我假设您只是在学习 PL/SQL 而不是试图管理多 TB数据仓库负载)并且只想循环处理记录

for stg in (select s.*,
                   count(*) over (partition by s.e_id) cnt
              from cmb_staging s)
loop
  if( stg.cnt > 1 )
  then
    insert into cmb_reject( e_id, e_name, e_loc )
      values( stg.e_id, stg.e_name, stg.e_loc );
    l_fail_cnt := l_fail_cnt + 1;
  else 
    merge into cmb_target tgt
      using( select stg.e_id e_id,
                    stg.e_name e_name,
                    stg.e_loc e_loc
               from dual ) src
         on( src.e_id = tgt.e_id )
       when not matched 
       then 
         insert( e_id, e_name, e_loc )
           values( src.e_id, src.e_name, src.e_loc )
       when matched
       then
         update set e_name = src.e_name,
                    e_loc  = src.e_loc;
      
    l_success_cnt := l_success_cnt + 1;
  end if;
end loop;
--If there are duplicates id then it should go into cmb_reject table
select e_id into lv_count from cmb_staging;

当您尝试将所有 5 个 e_id 值插入到单个变量中时,这会引发异常并引发 TOO_MANY_ROWS 异常。 (除了它不识别重复项之外。)


您可以在原始 UPDATE 中进行所有处理(在本例中,将其转换为 MERGE 语句),而不是尝试将重复项识别为流程的单独部分:

create or replace procedure sp_dup_rec(ov_err_msg OUT varchar2)
is
  lv_succ_rec number(30);
  lv_fail_rec number(30);
  lv_count number(30);
begin
  MERGE INTO cmb_staging dst
  USING (
    SELECT ROWID AS rid,
           CASE
           WHEN e_id IS NULL
           THEN 'Id is not present'
           WHEN LENGTH(e_id) > 4
           THEN 'Id is longer than expected'
           WHEN num_e_id > 1
           THEN 'Duplicate IDs'
           END AS failure_reason
    FROM   (
      SELECT e_id,
             COUNT(*) OVER (PARTITION BY e_id) AS num_e_id
      FROM   cmb_staging
    )
    WHERE  e_id IS NULL
    OR     LENGTH(e_id) > 4
    OR     num_e_id > 1
  ) src
  ON (src.rid = dst.ROWID)
  WHEN MATCHED THEN
    UPDATE
    SET validation_status = 'Fail',
        validation_result = failure_reason;

  MERGE INTO cmb_target t
  USING (
    SELECT e_id,
           e_name,
           e_loc
    FROM   cmb_staging
    WHERE  validation_status IS NULL
  ) S
  ON (t.e_id = S.e_id)
  WHEN MATCHED THEN
    UPDATE
    SET t.e_name = s.e_name,
        t.e_loc = s.e_loc
  WHEN NOT MATCHED THEN
    INSERT (t.e_id,t.e_name,t.e_loc)
    VALUES (s.e_id,s.e_name,s.e_loc);

  lv_succ_rec := SQL%ROWCOUNT;

  insert into cmb_reject
  select s.*
  from   cmb_staging s
  WHERE  validation_status = 'Fail';

  lv_fail_rec := SQL%ROWCOUNT;

  dbms_output.put_line('Inserting into Summary table');
  insert into summary_tab(
    tot_records,
    success_records,
    failed_records
  ) values (
    lv_succ_rec + lv_fail_rec,
    lv_succ_rec,
    lv_fail_rec
  );

  COMMIT;
  ov_err_msg := 'Procedure completed succesfully';
EXCEPTION
  WHEN OTHERS THEN
    ov_err_msg := 'Procedure end up with errors'|| sqlerrm;
    ROLLBACK;
END sp_dup_rec;
/

db<>fiddle here