文章目录

  • 一、one_id概述
  • 二、one_id的目的和本质
  • 三、one_id的基本假设
  • 四、one_id的源数据问题
  • 五、数据的可信度
  • 六、数据清洗
    • 1、去重
    • 2、标记来源表“信息可信度优先级”
    • 3、空字符串置为NULL
    • 4、去除字符串中特殊符号
    • 5、正则确保格式正确
    • 6、各字段清洗逻辑
    • 7、phone1和phone2融合
    • 8、同一数据源,确保一个手机号只能对应一个人
    • 9、同一数据源,确保一个身份证只能对应一个人
  • 七、数据融合
    • 1、给所有用户造一个“身份证号”phone_id
    • 2、基于信息可信度,补齐每个手机号的基本信息
    • 3、基于“身份证号”补齐每个人的基本信息
  • 八、生成one_id
    • 1、one_id初始化
    • 2、one_id角色表
    • 3、one_id拉链表
    • 4、one_id应用表

一、one_id概述

用户画像项目有两个核心内容:用户画像标签和用户one_id。
用户画像标签体系,是波士顿咨询公司的团队帮忙搭建的,而我本人则是负责one_id的设计和生成。one_id,是用户唯一标识,用于海量数据中识别出同一个人。目前市面上有两种one_id:无中生有的one_id和基于现有用户数据生成的one_id。后者是前者的子集。

  • 无中生有的one_id,就是基于用户访问数据:

    1. 获取用户的(上网设备)设备号、ip地址等生成one_id
    2. 基于用户注册后留下手机号、身份证等基本信息,将不同的one_id识别成同一个人,进一步融合成新的one_id。

    【难点】:无中生有的one_id的技术难点在于如何获取用户设备号,可是现在很多设备商都不让你获取设备号了。

  • 而我们用户画像项目组的one_id,则是基于现有的用户信息,去生成的one_id。

    • 现有的信息包括:
      1、线索数据:汽车垂直媒体获取到的用户线索。线索数据基本是外部数据。
      2、潜客数据:用户通过线索来到线下门店或自来客的用户数据
      3、车主数据:购车数据、车主认证数据、APP注册数据、续保数据、维修保养数据等等

    【难点】:这个项目的难点在于,如何将多条用户数据识别成同一个人。很多人马上就想到了,通过身份证号,那没有身份证号的呢,怎么识别成同一个人?没有身份证号,那咱们就给他造一个“身份证号”——phone_id,具体怎么操作,请继续往下看。

  • one_id维度表表结构

CREATE TABLE IF NOT EXISTS  dw.dim_one_id(sk_id          bigint  COMMENT  "自增主键",phone          string  COMMENT  "该表唯一手机号",one_id         bigint   COMMENT  "用户唯一标识",nature         string   COMMENT  "属性:个人/组织",one_id_role    string   COMMENT "one-id角色:线索、潜客、车主",user_name_first string COMMENT  "同一手机号取表优先级最高的名字" ,user_name string  COMMENT  "同一证件/手机号名字频次最高的名字" ,sex           string  COMMENT  "性别",user_license  string  COMMENT  "证件号",user_birth    string  COMMENT  "出生日期",user_email    string  COMMENT  "邮件地址",user_qq       string  COMMENT  "QQ",user_wx       string  COMMENT  "微信",user_addr     string  COMMENT  "住址",source_list   string  COMMENT  "数据源列表",source_pd_list  string  COMMENT  "主键列表"
)
COMMENT 'dw层--one_id维度表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;

二、one_id的目的和本质

one_id的目的就是,通过one_id去触达更多的客户。通过什么去触达呢?手机号。所以one_id的本质就是“电话簿”。

三、one_id的基本假设

one_id的基本假设,其实就是从one_id的本质抽象过来的,one_id的基本假设就是“电话簿”的特征。

  1. 一个手机号只能触达一个人。
  2. 一个人可以拥有多个手机号。

one_id的基本假设,同时也是one_id的数据清洗规则和融合逻辑。

四、one_id的源数据问题

  1. 一个手机号 有多个用户名称 或 多个证件号。
  2. 一个证件号 有多个用户名称。
  3. 只有一成数据有身份证号(包括重复证件号,如20个人拥有同一个证件号)

五、数据的可信度

假如同一个手机号,该用户在“懂车帝”上留的用户名是“妮可罗宾小可爱”,而他在购车(实销订单)的时候留的用户名是“李建钢”。你觉得,他在哪里留的信息可信度比较高?那肯定是购车的时候!
所以基于信息可信度,我会给数据来源表,分优先级1、2、3…数字越小,可信度越高。而我给优先级的逻辑是这样:

  1. 根据用户运营流程,给”数据来源表“标记:拉新、留存、转化,把来源表分成三类。信息可信度:转化>留存>拉新
  2. 基于步骤1得到的分类,然后在类里面进行排序,先给”转化类“的来源表进行排序,其次是”留存类“,最后才是“拉新类”。

注:排了表的优先级,一定要跟业务方同步和调整。

六、数据清洗

1、去重

  • 源数据汇总中间表
CREATE TABLE IF NOT EXISTS ods.etl_one_id1 ( source           STRING     COMMENT '数据源', source_level     INT        COMMENT '表优先级', source_pd        STRING     COMMENT '该条数据在源数据的主键', user_name        STRING     COMMENT '姓名', sex              STRING     COMMENT '性别', phone1           STRING     COMMENT '手机号1', phone2           STRING     COMMENT '手机号2', user_license     STRING     COMMENT '证件号', user_birth       STRING     COMMENT '出生日期', user_email       STRING     COMMENT '邮件地址', user_qq          STRING     COMMENT 'QQ', user_wx          STRING     COMMENT '微信', user_addr        STRING     COMMENT '住址', etl_dt           STRING     COMMENT 'ETL时间'
)
PARTITIONED BY (TBL_NAME STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 源数据去重
CREATE TABLE ODS.ETL_ONE_ID1A
AS
SELECT
*
FROM
(SELECTsource       , source_level , source_pd    , user_name    , sex          , phone1       , phone2       , user_license , user_birth   , user_email   , user_qq      , user_wx      , user_addr,row_number() over (partition by source,source_pd order by COALESCE(phone1 ,phone2) desc) as rnFROMODS.ETL_ONE_ID1where phone1 is not null OR phone2 IS NOT NULL
) t
WHERE RN=1
;

2、标记来源表“信息可信度优先级”

  • 表优先级维表
CREATE TABLE IF NOT EXISTS dw.dim_table_level (source           STRING     COMMENT '数据源--表名', source_level     INT        COMMENT '表优先级'
)
COMMENT 'dw层--来源表优先级 维度表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
INSERT INTO TABLE dw.dim_table_level VALUES ('某4S店销售订单表',2)
,('某汽车APP车主认证表',1)
,('某4S店访客登记表',3)
,('懂车帝用户表',4)
,('易车网用户表',5)
;
  • 获得优先级
--源表给定【表优先顺序】
CREATE TABLE ODS.ETL_ONE_ID1B
AS
SELECTA.source
, B.source_level
, A.source_pd
, A.user_name
, A.sex
, A.phone1
, A.phone2
, A.user_license
, A.user_birth
, A.user_email
, A.user_qq
, A.user_wx
, A.user_addr
FROMods.ETL_ONE_ID1A A
LEFT JOINdw.dim_table_level B
ON A.source=B.source
;在这里插入代码片

3、空字符串置为NULL

  • 目标表:ODS.ETL_ONE_ID2
CREATE TABLE ODS.ETL_ONE_ID2
AS
SELECTsource,source_level,IF(LENGTH(source_pd)=0,NULL,source_pd) AS source_pd,IF(LENGTH(user_name)=0,NULL,user_name) AS user_name,IF(LENGTH(sex)=0,NULL,sex) AS  sex,IF(LENGTH(phone1)=0,NULL,phone1) AS  phone1,IF(LENGTH(phone2)=0,NULL,phone2) AS  phone2,IF(LENGTH(user_license)=0,NULL,user_license) AS user_license,IF(LENGTH(user_birth)=0,NULL,user_birth) AS  user_birth,IF(LENGTH(user_email)=0,NULL,user_email) AS  user_email,IF(LENGTH(user_qq)=0,NULL,user_qq) AS user_qq,IF(LENGTH(user_wx)=0,NULL,user_wx) AS user_wx,IF(LENGTH(user_addr)=0,NULL,user_addr) AS user_addr
FROMODS.ETL_ONE_ID1B
;

4、去除字符串中特殊符号

  • 目标表:ODS.ETL_ONE_ID3
CREATE TABLE ODS.ETL_ONE_ID3
AS
SELECTsource,source_level,regexp_replace(source_pd,'[\\s]+|[\\u3000]+|[\,]','')  AS  source_pd,regexp_replace(user_name,'[\\s]+|[\\u3000]+|[\,]','')  AS  user_name,regexp_replace(sex,'[\\s]+|[\\u3001]+|[\,]','')    AS  sex,regexp_replace(phone1,"[\s+\!\/_,$%^*(+\"\')]+|[\\s]+|[\\u3002]+|[\,]+|[\\\u4E00-\\\u9FA5]+|[::+——()?【】“”!,。?、~@#¥%……&*().-]+", '')    AS  phone1,regexp_replace(phone2,"[\s+\!\/_,$%^*(+\"\')]+|[\\s]+|[\\u3002]+|[\,]+|[\\\u4E00-\\\u9FA5]+|[::+——()?【】“”!,。?、~@#¥%……&*().-]+", '')    AS  phone2,regexp_replace(user_license,'[\\s]+|[\\u3004]+|[\,]','')    AS  user_license,regexp_replace(user_birth,'[\\s]+|[\\u3005]+|[\,]','')    AS  user_birth,regexp_replace(user_email,'[\\s]+|[\\u3006]+|[\,]','')    AS  user_email,regexp_replace(user_qq,'[\\s]+|[\\u3007]+|[\,]','')    AS  user_qq,regexp_replace(user_wx,'[\\s]+|[\\u3008]+|[\,]','')    AS  user_wx,regexp_replace(user_addr,'[\\s]+|[\\u3009]+|[\,]','')    AS  user_addr
FROMODS.ETL_ONE_ID2
;

5、正则确保格式正确

  • 目标表:ODS.ETL_ONE_ID4
CREATE TABLE ODS.ETL_ONE_ID4
AS
SELECTsource      ,source_level      ,source_pd   ,CASE WHEN user_name IS NOT NULL THEN regexp_extract(user_name,'([\\\u4E00-\\\u9FA5]+)',1) WHEN LENGTH(user_name)=0 THEN NULLELSE NULL END user_name,sex         ,CASEWHEN phone1 IS NOT NULL AND phone1 regexp "^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$"=TRUE THEN  phone1  --座机号清洗WHEN phone1 IS NOT NULL AND phone1 regexp "^(010|02\\d|0[3-9]\\d{2})\\-?\\d{6,8}$"=TRUE THEN  phone1  --座机号清洗WHEN phone1 IS NOT NULL AND phone1 regexp "^1[3-9]\\d{9}$"=TRUE THEN  phone1  --手机号清洗WHEN phone1 IS NOT NULL AND phone1 regexp "^0+1[3-9]\\d{9}$"=TRUE THEN  substr(phone1,-11)  --手机号清洗ELSE  NULLEND AS phone1,CASEWHEN phone2 IS NOT NULL AND phone2 regexp "^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$"=TRUE THEN  phone2  --座机号清洗WHEN phone2 IS NOT NULL AND phone2 regexp "^(010|02\\d|0[3-9]\\d{2})\\-?\\d{6,8}$"=TRUE THEN  phone2  --座机号清洗WHEN phone2 IS NOT NULL AND phone2 regexp "^1[3-9]\\d{9}$"=TRUE THEN  phone2  --手机号清洗WHEN phone2 IS NOT NULL AND phone2 regexp "^0+1[3-9]\\d{9}$"=TRUE THEN  substr(phone2,-11)  --手机号清洗ELSE  NULLEND AS phone2     ,user_license,CASE WHEN user_birth  > ADD_MONTHS(CURRENT_DATE,-204) or user_birth  <ADD_MONTHS(CURRENT_DATE,-1200)  ---年龄大于100岁  或 小于17岁置为null THEN NULL ELSE SUBSTR(user_birth,0,10) END  AS  user_birth,user_email  ,CASE WHEN user_qq regexp('([0-9]{5,11})')=true then regexp_extract(user_qq,'([0-9]{5,11})',1) ELSE NULL end  AS user_qq  ,CASE WHEN user_wx regexp('([a-zA-Z]{1}[-_a-zA-Z0-9]{5,19})')=TRUE OR user_wx regexp('(([0][3-9]{1}[0-9]{9})|([1][3456789][0-9]{9}))')=TRUE OR user_wx regexp('([0-9]{5,11})')=trueTHEN regexp_extract(user_wx,'(([a-zA-Z]{1}[-_a-zA-Z0-9]{5,19})|([0][3-9]{1}[0-9]{9})|([1][3456789][0-9]{9})|([0-9]{5,11}))',1) ELSE NULL END  AS  user_wx,user_addr
FROMODS.ETL_ONE_ID3
;

6、各字段清洗逻辑

  • 目标表:ODS.ETL_ONE_ID5

注:以下代码包含了三个自定义函数,本文不进行具体展开:

  • idennum( ),身份证校验
  • phones( ),手机号/座机号校验
  • checks( ),企业信用代码校验
CREATE TABLE ODS.ETL_ONE_ID5
AS
SELECTsource      ,source_level      ,source_pd   ,IF(LENGTH(user_name)=0,NULL,user_name) AS user_name,CASE   WHEN LENGTH(user_license)=18  AND default.idennum(user_license)  IS NOT NULLTHEN IF(substr(user_license,17,1)%2 = 0,'女','男')--18位身份证提取性别WHEN LENGTH(user_license)=15AND  default.idennum(user_license)  IS NOT NULLTHEN IF(substr(user_license,15,1)%2 = 0,'女','男') --15位身份证提取性别ELSE  sexEND AS sex     ,default.phones(phone1) AS phone1      ,default.phones(phone2) AS phone2      ,CASE   when length(user_license)=18  AND user_license=regexp_extract(user_license,'(^[1-9][0-9]{5}[1-9][0-9]{3}((0[1-9])|(1[0-2]))((0[1-9])|([1|2][0-9])|(3[0|1]))[0-9Xx]{4})',1)THEN default.idennum(user_license)   --18位身份证校验when length(user_license)=15AND  default.idennum(user_license)  IS NOT NULLTHEN user_license  --15位身份证校验 when default.checks(user_license) is not null THEN default.checks(user_license)   --企业代码验证   else null end  as  user_license,CASE   when length(user_license)=18  AND user_license=regexp_extract(user_license,'(^[1-9][0-9]{5}[1-9][0-9]{3}((0[1-9])|(1[0-2]))((0[1-9])|([1|2][0-9])|(3[0|1]))[0-9Xx]{4})',1)  THEN  SUBSTR(CAST(from_unixtime(UNIX_TIMESTAMP(substr(user_license,7, 8),'yyyyMMdd')) AS STRING),0,10)--18位身份证提取生日when length(user_license)=15AND  default.idennum(user_license)  IS NOT NULLTHEN SUBSTR(CAST(from_unixtime(UNIX_TIMESTAMP(CONCAT('19',SUBSTR(user_license,7, 6)),'yyyyMMdd')) AS STRING),0,10) --15位身份证提取生日 ELSE  user_birthEND AS user_birth,CASE WHEN user_email IS NOT NULL AND user_email=regexp_extract(user_email,'([a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+)',1) THEN user_emailELSE CONCAT(user_qq,'@qq.com') END AS  user_email ,user_qq     ,user_wx ,user_addr
FROMODS.ETL_ONE_ID4
;

7、phone1和phone2融合

  • 目标表:ODS.ETL_ONE_ID7
  • 再次进行“空字串置为null”
CREATE  TABLE ODS.ETL_ONE_ID6
AS
SELECTsource      ,source_level      ,source_pd   ,IF(LENGTH(user_name)=0,NULL,user_name) AS user_name  --为了避免进行 【同源一机一人】的筛选被筛选掉 ,IF(LENGTH(sex)=0,NULL,sex) AS  sex,IF(LENGTH(phone1)=0,NULL,phone1) AS  phone1,IF(LENGTH(phone2)=0,NULL,phone2) AS  phone2,IF(LENGTH(user_license)=0,NULL,user_license) AS user_license,IF(LENGTH(user_birth)=0,NULL,user_birth) AS  user_birth,IF(LENGTH(user_email)=0,NULL,user_email) AS  user_email,IF(LENGTH(user_qq)=0,NULL,user_qq) AS user_qq,IF(LENGTH(user_wx)=0,NULL,user_wx) AS user_wx,IF(LENGTH(user_addr)=0,NULL,user_addr) AS user_addr
FROM ODS.ETL_ONE_ID5
;
  • phone1 和 phone2进行融合

    • 注:phone1和phone2有以下三种情况:

      1. 两个手机号/座机号是一样的
      2. 两个手机号/座机号不一样
      3. 其中一个null,另一个不为null
CREATE TABLE ODS.ETL_ONE_ID7
AS
SELECTA.source      ,A.source_level,A.source_pd    ,A.user_name   ,A.sex         ,A.phone,A.user_license,IF(A.user_birth  > ADD_MONTHS(CURRENT_DATE,-204) or A.user_birth  <ADD_MONTHS(CURRENT_DATE,-1200),NULL,A.user_birth)  AS user_birth   ---年龄大于100岁  或 小于17岁置为null  ,A.user_email  ,A.user_qq     ,A.user_wx     ,A.user_addr
FROM
(SELECTsource      ,source_level      ,source_pd   ,user_name   ,sex         ,phone1 as phone,user_license,user_birth  ,user_email  ,user_qq     ,user_wx     ,user_addrFROM ODS.ETL_ONE_ID6WHERE phone1  IS NOT NULLGROUP BY  source      ,source_level      ,source_pd   ,user_name   ,sex ,phone1           ,user_license,user_birth  ,user_email  ,user_qq     ,user_wx     ,user_addrUNION ALL SELECTsource      ,source_level         ,source_pd   ,user_name   ,sex              ,phone2 as phone  ,user_license,user_birth  ,user_email  ,user_qq     ,user_wx     ,user_addr  FROM ODS.ETL_ONE_ID6WHERE phone2  IS NOT NULL GROUP BY  source      ,source_level      ,source_pd   ,user_name   ,sex ,phone2        ,user_license,user_birth  ,user_email  ,user_qq     ,user_wx     ,user_addr
) A
GROUP BY  A.source      ,A.source_level      ,A.user_name,A.source_pd    ,A.sex,A.phone    ,A.user_license,IF(A.user_birth  > ADD_MONTHS(CURRENT_DATE,-204) or A.user_birth  <ADD_MONTHS(CURRENT_DATE,-1200),NULL,A.user_birth)  ---年龄大于100岁  或 小于17岁置为null,A.user_email  ,A.user_qq     ,A.user_wx     ,A.user_addr
;

8、同一数据源,确保一个手机号只能对应一个人

  • 目标表:ODS.tmp_source_phone_unique1
  • 表描述:【同源一机一人】表
CREATE TABLE ODS.tmp_source_phone_unique1  --同一数据源  【同源一机一人】
AS
SELECTA.source ,A.source_level      ,A.source_pd ,A.user_name ,A.sex  ,A.phone,A.user_license,A.user_birth  ,A.user_email ,A.user_qq     ,A.user_wx     ,A.user_addr
FROM ODS.ETL_ONE_ID7 A
JOIN
(SELECT*FROM(SELECTphone,source ,COUNT(DISTINCT user_name) AS num_uesr_name --同一个手机号,机主数量,基于用户名字  。风险:不同数源的用户名不同,从而会造成数据,COUNT(DISTINCT user_license) AS num_license --同一个手机号,机主数量,基于用户证件FROM ODS.ETL_ONE_ID7GROUP BY source,phone) B1WHERE B1.num_uesr_name<=1 OR B1.num_license=1   --确保同一个数据源,【一机一人】,考虑到一个人只有一个称呼或不填) B
ON A.phone=B.phone
AND  A.source=B.source
;

9、同一数据源,确保一个身份证只能对应一个人

  • 目标表:ODS.tmp_source_phone_unique
  • 表描述:【同源一机一人、同源一证一人】表
  • 逻辑:同一数据源,确保一个身份证最多只能对应两台手机,超过两台,该身份证号置为null
CREATE TABLE ODS.tmp_source_phone_unique --同一数据源  【同源一证一人】
AS
SELECTsource ,source_level      ,source_pd ,user_name ,sex  ,phone,IF(num_phone_license>2,NULL,user_license) AS user_license,user_birth  ,user_email ,user_qq     ,user_wx     ,user_addr
FROM
(SELECTsource ,source_level      ,source_pd ,user_name ,sex  ,phone,user_license,COUNT(DISTINCT phone) OVER(PARTITION BY source,user_license )  AS num_phone_license --同一个数据源一个身份证号对应的手机号数量,user_birth  ,user_email ,user_qq     ,user_wx     ,user_addrFROMODS.tmp_source_phone_unique1WHERE default.idennum(user_license)  IS NOT NULL
) TTUNION ALL SELECTsource ,source_level      ,source_pd ,user_name ,sex  ,phone,user_license,user_birth  ,user_email ,user_qq     ,user_wx     ,user_addr
FROMODS.tmp_source_phone_unique1
WHERE default.idennum(user_license) IS NULL
;

七、数据融合

1、给所有用户造一个“身份证号”phone_id

  • phone_id维度表 表结构
CREATE TABLE IF NOT EXISTS  dw.dim_phone_id(phone_id      string  COMMENT  "手机号标识",phone         string  COMMENT  "手机号",times   string  COMMENT  "手机号在全数据源中的频次"
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 生成 phone_id 的三个原则:

    1. 一个phone_id视为一个人
    2. 一个phone_id可以有多个手机号
    3. 一个phone只能对应一个phone_id

第二点原则呼应了“一个人可以有多个手机号”的基本假设。
第三点原则呼应了“一个手机号只能触达一个人”的基本假设。

  • phone_id的取数逻辑:

    1. 同一数据源同一自增主键,有两个手机号,取手机号出现频次最高的手机号作为这两条数据的phone_id
    2. 全部数据源,确保一个手机号只能对应一个phone_id
    3. 全部数据源,确保一个phone_id最多只能对应3个手机号,超过3个就将该行数据的phone列作为该行数据的phone_id
  • phone_id的作用:
    1. 数据先基于“身份证号”去融合,其次是“phone_id”
    2. 生成one_id,优先用“身份证号”进行hash值计算,其次是用“phone_id”进行hash值计算
  • 实现步骤:
  1. 基于全部数据源,统计手机号总共出现在几个数据源,即“手机号频次”
CREATE TABLE ods.etl_phone_times --手机号频次表
AS
SELECTphone,COUNT( DISTINCT source) AS  times
FROMods.tmp_source_phone_unique
GROUP BY phone
;
  1. 获取phone_id:【同源一机一人、同源一证一人】表ODS.tmp_source_phone_unique 去联表 【手机号频次表】ods.etl_phone_times获得每个手机号的频次,然后基于source,source_pd(数据源,该数据源自增主键)去分组,取频次高的手机号作为"phone_id":
  • 目标表:ods.etl_phone_id
CREATE TABLE ods.etl_phone_id
AS
SELECTphone_id,phone,timesFROM(SELECTFIRST_VALUE(phone) OVER (partition by source,source_pd  ORDER BY times DESC ) AS phone_id  --同一数据源,同一主键 有两个不同手机号,以频次高的手机号作为一个用户标识 phone_id,phone  ,timesFROM(SELECTA.source ,A.source_pd ,A.phone,B.timesFROMods.tmp_source_phone_unique  ALEFT JOINods.etl_phone_times BON A.phone=B.phone) t011WHERE phone regexp "^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$"=FALSE  --座机号频次太高,故只取 非座机号去生成 phone_idUNION ALL SELECTphone  AS  phone_id  --座机号作为主键一个用户标识,phone  ,timesFROM(SELECTA.source ,A.source_pd ,A.phone,B.timesFROMods.tmp_source_phone_unique  ALEFT JOINods.etl_phone_times BON A.phone=B.phone) t011WHERE phone regexp "^(010|02\\d|0[3-9]\\d{2})?\\d{6,8}$"=TRUE --筛选出座机号) t01GROUP BY phone_id,phone,times
;
  1. 确保一个手机号只对应一个phone_id
  • 目标表:ods.etl_phone_id1
CREATE TABLE ods.etl_phone_id1
AS
SELECTphone_id,phone,times
FROM
(SELECTIF(num_phone_id>1,phone,phone_id) AS  phone_id,phone,timesFROM(SELECTphone_id,phone,times,COUNT(DISTINCT phone_id) OVER (PARTITION BY phone )  AS num_phone_idFROMods.etl_phone_id) t1
) TT
GROUP BYphone_id,phone,times
;
  1. 确保全部数据源,一个phone_id最多对应3个手机号
  • 目标表:ods.etl_phone_id2
CREATE TABLE ods.etl_phone_id2
AS
SELECTIF(num_phone>3,phone,phone_id) AS phone_id,phone,times
FROM
(SELECTphone_id,COUNT(DISTINCT phone) OVER(PARTITION BY phone_id ) AS num_phone,phone,timesFROMods.etl_phone_id1
) TT
;
  1. 将数据加载到phone_id维度表
  • 目标表:dw.dim_phone_id
INSERT OVERWRITE TABLE dw.dim_phone_id
SELECTIF(B.phone_id IS NULL,A.phone_id,B.phone_id) AS  phone_id,A.phone,A.times
FROMods.etl_phone_id2  A
LEFT JOINdw.dim_phone_id B
ON A.phone=B.phone
;

2、基于信息可信度,补齐每个手机号的基本信息

目标表:ods.phone_fuse

CREATE TABLE IF NOT EXISTS  ods.phone_fuse(phone_id      string  COMMENT  "手机号识别成一个人",phone         string  COMMENT  "手机号",phone_times   string  COMMENT  "手机号在全数据源中的频次",user_name_first string COMMENT "基于表优先级取到的用户名",sex           string  COMMENT  "性别",user_license  string  COMMENT  "证件号",user_birth    string  COMMENT  "出生日期",user_email    string  COMMENT  "邮件地址",user_qq       string  COMMENT  "QQ",user_wx       string  COMMENT  "微信",user_addr     string  COMMENT  "住址",source_list   string  COMMENT  "数据源列表",source_pd_list  string  COMMENT  "主键列表"
)
COMMENT 'ods层--基于手机号基本信息融合表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;

假如同一个手机号,该用户在“懂车帝”上留的用户名是“妮可罗宾小可爱”,而他在购车(实销订单)的时候留的用户名是“李建钢”。你觉得,他在哪里留的信息可信度比较高?那肯定是购车的时候!
所以基于信息可信度,我会给数据来源表,分优先级1、2、3…数字越小,可信度越高。
有了这些条件,你怎么去补齐每个手机号的基本信息呢?我是这样操作的:
1、以dw.dim_phone_id作为主表,该表的phone是不重复的,作为ods.phone_fuse的主键。

    SELECTphone_id ,phone,times AS phone_times FROMcdp_dw.dim_phone_id

2、其他字段如用户名、性别、证件号、生日等字段,我是这样取的:
以“用户名”取值为例:

  1. 基于【同源一机一人、同源一证一人】表ods.tmp_source_phone_unique,筛选出“用户名”不为null的数据

  2. 基于步骤1,用以下代码取出优先级最高的“用户名”

     1. ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN2. where RN=1
    
    SELECT*FROM(SELECTphone,user_name  AS user_name_first,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN ,source,source_pd     FROMods.tmp_source_phone_uniqueWHERE user_name IS NOT NULL   AND phone IS NOT NULL) A21WHERE A21.RN=1

3、融合完整代码:

INSERT OVERWRITE TABLE ods.phone_fuse
SELECTA1.phone_id,A1.phone,A1.phone_times,A2.user_name_first   ,nvl(B.sex,'未知') AS sex,C.user_license,D.user_birth,E.user_email ,F.user_qq,G.user_wx,H.user_addr,CONCAT_WS(',',A2.source,B.source, C.source , D.source , E.source , F.source , G.source , H.source )  AS source_list,CONCAT_WS(',',A2.source_pd ,B.source_pd  , C.source_pd  , D.source_pd  , E.source_pd  , F.source_pd  , G.source_pd  , H.source_pd  )  AS source_pd_list
FROM
(SELECTphone_id ,phone,times AS phone_times FROMcdp_dw.dim_phone_id
) A1 --手机号主表 【全源一机频次】表
LEFT JOIN
(SELECT*FROM(SELECTphone,user_name  AS user_name_first,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN ,source,source_pd     FROMods.tmp_source_phone_uniqueWHERE user_name IS NOT NULL   AND phone IS NOT NULL) A21WHERE A21.RN=1
) A2 --姓名
ON  A1.phone=A2.phoneLEFT JOIN
(SELECT*FROM(SELECTphone,sex,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN ,source,source_pd     FROMods.tmp_source_phone_uniqueWHERE sex IS NOT NULL  AND sex<>'未知' AND phone IS NOT NULL) B1WHERE B1.RN=1
) B --性别
ON  A1.phone=B.phone
LEFT JOIN
(SELECT*FROM (SELECTphone,user_license,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN  ,source,source_pd        FROMods.tmp_source_phone_uniqueWHERE user_license IS NOT NULL AND phone IS NOT NULL) C1WHERE C1.RN=1
) C --证件号
ON  A1.phone=C.phone
LEFT JOIN
(SELECT*FROM(SELECTphone,user_birth,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN  ,source,source_pd     FROMods.tmp_source_phone_uniqueWHERE user_birth IS NOT NULL  AND phone IS NOT NULL) D1WHERE D1.RN=1
) D --出生日期
ON  A1.phone=D.phone
LEFT JOIN
(SELECT*FROM(SELECTphone,user_email ,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN  ,source,source_pd        FROMods.tmp_source_phone_uniqueWHERE user_email  IS NOT NULL AND phone IS NOT NULL) E1WHERE E1.RN=1
) E --邮件地址
ON  A1.phone=E.phone
LEFT JOIN
(SELECT*FROM(SELECTphone,user_qq,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN   ,source,source_pd        FROMods.tmp_source_phone_uniqueWHERE user_qq  IS NOT NULL AND phone IS NOT NULL) F1WHERE F1.RN=1
) F --qq
ON  A1.phone=F.phone
LEFT JOIN
(SELECT*FROM(SELECTphone,user_wx,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN   ,source,source_pd        FROMods.tmp_source_phone_uniqueWHERE user_wx  IS NOT NULL) G1WHERE G1.RN=1
) G --微信
ON  A1.phone=G.phone
LEFT JOIN
(SELECT*FROM(SELECTphone,user_addr,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY  source_level )  AS RN  ,source,source_pd        FROMods.tmp_source_phone_uniqueWHERE user_addr  IS NOT NULL AND phone IS NOT NULL) H1WHERE H1.RN=1) H --地址
ON  A1.phone=H.phone
;

3、基于“身份证号”补齐每个人的基本信息

  • 目标表:ods.user_license_fuse表结构
CREATE TABLE IF NOT EXISTS  ods.user_license_fuse(phone_id      string  COMMENT  "手机号识别成一个人",phone         string  COMMENT  "手机号",phone_times   INT     COMMENT  "手机号在全数据源中的频次",user_name_first string COMMENT  "同一手机号去表优先级最高的名字" ,user_name string  COMMENT  "同一证件/手机号名字频次最高的名字" ,sex           string  COMMENT  "性别",user_license  string  COMMENT  "证件号",user_birth    string  COMMENT  "出生日期",user_email    string  COMMENT  "邮件地址",user_qq       string  COMMENT  "QQ",user_wx       string  COMMENT  "微信",user_addr     string  COMMENT  "住址",source_list   string  COMMENT  "数据源列表",source_pd_list  string  COMMENT  "主键列表"
)
COMMENT 'ods层--基于证件号基本信息融合表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 融合步骤:
  1. 基于身份证、user_name去统计全部数据源的user_name的频次
CREATE TABLE ods.user_license_name1
AS
SELECTuser_license,user_name,count(user_name) AS user_name_times1
FROMods.tmp_source_phone_unique
WHERE user_license IS NOT NULL AND default.idennum(user_license)  IS NOT NULL
GROUP BY    user_license,user_name
;

2.基于phone_id去统计user_name的频次 ,同一phone_id取名字频次高的user_name

CREATE TABLE ods.user_name_times2
AS
SELECTA.phone_id,B.user_name,SUM(user_name_times2 ) AS user_name_times2
FROMcdp_dw.dim_phone_id A
LEFT JOIN
(SELECTphone,user_name,count(1) AS user_name_times2 FROMods.tmp_source_phone_uniqueWHERE user_name IS NOT NULL AND  default.idennum(user_license)  IS NULLGROUP BY phone,user_name
) B
ON A.PHONE=B.PHONE
GROUP BY  A.phone_id
,B.user_name
;
  1. 基于phone_id去统计sex的频次 ,同一phone_id取频次高的性别
CREATE TABLE ods.phone_id_sex
AS
SELECTA.phone_id,B.sex,SUM(num_sex) AS num_sex
FROMcdp_dw.dim_phone_id A
LEFT JOIN
(selectphone,sex,count(1) AS num_sexfromods.tmp_source_phone_uniquewhere sex is not null AND sex<>'未知'group by phone,sex
) B
ON A.PHONE=B.PHONE
GROUP BY  A.phone_id
,B.sex
;
  1. 基于身份证号 或 phone_id 去融合个人信息
INSERT OVERWRITE TABLE ods.user_license_fuse
SELECTA.phone_id,A.phone         ,A.phone_times   ,A.user_name_first,A.user_name  --同一证件号频次高的名字,CASE   WHEN LENGTH(A.user_license)=18  THEN IF(substr(A.user_license,17,1)%2 = 0,'女','男')--18位身份证提取性别WHEN LENGTH(A.user_license)=15THEN IF(substr(A.user_license,15,1)%2 = 0,'女','男') --15位身份证提取性别ELSE  A.sexEND AS sex             ,A.user_license  ,CASE   when length(A.user_license)=18  THEN  SUBSTR(CAST(from_unixtime(UNIX_TIMESTAMP(substr(A.user_license,7, 8),'yyyyMMdd')) AS STRING),0,10)--18位身份证提取生日when length(A.user_license)=15THEN SUBSTR(CAST(from_unixtime(UNIX_TIMESTAMP(CONCAT('19',SUBSTR(A.user_license,7, 6)),'yyyyMMdd')) AS STRING),0,10) --15位身份证提取生日 ELSE  A.user_birthEND AS user_birth,IF(A.user_email IS NULL ,FIRST_VALUE(A.user_email)  OVER (PARTITION BY A.user_license ORDER BY LENGTH(NVL(A.user_email ,'')) DESC) ,A.user_email)AS user_email    ,IF(A.user_qq IS NULL ,FIRST_VALUE(A.user_qq )  OVER (PARTITION BY A.user_license ORDER BY LENGTH(NVL(A.user_qq ,'')) DESC) ,A.user_qq )AS user_qq       ,IF(A.user_wx IS NULL ,FIRST_VALUE(A.user_wx)  OVER (PARTITION BY A.user_license ORDER BY LENGTH(NVL(A.user_wx,'')) DESC) ,A.user_wx)AS user_wx       ,IF(A.user_addr IS NULL,FIRST_VALUE(A.user_addr)  OVER (PARTITION BY A.user_license ORDER BY LENGTH(NVL(A.user_addr,'')) DESC),A.user_addr)   AS  user_addr     ,A.source_list   ,A.source_pd_list
FROM
(SELECTA.phone_id,A.phone         ,A.phone_times   ,A.user_name_first,B.user_name,A.sex           ,A.user_license  ,A.user_birth    ,A.user_email    ,A.user_qq       ,A.user_wx       ,A.user_addr     ,A.source_list   ,A.source_pd_list   FROM(SELECT*FROMods.phone_fuseWHERE default.idennum(user_license)  IS NOT NULL) ALEFT JOIN--同一证件号取频次最高的名字(    SELECT*FROM(SELECTuser_license,user_name,row_number() over (partition by user_license order by user_name_times1 desc ) AS RNFROMods.user_license_name1) B1WHERE RN=1) B    ON A.user_license=B.user_license
) A  --有身份证,同一身份证号,user_name 取频次最高的频次、其他字段取不为空的UNION ALL--【一人多机】信息融合:一个人的多个手机号信息进行融合
SELECTB.phone_id   ,B.phone         ,B.phone_times   ,B.user_name_first,B.user_name  --同一手机号取频次高的名字,B.sex,B.user_license  ,IF(B.user_birth IS NULL,FIRST_VALUE(B.user_birth)  OVER (PARTITION BY B.phone_id  ORDER BY LENGTH(NVL(B.user_birth,'')) DESC) ,B.user_birth) AS user_birth          ,IF (B.user_email  IS NULL ,FIRST_VALUE(B.user_email)  OVER (PARTITION BY B.phone_id  ORDER BY LENGTH(NVL(B.user_email,'')) DESC) ,B.user_email) AS user_email,IF(B.user_qq IS NULL,FIRST_VALUE(B.user_qq )  OVER (PARTITION BY B.phone_id  ORDER BY LENGTH(NVL(B.user_qq ,'')) DESC) ,B.user_qq ) AS user_qq           ,IF (B.user_wx  IS NULL,FIRST_VALUE(B.user_wx )  OVER (PARTITION BY B.phone_id  ORDER BY LENGTH(NVL(B.user_wx ,'')) DESC) ,B.user_wx ) AS user_wx           ,IF (B.user_addr  IS NULL,FIRST_VALUE(B.user_addr)  OVER (PARTITION BY B.phone_id  ORDER BY LENGTH(NVL(B.user_addr ,'')) DESC),B.user_addr) AS user_addr            ,B.source_list   ,B.source_pd_list
FROM
(SELECTA.phone_id   ,A.phone         ,A.phone_times   ,A.user_name_first,B.user_name,NVL(C.sex,'未知') AS sex           ,A.user_license  ,A.user_birth    ,A.user_email    ,A.user_qq       ,A.user_wx       ,A.user_addr     ,A.source_list   ,A.source_pd_list   FROM(SELECT*FROMods.phone_fuseWHERE default.idennum(user_license)  IS  NULL) A --这个表已经确保表手机号是唯一的了LEFT JOIN --同一phone_id取  频次最高的名字(    SELECT*FROM(SELECTphone_id,user_name,row_number() over (partition by phone_id order by user_name_times2 desc ) AS RNFROMods.user_name_times2) B1WHERE RN=1) BON A.phone_id=B.phone_idLEFT JOIN  --同一phone_id 取频次高的性别(    SELECT*FROM(SELECTphone_id,sex,row_number() over (partition by phone_id order by num_sex desc ) AS RNFROMods.phone_id_sex) B1WHERE RN=1) CON A.phone_id=C.phone_id) B  --无身份证号,同一phone_id频次高的名字和性别
;

八、生成one_id

1、one_id初始化

  • 目标表 表结构
CREATE TABLE IF NOT EXISTS  dw.dim_one_id_initialize(one_id         string   COMMENT  "ONE_ID",user_license   string     COMMENT  "证件号/phone_id",phone      string COMMENT  "手机号/座机号",phone_times  int  COMMENT  "手机号频次"
)
COMMENT 'dw层--one_id初始化表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 生成ond_id :最初one_id是基于手机号、座机号、身份证,进行hash()计算的

  • 警惕:hash会碰撞(不同的取数,却计算出来同样的hash值)

  • 碰撞概率:

    • hash():2的-32次方
      (计算结果约等1/42亿的概率会重复)
    • md5():2的-256次方
      (计算结果约等1/42亿的8次方的概率会重复)
  • 为了获得唯一one_id,我们采用了简单的“凯撒加密”,“凯撒加密”java代码如下:

package cn.ysw.com.aspect;import cn.hutool.core.util.StrUtil;import java.util.HashMap;
import java.util.Map;public class Test {private static Map<String,String> MAP = new HashMap<>();static {MAP.put("0","3");MAP.put("1","4");MAP.put("2","5");MAP.put("3","6");MAP.put("4","7");MAP.put("5","8");MAP.put("6","9");MAP.put("7","0");MAP.put("8","1");MAP.put("9","2");MAP.put("x","y");MAP.put("X","Z");}public static String getId(String str) {if (StrUtil.isBlank(str)) {return "";}char[] chars = str.toCharArray();StringBuffer sb = new StringBuffer();for (char a: chars) {String s = String.valueOf(a);String val = MAP.get(s);if (StrUtil.isBlank(val)) {sb.append(a);} else {sb.append(val);}}return sb.toString();}public static void main(String[] args) {String id = getId();System.out.println(id);}}
  • 如果不想麻烦可以直接用MD5( ),毕竟计算结果重复的概率是 1/42亿的8次方
INSERT OVERWRITE TABLE  dw.dim_one_id_initialize
SELECTmd5(A.user_license) AS ONE_ID,A.user_license,A.phone,A.phone_times
FROM
(SELECTuser_license,phone,phone_times FROMods.user_license_fuseWHERE default.idennum(user_license) IS NOT NULL  --有身份证号的数据UNION ALLSELECTphone_id  AS user_license,phone,phone_times FROM    ods.user_license_fuse  WHERE default.idennum(user_license) IS NULL --无身份证号,有企业信用代码 的数据
) A
;

2、one_id角色表

–ONE-ID 角色表
–1:线索
–2:潜客
–3:车主

  • 目标表 表结构
CREATE TABLE IF NOT EXISTS  dw.dim_one_id_role(phone          string  COMMENT  "手机号",role_id        bigint  COMMENT  "角色编码",role_name      string   COMMENT  "角色名称"
)
COMMENT 'dw层--one_id角色表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 这个角色判断,是按手机号来源表去判断的:

  • 插入数据:one_id角色表

INSERT OVERWRITE TABLE dw.dim_one_id_role
SELECTphone ,role_id,role_name
FROM
(SELECTphone ,CASEWHEN SOURCE IN ('易车网用户表','懂车帝用户表')  THEN 1WHEN SOURCE IN ('某4S店访客登记表') THEN 2WHEN SOURCE IN ('某4S店销售订单表','某汽车APP车主认证表') THEN 3ELSE NULLEND AS role_id,CASEWHEN SOURCE IN ('易车网用户表','懂车帝用户表')  THEN '线索'WHEN SOURCE IN ('某4S店访客登记表') THEN '潜客'WHEN SOURCE IN ('某4S店销售订单表','某汽车APP车主认证表') THEN '车主'ELSE NULLEND AS role_nameFROMods.tmp_source_phone_unique
) TT
GROUP BY phone ,role_id,role_name
;
  • 如果同一个手机号,同时拥有3个角色或2个角色,取“角色代码”最大的
        SELECT*FROM(SELECTphone  ,role_id,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY role_id desc)  AS RN  ,role_name AS one_id_role    FROMdw.dim_one_id_role) C1WHERE C1.RN=1

3、one_id拉链表

  • 表结构
CREATE TABLE IF NOT EXISTS  dw.dim_one_id_all(sk_id          bigint  COMMENT  "自增主键",phone          string  COMMENT  "该表唯一手机号",one_id         string   COMMENT  "用户唯一标识",nature         string   COMMENT  "属性:个人/组织",one_id_role    string   COMMENT "one-id角色:线索、潜客、车主",user_name_first string COMMENT  "同一手机号取表优先级最高的名字" ,user_name string  COMMENT  "同一证件/手机号名字频次最高的名字" ,sex           string  COMMENT  "性别",user_license  string  COMMENT  "证件号",user_birth    string  COMMENT  "出生日期",user_email    string  COMMENT  "邮件地址",user_qq       string  COMMENT  "QQ",user_wx       string  COMMENT  "微信",user_addr     string  COMMENT  "住址",source_list   string  COMMENT  "数据源列表",source_pd_list  string  COMMENT  "主键列表",valid_start_dt string COMMENT "当前行生效日期",valid_end_dt string  COMMENT "当前行失效日期"
)
COMMENT 'dw层--one_id拉链表'
PARTITIONED BY (PART_DAY STRING COMMENT '分区字段,当天系统时间')
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 拉链表更新
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
SET hive.strict.checks.type.safety=false;INSERT OVERWRITE TABLE dw.dim_one_id_all
PARTITION(PART_DAY)
SELECTROW_NUMBER() OVER (ORDER BY CONCAT_WS('_',t1.phone,cast(t1.one_id as string))) + max_sk AS sk_id         ,t1.phone         ,t1.one_id        ,t1.nature        ,t1.one_id_role   ,t1.user_name_first,t1.user_name,t1.sex           ,t1.user_license  ,t1.user_birth    ,t1.user_email    ,t1.user_qq       ,t1.user_wx       ,t1.user_addr     ,t1.source_list   ,t1.source_pd_list, DATE_FORMAT(CURRENT_DATE,'yyyyMMdd')  AS valid_start_dt, '99991231' AS valid_end_dt,CAST(CURRENT_DATE AS STRING) AS PART_DAY
FROM
(SELECTB.ONE_ID,A.phone         ,CASEWHEN  default.checks(A.user_license)  IS NOT NULL AND A.user_birth  IS NULL  THEN  '组织'WHEN SUBSTR(A.user_name_first,-1) IN ('司','处','局','馆')THEN  '组织'WHEN SUBSTR(user_name,-1) IN ('司','处','局','馆')THEN  '组织'ELSE '个人'END AS  nature   --one-id属性: 证件号经过 企业代码验证 不为null,且生日 为null(因为生日优先用证件号去判断),则判断 为"组织",C.one_id_role         ,A.user_name_first,A.user_name,A.sex           ,A.user_license,A.user_birth    ,A.user_email    ,A.user_qq  ,A.user_wx      ,A.user_addr    ,A.source_list   ,A.source_pd_listFROM ods.user_license_fuse ALEFT JOINdw.dim_one_id_initialize B --ONE_ID初始化表ON A.phone = B.phoneLEFT JOIN (SELECT*FROM(SELECTphone  ,role_id,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY role_id desc)  AS RN  ,role_name AS one_id_role    FROMdw.dim_one_id_role) C1WHERE C1.RN=1) C --角色表ON A.phone=C.phone
) t1 --源数据
LEFT JOIN
(SELECT*FROMdw.dim_one_id_allWHERE valid_start_dt <= DATE_FORMAT(DATE_SUB(CURRENT_DATE,1),'yyyyMMdd') --只取有效的数据AND valid_end_dt > DATE_FORMAT(DATE_SUB(CURRENT_DATE,1),'yyyyMMdd')AND PART_DAY=CAST(DATE_SUB(CURRENT_DATE,1) AS STRING) --取昨天的分区
)t2
ON t1.phone=t2.phone
--获取维度表上次最大的外键,以便为新数据造新的外键
CROSS JOIN
(SELECTCOALESCE(MAX(sk_id ),0) AS max_skFROM dw.dim_one_id_all
)dim_sk_tmp
WHERE t2.sk_id is nullOR t1.one_id  !=t2.one_id         OR t1.nature !=t2.nature         OR t1.one_id_role != t2.one_id_role    OR t1.user_name_first!=t2.user_name_firstOR t1.user_name !=t2.user_name OR t1.sex !=t2.sex OR t1.user_license  !=t2.user_license  OR t1.user_birth !=t2.user_birth    OR t1.user_email !=t2.user_email    OR t1.user_qq !=t2.user_qq       OR t1.user_wx !=t2.user_wx     UNION ALLSELECTt3.sk_id         ,t3.phone         ,t3.one_id        ,t3.nature        ,t3.one_id_role   ,t3.user_name_first,t3.user_name,t3.sex           ,t3.user_license  ,t3.user_birth    ,t3.user_email    ,t3.user_qq       ,t3.user_wx       ,t3.user_addr     ,t3.source_list   ,t3.source_pd_list,t3.valid_start_dt,CASEWHEN t3.one_id  !=t4.one_id         OR t3.nature !=t4.nature         OR t3.one_id_role !=t4.one_id_role    OR t3.user_name_first!=t4.user_name_firstOR t3.user_name !=t4.user_name OR t3.sex !=t4.sex OR t3.user_license  !=t4.user_license  OR t3.user_birth !=t4.user_birth    OR t3.user_email !=t4.user_email    OR t3.user_qq !=t4.user_qq       OR t3.user_wx !=t4.user_wx THEN DATE_FORMAT(DATE_SUB(CURRENT_DATE,1),'yyyyMMdd')ELSE t3.valid_end_dtEND AS  valid_end_dt
,CAST(CURRENT_DATE AS STRING) AS PART_DAY
FROM
(SELECT*FROMdw.dim_one_id_all WHERE valid_start_dt <= DATE_FORMAT(DATE_SUB(CURRENT_DATE,1),'yyyyMMdd') --只取有效的数据AND valid_end_dt > DATE_FORMAT(DATE_SUB(CURRENT_DATE,1),'yyyyMMdd')AND PART_DAY=CAST(DATE_SUB(CURRENT_DATE,1) AS STRING)  --取昨天的分区
)t3 --维度表
LEFT JOIN
(SELECTB.ONE_ID,A.phone         ,CASEWHEN  default.checks(A.user_license)  IS NOT NULL AND A.user_birth  IS NULL  THEN  '组织'WHEN SUBSTR(A.user_name_first,-1) IN ('司','处','局','馆')THEN  '组织'WHEN SUBSTR(user_name,-1) IN ('司','处','局','馆')THEN  '组织'ELSE '个人'END AS  nature   --one-id属性: 证件号经过 企业代码验证 不为null,且生日 为null(因为生日优先用证件号去判断),则判断 为"组织",C.one_id_role         ,A.user_name_first,A.user_name,A.sex           ,A.user_license,A.user_birth    ,A.user_email    ,A.user_qq  ,A.user_wx      ,A.user_addr    ,A.source_list   ,A.source_pd_listFROM ods.user_license_fuse ALEFT JOINdw.dim_one_id_initialize B --ONE_ID初始化表ON A.phone = B.phoneLEFT JOIN (SELECT*FROM(SELECTphone  ,role_id,ROW_NUMBER() OVER(PARTITION BY phone  ORDER BY role_id desc)  AS RN  ,role_name AS one_id_role    FROMdw.dim_one_id_role) C1WHERE C1.RN=1  --同个手机号有多个角色,取序列号最大的角色) C --角色表ON A.phone=C.phone
) t4 --源数据
ON t3.phone=t4.phoneUNION ALL
--
SELECTt4.sk_id          ,t4.phone          ,t4.one_id         ,t4.nature         ,t4.one_id_role    ,t4.user_name_first,t4.user_name,t4.sex          ,t4.user_license ,t4.user_birth   ,t4.user_email   ,t4.user_qq      ,t4.user_wx      ,t4.user_addr    ,t4.source_list  ,t4.source_pd_list ,t4.valid_start_dt ,t4.valid_end_dt,CAST(CURRENT_DATE AS STRING) AS PART_DAYFROMdw.dim_one_id_all t4
WHERE  valid_end_dt <= DATE_FORMAT(DATE_SUB(CURRENT_DATE,1),'yyyyMMdd')AND PART_DAY=CAST(DATE_SUB(CURRENT_DATE,1) AS STRING) --取昨天的分区;

4、one_id应用表

  • 表结构
CREATE TABLE IF NOT EXISTS  dw.dim_one_id(sk_id          bigint  COMMENT  "自增主键",phone          string  COMMENT  "该表唯一手机号",one_id         string   COMMENT  "用户唯一标识",nature         string   COMMENT  "属性:个人/组织",one_id_role    string   COMMENT "one-id角色:线索、潜客、车主",user_name_first string COMMENT  "同一手机号取表优先级最高的名字" ,user_name string  COMMENT  "同一证件/手机号名字频次最高的名字" ,sex           string  COMMENT  "性别",user_license  string  COMMENT  "证件号",user_birth    string  COMMENT  "出生日期",user_email    string  COMMENT  "邮件地址",user_qq       string  COMMENT  "QQ",user_wx       string  COMMENT  "微信",user_addr     string  COMMENT  "住址",source_list   string  COMMENT  "数据源列表",source_pd_list  string  COMMENT  "主键列表"
)
COMMENT 'dw层--one_id维度表'
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\036'
STORED AS PARQUET
;
  • 插入数据:基于one_id拉链表,取最新且有效的one_id数据
INSERT OVERWRITE TABLE  dw.dim_one_id
SELECTsk_id          ,phone          ,one_id         ,nature         ,one_id_role    ,user_name_first,user_name ,sex           ,user_license  ,user_birth    ,user_email    ,user_qq       ,user_wx       ,user_addr     ,source_list   ,source_pd_list FROMdw.dim_one_id_all WHERE valid_start_dt <= DATE_FORMAT(CURRENT_DATE,'yyyyMMdd') --只取有效的数据AND valid_end_dt > DATE_FORMAT(CURRENT_DATE,'yyyyMMdd')AND PART_DAY=CAST(CURRENT_DATE AS STRING)
;

用户画像项目两大核心内容之一“one_id”(含SQL实现代码)相关推荐

  1. 大数据用户画像项目实战 ETL数据抽取

    一.课程介绍 一个基于规则的大数据用户画像项目为什么要选这样一个项目:1.典型的批量计算场景2.基于规则.涵盖大数据批量处理的各个典型场景3.可视化.所有控制流程全程可视化4.可扩展.通过集成更多的计 ...

  2. 企业级360°全方位用户画像:项目介绍[二]

    絮叨两句: 博主是一名软件工程系的在校生,利用博客记录自己所学的知识,也希望能帮助到正在学习的同学们 人的一生中会遇到各种各样的困难和折磨,逃避是解决不了问题的,唯有以乐观的精神去迎接生活的挑战 少年 ...

  3. 金融用户画像项目总结

    金融用户画像项目总结 目录 理财用户画像 导读 I.用户画像概念 II.用户画像目的 III.用户画像应用简介 IV.用户画像体系 i.标签概述 ii.标签处理过程 iii.标签体系 V.用户画像构建 ...

  4. 用户画像系列——什么是用户画像?金融行业大数据用户画像实践

    进入移动互联网时代之后,金融业务地域限制被打破.金融企业没有固定业务区域,金融服务面对所有用户是平的. 金融消费者逐渐年轻化,80.90后成为客户主力,他们的消费意识和金融意识正在增强.金融服务正在从 ...

  5. 银行科技 | 招行CIO陈昆德:客户和科技是招行未来的两大核心主题

    银行科技 | 招行CIO陈昆德:客户和科技是招行未来的两大核心主题 金融科技研究 昨天 导读:3月23日,第十届中国(深圳)金融科技发展论坛在深举行.本文为招商银行首席信息官陈昆德的演讲内容. 陈昆德 ...

  6. 什么是用户画像?金融行业大数据用户画像实践

    金融消费者逐渐年轻化,80.90后成为客户主力,他们的消费意识和金融意识正在增强.金融服务正在从以产品为中心,转向以消费者为中心.所有金融行业面对的最大挑战是消费者的消费行为和消费需求的转变,金融企业 ...

  7. 什么是用户画像?金融行业大数据用户画像实践 [

    什么是用户画像?金融行业大数据用户画像实践 [复制链接]       电梯直达 楼主 发表于 昨天 14:36 | 只看该作者 | 只看大图 大数据系列零基础由入门到实战视频 本帖最后由 丫丫 于 2 ...

  8. #########什么是用户画像?金融行业大数据用户画像实践#####好文章

    文|鲍忠铁(微信号:daxiakanke),TalkingData首席金融行业布道师,上海大数据产业联盟金融行业专家,金融行业大数据实践推动者.鲍忠铁同时也是36大数据的专栏作者.进入 鲍忠铁 先生在 ...

  9. 游戏行业两大核心问题:数据挖掘与安全

    4月19-21日的2016云栖大会深圳峰会,有前沿技术深度分享.Workshop阿里云开放实验室.行业企业聚会.Tech Insight实战干货,更有20+场分论坛,120+高浓度实战演讲,100+阿 ...

  10. hadoop的两大核心之一:HDFS总结

    2019独角兽企业重金招聘Python工程师标准>>> hadoop的两大核心之一 海量数据的存储(HDFS) 什么是HDFS? hadoop distributed file sy ...

最新文章

  1. php pdo 中文乱码,php pdo oracle中文乱码的快速解决方法
  2. 编写MapReduce程序,统计每个买家收藏商品数量,实现统计排序功能
  3. 内部结构透视XRAY
  4. 无向图求割(找桥)tarjan
  5. 一个人的旅行(HDU-2066)
  6. mysql统计姓名为小明_Mysql 统计查询相同字段只统计一条
  7. nginx 升级http请求到websocket
  8. java step1:基础知识1
  9. 我的面试标准:能干活、基础要好、有潜力!
  10. 奥迪A8的L3级自动驾驶方案---奥迪A8的zFAS
  11. 第09章节-Python3.5-Django目录详解 8
  12. 全球搜索引擎Top10 可惜很多人只用过第四个
  13. 记一次跟突破360主机卫士上传
  14. (66)-- 多进程爬取腾讯招聘信息
  15. 2022茶艺师(中级)考试模拟100题及模拟考试
  16. C#下usb条码扫描枪的钩子实现的改进
  17. 单细胞分析:聚类流程(六)
  18. 如何关闭苹果手机自动扣费_手机自动扣费?三招教你关闭
  19. 计算机网络之公有IP和私有IP
  20. RRDTool和mrtg的比较

热门文章

  1. 如何制作手机自适应网页
  2. 互联网发展的成功经验,以及面临的挑战
  3. 触控笔和pencil笔一样吗?ipad可用的触控笔
  4. 项目管理PMP:项目绩效考核管理制度(全岗位流程图66页)
  5. autocad2007二维图画法_CAD2007如何画平面图
  6. A1013 Battle Over Cities [图的dfs遍历]
  7. mysql 获取两个月前的日期
  8. 本人面试两个月真实经历:面试了20家大厂之后,发现这样介绍项目经验,显得项目很牛逼!
  9. 基于人脸识别的宿舍门禁系统
  10. java毕业设计——基于java+J2ME的五子棋网络对战游戏设计与实现(毕业论文+程序源码)——五子棋网络对战游戏