oraclesql中慢相关查询的优化
我提出了一个有效的查询,但在我看来有点慢。当我将输出限制为10行时,执行查询需要13分钟。这是一个从一些东西中剥离出来的查询: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,
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一样进行比较