根据排名在Oracle中生成随机数据

时间:2015-12-13 17:45:00

标签: sql database oracle random-sample

给出以下场景。我有3000个名字的列表和2500个姓氏的列表。他们每个人都有一个“排名”,代表名字顶部的位置。两个或多个名称可以具有相同的排名。此外,还提供了一个包含1500个城市的表格,每个城市在某些年份都有4个人口普查值。

从上表中我必须生成500万随机条目,其中包含一个人的名字,姓氏,出生日期和出生地点,应遵循城市名称和人口数量排名给出的规则。

这必须仅使用Oracle(存储函数,存储过程等)生成。我怎样才能做到这一点?

1 个答案:

答案 0 :(得分:1)

免责声明:我不是统计专家,而且可能有更有效的方法来做到这一点。

最具挑战性的任务似乎是根据排名创建了500万个名字。在现实世界中,这些将在人群中分布不均:倒数第二个和最后一个之间的差异将是1-2人,并且第一和第二等级之间的差异可能是数千人。也就是说,我不知道如何实现这一目标,因此我们将以其他方式对其进行建模。假设我们总人口为100,并列出了四个名字:

Alice: 1
Bob: 2
Betty: 2
Claire: 3

我们可以使分布“均匀”,因此等级3有X个人,等级2有两倍,并且排名1倍。如果排名是唯一的,则公式将与X + 2X + 3X = 100一样简单,但我们在排名2中有两个名称,因此它应为X + 2*2X + 3X = 100,因此X = 12.5。我们可以将它截断为整数并获得除第一个(12,24和24)之外的所有等级的人数,并且第一等级将得到剩余的:40。似乎足够好,但是当你有多个第一个时它不适用于边缘情况行列。

但是有一点问题。对于3000个不同的名称,系数的总和将是4501500.因此,截断的X将是1,排名3000到排名2分别具有1到2999个人,并且排名1略低于500000.这不是很好。为了说明上面的四个名称,假设总数为15.使用当前算法,X也将为1,分布将为1-2-2-10。幸运的是,我们将在程序中逐个处理排名,因此我们可以从等式中删除已处理的人员并重新计算X。例如。首先是X + 2*2X + 3X = 15 X = 1,然后2*2X + 3X = 14 X = 2。这样,分布将是1-4-4-6,这远非理想,但更好。

现在,这已经可以表示为PL / SQL。我建议使用以下列创建表:LAST_NAMEFIRST_NAMEBIRTHDAYCITYRAND_ROWNO

首先,让我们用5M姓氏填充它。假设您的表格为last_names(name, name_rank),则需要以下内容:

declare
  cursor cur_last_name_ranks is
    select name_rank, count(*) cnt, row_number() over (order by name_rank desc) coeff
      from last_names l
     group by name_rank;
  cursor cur_last_names (c_rank number) is
    select name from last_names
     where name_rank = c_rank;
  v_coeff_sum number;
  v_total_people_count number:= 5000000;
  v_remaining_people number;
  v_x number;
  v_insert_cnt number;
begin

  --Get a sum of all coefficients for our formula
  select sum(coeff) into v_coeff_sum
    from 
    (
    select count(*) * row_number() over (order by name_rank desc) coeff
      from last_names l
     group by name_rank
    );

  v_remaining_people := v_total_people_count;

  --Now, loop for all coefficients
  for r in cur_last_name_ranks loop
    --Recalculate X
    v_x := trunc(v_remaining_people / v_coeff_sum);
    --First, determine how many rows should be inserted per last name with such rank
    if r.name_rank = 1 then
      if r.cnt > 1 then
        --raise an exception here, we don't allow multiple first ranks
        raise TOO_MANY_ROWS;
      end if;
      v_insert_cnt := v_remaining_people;
    else
      v_insert_cnt := v_x*r.coeff;
    end if;
    --Insert last names N times.
    --Instead of multiple INSERT statements, use select from dual with connect trick.
    for n in cur_last_names(r.name_rank) loop
      insert into result_table(last_name)
      select n.name from dual connect by level <= v_insert_cnt;
    end loop;
    commit;
    --Calculate remaining people count
    v_remaining_people := v_remaining_people - v_x*r.cnt*r.coeff;
    --Recalculate remmaining coefficients
    v_coeff_sum := v_coeff_sum - r.cnt*r.coeff;
  end loop;

end;

现在你有500万行,根据等级填写姓氏。现在,我们需要为每一行分配1到5000000的随机数 - 你会明白为什么。这是通过使用merge在self上的单个查询完成的:

merge into result_table t1
using (select rowid rid, row_number() over (ORDER BY DBMS_RANDOM.VALUE) rnk from result_table) t2
on (t1.rowid = t2.rid)
when matched then update set t1.rand_rowno = t2.rnk

请注意,由于尺寸较大,需要一些时间。

现在你必须对名字重复相同的程序。它将与姓氏非常相似,除非您将更新现有记录,而不是插入新记录。如果你跟踪你已经更新了多少行,那么将它放在内循环中会很简单:

  update result_table
    set first_name = n.name
   where rand_rowno between 
          (v_processed_rows+1) and 
          (v_processed_rows+v_insert_cnt);
  v_processed_rows := v_processed_rows+v_insert_cnt;

就是这样 - 根据你的排名,你现在有一个不错的5M名称样本,随机名字随机匹配。

现在,进行人口普查。我真的不了解你的格式,但这相对简单。如果您获得的数据为“N人在DATE1和DATE2之间的城市C出生”,您可以循环更新表格,将N行设置为CITY = C,BIRTHDAY = DATE1和DATE2之间的随机日期。您需要一个函数来返回一个时间段的随机日期,请参阅this。此外,在执行此操作之前,请不要忘记再次分配随机行号。

我将离开人口普查部分供你实施,我已经花了太多时间写这个。感谢大脑锻炼!