row_number() over (partition by category order by newid()) 是sql server中实现每类随机抽样的核心写法,因rand()在窗口函数中不可靠,需用newid()生成唯一guid保障行级随机性,且partition by列须真实存在并排除null值。

ROW_NUMBER() OVER (PARTITION BY category ORDER BY NEWID()) 是核心写法
SQL Server 不支持 RAND() 在窗口函数中稳定生成行级随机数,必须用 NEWID() 替代。直接写 ORDER BY RAND() 会导致每行 RAND() 值相同或执行计划复用,抽样失去随机性。
-
NEWID()每行生成唯一 GUID,排序效果等价于真随机,且 SQL Server 优化器不会缓存其值 -
PARTITION BY字段必须是表中真实存在的列(如status、region),不能是表达式或别名 - 若类别字段含
NULL,PARTITION BY会把所有NULL归为同一组——需提前加WHERE category IS NOT NULL - 示例:每类取前 50 行
SELECT id, category, value
FROM (
SELECT id, category, value,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY NEWID()) AS rn
FROM t_user
) t
WHERE rn
<h3>NTILE() 只适合等频切分,不保类别平衡</h3>
<p>单独用 <code>NTILE(5) OVER (ORDER BY id)</code> 是全局切分,完全无视类别分布。要按类别分组后切分,必须显式加上 <code>PARTITION BY</code>。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/ai/1929" title="TextCortex"><img
src="https://img.php.cn/upload/ai_manual/001/246/273/68b6d522da165474.png" alt="TextCortex" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/ai/1929" title="TextCortex" class="overflowclass">TextCortex</a>
<p class="overflowclass">一款集AI写作、改写和知识辅助于一体的内容创作工具,可帮助用户快速完成多种类型的文本撰写与编辑任务。</p>
</div>
<a rel="nofollow" href="/ai/1929" title="TextCortex" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
-
NTILE(5) OVER (PARTITION BY category ORDER BY NEWID())才能在每类内平均分成 5 组 - 但注意:每组行数可能差 1 行(因总数除以 5 的余数),这不是 bug,是整除分配的必然结果
- 如果某类总行数少于目标组数(比如只有 3 行却要
NTILE(5)),SQL Server 会把这 3 行全分到前 3 个组,后 2 组为空——无法避免
CTE 预计算比嵌套子查询更安全
别在 WHERE 子句里直接调窗口函数,SQL Server 会报错:Windowed functions can only appear in the SELECT or ORDER BY clauses。
- 用 CTE 把带
rn的中间结果固化下来,逻辑清晰且可复用 - 大表慎用多层 CTE,SQL Server 可能推导出低效执行计划;超千万行建议物化为临时表
- 若抽样后还要
JOIN其他表,CTE 能避免重复计算ROW_NUMBER(),减少排序开销
WITH sampled AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY NEWID()) AS rn
FROM t_user
)
SELECT s.*, o.order_amt
FROM sampled s
JOIN t_order o ON s.id = o.user_id
WHERE s.rn
<h3>类别字段脏数据会让抽样悄悄失效</h3>
<p>跑通了 SQL 却发现某几类样本极少?大概率不是语法问题,而是 <code>category</code> 字段本身有陷阱。</p>
- 检查是否有大量空格:
LEN(category) != LEN(LTRIM(RTRIM(category))) - 大小写混用(如 'Active' 和 'active' 被当不同类)——必要时统一转小写:
PARTITION BY LOWER(category) - 隐藏字符(如
CHAR(160)不间断空格),可用DATALENGTH(category)对比LEN()发现异常 - 空字符串
''和NULL是两类,得分别处理:WHERE ISNULL(NULLIF(category, ''), 'unknown') IS NOT NULL










