我有一个名为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。时间然后插入(必要时)。我现在担心表现。任何建议都会有很大的帮助。
谢谢!
答案 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;
您的手术中还有其他一些问题。
这是多余的,因为你正在使用的游标循环形式:
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