ClickHouse字段分组取TOP N

在clickhouse中通过字段进行分组排序最后取所需要的前N条

记录一下分组取TOP N的经验,以后说不定有机会要翻出来看一下当时的思路,在此先声明,本人只是初学者,写的SQL语句很烂。我在这里只是记录一下我在学习过程中遇到的某种案例的解决思路,并不是标准答案

查询要求

按产品维度统计2017年每月的业绩,指标包括 不同品类的采购量、采购额,每个产品里采购量top 10的商品

解决思路

1、先对产品和产品类别里的所有商品进行分组排序处理,分组先后顺序是产品  --> 月份  -->  商品,得出产品维度下每月所有的商品采购额的排序表t,排序方式是将采购额降序处理,即采购额从多到少的方式

SELECT 
	a1.chanpinID,
	a1.chanpinming ,
	toYear(a.dingdanriqi) as nian,
	toMonth(a.dingdanriqi) as yue,
	a.shangpinID,
	a.shangpinming ,
	SUM(a.caigouliang) as pfshu,
	SUM(a.caigoue) as cge
FROM 
	dingdanbiao a
left join chanpinbiao a1 on a.chanpID = a1.chanpinID
where 
	toYear(a.dingdanriqi) = 2017
group by 
	a1.chanpinID ,
	a1.chanpinming ,
	a.shangpinID ,
	a.shangpinming ,
	nian,
	yue
order by
	a1.chanpinID ASC ,
	yue,
	cge DESC

2、通过clickehouse的groupArray()函数将t表里的每个产品每个月采购额排名前10的商品的ID存放进groupArray数组里

SELECT 
	chanpinID,
	chanpinming,
	nian,
	yue,
	groupArray(10)(shangpinID) as shpin
from
	(SELECT 
		a1.chanpinID,
		a1.chanpinming ,
		toYear(a.dingdanriqi) as nian,
		toMonth(a.dingdanriqi) as yue,
		a.shangpinID,
		a.shangpinming ,
		SUM(a.caigouliang) as pfshu,
		SUM(a.caigoue) as cge
	FROM 
		dingdanbiao a
	left join chanpinbiao a1 on a.chanpID = a1.chanpinID
	where 
		toYear(a.dingdanriqi) = 2017
	group by 
		a1.chanpinID ,
		a1.chanpinming ,
		a.shangpinID ,
		a.shangpinming ,
		nian,
		yue
	order by
		a1.chanpinID ASC ,
		yue,
		cge DESC)
group by 
	chanpinID,
	chanpinming,
	yue,
	nian  
order by 
	chanpinID ,
	yue

3、再通过array join将存放在groupArray数组里的shangpinID取出来形成前10商品ID表t1

SELECT 
	chanpinID,
	chanpinming,
	nian,
	yue,
	shpin
FROM 
	(SELECT 
	    chanpinID,
		chanpinming,
		nian,
		yue,
		groupArray(10)(shangpinID) as shpin
    from
		(SELECT 
			a1.chanpinID,
			a1.chanpinming ,
			toYear(a.dingdanriqi) as nian,
			toMonth(a.dingdanriqi) as yue,
			a.shangpinID,
			a.shangpinming ,
			SUM(a.caigouliang) as pfshu,
			SUM(a.caigoue) as cge
		FROM 
			dingdanbiao a
		left join chanpinbiao a1 on a.chanpID = a1.chanpinID
		where 
			toYear(a.dingdanriqi) = 2017
		group by 
			a1.chanpinID ,
			a1.chanpinming ,
			a.shangpinID ,
			a.shangpinming ,
			nian,
			yue
		order by
			a1.chanpinID ASC ,
			yue,
			cge DESC)
	group by 
		chanpinID,
		chanpinming,
		yue,
		nian  
	order by 
		chanpinID ,
		yue)
ARRAY JOIN shpin

4、最后通过内连接inner join匹配排序表t和前10商品表t1,下面是整体的SQL语句

SELECT
	t.chanpinID as pleiID,
	t.chanpinming as plei,
	t.nian ,
	t.yue,
	t.shangpinID as spinID,
	t.shangpinming as spin,
	t.caigouliang,
	t.caigoue
FROM 
	(SELECT 
		a1.chanpinID,
		a1.chanpinming ,
		toYear(a.dingdanriqi) as nian,
		toMonth(a.dingdanriqi) as yue,
		a.shangpinID,
		a.shangpinming ,
		SUM(a.caigouliang) as pfshu,
		SUM(a.caigoue) as cge
	FROM 
		dingdanbiao a
		left chanpinbiao a1 on a.chanpID = a1.chanpinID
	where 
		toYear(a.dingdanriqi) = 2017
	group by 
		a1.chanpinID ,
		a1.chanpinming ,
		a.shangpinID ,
		a.shangpinming ,
		nian,
		yue
	order by
		a1.chanpinID ASC ,
		yue,
		cge DESC) t
inner join 
	(SELECT 
		chanpinID,
		chanpinming,
		nian,
		yue,
		shpin
	FROM 
		(SELECT 
			chanpinID,
			chanpinming,
			nian,
			yue,
			groupArray(10)(shangpinID) as shpin
		from
			(SELECT 
				a1.chanpinID,
				a1.chanpinming ,
				toYear(a.dingdanriqi) as nian,
				toMonth(a.dingdanriqi) as yue,
				a.shangpinID,
				a.shangpinming ,
				SUM(a.caigouliang) as pfshu,
				SUM(a.caigoue) as cge
			FROM 
				dingdanbiao a
			left join chanpinbiao a1 on a.chanpID = a1.chanpinID
			where 
				toYear(a.dingdanriqi) = 2017
			group by 
				a1.chanpinID ,
				a1.chanpinming ,
				a.shangpinID ,
				a.shangpinming ,
				nian,
				yue
			order by
				a1.chanpinID ASC ,
				yue,
				cge DESC)
		group by 
			chanpinID,
			chanpinming,
			yue,
			nian  
		order by 
			chanpinID ,
			yue)
	ARRAY JOIN shpin) t1
on t1.shpin = t.chanpID and t1.yue = t.yue

运行上面的SQL语句后最后输出的结果就是所需要的结果表

总结

我认为这种分组取TOP N的思路大致是一样的:对所需要的数据进行分组排序,用groupArray()函数对分组后所需的值先取出来存放进数组里,然后通过array join子句将数组里的值都列出来。

我在这里解释一下为什么不使用clickhouse里的topK函数,在官方的技术文档里,topK函数的定义

“返回指定列中近似最常见值的数组,生成的数组按值的近似频率降序排序(而不是值本身),此函数不提供保证的结果。 在某些情况下,可能会发生错误,并且可能会返回不是最高频的值。我们建议使用 N < 10 值,N 值越大,性能越低。最大值 N = 65536。”

topK函数文档:https://clickhouse.tech/docs/zh/sql-reference/aggregate-functions/reference/topk/

也就是说topK虽然高效但不保证最后求值的准确率。对于数据量较少的分组排序取top N时,topK函数的确是不错的选择,但从准确率方面来考虑的话,groupArray函数显然是更好的选择。

至于SQL语句的冗余问题,等SQL技术提高了并且空闲的时候再来修正。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: Spark RDD中分组TopN案例是指在一个RDD中,根据某个键值进行分组,然后对每个组内的数据进行排序,出每个组内的前N个数据。这种操作在数据分析和处理中非常常见,可以用于统计每个地区的销售额排名前N的产品、每个用户的消费排名前N的商品等。 优化方面,可以考虑使用Spark SQL或DataFrame来实现分组TopN操作,因为它们提供了更高级的API和优化技术,可以更快速地处理大规模数据。另外,可以使用分布式缓存技术将数据缓存到内存中,以加快数据访问速度。还可以使用分区和并行计算等技术来提高计算效率。 ### 回答2: Spark RDD中分组Top N的案例可以是对一个大数据集中的用户数据进行分组,然后每个组中消费金额最高的前N个用户。这个案例可以通过以下步骤来实现: 1. 将用户数据载入Spark RDD中,每个数据记录包含用户ID和消费金额。 2. 使用groupBy函数将RDD按照用户ID进行分组,得到一个以用户ID为key,包含相同用户ID的数据记录的value的RDD。 3. 对每个分组的value调用top函数,指定N的值,以获每个分组中消费金额最高的前N个用户。 4. 可以将每个分组中Top N的用户使用flatMap函数展开为多个记录,并可以添加一个新的字段表示该记录属于哪个分组。 5. 最后,可以使用collect函数将结果转化为数组或者保存到文件或数据库中。 在这个案例中,进行优化的关键是减少数据的传输和处理开销。可以使用缓存或持久化函数对RDD进行优化,以减少重复计算。另外,可以使用并行操作来加速计算,如使用并行的排序算法,或向集群中的多个节点分发计算任务。 对于分组Top N的优化,还可以考虑使用局部聚合和全局聚合的策略。首先对每个分组内的数据进行局部聚合,例如计算每个分组的前M个最大值。然后,对所有分组的局部聚合结果进行全局聚合,例如计算所有分组的前K个最大值。 另一个优化策略是使用采样技术,例如随机采样或分层采样,以减少需要处理的数据量。 最后,还可以考虑使用Spark的其他高级功能,如Broadcast变量共享数据,使用累加器进行计数或统计等,来进一步提高性能和效率。 ### 回答3: Spark RDD 是 Spark 提供的一种基于内存的分布式数据处理模型,其核心数据结构是弹性分布式数据集(RDD)。 在 Spark RDD 中,分组TopN 是一种常见的需求,即对 RDD 中的数据按某个字段进行分组,并出每个分组字段值最大的前 N 个数据。 下面以一个示例来说明分组 TopN 的用法和优化方法: 假设有一个包含学生信息的 RDD,其中每条数据都包括学生的学科和分数,我们希望对每个学科出分数最高的前 3 名学生。 ```python # 创建示例数据 data = [ ("语文", 80), ("数学", 90), ("语文", 85), ("数学", 95), ("语文", 75), ("数学", 92), ("英语", 88) ] rdd = sc.parallelize(data) # 分组TopN top3 = rdd.groupByKey().mapValues(lambda x: sorted(x, reverse=True)[:3]) # 输出结果 for subject, scores in top3.collect(): print(subject, scores) # 输出结果: # 数学 [95, 92, 90] # 语文 [85, 80, 75] # 英语 [88] ``` 在上述代码中,我们先使用 `groupByKey()` 对 RDD 进行分组操作,然后使用 `mapValues()` 对每个分组内的数据进行排序并前 3 个值。 这种方式的优化点在于,通过将分组操作和 TopN 操作分开,可以减轻数据倾斜的问题。同时,对每个分组进行排序会占用大量计算资源,可以考虑将数据转换为 Pair RDD,并利用 Spark 提供的 `top()` 算子来优化 TopN 的操作。 ```python # 转换为 Pair RDD pair_rdd = rdd.map(lambda x: (x[0], x[1])) # 分组TopN,使用top()算子代替排序操作 top3 = pair_rdd.groupByKey().mapValues(lambda x: sorted(x, reverse=True)).mapValues(lambda x: x[:3]) # 输出结果 for subject, scores in top3.collect(): print(subject, scores) # 输出结果: # 数学 [95, 92, 90] # 语文 [85, 80, 75] # 英语 [88] ``` 通过以上优化,我们可以更好地处理大规模数据集下的分组 TopN 的需求,提高计算性能和资源利用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值