既然可以从实际分布中得出结论,为什么还要满足于幂律分布?
我建议您更改 LastNames 表以包含一个数字列,该列将包含一个数值,该数值表示具有更常见名称的个人的实际数量。您可能需要一个较小但成比例的数字,例如,每个百分比的代表可能有 10,000 个。
然后列表将如下所示:
(除了问题中提到的 3 个名字,我猜是 White、Johnson 等人)
Smith 0
White 10,060
Johnson 19,123
Williams 28,456
...
Sanders 200,987
..
Alderink 999,997
名称选择是
SELECT TOP 1 [LastName]
FROM [LastNames] as LN
WHERE LN.[number_described_above] < ROUND(100000 * RAND(), 0)
ORDER BY [number_described_above] DESC
这是选择不超过[均匀分布]随机数的名字。请注意查询如何使用 小于 并以 desc 结尾的顺序进行排序;这将保证第一个条目(史密斯)被选中。另一种方法是从 10,060 而不是 0 开始与 Smith 的系列,并丢弃小于该值的随机抽奖。
除了上面提到的边界管理问题(从 0 开始而不是 10,060)之外,这个解决方案以及到目前为止的其他两个响应与 dmckee 中建议的解决方案相同'对这个问题中提到的问题的回答。本质上,这个想法是使用 CDF(累积分布函数)。
编辑:
如果您坚持使用数学函数而不是实际分布,则以下应提供幂律函数,该函数将以某种方式传达实际分布的“长尾”形状。您可能想调整 @PwrCoef 值(顺便说一句,它不必是整数),本质上系数越大,函数越偏向列表的开头。
DECLARE @PwrCoef INT
SET @PwrCoef = 2
SELECT 88799 - ROUND(POWER(POWER(88799.0, @PwrCoef) * RAND(), 1.0/@PwrCoef), 0)
注意事项:
- 上面函数中额外的“.0”对于强制 SQL 执行浮点运算而不是整数运算很重要。
- 我们从 88799 中减去幂计算的原因是计算的分布是这样的,一个数字越接近我们规模的末端,它就越有可能被绘制。姓氏列表以相反的顺序排序(最有可能是名字在前),我们需要这个减法。
假设幂为 3,则查询看起来像
SELECT [LastName]
FROM [LastNames] as LN
WHERE LN.[Rank]
= 88799 - ROUND(POWER(POWER(88799.0, 3) * RAND(), 1.0/3), 0)
除了最后一行之外,问题中的查询是什么。
重新编辑:
在查看实际分布时,如人口普查数据所示,曲线非常陡峭,需要非常大的功率系数,这反过来会导致溢出和/或极端舍入误差公式如上所示。
更明智的方法可能是在多个层级中操作,即在累积分布的三分之三(或四分之四或......)的每一层中执行相同数量的抽奖;在这些零件列表中的每一个中,我们将使用幂律函数进行绘制,可能具有相同的系数,但具有不同的范围。
例如
假设三分之一,列表划分如下:
- 前三分之一 = 425 个名字,从史密斯到阿尔瓦拉多
- 第二个第三个 = 6,277 个名字,从获得者到获得者
- 最后三分之一 = 82,097 个名字,从 Frisby 到最后
如果我们需要 1000 个名字,我们将从列表的前三分之一中抽取 334 个,从第二个三分之一抽取 333 个,从倒数第三个抽取 333 个。
对于每一个三分之一,我们会使用一个类似的公式,可能对前三分之一使用更大的功率系数(我们真的有兴趣偏爱列表中较早的名字,和频率更具统计相关性)。三个选择查询可能如下所示:
-- Random Drawing of a single Name in top third
-- Power Coef = 12
SELECT [LastName]
FROM [LastNames] as LN
WHERE LN.[Rank]
= 425 - ROUND(POWER(POWER(425.0, 12) * RAND(), 1.0/12), 0)
-- Second third; Power Coef = 7
...
WHERE LN.[Rank]
= (425 + 6277) - ROUND(POWER(POWER(6277.0, 7) * RAND(), 1.0/7), 0)
-- Bottom third; Power Coef = 4
...
WHERE LN.[Rank]
= (425 + 6277 + 82097) - ROUND(POWER(POWER(82097.0, 4) * RAND(), 1.0/4), 0)