# 背景
现实场景中经常会遇到字符串字段去重计数的场景,常见方式如下:
- 最简单的是count(distinct),性能最差,不推荐
- group by 后count(1),充分利用分布式并行计算的优点,但是容易遇到数据倾斜,总体上是推荐
除了以上两种方式,还可以借助rbm进行去重计数,接下来介绍代理键+rbm的方法
# 方案介绍
rbm是bitset的优化,把long型变量分为高32位和低32位分别存放,高32位用作索引(具体原理可参考https://blog.csdn.net/u011624157/article/details/108432494)。
rbm是很好用,但是只能存放int、long等数字类型,要对字符串做去重的话需要把字符串映射成数值,方案有两种:
- 取hashcode,实现较简单,但数据量太大会有hash碰撞,仅适用于中小规模数据量
- 使用row_number函数生成数字类型代理键,实现略复杂,需要做一个中间表,但是去重精确,适用于大数据量场景
## 详细实现
接下来说代理键+rbm的详细代码
- 1、先做代理键
```sql
select
log_id,
id
from(
select
log_id,
row_sequence() as id --使用row_sequence函数生成代理键
from(
select
log_id
from
(
select log_id
from base_log
) t1
group by log_id
) a) a
distribute by id
;
```
- 2、根据rbm udaf进行去重
# 附:代理键名词解释
## 代理键
在关系型数据库设计中,代理键(英语:surrogate key)是在当资料表中的候选键都不适合当主键时,例如资料太长,或是意义层面太多,就会请一个无意义的但唯一的字段来代为作主键。
代理键是:
Surrogate (1) – Hall, Owlett and Codd (1976)
一个代理键值确定了外部世界的一个实体。代理键值是数据库生成的,从来不显示给用户或应用程序看。
Surrogate (2) – Wieringa and De Jonge (1991)
一个代理键值确定了数据库中的一个对象。代理键值是数据库生成的,用户或应用程序看不到它。
在实践中,代理键值通常是个自动递增的数字。在Sybase或SQL Server,用identity column标识代理键,PostgreSQL用serial,Oracle用SEQUENCE,在MySQL用标记有AUTO_INCREMENT的字段。
## 何时使用代理键
以中华人民共和国的十八位身份证号为例,从左往右为六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
一家公司想要将它的客户记入数据库,以客户的身份证号作为主键当然是可以的;但是这18位身份证号是用于标识大陆13多亿人口的,一家公司的客户显然没有这么多,所以用18位的数字作为主键有点浪费空间。
另外,身份证号中包含了地区、生日信息,若以身份证号为主键,要不要另开字段记录客户的地区、生日也是个问题。如果不另开字段,从主键(身份证号)中提取地区、生日有点麻烦;如果开字段,主键和地区、生日字段的数据存在冗余。
所以,一般的做法是,根本不记录客户的身份证号(除非有其他需求),用一个代理键作为主键,另开字段记录客户的地区、生日等信息。