我提出了一个有效的查询,但在我看来有点慢。当我将输出抑制为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个排名答案。在我的代码的先前版本中,我没有subsubquery的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'))
导致查询正确,之前它不完全正确。它们还导致查询需要几个小时,而不是大约需要大约40秒。
我现在的问题是,如何重写此查询以使其更快?我将Oracle 11.2.40与Toad Data Point 3.5结合使用,但遗憾的是我无法看到解释计划。
答案 0 :(得分:2)
如果使用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
关键字来表示日期常量。
答案 1 :(得分:1)
你在这里有很多多余的结构,比如TRUNC(to_date('31/12/2014', 'dd/mm/yyyy'), 'DDD')
。您正在呼叫trunc
说"剥离任何时间组件"然后传递一个没有时间组件的构造日期。只需说出date '2014-01-01'
并完成它。
至于使用日期范围,如果您想选择2014年的日期,最好的方法是比较如下:myDate >= date '2014-01-01' and myDate < date '2015-01-01'
。这样您就不必担心myDate
有时间组件以及时间值可能是多少。保存between
数据类型,使用您知道的谨慎值或日期已包含在所需的谨慎组件中。
这些建议都不能解决您的特定问题。但要养成写作&#34; skinny&#34;首先是代码,如果问题运行得太慢,它将简化您对问题的搜索。
一个可能会加快查询速度的主要建议,但即使不是,也会大大简化它,从而提高可维护性,就是从选择列表中删除子查询。
一般来说,对于复杂的查询,不要试图一次性写出整个内容。选择一个表(在您的情况下处理)并选择您知道您将需要的数据。检查结果。如果你还没有了解它。确保它完整准确。
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';
现在加入下一个表格,将选择列表添加到您希望从该表格中查看的数据,从第一个数据中删除您满意的数据,否则不需要。
select t.CLIENT_ID, c.CLIENT_ID
from treatment t
join client c
on c.CLIENT_ID = t.CLIENT_ID
where t.TREATMENTCODE = 'K'
and t.ENDDATE >= date '2014-01-01'
and t.ENDDATE < date '2015-01-01';
在选择列表中添加所需的任何字段,以验证您是否获得了正确的结果(对于您目前指定的条件)。对每个其他表重复一次,一次添加一个表,直到得到最终结果。这样,如果您突然开始得到错误的结果,您就会知道哪个表启动了问题。
您的最终结果集可能包含许多您不需要的行。只要它包含您 所需的所有行,那就没问题。将过滤保存为最后一个,因为您希望能够查看查询生成的所有数据。当您知道数据包含您需要的所有内容时,最后一步是过滤掉不需要的结果,直到您只拥有所需内容。但是能够看到所有数据可以向您展示执行过滤的许多方法,如果您尽早过滤数据,这些方法可能并不明显。
我没有任何测试数据,所以我无法在下面测试我的候选人。但是,它应该相当接近,除非我完全错过了某些东西(一种明显的可能性)。如果没有别的,也许它可以指向你一个解决方案。
SELECT c.CLIENT_ID, to_number( fif.ANSWER, '999' ) form1
FROM treatment t
join CLIENT c
on c.CLIENT_ID = t.CLIENT_ID
join forms_filled ff
on ff.CLIENT_ID = c.CLIENT_ID
join forms_items_filled fif
on fif.FORM_ID = ff.FORM_ID
WHERE t.TREATMENTCODE = 'K'
and fif.FORM_NUMBER = 607
AND fif.FORM_ITEM_NUMBER = 3779
AND length( fif.ANSWER ) >= 1
AND t.ENDDATE >= date '2014-01-01'
AND t.ENDDATE < date '2015-01-01'
AND ff.STARTDATE BETWEEN t.STARTDATUM AND NVL(t.ENDDATE, date '9999-12-31');
另一个建议:当你有一个像end_date
这样的字段时,第一个冲动是使用NULL
作为&#34的指示符;还没有定义结束日期。&#34;尝试将其设置为NOT NULL并使用默认的最大日期值date '9999-12-31'
。这意味着同样的事情,并通过摆脱nvl
或其他处理NULL值的方式的需要来简化比较。
修改:糟糕。我已经移动了窗口函数以使其不受影响,因为我只是在部分结果之后。剪切/粘贴代码时包含它。
哦,好吧,也可以在最终答案中加入。
with
Partial( CLIENT_ID, form1, rnk )as(
SELECT c.CLIENT_ID, to_number( fif.ANSWER, '999' ) form1,
row_number() over(PARTITION BY ff.CLIENT_ID ORDER BY ff.FORM_ID ASC) rnk
FROM treatment t
join CLIENT c
on c.CLIENT_ID = t.CLIENT_ID
join forms_filled ff
on ff.CLIENT_ID = c.CLIENT_ID
join forms_items_filled fif
on fif.FORM_ID = ff.FORM_ID
WHERE t.TREATMENTCODE = 'K'
and fif.FORM_NUMBER = 607
AND fif.FORM_ITEM_NUMBER = 3779
AND fif.ANSWER is not null
AND t.ENDDATE >= date '2014-01-01'
AND t.ENDDATE < date '2015-01-01'
AND ff.STARTDATE BETWEEN t.STARTDATUM AND NVL(t.ENDDATE, date '9999-12-31')
)
select CLIENT_ID, form1
from Partial
where rnk = 1;
假设这让你非常接近,如果你看一下这个和原版之间的执行计划,你会看到一个显着的改进。
另一个变化。您正在测试一个字符串,以确保它至少有一个字符。在Oracle中,不需要将空字符串视为NULL
。只需检查NOT NULL。