【问题标题】:Select n amount of random rows where n is proportionate to each value's % of total population选择 n 个随机行,其中 n 与每个值占总人口的百分比成比例
【发布时间】:2025-12-06 03:20:04
【问题描述】:

我有一张包含 5800 万条客户记录的表格。每个客户都有一个市场价值(EN、US、FR 等)

我正在尝试选择一个包含来自每个市场的客户的 100k 样本集。样本中每个市场的客户比率必须与实际表格中的比率相匹配。

因此,如果英国客户占客户表中记录的 15%,则 100k 样本集中必须有 15k 英国客户,并且每个市场的英国客户都相同。

有没有办法做到这一点?

【问题讨论】:

    标签: sql sql-server random


    【解决方案1】:

    首先,一个简单的随机样本应该可以很好地代表市场规模。您要求的是分层样本。

    获取此类样本的一种方法是对数据进行随机排序,并在每组中分配一个序号。然后将序号归一化到 0 到 1 之间,最后按归一化值排序,选择前“n”行:

    select top 100000 c.*
    from (select c.*,
                 row_number() over (partition by market order by rand(checksum(newid()))
                                   ) as seqnum,
                 count(*) over (partition by market) as cnt
          from customers c
         ) c
    order by cast(seqnum as float) / cnt
    

    如果您查看数据,可能会清楚发生了什么。考虑从以下位置抽取 5 个样本:

    1    A
    2    B
    3    C
    4    D
    5    D
    6    D
    7    B
    8    A
    9    D
    10   C
    

    第一步在每个市场中随机分配一个序号:

    1    A      1
    2    B      1
    3    C      1
    4    D      1
    5    D      2
    6    D      3
    7    B      2
    8    A      2   
    9    D      4
    10   C      2
    

    接下来,标准化这些值:

    1    A      1      0.50
    2    B      1      0.50
    3    C      1      0.50
    4    D      1      0.25
    5    D      2      0.50
    6    D      3      0.75
    7    B      2      1.00
    8    A      2      1.00
    9    D      4      1.00
    10   C      2      1.00
    

    现在,如果您取前 5 个值,您将得到前五个值,这是一个分层样本。

    【讨论】:

    • 我已经尝试过您的查询,似乎newid() 附近有错误。 end 有必要吗?因为在 SQL Server 2008 上,它会将其标记为错误(以及之后的 ))。
    【解决方案2】:

    正如 Gordon Linoff 所指出的,使用随机抽取的大样本将为您提供一个与原始总体具有良好统计近似的样本。

    要强制总体和样本之间的百分比相等,您可以计算并使用所有需要的参数:总体维度和分区维度,并添加随机 ID。

    Declare @sampleSize INT
    Set @sampleSize = 100000
    
    With D AS (
      SELECT customerID
           , Country
           , Count(customerID) OVER (PARTITION BY Null) TotalData
           , Count(customerID) OVER (PARTITION BY Country) CountryData
           , Row_Number() OVER (PARTITION BY Country 
                                ORDER BY rand(checksum(newid()))) ID
      FROM   customer
    )
    SELECT customerID
         , Country
    FROM   D
    WHERE  ID <= Round((Cast(CountryData as Float) / TotalData) * @sampleSize, 0)
    ORDER BY Country
    

    SQLFiddle demo 数据较少。

    请注意,WHERE 条件中函数的近似值会使返回的数据比所需数据少一点或多一点,例如在演示中,返回的行数是 9 而不是 10。

    【讨论】: