楼主: ocpdba591

[精华] 递归分组问题求解(疑似相似客户分组)

[复制链接]
论坛徽章:
520
奥运会纪念徽章:垒球
日期: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
41#
发表于 2014-3-24 03:01 | 只看该作者
f_find_parent写成递归了,代码确实少一些。rankX+1的bug也改过来了:

DECLARE
    cust_count int;

    TYPE t_num IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    parents t_num;
    ranks t_num;
    lv_id t_num;

    parentX int;
    parentY int;
    rankX int;
    rankY int;

    last_phone int;

    FUNCTION f_find_parent(p_cur_cust IN INTEGER) RETURN INTEGER
    AS
       parent int;
    BEGIN
       parent := parents(p_cur_cust);
       IF parent <> p_cur_cust THEN
          parent := f_find_parent(parent);
          parents(p_cur_cust) := parent;
       END IF;
       RETURN parent;
    END f_find_parent;
   
BEGIN
    SELECT count(*) INTO cust_count from cust;
    FOR i IN 1..cust_count LOOP
        parents(i) := i;
        ranks(i) := 0;

    END LOOP;
    lv_id := parents;

    last_phone :=0;

    -- union

    FOR cur IN (select id, phone_num from phone order by phone_num, id) LOOP
        if cur.phone_num <> last_phone then
           -- find parent for x
           parentX := f_find_parent(cur.id);
           rankX := ranks(parentX);
           last_phone := cur.phone_num;
           CONTINUE;
        END IF;

        -- find parent for y
        parentY := f_find_parent(cur.id);
        rankY := ranks(parentY);
        
        CASE
        WHEN parentX = parentY THEN
             continue;
        WHEN rankX < rankY THEN
             parents(parentX) := parentY;
        WHEN rankX > rankY THEN
             parents(parentY) := parentX;
        WHEN rankX = rankY THEN
             parents(parentY) := parentX;
             ranks(parentX) := rankX+1;
        END CASE;

    END LOOP;

    -- find for each cust
    FOR i IN 1..cust_count LOOP
        parentY := f_find_parent(i);
    END LOOP;

    FORALL i IN 1..cust_count
       UPDATE cust SET gid=parents(i) WHERE id=lv_id(i);

END;
/

使用道具 举报

回复
论坛徽章:
8
玉兔
日期:2015-11-16 10:18:00铁扇公主
日期:2015-10-27 21:47:42九尾狐狸
日期:2015-12-11 22:31:15
42#
发表于 2014-3-24 07:20 | 只看该作者
newkid 发表于 2014-3-24 03:01
f_find_parent写成递归了,代码确实少一些。rankX+1的bug也改过来了:

DECLARE

cool,最后一个回写,oracle支持这么写么

UPDATE cust SET gid=parents(id);

这样就不用循环了

使用道具 举报

回复
论坛徽章:
8
玉兔
日期:2015-11-16 10:18:00铁扇公主
日期:2015-10-27 21:47:42九尾狐狸
日期:2015-12-11 22:31:15
43#
发表于 2014-3-24 07:22 | 只看该作者
本帖最后由 lugionline 于 2014-3-24 10:11 编辑

或者 UPDATE cust SET gid=f_find_parent(id); 这样可以把上面那个循环也可以去掉
另外刚才想到 rank 其实不会超过32 (平衡二叉树),所以用byte数组就够了,这样可以节省些内存

不过无论SQL中如何优化都做不过C的,刚才看了个PDF说是10^9个点,10^10个union,总共时间大概1分钟

使用道具 举报

回复
论坛徽章:
9
2011新春纪念徽章
日期:2011-02-18 11:43:33ITPUB 11周年纪念徽章
日期:2012-10-10 13:11:142013年新春福章
日期:2013-02-25 14:51:24秀才
日期:2016-06-23 14:15:06秀才
日期:2017-03-20 13:42:20秀才
日期:2017-03-28 15:11:09秀才
日期:2017-03-28 15:59:38秀才
日期:2017-04-06 18:09:28秀才
日期:2017-07-11 14:19:35
受到警告 44#
发表于 2014-3-24 20:21 | 只看该作者
这些我都不懂,我是来看热闹的。

使用道具 举报

回复
论坛徽章:
520
奥运会纪念徽章:垒球
日期: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
45#
发表于 2014-3-24 23:02 | 只看该作者
再改写一下,就不要求CUST_ID是连续的了:

DECLARE
    i int;

    TYPE t_num IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
    parents t_num;
    ranks t_num;
    lv_id t_num;
    lv_gid t_num;

    parentX int;
    parentY int;
    rankX int;
    rankY int;

    last_phone int;

    FUNCTION f_find_parent(p_cur_cust IN INTEGER) RETURN INTEGER
    AS
       parent int;
    BEGIN
       parent := parents(p_cur_cust);
       IF parent <> p_cur_cust THEN
          parent := f_find_parent(parent);
          parents(p_cur_cust) := parent;
       END IF;
       RETURN parent;
    END f_find_parent;
   
BEGIN
    SELECT id BULK COLLECT INTO lv_id from cust ORDER BY id;
    FOR i IN 1..lv_id.COUNT LOOP
        ranks(lv_id(i)) := 0;
        parents(lv_id(i)):=lv_id(i);
    END LOOP;
   
    last_phone :=0;

    -- union

    FOR cur IN (select id, phone_num from phone order by phone_num, id) LOOP
        if cur.phone_num <> last_phone then
           -- find parent for x
           parentX := f_find_parent(cur.id);
           rankX := ranks(parentX);
           last_phone := cur.phone_num;
           CONTINUE;
        END IF;

        -- find parent for y
        parentY := f_find_parent(cur.id);
        rankY := ranks(parentY);
        
        CASE
        WHEN parentX = parentY THEN
             continue;
        WHEN rankX < rankY THEN
             parents(parentX) := parentY;
        WHEN rankX > rankY THEN
             parents(parentY) := parentX;
        WHEN rankX = rankY THEN
             parents(parentY) := parentX;
             ranks(parentX) := rankX+1;
        END CASE;

    END LOOP;

    -- find for each cust
    FOR i IN 1..lv_id.count LOOP
        lv_gid(i) := f_find_parent(lv_id(i));
    END LOOP;

    FORALL i IN 1..lv_id.count
       UPDATE cust SET gid=lv_gid(i) WHERE id=lv_id(i);

END;
/

使用道具 举报

回复
论坛徽章:
520
奥运会纪念徽章:垒球
日期: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
46#
发表于 2014-3-24 23:05 | 只看该作者
lugionline 发表于 2014-3-24 07:20
cool,最后一个回写,oracle支持这么写么

UPDATE cust SET gid=parents(id);

这样更糟糕,每行UPDATE的时候都会调用一次PLSQL函数,有大量的上下文切换。
FORALL UPDATE是一次性将整个数组写回表中。
PL/SQL没有byte数组,我以前试过即使是BOOLEAN也不会节省内存。这个当然和C不能相比了。

使用道具 举报

回复
论坛徽章:
0
47#
发表于 2014-11-21 16:37 | 只看该作者
newkid 发表于 2014-3-24 23:02
再改写一下,就不要求CUST_ID是连续的了:

DECLARE

目前也遇到这个问题,是直接采用的connect by 做递归,测试时,数据根本就出不来。

跪求大神用文字注释一下这段逻辑!

使用道具 举报

回复
论坛徽章:
520
奥运会纪念徽章:垒球
日期: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
48#
发表于 2014-11-22 00:52 | 只看该作者
tanzuwen 发表于 2014-11-21 16:37
目前也遇到这个问题,是直接采用的connect by 做递归,测试时,数据根本就出不来。

跪求大神用文字注释 ...

按照提问的智慧给出一个例子。

使用道具 举报

回复
论坛徽章:
0
49#
发表于 2014-11-24 11:02 | 只看该作者
newkid 发表于 2014-11-22 00:52
按照提问的智慧给出一个例子。

  FOR i IN 1..lv_id.COUNT LOOP
        ranks(lv_id(i)) := 0;             ----  这个地方好像是有问题吧,lv_id(i)这个应该是一个数组中的值,能把它当作一个下标用么!
        parents(lv_id(i)):=lv_id(i);
    END LOOP;

使用道具 举报

回复
论坛徽章:
0
50#
发表于 2014-11-24 11:05 | 只看该作者
tanzuwen 发表于 2014-11-24 11:02
FOR i IN 1..lv_id.COUNT LOOP
        ranks(lv_id(i)) := 0;             ----  这个地方好像是有问 ...

肯定会报下标溢出错识吧!

使用道具 举报

回复

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

本版积分规则 发表回复

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