Flink SQL的基本语法与概念

      Flink 通过支持标准 ANSI SQL的 Apache Calcite解析 SQL。

1 DDL

1.1 CREATE语句

      CREATE语句适用于当前或指定的Catalog中注册表、视图或函数。注册后的表、视图和函数可以在SQL查询中适用。

1.1.1 CREATE TABLE
CREATE TABLE [catalog_name.][db_name.]table_name
  (
    { <column_definition> | <computed_column_definition> }[ , ...n]
    [ <watermark_definition> ]
  )
  [COMMENT table_comment]
  [PARTITIONED BY (partition_column_name1, partition_column_name2, ...)]
  WITH (key1=val1, key2=val2, ...)

<column_definition>:
  column_name column_type [COMMENT column_comment]

<computed_column_definition>:
  column_name AS computed_column_expression [COMMENT column_comment]

<watermark_definition>:
  WATERMARK FOR rowtime_column_name AS watermark_strategy_expression

      根据指定的表名常见一个表,如果同名表已经在Catalog中存在了,则无法注册。
(1)计算列(COMPUTED COLUMN)
      计算列是一个适用“column_name AS computed_column_expression”语法生成的虚拟列。它由使用同一表中其他列的非查询表达式生成,并且不会在表中进行物理存储。例如,一个计算列可以适用cost as price * quantity进行定义,这个表达式可以包含物理列、常量、函数或变量的任意组合,但这个表达式不能存在任何子查询。
      在Flink中计算列一般用于为CREATE TABLE语句定义时间属性。处理时间属性可以简单地通过使用系统函数PROCETIME()的proc AS PROCTIME()语句进行定义。另一方面,由于事件时间列可能需要从现有的字段中获得,因此计算列可用于获得事件时间列。例如,原始字段的类型不是TIMESTAMP(3)或嵌套在JSON字符串中。
      注意:
      ① 定义在一个数据源表(source table)上的计算列会在从数据源读取数据后被计算,它们可以在SELECT语句中使用。
      ② 计算列不可以作为INSERT语句的目标,在INSERT语句中,SELECT语句的schema需要与目标表不带有计算列的schema一致
(2)WATERMARK
      WATERMARK定义了表的事件时间属性,其形式化为WATERMARK FOR rowtime_column_name AS watermark_strategy_expression。
      rowtime_column_name把一个现有的列定义为一个为表标记事件时间的属性。该列的类型必须为TIMESTAME(3),且是schema中的顶层列,它也可以是一个计算列
      watermark_strategy_expression定义了watermark的生成策略。它允许使用包括计算列在内的任意非查询表达式来计算watermark;表达式的返回类型必须是TIMESTAMP(3),表示了从Epoch以来的经过的时间。返回的watermark只有当其不为空且其值大于之前发出的本地watermark时才会被发现(以保证watermark递增)。每条记录的watermark生成表达式计算都会由框架完成。框架会定期发出所生成的最大的watermark,如果当前watermark仍然与前一个watermark相同、为空、或返回的watermark的值小于最后一个发出的watermark,则新的watermark不会被发出。Watermark根据pipline.auto-watermark-interval中所配置的间隔发出。若watermark的间隔是0ms,那么每条记录都会产生一个watermark,且watermark会在不为空并大于上一个发出的watermark时发出。
      使用事件时间语义时,表必须包含事件时间属性和watermark策略。
      Flink提供了集中常用的watermark策略。
      ① 严格递增时间戳:watermark for rowtime_column as rowtime_column。
      发出到目前为止已观察到的最大时间戳的watermark,时间戳小于最大时间戳的行被认为没有迟到。
      ② 递增时间戳:watermark for rowtime_colum as rowtime_column – INTERVAL ‘0.001’ SECOND
      发出到目前为止已观察到的最大时间戳减1的watermark,时间戳等于或小于最大时间戳的行被认为没有迟到。
      ③ 有界乱序时间戳:watermark for rowtime_column as rowtime_column – INTERVAL ‘string’ timeunit。
      发出到目前为止已观察到的最大时间戳减去指定延迟的watermark,例如,watermark for rowtime_column as rowtime_column – interval ‘5’ SECOND是一个5秒延迟的watermark策略。

CREATE TABLE Orders (
    user BIGINT,
    product STRING,
    order_time TIMESTAMP(3),
    WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH ( . . . );

(3)PARTITIONED BY
      根据指定的列对已经创建的表进行分区。若表使用filesystem sink,则将会为每个分区创建一个目录。
(4)WITH OPTIONS
      表属性用于创建table source/sink,一般用于寻找和创建底层的连接器。具体用法参见
      表达式key1=val1的键和值必须为字符串文本常量。
      **注意:**表名可以为以下三种格式① catalog_name.db_name.table_name;② db_name.table_name;③ table_name。使用catalog_name.db_name.table_name 的表将会与名为 “catalog_name” 的 catalog 和名为 “db_name” 的数据库一起注册到 metastore 中。使用 db_name.table_name 的表将会被注册到当前执行的 table environment 中的 catalog 且数据库会被命名为 “db_name”;对于 table_name, 数据表将会被注册到当前正在运行的catalog和数据库中。
      使用CREATE TABLE语句注册的表均可用作table source和table sink。在被 DML语句引用前,我们无法决定其实际用于source抑或是sink。

1.1.2 CREATE DATABASE
CREATE DATABASE [IF NOT EXISTS] [catalog_name.]db_name
  [COMMENT database_comment]
  WITH (key1=val1, key2=val2, ...)

      根据给定的数据库属性创建数据库。若数据库中已存在同名数据库会抛出异常。
(1)IF NOT EXISTS
      若数据库已经存在,则不会进行任何操作。
(2)WITH OPTIONS
      数据库属性一般用于存储关于这个数据库额外的信息。表达式key1=val1中的键和值都需要是字符串文本常量。

1.1.3 CREATE FUNCTION
CREATE [TEMPORARY|TEMPORARY SYSTEM] FUNCTION
  [IF NOT EXISTS] [[catalog_name.]db_name.]function_name
  AS identifier [LANGUAGE JAVA|SCALA]

      创建一个有catalog和数据库命名空间的catalog function,其需要指定JAVA/SCALA或其他language tag完整的classpath。若catalog中,已经有同名的函数注册了,则无法注册。
(1)TEMPORARY
      创建一个有catalog和数据库命名空间的临时catalog function,并覆盖原有的catalog function。
(2)TEMPORARY SYSTEM
      创建一个没有数据库命名空间的临时系统catalog function,并覆盖系统内置的函数。
(3)IF NOT ESISTS
      若该函数已经存在,则不会进行任何操作。
(4)LANGUAGE JAVA|SCALA
      Language tag用于指定Flink runtime如何执行这个函数。目前只支持JAVA和SCALA,且函数的默认语言为JAVA。

1.2 DROP语句

      DROP 语句用于从当前或指定的 Catalog 中删除一个已经注册的表、视图或函数。

1.2.1 DROP TABLE
DROP TABLE [IF EXISTS] [catalog_name.][db_name.]table_name

      根据给定的表名删除某个表。若需要删除的表不存在,则抛出异常。
(1)IF EXISTS
      表不存在时不会进行任何操作。

1.2.2 DROP DATABASE
DROP DATABASE [IF EXISTS] [catalog_name.]db_name [ (RESTRICT | CASCADE) ]

      根据给定的表名删除数据库。若需要删除的数据库不存在会抛出异常。
(1)IF EXISTS
      若数据库不存在,不执行任何操作。
(2)RESTRICT
      当删除一个非空数据库时,会触发异常。(默认打开)
(3)CASCADE
      删除一个非空数据库时,把相关联的表与函数一并删除。

1.2.3 DROP FUNCTION
DROP [TEMPORARY|TEMPORARY SYSTEM] FUNCTION [IF EXISTS] [catalog_name.][db_name.]function_name

      删除一个有catalog和数据库命名空间的catalog function。若需要删除的函数不存在,则会产生异常。
(1)TEMPORARY
      删除一个有catalog和数据库命名空间的临时catalog function。
(2)TEMPORARY SYSTEM
      删除一个没有数据库命名空间的临时系统函数。
(3)IF EXISTS
      若函数不存在,则不会进行任何操作。

1.3 ALTER语句

      ALTER 语句用于修改一个已经在 Catalog 中注册的表、视图或函数定义。

1.3.1 ALTER TABLE

(1)重命名表

ALTER TABLE [catalog_name.][db_name.]table_name RENAME TO new_table_name

      把原有的表名更改为新的表名。
(2)设置或修改表属性

ALTER TABLE [catalog_name.][db_name.]table_name SET (key1=val1, key2=val2, ...)

      为指定的表设置一个或多个属性。若个别属性已经存在于表中,则使用新的值覆盖旧的值。

1.3.2 ALTER DATABASE
ALTER DATABASE [catalog_name.]db_name SET (key1=val1, key2=val2, ...)

      在数据库中设置一个或多个属性。若个别属性已经在数据库中设定,将会使用新值覆盖旧值。

1.3.3 ALTER FUNCTION
ALTER [TEMPORARY|TEMPORARY SYSTEM] FUNCTION
  [IF EXISTS] [catalog_name.][db_name.]function_name
  AS identifier [LANGUAGE JAVA|SCALA|

      修改一个有catalog和数据库命名空间的catalog function,其需要指定JAVA /SCALA或其他language tag完整的classpath。若函数不存在,删除会抛出异常。
(1)TEMPORARY
      修改一个有catalog和数据库命名空间的临时catalog function,并覆盖原有的catalog function。
(2)TEMPORARY SYSTEM
      修改一个没有数据库命名空间的临时系统catalog function,并覆盖系统内置的函数。
(3)IF EXISTS
      若函数不存在,则不进行任何操作。
(4)LANGUAGE JAVA|SCALA
      Language tag用于指定Flink runtime如何执行这个函数。目前,只支持JAVA和SCALA,且函数的默认语言为JAVA。

2 DML

2.1 SELECT语句

      以下 BNF-语法 描述了批处理和流处理查询中所支持的 SQL 特性的超集。

query:
  values
  | {
      select
      | selectWithoutFrom
      | query UNION [ ALL ] query
      | query EXCEPT query
      | query INTERSECT query
    }
    [ ORDER BY orderItem [, orderItem ]* ]
    [ LIMIT { count | ALL } ]
    [ OFFSET start { ROW | ROWS } ]
    [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY]

orderItem:
  expression [ ASC | DESC ]

select:
  SELECT [ ALL | DISTINCT ]
  { * | projectItem [, projectItem ]* }
  FROM tableExpression
  [ WHERE booleanExpression ]
  [ GROUP BY { groupItem [, groupItem ]* } ]
  [ HAVING booleanExpression ]
  [ WINDOW windowName AS windowSpec [, windowName AS windowSpec ]* ]

selectWithoutFrom:
  SELECT [ ALL | DISTINCT ]
  { * | projectItem [, projectItem ]* }

projectItem:
  expression [ [ AS ] columnAlias ]
  | tableAlias . *

tableExpression:
  tableReference [, tableReference ]*
  | tableExpression [ NATURAL ] [ LEFT | RIGHT | FULL ] JOIN tableExpression [ joinCondition ]

joinCondition:
  ON booleanExpression
  | USING '(' column [, column ]* ')'

tableReference:
  tablePrimary
  [ matchRecognize ]
  [ [ AS ] alias [ '(' columnAlias [, columnAlias ]* ')' ] ]

tablePrimary:
  [ TABLE ] [ [ catalogName . ] schemaName . ] tableName
  | LATERAL TABLE '(' functionName '(' expression [, expression ]* ')' ')'
  | UNNEST '(' expression ')'

values:
  VALUES expression [, expression ]*

groupItem:
  expression
  | '(' ')'
  | '(' expression [, expression ]* ')'
  | CUBE '(' expression [, expression ]* ')'
  | ROLLUP '(' expression [, expression ]* ')'
  | GROUPING SETS '(' groupItem [, groupItem ]* ')'

windowRef:
    windowName
  | windowSpec

windowSpec:
    [ windowName ]
    '('
    [ ORDER BY orderItem [, orderItem ]* ]
    [ PARTITION BY expression [, expression ]* ]
    [
        RANGE numericOrIntervalExpression {PRECEDING}
      | ROWS numericExpression {PRECEDING}
    ]
    ')'

matchRecognize:
      MATCH_RECOGNIZE '('
      [ PARTITION BY expression [, expression ]* ]
      [ ORDER BY orderItem [, orderItem ]* ]
      [ MEASURES measureColumn [, measureColumn ]* ]
      [ ONE ROW PER MATCH ]
      [ AFTER MATCH
            ( SKIP TO NEXT ROW
            | SKIP PAST LAST ROW
            | SKIP TO FIRST variable
            | SKIP TO LAST variable
            | SKIP TO variable )
      ]
      PATTERN '(' pattern ')'
      [ WITHIN intervalLiteral ]
      DEFINE variable AS condition [, variable AS condition ]*
      ')'

measureColumn:
      expression AS alias

pattern:
      patternTerm [ '|' patternTerm ]*

patternTerm:
      patternFactor [ patternFactor ]*

patternFactor:
      variable [ patternQuantifier ]

patternQuantifier:
      '*'
  |   '*?'
  |   '+'
  |   '+?'
  |   '?'
  |   '??'
  |   '{' { [ minRepeat ], [ maxRepeat ] } '}' ['?']
  |   '{' repeat '}'

      Flink SQL对于标识符(表、属性和函数名)有类似Java的此法约定:
      ① 不管是否引用标识符,都保留标识符的大小写;
      ② 标识符区分大小写;
      ③ 与Java不同的地方在于,通过反引号,可以允许标识符带有非字母的字符(如:“SELECT a AS ‘my field’ FROM t”)。
      字符串文本常量需要被单引号包起来(如SELECT ‘Hello World’)。两个单引号表示转义(如 SELECT ‘It’‘s me.’)。字符串文本常量支持Unicode字符,如需明确使用Unicode编码,请使用以下语法:
      ① 使用反斜杠(\)作为转义字符(默认):SELECT u& ‘\263A’;
      ② 使用自定义的转义字符:SELECT U&’#263A’ UESCAPE ‘#’。

2.2 INSERT语句

      INSERT 语句用来向表中添加行。

2.2.1将 SELECT 查询数据插入表中
INSERT { INTO | OVERWRITE } [catalog_name.][db_name.]table_name [PARTITION part_spec] select_statement

part_spec:
  (part_col_name1=val1 [, part_col_name2=val2, ...])

(1)OVERWRITE
      INSERT OVERWRITE将覆盖表中或分区中的任何已存在的数据。否则,新数据就会追加到表中或分区中。
(2)PARTITION
      PARTITION语句应该包含需要插入的静态分区列与值。
(3)示例

-- 创建一个分区表
CREATE TABLE country_page_view (user STRING, cnt INT, date STRING, country STRING)
PARTITIONED BY (date, country)
WITH (...)

-- 追加行到该静态分区中 (date='2019-8-30', country='China')
INSERT INTO country_page_view PARTITION (date='2019-8-30', country='China')
  SELECT user, cnt FROM page_view_source;

-- 追加行到分区 (date, country) 中,其中 date 是静态分区 '2019-8-30';country 是动态分区,其值由每一行动态决定
INSERT INTO country_page_view PARTITION (date='2019-8-30')
  SELECT user, cnt, country FROM page_view_source;

-- 覆盖行到静态分区 (date='2019-8-30', country='China')
INSERT OVERWRITE country_page_view PARTITION (date='2019-8-30', country='China')
  SELECT user, cnt FROM page_view_source;

-- 覆盖行到分区 (date, country) 中,其中 date 是静态分区 '2019-8-30';country 是动态分区,其值由每一行动态决定
INSERT OVERWRITE country_page_view PARTITION (date='2019-8-30')
  SELECT user, cnt, country FROM page_view_source;
2.2.2 将值插入表中
INSERT { INTO | OVERWRITE } [catalog_name.][db_name.]table_name VALUES values_row [, values_row ...]

values_row:
    : (val1 [, val2, ...])

(1)OVERWRITE
      INSERT OVERWRITE 将会覆盖表中的任何已存在的数据。否则,新数据会追加到表中。
(2)示例

CREATE TABLE students (name STRING, age INT, gpa DECIMAL(3, 2)) WITH (...);

INSERT INTO students
  VALUES ('fred flintstone', 35, 1.28), ('barney rubble', 32, 2.32);

3 总结

      本章主要是对Flink SQL的语法进行了总结,同时,对一些比较重要的知识点(例如:计算列和watermark等)进行了相关介绍。目前,也在看阿里云社区的文章,这些文章主要分为两个方面:第一,内置函数,我觉得这方面知识,可以在实战中,边使用边学习,效果会更好;第二,Flink SQL的调优(比较难,靠经验,应该是一直学习的内容),这方面知识点,我觉得对个人提升比较大,后续,我会在这方面多下功夫。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值