【问题标题】:Loop through date range and insert when no data found遍历日期范围并在未找到数据时插入
【发布时间】:2014-11-05 07:26:31
【问题描述】:

我有一个名为日历的表,其中只有一列 Date_I(日期类型)。现在的要求是创建一个带有 start_date 和 end_date 的 plsql 函数 insert_data。此函数现在将检查 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。次,然后插入(需要时)。我现在很担心性能。任何建议都会有很大帮助。

谢谢!

【问题讨论】:

  • 为什么要打开一个显式游标并循环遍历它?难道仅仅内部循环就足以插入记录了吗?
  • 由于内部选择没有 INTO 子句,因此无法正常工作,甚至无法编译

标签: sql database oracle plsql


【解决方案1】:

作为已经建议的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.


您的程序中还有一些其他问题。

这是多余的,因为您使用的是 cursor-for 循环的形式:

  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 表示第一个选项
  • @Alex,很高兴看到你进一步阐述了我的建议。但我想让 OP 回答他为什么要使用 PL/SQL。对我来说,它看起来是 SQL 中的简单 MERGE。
  • @Alex:感谢您的解决方案。我测试了它,它工作正常..但是你能解释一下内部选择子句吗..这里的 level 值是什么?它是否循环来自 CONNECT BY 子句的 Level 值?我正在努力正确理解它..谢谢!
  • @LalitKumarB - 完全同意,我提到它可以是独立的,但问题中说明了对功能的要求 - “它必须像这种方式”。无论如何,该语句都是相同的,只是开始/结束范围值的来源发生了变化。将它包装在一个过程中似乎没有必要,但我猜有些地方坚持使用 CRUD 包装器。
  • @LalitKumarB :因为它是从前端调用的。这是需求不能帮助它。
【解决方案2】:

尝试使用 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

您也可以将合并语句包装在一个过程中。

【讨论】:

  • @Lalit 和 Polppan:谢谢你们。但我想知道 MERGE 在内部是如何工作的?它是否也循环遍历表格? (外循环和内循环)
  • @LittleMissSunshine 查看this 文档,该文档很好地解释了 MERGE 语句。不使用 PLSQL 或不使用循环,您可以达到预期的结果。
  • 谢谢!这说明了一切。并行执行操作的能力使其更高效。感谢您提供链接。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

    既然可以在 SQL 中高效地执行相同的操作,为什么还要使用 PL/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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多