在Oracle中根据等级生成随机数据
Posted
技术标签:
【中文标题】在Oracle中根据等级生成随机数据【英文标题】:Generate random data in Oracle based on ranks 【发布时间】:2015-12-13 17:45:00 【问题描述】:给出以下场景。我有一个包含 3000 个名字的列表和一个包含 2500 个姓氏的列表。他们每个人都有一个“排名”,代表一个名字的顶部位置。两个或多个名称可以具有相同的排名。此外,还给出了一个包含 1500 个城市的表格,每个城市都有 4 个特定年份的人口普查值。
从上面的表格中,我必须生成 500 万个随机条目,其中包含一个人的名字、姓氏、出生日期和出生地,这些条目应该遵循城市名称和人口数量的排名规则。
这必须仅使用 Oracle(存储函数、存储过程等)生成。我怎样才能做到这一点?
【问题讨论】:
你可以创建更多的表吗?您需要准确或近似的名称流行度分布吗?另外,请出示城市/人口普查表样本。如果您自己尝试过任何事情,也包括在内,这将节省其他人一些时间。 是的,我可以创建任何其他表。我没有尝试任何事情,因为我不知道如何深入研究这个问题。 |身份证 |排名 |城市 | J1980 | J1990 | J2000 | J2006 | \n | 1 | 1 |柏林 | 3048759 | 343695 | 3382169 | 3404037 | 你能解释一下你的问题的规则吗:.... should follow the rules given by ranking of the names and population number of the cities.
比如生成数据后,应该可以得到初始数据。如果我想把名字和姓氏放在首位,它应该和初始列表一样。如果我把所有出生在一个城镇的人都计算在内,它应该和人口普查值一样。我希望我说清楚了:D
【参考方案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
。
首先,让我们用 500 万个姓氏来填充它。假设您为他们准备的表格是 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 的随机数——你会明白为什么。这是通过在 self 上使用 merge
的单个查询完成的:
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;
就是这样 - 根据您的排名,您现在有 500 万个姓名样本,姓氏与名字随机匹配。
现在,进行人口普查。我不太了解您的格式,但这相对简单。如果您获取“N 人在 DATE1 和 DATE2 之间出生在城市 C”形式的数据,您可以循环更新表,将 N 行设置为 CITY = C 和 BIRTHDAY = DATE1 和 DATE2 之间的随机日期。您需要一个函数来从某个时间段返回随机日期,请参阅this。另外,在此之前不要忘记再次分配随机行号。
我将把人口普查部分留给你来实施,我已经花了太多时间来写这个。感谢您的大脑锻炼!
【讨论】:
谢谢@Timekiller !!你太棒了!现在我有一个快速的问题。如果我使用某种数组类型来生成数据然后插入表中,而不是在循环中一遍又一遍地插入,效率会更高吗?因为 5M 行需要一段时间。 没有测试很难说。从理论上讲,是的,有FORALL
语句从嵌套表中进行批量插入。但是您应该记住,嵌套表也不是免费的,它们驻留在内存中,如果有 5m 行,您的系统可能会出现内存不足或交换,从而导致速度变慢。当前实现中的插入已经进行了一些优化 - N 行插入到带有select from dual connect by level <= N
的单个语句中。您必须进行试验才能找出更快的方法。以上是关于在Oracle中根据等级生成随机数据的主要内容,如果未能解决你的问题,请参考以下文章