2012-05-31 41 views
3

我有一个ORACLE表,有几百万行数据。其中一个属性是DATE类型。我需要在该函数中使用该DATE属性选择该表。该函数告诉我哪些行符合我的标准。问题是当我运行这个查询时,它必须通过函数传递表中的每一行(显然)来确定哪些行匹配。这一点表现不错。我试图找到一个很好的解决方案,使这个过程执行得更快。在数据枚举前减少结果集的一种方法

这里有几个想法,我要去尝试:

  1. 创建的数据子集的视图,则这些行进入 功能。
  2. 将数据的一个子集转储到一个新的独立表中,然后将这些行传递给该函数。
  3. 使用数据的子集创建物化视图,然后将这些行传递给函数。

我还应该提一下,我没有太多的可以添加到WHERE子句来减少结果,只是这个DATE和函数的使用。

对这些或其他人使用成功的意见会很好。如果可能的话,SQL解决方案将是我的第一选择。

编辑 功能:

FUNCTION add_business_days (in_date IN DATE, in_number_of_days IN NUMBER,in_skip_fridays IN number DEFAULT 0,in_skip_bank_holidays IN NUMBER DEFAULT 0) 
    RETURN DATE 
    IS 
    v_return_date DATE := in_date; 
    BEGIN 
    FOR i IN 1..in_number_of_days 
    LOOP 
     v_return_date := next_business_day(v_return_date,in_skip_fridays,in_skip_bank_holidays); 
    END LOOP; 
    RETURN v_return_date; 
    END; 

的函数被调用是这样的:

SELECT * 
    FROM tableA 
WHERE tableA.begin_dt < TRUNC(SYSDATE) 
    AND CUBS_DATE_PKG.add_business_days(file_dt,15) = TRUNC(SYSDATE) 

功能next_business_day

FUNCTION NEXT_BUSINESS_DAY (in_date DATE) 
RETURN DATE IS 
    v_next_day DATE; 
    --set up the holidays 
    c_new_years_day CONSTANT DATE := holiday_observed(TRUNC(in_date,'YYYY')); 
    c_next_new_year CONSTANT DATE := holiday_observed(TRUNC(ADD_MONTHS(in_date,12),'YYYY')); 
    c_mlk_day  CONSTANT DATE := first_weekday(TRUNC(in_date,'YYYY'),'MONDAY') + 14; 
    c_presidents_day CONSTANT DATE := first_weekday(ADD_MONTHS(TRUNC(in_date,'YYYY'),1),'MONDAY')+14; 
    c_memorial_day CONSTANT DATE := first_weekday(ADD_MONTHS(TRUNC(in_date,'YYYY'),5),'MONDAY')-7; 
    c_july_4   CONSTANT DATE := holiday_observed(TO_DATE('04-JUL-'||TO_CHAR(in_date,'YYYY'),'DD-MON-YYYY')); 
    c_pioneer_day CONSTANT DATE := holiday_observed(TO_DATE('24-JUL-'||TO_CHAR(in_date,'YYYY'),'DD-MON-YYYY')); 
    c_labor_day  CONSTANT DATE := first_weekday(ADD_MONTHS(TRUNC(in_date,'YYYY'),8),'Monday'); 
    c_veterans_day CONSTANT DATE := holiday_observed(TO_DATE('11-NOV-'||TO_CHAR(in_date,'YYYY'),'DD-MON-YYYY')); 
    c_thanksgiving CONSTANT DATE := first_weekday(ADD_MONTHS(TRUNC(in_date,'YYYY'),10),'THURSDAY')+21; 
    c_christmas  CONSTANT DATE := holiday_observed(TO_DATE('25-DEC-'||TO_CHAR(in_date,'YYYY'),'DD-MON-YYYY')); 

    BEGIN 
    IF LTRIM(RTRIM(TO_CHAR(in_date,'DAY'))) IN ('FRIDAY','SATURDAY','SUNDAY') 
    THEN 
     v_next_day := NEXT_DAY(in_date,'MONDAY'); 
    ELSE 
     v_next_day := in_date + 1; 
    END IF; 

    v_next_day := TRUNC(v_next_day); 
    --now, we have to check to see if v_next_day falls on a holiday 
    IF v_next_day IN (c_new_years_day, c_next_new_year, c_mlk_day, c_presidents_day, 
         c_memorial_day,c_july_4, c_pioneer_day, c_labor_day, 
         c_veterans_day,c_thanksgiving, c_christmas) 
    THEN 
     v_next_day := next_business_day(v_next_day); 
    END IF; 
    RETURN TRUNC(v_next_day); 
    END next_business_day; 

SOLUTION:

我在这里输入的解决方案,因为没有一个确切的解决方案,但是,@JustinCave给出了正确的概念。它归结为使函数成为确定性的。所以我只是将现有的功能封装在一个新的确定性功能中。然后我在必要的表上为这个函数创建了一个索引。从22分钟开始,它不到一秒钟。另外,我确实使用了@Sebas公式来减少结果集。

CREATE OR REPLACE FUNCTION deter_add_business_days (p_date DATE,p_days NUMBER) 
    RETURN DATE 
    DETERMINISTIC 
IS 
BEGIN 
    RETURN cubs_owner.cubs_date_pkg.add_business_days (p_date, p_days); 
END; 
+0

功能有多复杂?如果它很简单,你可以在你的SQL语句中使用等价的代码。 –

+0

查看关于此功能的更多详细信息会很有帮助 - 最好查看完整的代码。 –

+0

@BobJarvis - 我添加了该功能。它不是超级复杂的,但我不能创建索引,因为错误“ORA-30553:函数不确定” – northpole

回答

3

函数是确定性的吗?如果是这样,它被标记为确定性的?它可以成为表格中基于函数的索引的一部分吗?

如果您可以识别可以使用的数据的子集而不是查询整个表,这意味着您可以在查询中应用一些其他谓词。无论适用于生成视图/物化视图/单独表的条件看起来都适合作为谓词添加到查询中。

+0

+ +1添加示例查询,正是我所建议的...... – RedFilter

+0

它是非确定性的。但这是我从未想过的伟大建议,所以我会尝试使之成为确定性的。至于我可以通过一些额外的谓词减少数据的含义,我只是想抓住最近的“大块”数据。我可以在SQL的谓词中使用它,我只是在想,无论谓词如何,具有100K行的表将比具有500万行的表快。 – northpole

+1

@northpole:为了使它具有确定性,它看起来像你必须嵌入“next_business_day”的代码 - 但我** **猜测** next_business_day看着各种日历表,这将使它不是无论如何。嗯......我们可以看到'next_business_day'的代码和日历表的结构(如果存在)吗? –

2

功能next_business_day:

  • IF LTRIM(RTRIM(TO_CHAR(in_date, '日')))IN( 'FRIDAY', 'SATURDAY', '星期日') =>跳过LTRIM/RTRIM ?由于你正在处理oracle预格式化的日子,你可能不需要删除空格

  • RETURN TRUNC(v_next_day); =>将trunc是没有必要的,你只是做了以上

OK了几行,这是一个非常小的事情,但每百万乘...

对于查询,我建议上来有了这个小黑客:因为你只有11个假期,你可以确定返回的行必须有file_dt以下或等于TRUNC(SYSDATE)+ 7 *(15 + 11)/ 5天。然后,PL/SQL块看起来是这样的:

DECLARE 
    TYPE T_IDS IS TABLE OF tableA.id%TYPE; 
    arrDays T_IDS; 
    iDays NUMBER := 15; 
BEGIN 
    --reduce the amount of rows the gross way: 
    SELECT tableA.id BULK COLLECT INTO arrDays 
     FROM tableA 
    WHERE tableA.begin_dt < TRUNC(SYSDATE) 
     AND tableA.file_dt <= (TRUNC(SYSDATE) + FLOOR(7*(iDays+11)/5))); 

    --use the reduced recordset against the businessdays validation to retrieve 
    --correct rows: 
    --here you ahve to store/process the results the way you want 
    SELECT t2.* 
     FROM TABLE (CAST(arrDays) AS T_IDS) t1 
     INNER JOIN tableA t2 ON t1.column_value = t2.id 
    WHERE CUBS_DATE_PKG.add_business_days(t2.file_dt, iDays) = TRUNC(SYSDATE); 
END; 

我没有测试它在所有,道歉可能的错误。 干杯

+0

这太棒了!推定一组精确的范围是很好的。我使用任意数字来提出“大量数据”,但这是一个优雅的黑客攻击。我仍然希望我能够索引这个函数,但是我也可以处理一部分数据。 – northpole

+0

如果子集仍然返回很多行,则尝试通过在select self中使用CONNECT BY语句来替换add_business_days函数。 – Sebas

2

将公共假期表和下一个工作日计算在下一个工作日的成本非常低,该工作日将根据一年中的每一天的某一天取得。然后,您的查询可以包含一个便宜的连接到该表并且不再有功能。

+0

这是一个好主意,我应该从一开始就这样做。 – northpole