SparkSql 에서 시간 한도 조작 [창 함수]

38420 단어 SQLSparkHadoop
본 고 는 주로 sql 이 시간 한도 에서 의 조작 을 정리 했다. 이 는 연속 소비, 최 장 출석, 누적 소비 등 문 제 를 포함 하고 다른 업무 장면 에 투사 하면 비슷 한 계산 이 된다.예 를 들 어 게임 분야, 연속 로그 인 시간, 연속 출석 시간, 최대 연속 출석 일수 등 흔히 볼 수 있 는 업무 장면;방법 은 모두 공 통 된 것 입 니 다. 여 기 는 sparksql 로 몇 가지 방법 을 실현 합 니 다. hivesql 의 경우 일부 코드 는 약간 수정 해 야 할 수도 있 습 니 다. 예 를 들 어 having 같은 것 은 외부 에서 where 로 한 번 더 바 꾸 는 등 더 이상 군말 하지 않 습 니 다.
구조 데이터 테스트
잘 자 르 기 위해 저 는 @ 로 맞 췄 습 니 다. 첫 번 째 는 날짜 이 고 두 번 째 는 사용자 이 며 세 번 째 는 소비 여부 이 며 네 번 째 는 소비 금액 입 니 다.
20190531@156@1@20
20190601@156@1@20
20190602@156@1@10
20190603@156@0@0
20190604@156@0@0
20190605@156@1@10
20190606@156@1@10
20190607@156@1@10
20190608@156@0@0
20190609@156@1@20
20190610@156@1@20
20190531@187@0@0
20190601@187@1@10
20190602@187@1@20
20190603@187@1@30
20190604@187@1@40
20190605@187@0@0
20190606@187@1@10
20190607@187@0@0
20190608@187@1@20
20190609@187@1@20
20190610@187@1@10
20190609@173@0@0
20190610@173@1@10

다음 구조
create table tmp_time_exp 
(
    dt string,  
    passenger_phone string,
    is_call string comment '    ',
    cost bigint comment '    '
)
row format DELIMITED fields terminated by '@'
STORED AS INPUTFORMAT
'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
location '/hdfslocation'

일치 하 는 지 확인 해 보 세 요.
tmp_time_exp.dt	tmp_time_exp.passenger_phone	tmp_time_exp.is_call	tmp_time_exp.cost
20190531	156	1	20
20190601	156	1	20
20190602	156	1	10
20190603	156	0	0
20190604	156	0	0
20190605	156	1	10
20190606	156	1	10
20190607	156	1	10
20190608	156	0	0
20190609	156	1	20
20190610	156	1	20
20190531	187	0	0
20190601	187	1	10
20190602	187	1	20
20190603	187	1	30
20190604	187	1	40
20190605	187	0	0
20190606	187	1	10
20190607	187	0	0
20190608	187	1	20
20190609	187	1	20
20190610	187	1	10
20190609	173	0	0
20190610	173	1	10

흔 한 문제
1. n 일 연속 소비 유저 구하 기
예: 3 일 연속 소비 하 는 사용 자 를 찾 아야 한다 면 그의 연속 소비 시작 시간 과 종료 시간
select
    passenger_phone,
    is_call,
    cost,
    unix_timestamp(lag(dt,2,0) over(partition by passenger_phone order by dt),'yyyyMMdd') as start_dt,
    dt as end_dt,
    datediff(from_unixtime(unix_timestamp(dt,'yyyyMMdd'),'yyyy-MM-dd'),from_unixtime(unix_timestamp(lag(dt,2,0) over(partition by passenger_phone order by dt),'yyyyMMdd'),'yyyy-MM-dd')) as last3day  
from
    tmp_time_exp
where
    is_call != 0 
having  
    last3day = 2 

결과 출력
passenger_phone	is_call	cost	start_dt	end_dt	last3day
156	1	10	1559232000	20190602	2
156	1	10	1559664000	20190607	2
187	1	30	1559318400	20190603	2
187	1	40	1559404800	20190604	2
187	1	10	1559923200	20190610	2

1. datediff 을 사용 할 때 전달 하 는 매개 변 수 는 반드시 표준 날짜 형식 이 어야 하기 때문에 전환 해 야 합 니 다.2. lag 또는 lead 를 사용 하면 유사 한 조작 을 할 수 있다. 먼저 사용 자 를 그룹 으로 나 눈 다음 에 소비 시간 을 정렬 한 다음 에 다음 소비 시간 을 이동 시 킨 다음 에 차 이 를 낸다.위 와 같이 연속 날 짜 를 두 위치 로 옮 기 고 2 로 줄 이면 3 일 동안 계속 로그 인해 야 한 다 는 것 을 잘 이해 할 수 있 습 니 다.
2. 사용자 가 연속적으로 소비 하 는 시간 대, 지속 시간 및 해당 시간 대의 소비 금액 총계
예 를 들 어 156 명의 사용자 가 연속 으로 소비 하 는 시간 대 는 5.31 - 6.2 이다.6.5-6.7;6.9 - 6.10, 금액 은 각각 50, 30, 40 이다.
select
    passenger_phone,
    min(dt) as start_day,
    max(dt) as end_day,
    count(1) as last_days,
    sum(cost) as cost_sum
from
(
    select
        *,
        row_number() over(partition by passenger_phone order by dt) as ranker
    from
        tmp_time_exp
    where
        is_call != 0
)a
group by
    passenger_phone,date_sub(from_unixtime(unix_timestamp(dt,'yyyyMMdd'),'yyyy-MM-dd'),ranker)


출력 결과
passenger_phone	start_day	end_day	last_days	cost_sum
156	20190531	20190602	3	50
156	20190605	20190607	3	30
156	20190609	20190610	2	40
173	20190610	20190610	1	10
187	20190601	20190604	4	100
187	20190606	20190606	1	10
187	20190608	20190610	3	50

상기 처리 방식 도 블 로그 의 처 리 를 참고 하여 링크 를 찾 을 수 없습니다. 교묘 하 게 처리 되 었 습 니 다. 날짜 정렬 방식 으로 자신의 날짜 와 차 이 를 나 누 어 조 를 나 누 었 습 니 다. 차이 가 모두 같다 면 연속 적 인 날짜 임 을 설명 하고 이 차이 가 같은 개 수 는 연속 적 인 일수 입 니 다.
3. 6.10, 연속 소비 일 수 를 포함 하여 절대 계산 하지 않 음 (소비 출석 일수)
예: 156 의 사용자.6.10 소 비 했 고 앞으로 밀 었 고 6.9 도 소 비 했 지만 6.8 은 소 비 를 하지 않 았 기 때문에 지금까지 계속 소비 한 시간 은 이틀 이다.이것 은 출석 과 유사 한 기능 에 많이 사용 되 는데, 만약 오늘 서명 을 끊 으 면, 누 적 된 출석 일 수 를 다시 계산 하기 시작한다
방법
select
    *
from
(
    select
        passenger_phone,
        min(dt) as start_time,
        max(dt) as end_time,
        count(1) as day_cnt
    from
    (
        select
            *,
            row_number() over(partition by passenger_phone order by dt) as ranker
        from
            tmp_time_exp
        where
            is_call = 1
    )aa
    group by
        passenger_phone,date_sub(from_unixtime(unix_timestamp(dt,'yyyyMMdd'),'yyyy-MM-dd'),ranker)
)bb
where
    end_time = '20190610'

문제 2 에 서 는 종료 일 자 를 오늘 (6.10) 로 직접 한정 하면 나 올 수 있다.
방법
with end_dt as
(
    select
        passenger_phone,
        max(dt) as end_dt
    from
        tmp_time_exp
    where
        dt between '20190531' and '20190610'
        and is_call = 0  --             
    group by
        passenger_phone
)
select
    aa.dt,
    aa.passenger_phone,
    datediff(from_unixtime(unix_timestamp(aa.dt,'yyyyMMdd'),'yyyy-MM-dd'),from_unixtime(unix_timestamp(bb.end_dt,'yyyyMMdd'),'yyyy-MM-dd')) as day_cnt
from
(
    select
        dt,
        passenger_phone
    from
        tmp_time_exp
    where
        dt = '20190610'  --       
)aa
join
    end_dt as bb
on
    aa.passenger_phone = bb.passenger_phone

각 사용자 의 가장 큰 소비 하지 않 는 날 짜 를 먼저 가 져 옵 니 다. 6.10 부터 앞으로 밀어 서 첫 번 째 소비 하지 않 는 날 짜 를 만 날 때 까지 멈 출 수 있 기 때 문 입 니 다. 그러면 6.10 까지 소비 가 끊 이지 않 는 시간 을 얻 을 수 있 습 니 다.
결국
passenger_phone start_time      end_time        day_cnt
156	20190609	20190610	2
173	20190610	20190610	1
187	20190608	20190610	3

4. 최 장 연속 소비 일수
예 를 들 어 156 명의 사용자 가 연속 으로 소비 하 는 시간 대 는 5.31 - 6.2 이다.6.5-6.7;6.9 - 6.10, 시간 은 각각 3, 3, 2 이다.금액 은 각각 50, 30, 40 이 문제 2 의 파생 이다.
방법
select
    passenger_phone,
    start_day,
    end_day,
    last_days,
    rank() over(partition by passenger_phone order by last_days desc) as appose_rank, --           
    row_number() over(partition by passenger_phone order by last_days desc) as last_ranker  --      
from
(
    select
        passenger_phone,
        min(dt) as start_day,
        max(dt) as end_day,
        count(1) as last_days
    from
    (
        select
            *,
            row_number() over(partition by passenger_phone order by dt) as ranker
        from
            tmp_time_exp
        where
            is_call != 0
    )a
    group by
        passenger_phone,date_sub(from_unixtime(unix_timestamp(dt,'yyyyMMdd'),'yyyy-MM-dd'),ranker)
)aa
having
    -- last_ranker = 1
    appose_rank = 1



문제 2 의 해법 을 사용 하여 그 결 과 를 직접 다음 층 으로 계산 하면 된다. 즉, 가장 긴 소비 시간 을 직접 꺼 내 는 것 이다.
방법
select
    cc.*,
    length(dd) as max_length,
    row_number() over(partition by passenger_phone order by length(dd) desc) as ranker
from
(
    select
        passenger_phone,
        concat_ws('',collect(is_call)) as call_list
    from
    (
        select
            dt,
            passenger_phone,
            is_call
        from
            tmp_time_exp
        order by
            passenger_phone desc, dt desc
    )aa
    group by
        passenger_phone
)cc
lateral view explode(split(call_list,'0')) asTable as dd
having
    ranker = 1

비교적 교묘 한 방식 은 한 번 의 면접 과정 에서 면접 관 이 저 에 게 알려 준 해법 입 니 다. 똑 같이 이 문 제 를 해결 할 수 있 지만 날 짜 를 더 해 야 한다 면 조금 더 복잡 할 것 입 니 다. 전기 concat 일부 날짜 의 데 이 터 를 필요 로 한 다음 에 후기 에 풀 어야 합 니 다.
결 과 는 모두 일치한다.
passenger_phone start_day       end_day last_days       appose_rank     last_ranker
156	20190531	20190602	3	1	1
156	20190605	20190607	3	1	2
173	20190610	20190610	1	1	1
187	20190601	20190604	4	1	1

5. 소비 피크 날짜
예: 당일 소비자 수가 가장 많은 날짜
방법
select
    dt,
    passenger_phone,
    is_call_cnt,
    rank() over(order by is_call_cnt desc) as call_ord_ranker
from
(
    select
        *,
        sum(is_call) over(partition by dt) as is_call_cnt
    from
        tmp_time_exp
)aa
having
    call_ord_ranker = 1

방법
select
    *,
    first_value(dt) over(order by is_call_cnt desc) as max_dt
from
(
    select
        *,
        sum(is_call) over(partition by dt) as is_call_cnt
    from
        tmp_time_exp
)aa
having
    max_dt = dt

결실
dt	passenger_phone	is_call	cost	is_call_cnt	max_dt
20190610	187	1	10	3.0	20190610
20190610	173	1	10	3.0	20190610
20190610	156	1	20	3.0	20190610

6. 소비 누적 x 원 도착 날짜
예 를 들 어 156 명의 사용자 가 소비 가 50 위안 에 처음 도착 한 날 짜 는 6.2 호 이 고 100 위안 에 처음 도착 한 날 짜 는 6.9 호 이다.
select
    passenger_phone,
    max(min_gt50_dt) as min_gt50_dt,
    max(min_gt100_dt) as min_gt100_dt
from
(
    select
        *,
        min(dt) over(partition by passenger_phone,if(cost_until_today >= 50,1,0)) as min_gt50_dt,
        min(dt) over(partition by passenger_phone,if(cost_until_today >= 100,1,0)) as min_gt100_dt
    from
    (
        select
            dt,
            passenger_phone,
            cost,
            sum(cost) over(partition by passenger_phone order by dt) as cost_until_today
        from
            tmp_time_exp
    )aa
)bb
group by 
    passenger_phone

결실
passenger_phone	min_gt50_dt	min_gt100_dt
156	20190602	20190609
173	20190609	20190609
187	20190603	20190604

그 중에서 가장 핵심 적 인 것 은 sum() over(partition by ... order by dt) 문 구 를 사용 하여 dt 까지 의 조별 총 화 를 나타 내 는 것 이다. 즉, 누적 마감 표현 이다. 일부 분 구 경계 에 대한 한정 적 인 고려 는 다음 과 같은 7 번 째 문 제 를 참고 할 수 있다.
7. 특정 시간 대 내 소비의 최대 치 찾기
예: 예 를 들 어 하나의 요 구 는 6.5 호 전후 3 일 중 소비 금액 이 가장 큰 날 을 찾 는 것 이다. 이런 구간 의 성질 이 가장 큰 값 을 찾 으 면 대략 창 함수 로 이 루어 진다. max() over(partition by ... order by dt rows between 3 preceding and 3 following) 와 비슷 하 게 dt 이날 까지 3 일 앞으로 미 루 고 3 일 뒤로 미 루 는 것 을 나타 낸다. 즉, 총 7 일 (자신 포함) 내 에서 이 구간 의 최대 치 를 찾 는 것 이다.같은 이치 로 창 집합 을 sum 으로 바 꾸 면 이 구간 내의 총화 가 된다.
select
    dt,
    passenger_phone,
    cost,
    max(cost) over(partition by passenger_phone order by dt rows between unbounded preceding and current row) as until_cur_max,
    max(cost) over(partition by passenger_phone order by dt) as until_cur_max2,  --     
    max(cost) over(partition by passenger_phone order by dt rows between 3 preceding and 3 following) as before3later3_max,
    sum(cost) over(partition by passenger_phone order by dt rows between 3 preceding and 3 following) as before3later3_sum
from
    tmp_time_exp

결실
dt	passenger_phone	cost	until_cur_max	until_cur_max2	before3later3_max	before3later3_sum
20190531	156	20	20	20	20	50
20190601	156	20	20	20	20	50
20190602	156	10	20	20	20	60
20190603	156	0	20	20	20	70
20190604	156	0	20	20	20	60
20190605	156	10	20	20	10	40
20190606	156	10	20	20	20	50
20190607	156	10	20	20	20	70
20190608	156	0	20	20	20	70
20190609	156	20	20	20	20	60
20190610	156	20	20	20	20	50
20190609	173	0	0	0	10	10
20190610	173	10	10	10	10	10
20190531	187	0	0	0	30	60
20190601	187	10	10	10	40	100
20190602	187	20	20	20	40	100
20190603	187	30	30	30	40	110
20190604	187	40	40	40	40	110
20190605	187	0	40	40	40	120
20190606	187	10	40	40	40	120
20190607	187	0	40	40	40	100
20190608	187	20	40	40	20	60
20190609	187	20	40	40	20	60
20190610	187	10	40	40	20	50

좋은 웹페이지 즐겨찾기