循环浏览日期范围,并在未找到数据时插入

时间:2014-09-11 11:08:45

标签: sql database oracle plsql

我有一个名为calendar的表,只有一列Date_I(类型为Date)。现在要求创建一个plsql函数insert_data,它接受start_date和end_date。此函数现在将检查start_date和end_date之间的每个日期是否显示在表日历中。如果没有,则将该日期插入表中。

我知道要求是非常不正常的,但它必须是这样的。以下是我编写的代码,但我想知道是否有更好的方法来做同样的事情:

PROCEDURE Insert_Data(Start_Date DATE, End_Date DATE)
  IS
  cal_v        calendar%rowtype;
  BEGIN
  DECLARE 
      CURSOR cal_c IS
      SELECT * 
      FROM Calendar;
  BEGIN
      FOR cal_v IN calc_c LOOP
       FOR date_v IN Insert_Data.Start_Date..Insert_Data.End_Date LOOP
        BEGIN
          SELECT * FROM calendar WHERE calc_v.calc_date = date_v;
        EXCEPTION 
          WHEN NO_DATA_FOUND THEN
            INSERT INTO calendar VALUES date_v;
        END;
       END LOOP;
      END LOOP;
  END;
END Insert_Data;

上面的代码循环遍历表日历中的每条记录,并使用该日期对其执行选择。如果未找到任何数据,则在表中插入相同的数据。此代码正在运行,但正在执行select no。时间然后插入(必要时)。我现在担心表现。任何建议都会有很大的帮助。

谢谢!

4 个答案:

答案 0 :(得分:2)

作为已经建议的MERGE方法的更具体的例子:

PROCEDURE Insert_Data(Start_Date DATE, End_Date DATE)
IS
BEGIN
  MERGE INTO calendar ca
  USING (
    SELECT Insert_Data.Start_Date + level - 1 as cal_date
    FROM dual
    CONNECT BY level <= Insert_Data.End_Date - Insert_Data.Start_Date + 1
  ) t
  ON (t.cal_date = ca.cal_date)
  WHEN NOT MATCHED THEN INSERT VALUES (t.cal_date);
END Insert_Data;

它根本不需要是一个程序,但这似乎是一个独立的要求。您可以直接使用日期范围而不是通过变量来运行合并为纯SQL。 (或者作为绑定变量,取决于你如何/在哪里运行它。)

USING子句是一个生成的表,它使用通用的CONNECT BY方法创建所提供范围内的所有日期。 The LEVEL pseudocolumn类似于您尝试做的循环;总体而言,内部查询会将您范围内的所有日期生成为内联视图,然后您可以使用该视图来检查实际表。如果声明尚未退出,则该声明的其余部分仅会插入该范围内的新记录。

通过NOT EXISTS检查,您也可以手动执行相同的操作,效率也会降低:

PROCEDURE Insert_Data(Start_Date DATE, End_Date DATE)
IS
BEGIN
  INSERT INTO calendar
  WITH t AS (
    SELECT Insert_Data.Start_Date + level - 1 as cal_date
    FROM dual
    CONNECT BY level <= Insert_Data.End_Date - Insert_Data.Start_Date + 1
  )
  SELECT cal_date
  FROM t
  WHERE NOT EXISTS (
    SELECT 1
    FROM Calendar
    WHERE Calendar.cal_date = t.cal_date
  );
END Insert_Data;

SQL Fiddle


您的手术中还有其他一些问题。

这是多余的,因为你正在使用的游标循环形式:

  cal_v        calendar%rowtype;

这里有一个不必要的嵌套块;我想它并没有受到伤害,但它也没有添加任何东西。可以删除第一个BEGIN,DECLARE和第一个END(并且对齐有点偏离):

  BEGIN  -- remove
  DECLARE -- remove
      CURSOR cal_c IS
      SELECT * 
      FROM Calendar;
  BEGIN
...
  END; -- remove
END Insert_Data;

不需要外环和整个光标;它实际上意味着你重复实际完成工作的内循环(或者第一次尝试),就像日历表中存在的记录一样多次,这是毫无意义和缓慢的:

      FOR cal_v IN calc_c LOOP
       FOR date_v IN Insert_Data.Start_Date..Insert_Data.End_Date LOOP
...
       END LOOP;
      END LOOP;

内部循环不会编译,因为你不能使用日期作为范围循环,只有整数(给出PLS-00382):

       FOR date_v IN Insert_Data.Start_Date..Insert_Data.End_Date LOOP

最里面的选择没有INTO;这不会编译:

          SELECT * FROM calendar WHERE calc_v.calc_date = date_v;

插入需要将值括在括号中:

            INSERT INTO calendar VALUES date_v;

所以,如果你真的想这样做,你可以这样做:

PROCEDURE Insert_Data(Start_Date DATE, End_Date DATE)
IS
  tmp_date DATE;
BEGIN
  FOR i IN 0..(Insert_Data.End_Date - Insert_Data.Start_Date) LOOP
  BEGIN
  dbms_output.put_line(i);
    SELECT cal_date INTO tmp_date FROM calendar
    WHERE cal_date = Insert_Data.Start_Date + i;
  EXCEPTION 
    WHEN NO_DATA_FOUND THEN
      INSERT INTO calendar VALUES (Insert_Data.Start_Date + i);
  END;
  END LOOP;
END Insert_Data;

...但实际上,请使用合并。

答案 1 :(得分:1)

尝试使用MERGE

将值插入表格
MERGE INTO calendar ca
     USING (SELECT *
              FROM calendar
             WHERE < your sql condition>
)qry ON (ca.<your_primary_key> = qry.<your_primary_key>)
WHEN NOT MATCHED THEN
  INSERT INTO calendar VALUES....

关于绩效的大量内容已经阐明here

您也可以在程序中包装merge语句。

答案 2 :(得分:0)

为什么使用PL / SQL时可以非常有效地在SQL中执行相同操作?

只需使用MERGE INTO语句,只使用一个子句WHEN NOT MATCHED THEN INSERT

例如,

MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN NOT MATCHED THEN
    INSERT (object_id, status)
    VALUES (b.object_id, b.status);

答案 3 :(得分:0)

这应该为您提供日历表中不存在的范围内的所有日期:

SELECT Date_I FROM (
  SELECT
    Start_Date + NUMTODSINTERVAL(n,'day') AS Date_I
  FROM (
    select level n
    from dual
    connect by level <= EXTRACT(DAY FROM Insert_Data.End_Date - Insert_Data.Start_Date)
  )) d 
WHERE NOT EXISTS (SELECT * FROM calendar WHERE Date_I = d.Date_I);

https://community.oracle.com/thread/2158102?start=0&tstart=0