楼主: newkid

[精华] 利用递归WITH子查询进行优化的实例

[复制链接]
论坛徽章:
3
ITPUB官方微博粉丝徽章
日期:2011-06-29 09:48:25ITPUB十周年纪念徽章
日期:2011-11-01 16:21:15秀才
日期:2016-02-18 09:23:46
11#
发表于 2011-6-16 17:28 | 只看该作者
不过怎么说这个创建日期上不加索引有点说不过去.newkid的SQL确实炉火纯情。

使用道具 举报

回复
论坛徽章:
0
12#
发表于 2011-6-16 21:38 | 只看该作者
赞一个,用得炉火纯清啊!

[ 本帖最后由 perzer 于 2011-6-16 21:42 编辑 ]

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
13#
 楼主| 发表于 2011-6-16 21:56 | 只看该作者
原帖由 爱乐 于 2011-6-16 11:32 发表
一直没理解newkid 大侠的思路,所以依葫芦画瓢照着步骤做了一遍!




琢磨了很久也没能解决这个问题,还请newkid 出手相助了!

11201的SQL解析有BUG, 11202就好了。

必须这么改:
WITH t (item_id,cnt) AS (
SELECT max(item_id),1 FROM items   ---先取最近的
GROUP BY 1  --------- 强行加个GROUP BY
UNION ALL
select (SELECT MIN(item_id) FROM (SELECT item_id FROM  items ORDER BY item_id DESC) WHERE ROWNUM<=t.cnt+2000) ---- 跳跃取2000行之后的ID
      ,cnt+2000 ----- 当日期还在区间内则递增取ID的范围。根据每天的数据两选取合适的步长,这里定为2000
FROM t
WHERE (SELECT created_date FROM items WHERE item_id=t.item_id)>=TRUNC(SYSDATE)-2) ---- 当取到的ID落在区间外则停止递归
CYCLE item_id SET cycle_flag TO 'Y' DEFAULT 'N'  ---- 虽然ID都不重复但是ORACLE会报告有循环数据,所以在这里加上CYCLE语句
select *
FROM items
WHERE item_id>=(SELECT min(item_id) from t)  ----- 利用前面的搜索结果
      AND item_type=14
      AND created_date >= TRUNC(SYSDATE)-2;

------------------
WITH t (item_id,cnt) AS (
SELECT (SELECT max(item_id) FROM items),1 FROM DUAL   ---改为标量子查询
UNION ALL
select (SELECT MIN(item_id) FROM (SELECT item_id FROM  items ORDER BY item_id DESC) WHERE ROWNUM<=t.cnt+2000) ---- 跳跃取2000行之后的ID
      ,cnt+2000 ----- 当日期还在区间内则递增取ID的范围。根据每天的数据两选取合适的步长,这里定为2000
FROM t
WHERE (SELECT created_date FROM items WHERE item_id=t.item_id)>=TRUNC(SYSDATE)-2) ---- 当取到的ID落在区间外则停止递归
CYCLE item_id SET cycle_flag TO 'Y' DEFAULT 'N'  ---- 虽然ID都不重复但是ORACLE会报告有循环数据,所以在这里加上CYCLE语句
select *
FROM items
WHERE item_id>=(SELECT min(item_id) from t)  ----- 利用前面的搜索结果
      AND item_type=14
      AND created_date >= TRUNC(SY

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
14#
发表于 2011-6-16 23:45 | 只看该作者
我这里有张上亿的日志表,9i
时间上没做索引,我打算用ID范围来圈定数据的时间区间,这其实比在日期上建索引要节省空间,而且我多是做统计,所以性能不会差

先期的测试已证明此方面非常高效

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
15#
 楼主| 发表于 2011-6-17 00:30 | 只看该作者
原帖由 lastwinner 于 2011-6-16 23:45 发表
我这里有张上亿的日志表,9i
时间上没做索引,我打算用ID范围来圈定数据的时间区间,这其实比在日期上建索引要节省空间,而且我多是做统计,所以性能不会差

先期的测试已证明此方面非常高效


9i能用一个SQL搞出来么?我试了CONNECT BY不行。

使用道具 举报

回复
论坛徽章:
10
2010新春纪念徽章
日期:2010-03-01 11:20:062014年新春福章
日期:2014-02-18 16:41:112013年新春福章
日期:2013-02-25 14:51:24奥运会纪念徽章:蹦床
日期:2012-10-12 14:07:54紫蛋头
日期:2012-06-05 23:31:082012新春纪念徽章
日期:2012-01-04 11:50:44ITPUB十周年纪念徽章
日期:2011-11-01 16:20:282011新春纪念徽章
日期:2011-02-18 11:43:35ITPUB9周年纪念徽章
日期:2010-10-08 09:32:26马上有车
日期:2014-02-18 16:41:11
16#
发表于 2011-6-17 09:59 | 只看该作者
谢谢 newkid 在凌晨都上来细心回答问题!  敬佩!    


测试了结果好像有点出入。
   
---------------------------------------
SQL> WITH t (item_id,cnt) AS (
SELECT (SELECT max(item_id) FROM items),1 FROM DUAL   ---改为标量子查询
UNION ALL
select (SELECT MIN(item_id) FROM (SELECT item_id FROM  items ORDER BY item_id DESC) WHERE ROWNUM<=t.cnt+2000) ---- 跳跃取2000行之后的ID
      ,cnt+2000 ----- 当日期还在区间内则递增取ID的范围。根据每天的数据两选取合适的步长,这里定为2000
FROM t
WHERE (SELECT created_date FROM items WHERE item_id=t.item_id)>=TRUNC(SYSDATE)-2) ---- 当取到的ID落在区间外则停止递归
CYCLE item_id SET cycle_flag TO 'Y' DEFAULT 'N'  ---- 虽然ID都不重复但是ORACLE会报告有循环数据,所以在这里加上CYCLE语句
select *
FROM items
WHERE item_id>=(SELECT min(item_id) from t)  ----- 利用前面的搜索结果
      AND item_type=14
      AND created_date >= TRUNC(SYSDATE) - 2 ;  

已选择20行。
------------------------------------------------


------------------------------------------------
SQL> SELECT * FROM items WHERE created_date >= TRUNC(SYSDATE)-2 AND item_type=14;

已选择140行。
-------------------------------------------------

结果集不一样,想修改。无奈还未能完全理解。

使用道具 举报

回复
论坛徽章:
484
ITPUB北京香山2007年会纪念徽章
日期:2007-01-24 14:35:02ITPUB北京九华山庄2008年会纪念徽章
日期:2008-01-21 16:50:24ITPUB北京2009年会纪念徽章
日期:2009-02-09 11:42:452010新春纪念徽章
日期:2010-03-01 11:04:552010数据库技术大会纪念徽章
日期:2010-05-13 10:04:272010系统架构师大会纪念
日期:2010-09-04 13:35:54ITPUB9周年纪念徽章
日期:2010-10-08 09:28:512011新春纪念徽章
日期:2011-02-18 11:43:32ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:54
17#
发表于 2011-6-17 15:22 | 只看该作者
原帖由 newkid 于 11-6-17 00:30 发表


9i能用一个SQL搞出来么?我试了CONNECT BY不行。



我的思路与目标是fewer disk space exchange good performance
不建日期索引,因为那样太占用空间了,现在一天的数据大约100万,若建日期索引少说占得8M空间(还没算rowid呢……)
而其实这里只要不到100个字节就可以存储差不多等价的信息,这里需要新建一张表——日期Id 表
表中就三列trunc(date), startid, endid
startid是时间为trunc(date)的最小的主键, endid是时间为trunc(date)+1-1/86400的最大的主键

查最近两天的数据怎么办?简单,先用 where date between trunc(sysdate)-2 and trunc(sysdate)-1/86400即可从日期ID表中查知startid和endid
然后以此作为主键的查询区间,就能高效定位这区间的数据了

在初始化数据完成后,后续的日期ID可以在前一日的日期ID的基础上获得,这同样是高效的
有些固定的东西,就找个地方将之固定存放下来,无需每次都去海量数据中寻找,这样不就达到快速查找的目的了么?


你的sql太长,我没看完,只大致看了你的情景描述,不知对不对?

使用道具 举报

回复
论坛徽章:
69
生肖徽章2007版:羊
日期:2008-11-14 14:42:19复活蛋
日期:2011-08-06 08:59:05ITPUB十周年纪念徽章
日期:2011-11-01 16:19:412012新春纪念徽章
日期:2012-01-04 11:49:542012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:202012新春纪念徽章
日期:2012-02-13 15:13:20版主4段
日期:2012-05-15 15:24:11
18#
发表于 2011-6-17 18:36 | 只看该作者
NewKid的解法很新颖
野花的方法也很不错

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
19#
 楼主| 发表于 2011-6-17 23:08 | 只看该作者
原帖由 爱乐 于 2011-6-17 09:59 发表
谢谢 newkid 在凌晨都上来细心回答问题!  敬佩!    


测试了结果好像有点出入。
   
---------------------------------------

已选择20行。
------------------------------------------------


------------------------------------------------

已选择140行。
-------------------------------------------------

结果集不一样,想修改。无奈还未能完全理解。


我这里无法重现这个错误。单独运行一下这个,看看item_id是多少,是否落在区间之外?已经过去一天,你可能要改为 TRUNC(SYSDATE)-3

WITH t (item_id,cnt) AS (
SELECT (SELECT max(item_id) FROM items),1 FROM DUAL   ---改为标量子查询
UNION ALL
select (SELECT MIN(item_id) FROM (SELECT item_id FROM  items ORDER BY item_id DESC) WHERE ROWNUM<=t.cnt+2000) ---- 跳跃取2000行之后的ID
      ,cnt+2000 ----- 当日期还在区间内则递增取ID的范围。根据每天的数据两选取合适的步长,这里定为2000
FROM t
WHERE (SELECT created_date FROM items WHERE item_id=t.item_id)>=TRUNC(SYSDATE)-2) ---- 当取到的ID落在区间外则停止递归
CYCLE item_id SET cycle_flag TO 'Y' DEFAULT 'N'
SELECT MIN(item_id) FROM t;

使用道具 举报

回复
论坛徽章:
519
奥运会纪念徽章:垒球
日期:2008-09-15 01:28:12生肖徽章2007版:鸡
日期:2008-11-17 23:40:58生肖徽章2007版:马
日期:2008-11-18 05:09:48数据库板块每日发贴之星
日期:2008-11-29 01:01:02数据库板块每日发贴之星
日期:2008-12-05 01:01:03生肖徽章2007版:虎
日期:2008-12-10 07:47:462009新春纪念徽章
日期:2009-01-04 14:52:28数据库板块每日发贴之星
日期:2009-02-08 01:01:03生肖徽章2007版:蛇
日期:2009-03-09 22:18:532009日食纪念
日期:2009-07-22 09:30:00
20#
 楼主| 发表于 2011-6-17 23:32 | 只看该作者
原帖由 lastwinner 于 2011-6-17 15:22 发表



我的思路与目标是fewer disk space exchange good performance
不建日期索引,因为那样太占用空间了,现在一天的数据大约100万,若建日期索引少说占得8M空间(还没算rowid呢……)
而其实这里只要不到100个字节就可以存储差不多等价的信息,这里需要新建一张表——日期Id 表
表中就三列trunc(date), startid, endid
startid是时间为trunc(date)的最小的主键, endid是时间为trunc(date)+1-1/86400的最大的主键

查最近两天的数据怎么办?简单,先用 where date between trunc(sysdate)-2 and trunc(sysdate)-1/86400即可从日期ID表中查知startid和endid
然后以此作为主键的查询区间,就能高效定位这区间的数据了

在初始化数据完成后,后续的日期ID可以在前一日的日期ID的基础上获得,这同样是高效的
有些固定的东西,就找个地方将之固定存放下来,无需每次都去海量数据中寻找,这样不就达到快速查找的目的了么?


你的sql太长,我没看完,只大致看了你的情景描述,不知对不对?


不对,我的SQL相当于:
DECLARE
   lv_min_id NUMBER;
   lv_cnt    NUMBER :=1;
   lv_date   DATE;
BEGIN
   SELECT max(item_id) INTO lv_min_id FROM items;
   LOOP
       SELECT created_date INTO lv_date FROM items WHERE item_id=lv_min_id;
       EXIT WHEN lv_date<TRUNC(SYSDATE)-2;
       SELECT MIN(item_id) INTO lv_min_id
         FROM (SELECT item_id FROM  items ORDER BY item_id DESC)
        WHERE ROWNUM<=lv_cnt+2000;
       lv_cnt := lv_cnt+2000;
   END LOOP;
   
END;
/

你这方法相当于自己发明了一个索引,但是我不知道你打算如何维护,是放到事务?那么就有并发问题;每天维护一次?那就不能实时提供数据。

使用道具 举报

回复

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

TOP技术积分榜 社区积分榜 徽章 团队 统计 知识索引树 积分竞拍 文本模式 帮助
  ITPUB首页 | ITPUB论坛 | 数据库技术 | 企业信息化 | 开发技术 | 微软技术 | 软件工程与项目管理 | IBM技术园地 | 行业纵向讨论 | IT招聘 | IT文档
  ChinaUnix | ChinaUnix博客 | ChinaUnix论坛
CopyRight 1999-2011 itpub.net All Right Reserved. 北京盛拓优讯信息技术有限公司版权所有 联系我们 未成年人举报专区 
京ICP备16024965号-8  北京市公安局海淀分局网监中心备案编号:11010802021510 广播电视节目制作经营许可证:编号(京)字第1149号
  
快速回复 返回顶部 返回列表