给出以下场景。我有3000个名字的列表和2500个姓氏的列表。他们每个人都有一个“排名”,代表名字顶部的位置。两个或多个名称可以具有相同的排名。此外,还提供了一个包含1500个城市的表格,每个城市在某些年份都有4个人口普查值。
从上表中我必须生成500万随机条目,其中包含一个人的名字,姓氏,出生日期和出生地点,应遵循城市名称和人口数量排名给出的规则。
这必须仅使用Oracle(存储函数,存储过程等)生成。我怎样才能做到这一点?
答案 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_NAME
,FIRST_NAME
,BIRTHDAY
,CITY
,RAND_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。此外,在执行此操作之前,请不要忘记再次分配随机行号。
我将离开人口普查部分供你实施,我已经花了太多时间写这个。感谢大脑锻炼!