SQL Server中为每个客户随机选取10%行(向上取整)的方法
问题描述
现有一张商品销售表products_sold,表结构及示例数据如下:
| row_id | customer | product | date_sold |
|---|---|---|---|
| 1 | customer_1 | thingamajig | 01.01.2023 |
| 2 | customer_12 | whosi-whatsi | 03.01.2023 |
| 3 | customer_1 | watchamacallit | 04.01.2023 |
| 4 | customer_4 | whosi-whatsi | 06.01.2023 |
| ... | ... | ... | ... |
表中每一行对应一件商品,需求如下:
- 在SQL Server中为每个客户随机选取10%的行,选取行数需向上取整(例如客户有12行数据时,需选取2行)
- 每个至少购买一件商品的客户都要出现在结果表中
举例:customer_1共订购100件商品,customer_2订购50件,customer_3订购17件,结果表应有10+5+2=17行。
初始思路是创建临时表计算每个客户所需行数,再通过游标循环选取随机行插入最终表,代码如下:
drop table if exists #row_counts select customer, ceiling(convert(decimal(10, 2), count(product)) / 10) as row_count into #row_counts from products_sold group by customer -- 后续通过游标循环#row_counts,将随机行插入最终表 -- 用order by newid()实现随机排序
但该方案效率较低,寻求更优实现方式。
更优实现方案
可以利用ROW_NUMBER()窗口函数结合随机排序,再关联每个客户的应选行数,一次性完成筛选,避免游标循环的性能问题:
WITH customer_row_counts AS ( -- 计算每个客户需要选取的行数(向上取整10%) SELECT customer, CEILING(COUNT(*) / 10.0) AS required_rows FROM products_sold GROUP BY customer ), ranked_products AS ( -- 对每个客户的商品行随机排序并编号 SELECT ps.*, ROW_NUMBER() OVER (PARTITION BY ps.customer ORDER BY NEWID()) AS row_num FROM products_sold ps ) -- 筛选出每个客户前required_rows行的数据 SELECT rp.* FROM ranked_products rp JOIN customer_row_counts crc ON rp.customer = crc.customer WHERE rp.row_num <= crc.required_rows;
方案说明
- customer_row_counts CTE:先统计每个客户的总商品数,用
COUNT(*) / 10.0确保浮点运算精度,再通过CEILING函数完成向上取整,得到每个客户的应选行数 - ranked_products CTE:对每个客户的所有商品行,用
NEWID()生成随机值实现无序排序,再用ROW_NUMBER()为每行分配唯一序号 - 最终通过关联两个CTE,筛选出每个客户序号小于等于应选行数的记录,直接得到符合要求的结果集
这个方案全程基于集合运算,相比游标循环效率提升明显,尤其在数据量较大时优势突出,同时能保证所有有购买记录的客户都出现在结果中(哪怕只有1件商品也会被选中)。
内容的提问来源于stack exchange,提问作者user15634990
相关产品推荐
相关产品推荐

