El uso flexible de las funciones de la ventana Spark adelanta y retrasa las estadísticas de tiempo en línea

Introducción

En el sistema de datos, a menudo es necesario contar algunos datos de duración, como el tiempo en línea. Algunos de estos datos son mejores para las estadísticas, mientras que otros son un poco más problemáticos. Por ejemplo, las estadísticas sobre el tiempo en línea del usuario basadas en los registros de inicio y cierre de sesión.

Podemos usar las funciones de ventana adelantar y retrasar para completarlo, lo cual es muy conveniente. La función de adelantar es empalmar los datos de la enésima fila después de una cierta columna de datos a la fila actual, y la demora es empalmar los datos de la enésima fila en frente de la columna especificada a la fila actual.

lag(column,n,default)
lead(column,n,default)

La columna de parámetro es para seleccionar la columna que se empalmará, el parámetro n indica cuántas filas se moverán, generalmente se mueve una fila, el valor predeterminado es el valor predeterminado, si no hay una fila antes de la demora y no hay una fila después de la salida, se usa el valor predeterminado.

Los puntos clave del uso de estas 2 funciones son: particionar y ordenar

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;

adelantar y retrasar

La partición es agrupación. Utilice la partición por para agrupar varias columnas con comas.

La clasificación se especifica por orden y varias columnas de clasificación están separadas por comas

La combinación de adelanto y retraso puede funcionar más allá de nuestra imaginación.

Por ejemplo, para realizar estadísticas de tiempo en línea a través de los registros de inicio de sesión y cierre de sesión, si los requisitos no son altos, sencillo: agrupación de ID de usuario, tiempo ascendente y luego usar plomo para hacer que el próximo tiempo de cierre de sesión empalmado con la línea de tiempo de inicio de sesión actual se pueda calcular fácilmente.

Pero teniendo en cuenta que hay problemas entre días y pérdida de registros, no estoy seguro de que el primero sea el registro de inicio de sesión y el segundo sea el registro de salida.

Al combinar el adelanto y el retraso, podemos filtrar fácilmente los datos ilegales.

Código específico

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;
        }
    }
}

resultado

Otros ejemplos

Supongo que te gusta

Origin blog.csdn.net/trayvontang/article/details/106659963
Recomendado
Clasificación