Flink SQL的使用

文章目录

  • 一、Flink SQL简介
    • 1.1 Flink SQL 特点
    • 1.2 工作原理
    • 1.3 应用场景
  • 二、Flink SQL语法
    • 2.1 CREATE create语句
    • 2.2 SELECT select语句
      • 2.2.1 简单查询
      • 2.2.2 过滤操作
      • 2.2.3 聚合操作
      • 2.2.4 窗口操作(针对流数据)
    • 2.3 INSERT 语句
    • 2.4 输出到控制台(用于调试)
  • 三、基础流程
    • 3.1、所有Flink的操作都是基于StreamExecutionEnvironment;
    • 3.2、TableEnvironment 是 Flink 中用于统一处理表和 SQL 操作的核心接口;
    • 3.3、注册数据源
    • 3.4 执行SQL
    • 3.5 将结果输出
    • 3.6 执行作业
  • 四、对于JDBC的操作案例
    • 4.1 引入依赖
    • 4.2 创建sql文本
    • 4.3 读取sql文本中的SQL
  • 五、TableEnvironment类的方法使用
    • 5.1 from()指定操作的表对象--执行一个注册过的表的扫描
    • 5.2 FromValues ()
    • 5.3 Select ()
    • 5.4 as()
    • 5.5 filter()/where()用法一样
      • 5.5.1 简单的相等条件过滤:
      • 5.5.2 基于字符串列的模糊匹配过滤(使用LIKE操作符):
      • 5.5.3多条件组合过滤:
    • 5.6 AddColumns
  • 窗口操作

一、Flink SQL简介

Flink SQL是apache Flink中的一种声明式的SQL API,它允许用户已SQL语句的形式对有界(批处理)和无界(流处理)数据进行查询和分析。
中文官网:【https://nightlies.apache.org/flink/flink-docs-release-1.18/zh/docs/dev/】

1.1 Flink SQL 特点

  1. 统一的批流处理:
    Flink SQL对批处理和流处理提供了统一的编程模型。 使用相同的SQL雨具,可以处理静态数据集(批处理)以及实时流入的动态数据(流处理)

  2. 声明式编程:
    用户只需要通过SQL语句描述所需的数据转换和查询逻辑,而无需关心底层的世贤细节。
    比如,要从一个包含用户行为数据的表中筛选出特定时间段内活跃用户的行为记录,只需要用SQL的WHERE子句指定时间范围和活跃用户的条件即可,无需手动编写复杂的代码逻辑来实现数据筛选和处理。

  3. 与标准SQL高度兼容:
    Flink SQL支持大部分标准的SQL语法。
    比如,常见的SELECT、JOIN、GROUP BY等操作在Flink SQL中都有相应的视线,并且语法类似。

  4. 可扩展性:
    Flink SQL可以与FLINK的其他功能模块(如用户自定义函数、连接器等)结合使用,满足复杂的业务需求。
    比如,如果要对数据进行特定的业务逻辑处理,可以自定义一个函数并在FlInk SQL 中调用,同时,可以通过连接器链接到各种数据源(包kafka、hive、JDBC数据源等)进行数据的输入和输出

1.2 工作原理

  1. 解析和验证:
    当用户提交一个Flink SQL查询时,首先会被解析器 解析成 抽象语法树(AST)。然后,验证器 会对AST进行验证,检查语法是否正确以及所引用的表和列是否存在等。

  2. 优化:
    进过验证的查询 会被 优化器 进行优化。优化器会根据查询的特点和数据的分布情况,生成最优的执行计划。

  3. 执行:
    优化后的执行计划会被 转换成 Flink的数据流图(Dataflow Graph),并由Flink的 执行引擎 执行。
    在流处理模式下,数据会持续流入,Flink SQL 会实时处理数据并输出结果;
    在批处理模式下,数据会一次性加载并进行处理;

1.3 应用场景

  1. 实时数据分析:
    在金融领域,实时监控股票交易数据,计算股票价格的移动平均线、涨跌幅等指标;
    在电商领域,实时分析用户的行为数据,如用户点击、购买行为等,一遍及时调整营销策略;

  2. 数据集成
    可以使用Flink SQL从多个数据源(如数据库、消息队列、文件系统等)读取数据,进行清洗、转换 和整合,然后将结果写入到目标数据源中。

  3. ETL(Extract,Transformation,Load)任务
    执行数据抽取、转换和加载任务。 从原始数据源中抽取数据,进行各种数据转换操作(如数据清洗、格式转换、字段计算等),然后将处理后的数据加载到目标存储系统中。
    例如,从日志文件中抽取数据,去除无效记录,转换数据格式,然后加载到数据库中供后续分析使用。

二、Flink SQL语法

2.1 CREATE create语句

根据指定的表名创建一个表,如果同名表存在,则无法创建。

  1. 定义表
    Flink SQL使用CREATE TABLE语句来定义数据源。
CREATE TABLE kafka_table(
	id BIGINT,
	name STRING,
	ts TIMESTAMP(3)WITH (
	'connector' = 'kafka',
	'topic' = 'test_topic',
	'properties.bootstrp.servers' = 'localhost:9092',
	'format' = 'json',
	'scan.startup.mode' ='earliest - offset'
);

定义一个名为mytable的表,包含id(长整型)、name(字符串)、ts(时间戳,进度为毫秒)的三个列。
WITH子句指定了 kafka 连接器的相关属性,包括
connector(连接器类型为kafka)、
topic(读取的kafka主题)、
properties.bootstrp.servers(卡发卡服务器的地址)、
format(数据格式为json)、
scan.startup.mode(从最早的偏移量开始读取)

   CREATE TABLE my_mysql_table (
       id INT,
       name VARCHAR(50),
       age INT
   ) WITH (
       'connector' = 'jdbc',
       'url' = 'jdbc:mysql://localhost:3306/mydatabase',
       'username' = 'your_username',
       'password' = 'your_password',
       'table - name' = 'mytable_in_mysql'
   );

在上述示例中:
connector = ‘jdbc’:指定使用 JDBC Connector 来连接外部数据库。
url:指定了 MySQL 数据库的连接地址。localhost表示本地主机,3306是 MySQL 的默认端口,mydatabase是你要连接的数据库名称。
username和password:是用于登录 MySQL 数据库的用户名和密码。
table - name:是 MySQL 数据库中你要与之交互的表的名称。

  1. 自定义数据源(通过实现接口)
    除了连接常见的数据源,我们还可以自定义数据源。这需要实现TableSource接口。例如,定义一个简单的自定义数据源来生成数字序列
   import org.apache.flink.streaming.api.datastream.DataStream;
   import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
   import org.apache.flink.table.api.DataTypes;
   import org.apache.flink.table.api.TableSchema;
   import org.apache.flink.table.sources.StreamTableSource;
   import java.util.Arrays;
   import java.util.List;
   public class CustomNumberSource implements StreamTableSource<Integer> {
       private final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
       @Override
       public DataStream<Integer> getDataStream(StreamExecutionEnvironment execEnv) {
           return execEnv.fromCollection(numbers);
       }
       @Override
       public TableSchema getTableSchema() {
           return TableSchema.builder()
                  .field("number", DataTypes.INT())
                  .build();
       }
   }

然后再Flink SQL环境中注册这个定义数据源:

   StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
   EnvironmentSettings settings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
   StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env, settings);
   tableEnv.registerTableSource("custom_number_table", new CustomNumberSource());

2.2 SELECT select语句

2.2.1 简单查询

从前面定义的kafka_table中查询id和name 列:
SELECT id,name From kafka_table;

这里返回一个包含id和name的两列数据的结果集

2.2.2 过滤操作

使用where 子句进行过滤,例如 从kafka_name中查询id 大于10且name以“A”开头 的记录
select * from kafka_name where id > 10 and name like ‘A%’;

2.2.3 聚合操作

sum、count、avg、group by等,
比如计算kafka_table中id的总和:select sum(id) from kafka_name;
分组聚合,按name分组计算id的平均值:select name, avg(id) from kafka_name group name;

2.2.4 窗口操作(针对流数据)

对于流数据,窗口操作非常重要。
例如,定义一个滚动窗口来计算每10秒内id的总和:

   SELECT TUMBLE_START(ts, INTERVAL '10' SECOND) as window_start, SUM(id)
   FROM kafka_table
   GROUP BY TUMBLE(ts, INTERVAL '10' SECOND);

这里使用TUMBLE函数来定义滚动窗口,TUMBLE_START函数用于获取窗口的开始时间,对id进行求和操作实在每个10秒的窗口内惊醒的。

2.3 INSERT 语句

插入到另一个表(可以是外部存储)

使用insert into 语句将查询到的结果插入到另一个表中。

例如,将前面查询到的id>10的记录插入到一个名为filtered_table表中

   INSERT INTO filtered_table
       SELECT * FROM kafka_table WHERE id > 10;

2.4 输出到控制台(用于调试)

可以将结果输出到控制台进行调试。
在java代码中,执行查询并将结果转换为DataStream后打印输出:

   Table resultTable = tableEnv.sqlQuery("SELECT * FROM kafka_table WHERE id > 10");
   DataStream<Row> resultStream = tableEnv.toDataStream(resultTable);
   resultStream.print();

三、基础流程

1. 所有Flink的操作都是基于StreamExecutionEnvironment;
2. TableEnvironment 是 Flink 中用于统一处理表和 SQL 操作的核心接口;
3. 注册数据源
4. 执行SQL
5. 将结果输出
6. 执行作业

3.1、所有Flink的操作都是基于StreamExecutionEnvironment;

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

// 创建流执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

3.2、TableEnvironment 是 Flink 中用于统一处理表和 SQL 操作的核心接口;

#创建TableEnviroment 的方法
# 方式一:使用默认的配置创建流处理的TableEnvironment  --  inStreamingMode
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
EnvironmentSettings settings = EnvironmentSettings.newInstance().inStreamingMode().build();
TableEnvironment tableEnv = TableEnvironment.create(settings);

# 方式二:创建用于批处理的 TableEnvironment  --  inBatchMode
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;

ExecutionEnvironment batchEnv = ExecutionEnvironment.getExecutionEnvironment();
EnvironmentSettings settings = EnvironmentSettings.newInstance().inBatchMode().build();
TableEnvironment tableEnv = TableEnvironment.create(settings);

3.3、注册数据源

# 方式一:从文件系统注册CSV数据源
// 定义 CSV 文件的路径和格式
String filePath = "/path/to/csv/file.csv";
String[] fieldNames = {"col1", "col2", "col3"};
TypeInformation[] fieldTypes = {Types.STRING, Types.INT, Types.DOUBLE};

// 注册表
tableEnv.connect(new FileSystem().path(filePath))
       .withFormat(new Csv()
               .fieldDelimiter(',')
               .fieldNames(fieldNames)
               .deriveSchema())
       .withSchema(new Schema()
               .field("col1", DataTypes.STRING())
               .field("col2", DataTypes.INT())
               .field("col3", DataTypes.DOUBLE()))
       .createTemporaryTable("myCsvTable");
# 方式二:从 Kafka 注册数据源(示例)
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
properties.setProperty("group.id", "testGroup");

tableEnv.connect(new Kafka()
               .version("0.11")
               .topic("myTopic")
               .startFromEarliest()
               .properties(properties))
       .withFormat(new Json())
       .withSchema(new Schema()
                // 定义 Kafka 消息中的数据结构对应的表结构
               .field("key", DataTypes.STRING())
               .field("value", DataTypes.STRING()))
       .createTemporaryTable("myKafkaTable");


# 方式三:注册自定义函数(如果需要)--注册一个简单的自定义标量函数
// 自定义函数类
public class MyCustomFunction extends ScalarFunction {
    public int addOne(int i) {
        return i + 1;
    }
}

// 注册函数
tableEnv.createTemporarySystemFunction("myAddOneFunction", new MyCustomFunction());

3.4 执行SQL

// 执行一个简单的 SQL 查询
Table resultTable = tableEnv.sqlQuery("SELECT col1, col2 + 1 AS newCol2 FROM myCsvTable");

// 或者使用 Table API 操作创建和转换表
// 使用 Table API
Table myTable = tableEnv.from("myCsvTable");
Table filteredTable = myTable.filter($("col2").isGreaterThan(10));

常用的方法

// 创建表环境
TableEnvironment tableEnv = ...;
// 创建输入表,连接外部系统读取数据
tableEnv.executeSql("CREATE TEMPORARY TABLE inputTable ... WITH ( 'connector' 
= ... )");
// 注册一个表,连接到外部系统,用于输出
tableEnv.executeSql("CREATE TEMPORARY TABLE outputTable ... WITH ( 'connector' 
= ... )");
// 执行 SQL 对表进行查询转换,得到一个新的表
Table table1 = tableEnv.sqlQuery("SELECT ... FROM inputTable... ");
// 使用 Table API 对表进行查询转换,得到一个新的表
Table table2 = tableEnv.from("inputTable").select(...);
// 将得到的结果写入输出表
TableResult tableResult = table1.executeInsert("outputTable");

3.5 将结果输出

# 方法一:输出到标准输出(示例)
tableEnv.toAppendStream(resultTable, Row.class).print();

# 方法二:输出到文件系统(示例)
tableEnv.connect(new FileSystem().path("/path/to/output/file.csv"))
       .withFormat(new Csv().fieldDelimiter(','))
       .withSchema(new Schema()
               .field("col1", DataTypes.STRING())
               .field("file:///path/to/output/file.csv", DataTypes.INT()))
       .createTemporaryTable("outputTable");
tableEnv.insertInto("outputTable", resultTable);

3.6 执行作业

env.execute("My Table API and SQL Job");

四、对于JDBC的操作案例

4.1 引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>FlinkDemo-1-17</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>1.16.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java</artifactId>
            <version>1.16.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients</artifactId>
            <version>1.16.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-api-java-bridge</artifactId>
            <version>1.16.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-planner_2.12</artifactId>
            <version>1.16.3</version>
        </dependency>
        <!-- 从jdbc中读取数据的依赖-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-jdbc</artifactId>
            <version>1.16.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.30</version>
        </dependency>

        <!-- 从文件中读取数据的依赖-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-files</artifactId>
            <version>1.16.3</version>
        </dependency>
        <!-- 从kafka中读取数据的依赖-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka</artifactId>
            <version>1.16.3</version>
        </dependency>

        <!-- 从数据生成器中地区数据的依赖-->
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-datagen</artifactId>
            <version>1.17.0</version>
        </dependency>
    </dependencies>



</project>

4.2 创建sql文本

   CREATE TABLE myflink (
       id BIGINT,
       apply_name VARCHAR(50)
   ) WITH (
   'connector' = 'jdbc',
   'url' = 'jdbc:mysql://localhost:3306/eam_pc',
   'username' = 'root',
   'password' = 'root',
   'table-name' = 'pc_apply'
   );

4.3 读取sql文本中的SQL

package com.flink17.demo;


import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.table.api.EnvironmentSettings;
import org.apache.flink.table.api.TableEnvironment;
import org.apache.flink.table.api.TableResult;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;


/**
 * @author lc
 * @version 1.0
 * @date 2024/10/25 0025 14:52
 */
public class FlinkSQLMain {
    public static void main(String[] args) throws Exception {
         // 创建流执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        EnvironmentSettings settings = EnvironmentSettings.newInstance().inStreamingMode().build();
        TableEnvironment tableEnv  = TableEnvironment.create(settings);

        try {
            // 1.连接jdbc数据源
            String sql = readSqlFromFile("src\\main\\resources\\flink.sql");//数据源信息
            tableEnv.executeSql(sql);
            // 2.使用 tableEnv 执行读取到的 SQL 语句
            TableResult result = tableEnv.executeSql("SELECT id, apply_name FROM myflink");
            result.print();
        } catch (IOException e) {
            e.printStackTrace();
        }

        //查看转换的sql文件变化
        DataStreamSource<String> sqltxt = env.readTextFile("src\\main\\resources\\flink.sql");
        sqltxt.print();
        try {
            env.execute("Flink SQL Read File Example");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String readSqlFromFile(String filePath) throws IOException {
        StringBuilder content = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine())!= null) {
                content.append(line).append("\n");
            }
        }
        return content.toString();
    }

}


五、TableEnvironment类的方法使用

Table API支持如下操作。请注意不是所有的操作都可以既支持流也支持批;这些操作都具有相应的标记。
官网:https://nightlies.apache.org/flink/flink-docs-release-1.18/zh/docs/dev/table/tableapi/

5.1 from()指定操作的表对象–执行一个注册过的表的扫描

和 SQL 查询的 FROM 子句类似。 执行一个注册过的表的扫描
tableEnv.from(“已注册的myflink表名”);

这里的前提是需要表已经注册进了flink中,比如4.2 中的myflink表;

5.2 FromValues ()

和 SQL 查询中的 VALUES 子句类似。 基于提供的行生成一张内联表。

你可以使用 row(…) 表达式创建复合行:

5.3 Select ()

和 SQL 的 SELECT 子句类似。 执行一个 select 操作。

你可以选择星号(*)作为通配符,select 表中的所有列。

Table orders = tableEnv.from("Orders");
Table result = orders.select($("a"), $("c").as("d"));

Table result = orders.select($("*"));

5.4 as()

重命名字段

Table orders = tableEnv.from("Orders");
Table result = orders.as("x, y, z, t");

5.5 filter()/where()用法一样

filter()方法用于对表中的数据进行过滤,它接受一个表示过滤条件的表达式作为参数。表达式通常基于表中的列和一些操作符来构建。

支持的操作符和数据类型相关如下:

  • 数值类型操作:对于数值类型(如INT、BIGINT、DOUBLE等)的列,可以使用比较操作符(如>、<、>=、<=、!=、===)进行过滤。例如,对于INT类型的age列,tableEnv.from(“myflink”).filter($(“age”) > 18)可以过滤出年龄大于18的行。
  • 字符串类型操作:除了like操作符,还可以使用相等比较操作符来进行精确匹配。对于字符串列address,tableEnv.from(“myflink”).filter($(“address”) === “New York”)可以过滤出地址为New York的行。
  • 布尔类型操作:如果表中有布尔类型的列,例如is_active,可以直接使用tableEnv.from(“myflink”).filter( ( " i s a c t i v e " ) ) 来过滤出 i s a c t i v e 为 t r u e 的行,或者 t a b l e E n v . f r o m ( " m y f l i n k " ) . f i l t e r ( ! ("is_active"))来过滤出is_active为true的行,或者tableEnv.from("myflink").filter(! ("isactive"))来过滤出isactivetrue的行,或者tableEnv.from("myflink").filter(!(“is_active”))来过滤出is_active为false的行。

filter()方法的参数是一个org.apache.flink.table.expressions.Expression类型的对象,它可以通过 符号结合列名和各种操作符来构建,也可以使用更复杂的表达式对象构建方式,不过 符号结合列名和各种操作符来构建,也可以使用更复杂的表达式对象构建方式,不过 符号结合列名和各种操作符来构建,也可以使用更复杂的表达式对象构建方式,不过符号是一种比较简洁直观的方法在简单场景下使用。

filter()操作返回一个新的Table对象,这个新表包含了满足过滤条件的行。后续可以继续对这个新表进行其他操作,如select、groupBy、join等操作,以进一步处理和分析数据。

需要注意的是,在实际使用中,要确保myflink表已经正确注册并且表的结构(列名和数据类型)与代码中的操作相匹配,否则可能会出现运行时错误。

5.5.1 简单的相等条件过滤:

假设myflink表中有一个名为id的列,要过滤出id等于10的行,可以使用tableEnv.from(“myflink”).filter( ( " i d " ) = = = 10 ) 。这里 ("id") === 10)。这里 ("id")===10)。这里(“id”)用于引用id列,===是相等比较操作符(在 Flink SQL 或 Table API 中用于严格相等比较)。

5.5.2 基于字符串列的模糊匹配过滤(使用LIKE操作符):

如果表中有一个名为name的字符串列,要过滤出name以"John"开头的行,可以使用tableEnv.from(“myflink”).filter($(“name”).like(“John%”))。

5.5.3多条件组合过滤:

可以使用逻辑操作符(如AND、OR)组合多个过滤条件。例如,要过滤出id大于5并且name以"A"开头的行,可以使用tableEnv.from(“myflink”).filter(( ( " i d " ) > 5 ) . a n d ( ("id") > 5).and( ("id")>5).and((“name”).like(“A%”)))。

Table orders = tableEnv.from("Orders");
Table result = orders.filter($("b").isEqual("red"));

5.6 AddColumns

执行字段添加操作。 如果所添加的字段已经存在,将抛出异常

Table orders = tableEnv.from("Orders");
Table result = orders.addColumns(concat($("c"), "sunny"));

窗口操作

你可能感兴趣的:(Flink,1024程序员节,flink)