소개
데이터 시스템에서는 온라인 시간과 같은 일부 시간 데이터를 계산해야하는 경우가 많습니다. 이러한 데이터 중 일부는 통계에 더 적합하고 일부는 좀 더 문제가됩니다. 예를 들어 로그인 및 로그 아웃 로그를 기반으로하는 사용자 온라인 시간에 대한 통계가 있습니다.
윈도우 함수 lead와 lag를 사용하여 완성 할 수 있는데, 이는 매우 편리합니다 .lead의 기능은 특정 열의 데이터 이후 n 번째 행의 데이터를 현재 행에 스플 라이스하는 것이고, lag는 지정된 열 앞의 n 번째 행의 데이터를 현재 행에 스플 라이스하는 것입니다.
lag(column,n,default)
lead(column,n,default)
매개 변수 열은 스 플라이 싱 할 열을 선택하고 매개 변수 n은 이동할 행 수를 나타내며 일반적으로 한 행이 이동되며 기본값은 기본값입니다. 시차 전에 행이없는 경우 리드 뒤에 행이 없으면 기본값이 사용됩니다.
이 두 가지 기능을 사용하는 핵심 포인트는 파티션 및 정렬입니다.
select gid,
lag(time,1,'0') over (partition by gid order by time) as lag_time,
lead(time,1,'0') over (partition by gid order by time) as lead_time
from table_name;
분할은 그룹화입니다. 분할 기준을 사용하여 쉼표로 여러 열을 그룹화하세요.
정렬은 정렬 기준으로 지정되며 여러 정렬 열은 쉼표로 구분됩니다.
납과 지연의 조합은 우리의 상상을 뛰어 넘을 수 있습니다.
예를 들어, 로그인 및 로그 아웃 로그를 통해 온라인 시간 통계를 수행하려면 요구 사항이 높지 않은 경우 간단합니다. 사용자 ID 그룹화, 시간 오름차순, 리드를 사용하여 현재 로그인 타임 라인에 연결되는 다음 로그 아웃 시간을 쉽게 계산할 수 있습니다.
그러나 크로스 데이 문제와 로그 손실이 있다는 점을 고려할 때 첫 번째가 로그인 로그이고 후자가 종료 로그인 지 확실하지 않습니다.
리드와 지연을 결합하여 불법 데이터를 쉽게 걸러 낼 수 있습니다.
특정 코드
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Encoders;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.api.java.UDF6;
import org.apache.spark.sql.functions;
import org.apache.spark.sql.types.DataTypes;
import org.junit.Before;
import org.junit.Test;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.LinkedList;
import java.util.List;
public class SparkLoginTimeTest implements Serializable {
private SparkSession sparkSession;
@Before
public void setUp() {
sparkSession = SparkSession
.builder()
.appName("test")
.master("local")
.getOrCreate();
}
private static List<Info> getInfos() {
String[] gids = {
"10001","10001","10002","10002","10003","10003","10004","10004","10005","10005"};
LocalDateTime base = LocalDateTime.of(2020, 1, 1,0,0,0);
LinkedList<Info> infos = new LinkedList<>();
for(int i=0;i<50;i++){
Info info = new Info();
info.setGid(gids[i%10]);
info.setResult(i % 2);
info.setDate(base.plus(i * 5, ChronoUnit.MINUTES).toInstant(ZoneOffset.UTC).toEpochMilli());
infos.add(info);
}
return infos;
}
@Test
public void lag(){
List<Info> infos = getInfos();
sparkSession.udf().register("accTimes",accTimes(), DataTypes.LongType);
Dataset<Info> dataset = sparkSession.createDataset(infos, Encoders.bean(Info.class));
dataset.show(100);
dataset.createOrReplaceTempView("temp");
String sql = "select gid,result,date," +
"lead(date,1,-1) over(partition by gid order by date) lead_date," +
"lead(result,1,-1) over(partition by gid order by date) lead_result," +
"lag(result,1,-1) over(partition by gid order by date) lag_result," +
"lag(date,1,-1) over(partition by gid order by date) lag_date" +
" from temp";
Dataset<Row> baseDs = sparkSession.sql(sql);
Dataset<Row> rs = baseDs.withColumn("acc_times",
functions.callUDF("accTimes",
baseDs.col("result"),
baseDs.col("date"),
baseDs.col("lead_result"),
baseDs.col("lead_date"),
baseDs.col("lag_result"),
baseDs.col("lag_date")
)).groupBy("gid")
.agg(functions.sum("acc_times").alias("accTimes")).na().fill(0)
.select("gid", "accTimes");
rs.show(100);
}
private static UDF6<Integer,Long,Integer,Long,Integer,Long,Long> accTimes(){
return new UDF6<Integer, Long, Integer, Long, Integer, Long, Long>() {
long dayMill = 86400000;
@Override
public Long call(Integer result, Long time, Integer headResult, Long headTime, Integer lagResult, Long lagTime) {
if(lagResult == -1){
//第一行
if(result == 1){
//退出,计算退出到这一天的开始时间
return time - (time / dayMill) * dayMill ;
}
}
if(headResult == -1){
//最后一行
if(result == 0){
//进入,计算到这一天结束
return (time / dayMill + 1) * dayMill - time;
}
}
if(result == 0 && headResult == 1){
//当前行是进入,并且下移行是退出
long rs;
rs = headTime - time;
if(rs > 0) {
return rs;
}
}
return 0L;
}
};
}
public static class Info implements Serializable {
/**
* 用户唯一标识
*/
private String gid;
/**
* 登录、退出时间
*/
private Long date;
/**
* 0-登录、1-退出
*/
private Integer result;
public Integer getResult() {
return result;
}
public void setResult(Integer result) {
this.result = result;
}
public String getGid() {
return gid;
}
public void setGid(String gid) {
this.gid = gid;
}
public Long getDate() {
return date;
}
public void setDate(Long date) {
this.date = date;
}
}
}