flink sql 自定义函数UDF

flink sql 自定义函数UDF

flink版本:1.13.1
scala版本:2.12

1 maven 依赖引用

    <properties>
        <flink.version>1.13.1</flink.version>
        <scala.version>2.12</scala.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-planner-blink_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-api-java-bridge_${scala.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <!-- 实现自定义的数据格式来做序列化,可以引入下面的依赖 -->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-common</artifactId>
            <version>${flink.version}</version>
        </dependency> 
    </dependencies>

2. 自定义UDF函数

2.1 标量函数 (Scalar Function)

  • 自定义标量函数可以把 0 个、 1 个或多个标量值转换成一个标量值,它对应的输入是一
    行数据中的字段,输出则是唯一的值。所以从输入和输出表中行数据的对应关系看,标量函数
    是“一对一”的转换。
  • 想要实现自定义的标量函数,我们需要自定义一个类来继承抽象类 ScalarFunction,并实
    现叫作 eval() 的求值方法。标量函数的行为就取决于求值方法的定义,它必须是公有的(public),
    而且名字必须是 eval。求值方法 eval 可以重载多次,任何数据类型都可作为求值方法的参数
    和返回值类型。
  • 这里需要特别说明的是,ScalarFunction 抽象类中并没有定义 eval()方法,所以我们不能直
    接在代码中重写(override);但 Table API 的框架底层又要求了求值方法必须名字为 eval()。这
    是 Table API 和 SQL 目前还显得不够完善的地方,未来的版本应该会有所改进。
2.1.1 example
import com.flink.dto.Event;
import com.flink.source.ClickSource;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.functions.ScalarFunction;

import java.time.Duration;

import static org.apache.flink.table.api.Expressions.$;
import static org.apache.flink.table.api.Expressions.call;

public class SqlUdfTest {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        SingleOutputStreamOperator<Event> dataStream = env.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event element, long recordTimestamp) {
                                return element.getTimestamp();
                            }
                        }));

        tableEnv.createTemporaryView("table_click", dataStream);
        Table tableClick = tableEnv.fromDataStream(dataStream);

        // 1. 注册标量函数
        tableEnv.createTemporaryFunction("HashFunction", HashFunction.class);

        // 2. sql执行标量函数(推荐使用)
        Table sqlResult = tableEnv.sqlQuery("select user, HashFunction(url) from table_click");

        // 3. table 执行标量函数(不推荐)
        Table tableResult = tableClick.select($("user"), call("HashFunction", $("user")));

        // 4. 打印输出
        //tableEnv.toDataStream(tableResult).print("tableResult");
        tableEnv.toDataStream(sqlResult).print("sqlResult");

        env.execute();
    }


    /**
     * 自定义UDF标量函数
     */
    public static class HashFunction extends ScalarFunction {

        // 转换得方法名是eval,必须自己手动写
        public String eval(String val) {
            return val + "_" + val.hashCode();
        }

    }

}

输出结果

 sqlResult> +I[user_3, /product?id=3_79635664]
sqlResult> +I[user_3, /product?id=3_79635664]
sqlResult> +I[user_4, /product?id=4_79635665]
sqlResult> +I[user_1, /product?id=1_79635662]
sqlResult> +I[user_1, /product?id=1_79635662]

2.2 表函数(Table Function)

  • 跟标量函数一样,表函数的输入参数也可以是 0 个、1 个或多个标量值;不同的是,它可以返回任意多行数据。“多行数据”事实上就构成了一个表,所以“表函数”可以认为就是返回一个表的函数,这是一个“一对多”的转换关系。窗口 TVF,本质上就是表函数。
  • 类似地,要实现自定义的表函数,需要自定义类来继承抽象类 TableFunction,内部必须要实现的也是一个名为 eval 的求值方法。与标量函数不同的是,TableFunction 类本身是有一个泛型参数T 的,这就是表函数返回数据的类型;而 eval()方法没有返回类型,内部也没有 return语句,是通过调用 collect()方法来发送想要输出的行数据的。也是通过 out.collect()来向下游发送数据的。
  • 我们使用表函数,可以对一行数据得到一个表,这和 Hive 中的 UDTF 非常相似。那对于原先输入的整张表来说,又该得到什么呢?一个简单的想法是,就让输入表中的每一行,与它转换得到的表进行联结(join),然后再拼成一个完整的大表,这就相当于对原来的表进行了扩展。在 Hive 的 SQL 语法中,提供了“侧向视图”(lateral view,也叫横向视图)的功能,可以将表中的一行数据拆分成多行;Flink SQL 也有类似的功能,是用 LATERAL TABLE 语法来实现的。
  • 在 SQL 中调用表函数,需要使用 LATERAL TABLE()来生成扩展的“侧向表”,然后与原始表进行联结(Join)。这里的 Join 操作可以是直接做交叉联结(cross join),在 FROM 后用逗号分隔两个表就可以;也可以是以 ON TRUE 为条件的左联结(LEFT JOIN)。
2.2.1 example
import com.flink.dto.Event;
import com.flink.source.ClickSource;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.functions.TableFunction;

import java.time.Duration;

public class Udf_tableFunction {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        SingleOutputStreamOperator<Event> dataStream = env.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event element, long recordTimestamp) {
                                return element.getTimestamp();
                            }
                        }));
        tableEnv.createTemporaryView("table_click", dataStream);
        Table tableClick = tableEnv.fromDataStream(dataStream);

        // 1. 注册函数
        tableEnv.createTemporaryFunction("Split", SplitFunction.class);

        // 2.查询
        Table sqlResult = tableEnv.sqlQuery("select user, url, word, length " +
                "from table_click, LATERAL TABLE(Split(url)) AS T(word, length)");

        dataStream.print("source");
        tableEnv.toDataStream(sqlResult).print("sqlResult");

        env.execute();
    }


    /**
     * 创建table Function
     */
    public static class SplitFunction extends TableFunction<Tuple2<String, Integer>> {

        // 转换得方法名是eval,必须自己手动写,通过调用super.collect(T)输出
        public void eval(String val) {
            String[] splits = val.split("\\?");
            for(String filed: splits) {
                super.collect(Tuple2.of(filed, filed.length()));
            }
        }
    }

}

输出

source> Event{user='user_1', url='/product?id=1', timestamp=2022-4-15 16:09:14}
sqlResult> +I[user_1, /product?id=1, /product, 8]
sqlResult> +I[user_1, /product?id=1, id=1, 4]
source> Event{user='user_0', url='/product?id=0', timestamp=2022-4-15 16:09:15}
sqlResult> +I[user_0, /product?id=0, /product, 8]
sqlResult> +I[user_0, /product?id=0, id=0, 4]
source> Event{user='user_0', url='/product?id=0', timestamp=2022-4-15 16:09:16}
sqlResult> +I[user_0, /product?id=0, /product, 8]
sqlResult> +I[user_0, /product?id=0, id=0, 4]

2.3 聚合函数(Aggregate Function)

2.3.1 example
import com.flink.dto.Event;
import com.flink.source.ClickSource;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.Table;
import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
import org.apache.flink.table.functions.AggregateFunction;

import java.time.Duration;

public class UDF_AggFunction {

    public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env);

        SingleOutputStreamOperator<Event> dataStream = env.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event element, long recordTimestamp) {
                                return element.getTimestamp();
                            }
                        }));
        tableEnv.createTemporaryView("table_click", dataStream);
        Table tableClick = tableEnv.fromDataStream(dataStream);

        // 1. 注册函数
        tableEnv.createTemporaryFunction("WeightAgg", WeightAggFunction.class);

        // 2.查询
        Table sqlResult = tableEnv.sqlQuery("select user, WeightAgg(user,2) " +
                "from table_click group by user");

        dataStream.print("source");
        tableEnv.toChangelogStream(sqlResult).print("sqlResult");

        env.execute();

    }


    public static class WeightAggFunction extends AggregateFunction<String, WeightedAvgAccumulator> {

        @Override
        public String getValue(WeightedAvgAccumulator acc) {
            return String.valueOf(acc.sum);
        }

        @Override
        public WeightedAvgAccumulator createAccumulator() {
            return new WeightedAvgAccumulator();
        }

        // 累加计算方法,每来一行数据都会调用
        // 累加器WeightAgg(user,1)与参数2,3意义对应
        public void accumulate(WeightedAvgAccumulator acc, String iValue, Integer
                iWeight) {
            String[] s = iValue.split("_");
            acc.sum +=  Integer.valueOf(s[1]) * iWeight;
        }

    }

    // 累加器类型定义
    public static class WeightedAvgAccumulator {
        public long sum = 0; // 加权和
    }

}

2.4 表聚合函数(Table Aggregate Functions)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值