楼主: ocpdba591

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

[复制链接]
论坛徽章:
26
2010年世界杯参赛球队:智利
日期:2010-07-03 17:16:26比亚迪
日期:2014-01-16 17:12:41宝马
日期:2014-01-24 10:32:252014年新春福章
日期:2014-02-18 16:44:08马上有对象
日期:2014-02-18 16:44:08马上有对象
日期:2014-03-05 21:30:32马上有车
日期:2014-03-11 16:46:45优秀写手
日期:2014-03-25 05:59:50马上加薪
日期:2014-03-26 16:46:30问答徽章
日期:2014-05-09 16:40:36
31#
 楼主| 发表于 2014-3-22 16:24 | 只看该作者
〇〇 发表于 2014-3-22 16:21
越来越有意思了

期待被翻译成oracle

使用道具 举报

回复
论坛徽章:
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
32#
发表于 2014-3-23 09:27 | 只看该作者
Oracle 版本:
/*
drop table cust;
drop table phone;

create table cust(id int primary key,name varchar2(30),gid int);

insert into cust(id,name) values(1,'A');
insert into cust(id,name) values(2,'B');
insert into cust(id,name) values(3,'C');
insert into cust(id,name) values(4,'D');
insert into cust(id,name) values(5,'E');

create table phone(id int, phone_num varchar2(40));

insert into phone values(1,3);
insert into phone values(1,4);
insert into phone values(2,4);
insert into phone values(3,6);
insert into phone values(2,6);

insert into phone values(4,1);
insert into phone values(5,1);

-- large test
delete from cust;
delete from phone;

declare
   N int := 1000;
   M int := 2;
   L int := 500;
begin

    insert into cust (id, name) SELECT LEVEL, 'cust' ||LEVEL FROM DUAL CONNECT BY LEVEL<=N;

    insert into phone (id, phone_num)
    SELECT TRUNC(dbms_random.value(1,N+1)),num
       FROM (SELECT LEVEL FROM DUAL CONNECT BY LEVEL<=M),(SELECT LEVEL num FROM DUAL CONNECT BY LEVEL<L);
end;
/

select * from cust;
select * from phone order by phone_num;

*/

------下面代码被我改写过,三个雷同的地方抽出来变成了子函数f_find_parent

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
       stack t_num;
       stack_cnt int;
       cur_cust int := p_cur_cust;
       parent int;
    BEGIN
       stack_cnt :=0;

       LOOP
           parent := parents(cur_cust);
           EXIT WHEN parent = cur_cust ;
           stack_cnt := stack_cnt+1;
           stack(stack_cnt):=cur_cust;
           cur_cust := parent;
       end LOOP;

       FOR i IN 1..stack_cnt LOOP
           parents(stack(i)) := parent;
       END LOOP;

       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;
        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;
/

该算法的限制是:假定客户号是从1开始的连续整数,这个很容易改写;
另外要求在内存里分配几个大数组,楼主的客户表如果大了很容易撑爆。

使用道具 举报

回复
论坛徽章:
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
33#
发表于 2014-3-23 09:30 | 只看该作者
lugionline 发表于 2014-3-22 13:35
oracle支持数组耶,你说一个集合语言提供数组,这还有没有下限

T-SQL不支持数组所以我只好用字符串模拟, ...

你说的是PL/SQL中的数组吧,它可是一种过程性的语言。对PLSQL的支持是ORACLE的一大优势。

话说我们上回的第二届SQL比赛,你做了没有?

使用道具 举报

回复
论坛徽章:
26
2010年世界杯参赛球队:智利
日期:2010-07-03 17:16:26比亚迪
日期:2014-01-16 17:12:41宝马
日期:2014-01-24 10:32:252014年新春福章
日期:2014-02-18 16:44:08马上有对象
日期:2014-02-18 16:44:08马上有对象
日期:2014-03-05 21:30:32马上有车
日期:2014-03-11 16:46:45优秀写手
日期:2014-03-25 05:59:50马上加薪
日期:2014-03-26 16:46:30问答徽章
日期:2014-05-09 16:40:36
34#
 楼主| 发表于 2014-3-23 11:01 | 只看该作者
newkid 发表于 2014-3-23 09:27
Oracle 版本:
/*
drop table cust;

我验证了以下几个团伙,完全正确,而且性能比我的好太多了
  1. 示例组1:
  2. 66
  3. 559
  4. 3

  5. 示例组2:
  6. 311
  7. 4

  8. 示例组3:
  9. 1
  10. 498
  11. 247
  12. 423
  13. 51
  14. 892
  15. 408
  16. 692
  17. 213
  18. 475
  19. 271
  20. 134
  21. 194
  22. 469
  23. 218
  24. 534
  25. 128
  26. 968
  27. 807
  28. 59
  29. 708
  30. 21
  31. 95
  32. 847
  33. 210
  34. 148
  35. 193
  36. 622
  37. 825
  38. 96
  39. 609
  40. 829
  41. 549
  42. 801
  43. 331
  44. 562
  45. 592
  46. 640
  47. 293
  48. 698
  49. 576
  50. 37
  51. 277
  52. 772
  53. 540
  54. 773
  55. 898
  56. 568
  57. 871
  58. 746
  59. 804
  60. 824
  61. 503
  62. 863
  63. 414
  64. 951
  65. 187
  66. 630
  67. 803
  68. 531
  69. 127
  70. 950
  71. 204
  72. 855


  73. 示例组4:
  74. 16
  75. 53
  76. 61
  77. 63
  78. 175
  79. 382
  80. 401
  81. 747
  82. 832
  83. 979
复制代码

使用道具 举报

回复
论坛徽章:
407
紫蛋头
日期:2012-05-21 10:19:41迷宫蛋
日期:2012-06-06 16:02:49奥运会纪念徽章:足球
日期:2012-06-29 15:30:06奥运会纪念徽章:排球
日期:2012-07-10 21:24:24鲜花蛋
日期:2012-07-16 15:24:59奥运会纪念徽章:拳击
日期:2012-08-07 10:54:50奥运会纪念徽章:羽毛球
日期:2012-08-21 15:55:33奥运会纪念徽章:蹦床
日期:2012-08-21 21:09:51奥运会纪念徽章:篮球
日期:2012-08-24 10:29:11奥运会纪念徽章:体操
日期:2012-09-07 16:40:00
35#
发表于 2014-3-23 11:15 | 只看该作者
newkid 发表于 2014-3-23 09:27
Oracle 版本:
/*
drop table cust;

welldone

使用道具 举报

回复
论坛徽章:
8
玉兔
日期:2015-11-16 10:18:00铁扇公主
日期:2015-10-27 21:47:42九尾狐狸
日期:2015-12-11 22:31:15
36#
发表于 2014-3-23 18:29 | 只看该作者
newkid 发表于 2014-3-23 09:27
Oracle 版本:
/*
drop table cust;

如果是把那个函数提取出来了,那其实也没必要用堆栈,直接递归也可以,客户的ID很容易处理的,至于内存,我大致计算过,6千万大概是60M * 4 * 2=500M,的确是有点大,不过对于服务器来说这点内存实在算不了什么,你试过1千万记录大概跑了多少时间吗?这个版本的SQL Server10万记录就跑不出来了,应当是STUFF函数每次都是拷贝整块内存

使用道具 举报

回复
论坛徽章:
8
玉兔
日期:2015-11-16 10:18:00铁扇公主
日期:2015-10-27 21:47:42九尾狐狸
日期:2015-12-11 22:31:15
37#
发表于 2014-3-23 18:32 | 只看该作者
比赛没参加,哪里有时间啊

使用道具 举报

回复
论坛徽章:
8
玉兔
日期:2015-11-16 10:18:00铁扇公主
日期:2015-10-27 21:47:42九尾狐狸
日期:2015-12-11 22:31:15
38#
发表于 2014-3-23 18:35 | 只看该作者
ranks(parentX) := rankX;  这里好像翻错了,应当是

ranks(parentX) := rankX + 1;

使用道具 举报

回复
论坛徽章:
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
39#
发表于 2014-3-23 21:10 | 只看该作者
lugionline 发表于 2014-3-23 18:35
ranks(parentX) := rankX;  这里好像翻错了,应当是

ranks(parentX) := rankX + 1;

这个是我看漏了,应该是rankx+1
那个堆栈的翻译是依样画葫芦,我没想过要改成递归。不过循环的效率要优于递归。
PLSQL的数组要加上一些开销的,没试过大的例子真正使用多少内存。

使用道具 举报

回复
论坛徽章:
26
2010年世界杯参赛球队:智利
日期:2010-07-03 17:16:26比亚迪
日期:2014-01-16 17:12:41宝马
日期:2014-01-24 10:32:252014年新春福章
日期:2014-02-18 16:44:08马上有对象
日期:2014-02-18 16:44:08马上有对象
日期:2014-03-05 21:30:32马上有车
日期:2014-03-11 16:46:45优秀写手
日期:2014-03-25 05:59:50马上加薪
日期:2014-03-26 16:46:30问答徽章
日期:2014-05-09 16:40:36
40#
 楼主| 发表于 2014-3-23 21:54 | 只看该作者
沾lugionline前辈的光,第一个精华

使用道具 举报

回复

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

本版积分规则 发表回复

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