oraclesql中慢相关查询的优化

oraclesql中慢相关查询的优化,sql,oracle,correlated-subquery,Sql,Oracle,Correlated Subquery,我提出了一个有效的查询,但在我看来有点慢。当我将输出限制为10行时,执行查询需要13分钟。这是一个从一些东西中剥离出来的查询: SELECT (SELECT ANSWER FROM ( SELECT to_number(fiit.ANSWER, '999') ANSWER, foin.CLIENT_ID id, foin.STARTDATE start_date,

我提出了一个有效的查询,但在我看来有点慢。当我将输出限制为10行时,执行查询需要13分钟。这是一个从一些东西中剥离出来的查询:

SELECT
    (SELECT ANSWER
        FROM (
            SELECT to_number(fiit.ANSWER, '999') ANSWER,
                    foin.CLIENT_ID id,
                    foin.STARTDATE start_date,
                    row_number() over(PARTITION BY foin.CLIENT_ID ORDER BY foin.FORM_ID ASC) rnk
                FROM forms_filled foin, forms_items_filled fiit, treatment trtm
                WHERE foin.FORM_ID = fiit.FORM_ID
                AND foin.CLIENT_ID = trtm.CLIENT_ID
                AND fiit.FORM_NUMBER = 607
                AND fiit.FORM_ITEM_NUMBER = 3779
                AND length(fiit.ANSWER) >= 1
                AND trtm.TREATMENTCODE = 'K'
                AND trtm.ENDDATE BETWEEN TRUNC(to_date('01/01/2014', 'dd/mm/yyyy'), 'DDD') AND TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD')
                AND foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, to_date('01/01/9999', 'dd/mm/yyyy'))
                ) inn
    WHERE rnk = 1
    AND inn.id = client.CLIENT_ID
    ) form1
FROM treatment trtm, CLIENT client
WHERE trtm.TREATMENTCODE = 'K'
AND client.CLIENT_ID = trtm.CLIENT_ID
AND trtm.ENDDATE BETWEEN TRUNC(to_date('01/01/2014', 'dd/mm/yyyy'), 'DDD') AND TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD')
外部查询将产生175名客户,他们有特定的治疗代码,治疗结束日期为2014年。现在,对于这些客户中的每一位,都会检索到很多其他数据(比如姓名、年龄、治疗时间),这些数据都是不相关的,我现在忽略了这些数据。然后有大约30个类似的子查询,它们从表单中检索答案。我使用了相关查询,因为要从这些表单中检索答案,必须知道客户机id。如果这是子查询查找数据所需的唯一内容,这不会是一个问题,但还有一个要求:检索的表单必须在处理期内填写,因为我找不到将此数据从外部查询推送到子查询的方法,我在子查询中再次查询,这导致了速度变慢

拥有子查询和子子查询的原因是必须找到表单中的第N个排名答案。在以前版本的代码中,我在子子查询的where子句中没有treatmentcode、treatment start和enddate要求。这导致子子查询得出4个结果,排名为1,2,3,4,但不一定是治疗期间创建的表单,这是错误的

因此,添加这些行:

AND trtm.TREATMENTCODE = 'K'
AND trtm.ENDDATE BETWEEN TRUNC(to_date('01/01/2014', 'dd/mm/yyyy'), 'DDD')
AND TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD')
AND foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, to_date('01/01/9999', 'dd/mm/yyyy'))
导致查询是正确的,而以前查询不是完全正确的。它们还导致查询花费数小时,而不是175行的约40秒


我现在的问题是,如何重写此查询以使其更快?我将Oracle 11.2.40与Toad数据点3.5结合使用,但不幸的是,我看不到解释计划。

如果使用
keep
关键字获取第一个值,则可以省去嵌套的子查询。这反过来允许您使用与外部查询相关的查询,因此您不必重新计算所有行的结果来获得给定行的值

查询如下所示:

SELECT (SELECT max(to_number(fiit.ANSWER, '999')) keep (dense_rank first order by foin.FORM_ID ASC)
        FROM forms_filled foin JOIN
             forms_items_filled fiit
             ON foin.FORM_ID = fiit.FORM_ID
        WHERE foin.CLIENT_ID = trtm.CLIENT_ID AND
              fiit.FORM_NUMBER = 607
              fiit.FORM_ITEM_NUMBER = 3779 AND
              length(fiit.ANSWER) >= 1 AND
              foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, DATE '1999-01-01')
       )

我还鼓励您使用现代显式
join
语法和
date
关键字来表示日期常量。

如果您使用
keep
关键字来获取第一个值,则可以省去嵌套的子查询。这反过来允许您使用与外部查询相关的查询,因此您不必重新计算所有行的结果来获得给定行的值

查询如下所示:

SELECT (SELECT max(to_number(fiit.ANSWER, '999')) keep (dense_rank first order by foin.FORM_ID ASC)
        FROM forms_filled foin JOIN
             forms_items_filled fiit
             ON foin.FORM_ID = fiit.FORM_ID
        WHERE foin.CLIENT_ID = trtm.CLIENT_ID AND
              fiit.FORM_NUMBER = 607
              fiit.FORM_ITEM_NUMBER = 3779 AND
              length(fiit.ANSWER) >= 1 AND
              foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, DATE '1999-01-01')
       )

我还鼓励您使用现代显式
join
语法和
date
关键字来表示日期常量。

如果您使用
keep
关键字来获取第一个值,则可以省去嵌套的子查询。这反过来允许您使用与外部查询相关的查询,因此您不必重新计算所有行的结果来获得给定行的值

查询如下所示:

SELECT (SELECT max(to_number(fiit.ANSWER, '999')) keep (dense_rank first order by foin.FORM_ID ASC)
        FROM forms_filled foin JOIN
             forms_items_filled fiit
             ON foin.FORM_ID = fiit.FORM_ID
        WHERE foin.CLIENT_ID = trtm.CLIENT_ID AND
              fiit.FORM_NUMBER = 607
              fiit.FORM_ITEM_NUMBER = 3779 AND
              length(fiit.ANSWER) >= 1 AND
              foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, DATE '1999-01-01')
       )

我还鼓励您使用现代显式
join
语法和
date
关键字来表示日期常量。

如果您使用
keep
关键字来获取第一个值,则可以省去嵌套的子查询。这反过来允许您使用与外部查询相关的查询,因此您不必重新计算所有行的结果来获得给定行的值

查询如下所示:

SELECT (SELECT max(to_number(fiit.ANSWER, '999')) keep (dense_rank first order by foin.FORM_ID ASC)
        FROM forms_filled foin JOIN
             forms_items_filled fiit
             ON foin.FORM_ID = fiit.FORM_ID
        WHERE foin.CLIENT_ID = trtm.CLIENT_ID AND
              fiit.FORM_NUMBER = 607
              fiit.FORM_ITEM_NUMBER = 3779 AND
              length(fiit.ANSWER) >= 1 AND
              foin.STARTDATE BETWEEN trtm.STARTDATUM AND NVL(trtm.ENDDATE, DATE '1999-01-01')
       )

我还鼓励您使用现代显式
join
语法和
date
关键字来表示日期常量。

这里有很多多余的构造,比如
TRUNC(to_date('31/12/2014','dd/mm/yyyyy'),'DDD')
。您正在调用
trunc
说“去掉任何时间组件”,然后在没有时间组件的情况下向其传递构造日期。只需说出日期“2014-01-01”就可以了

至于使用日期范围,如果您想选择2014年的日期,最好的方法是这样比较:
myDate>=date'2014-01-01'和myDate
。这样,您就不必担心
myDate
有时间组件以及该时间值可能是什么。在
之间保存
,用于具有离散值或日期的数据类型,您知道这些数据类型已经在所需的离散组件中

这些建议都不能解决你的问题。但是,首先要养成编写“瘦”代码的习惯,如果它运行得太慢,就会简化对问题的搜索

一个主要的建议是从选择列表中删除子查询,它可能会加快查询速度,但即使没有,也会大大简化查询,从而提高可维护性

一般来说,对于复杂的查询,不要试图一次完成全部内容。选择一个表格(在您的情况下进行治疗)并选择您知道需要的数据。检查结果。如果你还不知道,就去了解它。确保它是完整和准确的

select  t.CLIENT_ID, t.TREATMENTCODE, t.ENDDATE
from    treatment t
where   t.TREATMENTCODE = 'K'
    and t.ENDDATE >= date '2014-01-01'
    and t.ENDDATE  < date '2015-01-01';
假设这让你非常接近,如果你看一下这个和你的原始计划之间的执行计划,你应该会看到一个显著的改进


还有一个变化。您正在测试字符串,以确保它至少有一个字符。在Oracle中,不需要作为空字符串的字符串被视为
NULL
。只需检查NOTNULL。

这里有很多多余的构造,比如
TRUNC(截止日期('31/12/2014','dd/mm/yyyy'),'DDD')
。您正在调用
trunc
说“去掉任何时间组件”,然后在没有时间组件的情况下向其传递构造日期。只需说出日期“2014-01-01”
就可以了

至于使用日期范围,如果您想选择2014年的日期,最好的方法是像t一样进行比较