DDL
Flink SQL DDL(Data Definition Language)是Flink SQL中用于定义和管理数据结构和数据库对象的语法。以下是对Flink SQL DDL的详细解析:
一、创建数据库(CREATE DATABASE)
- 语法:CREATE DATABASE [IF NOT EXISTS] [catalog_name.]db_name [COMMENT database_comment] WITH (key1=val1, key2=val2, ...)
- 说明:用于在当前或指定的Catalog中创建一个新的数据库。如果指定了IF NOT EXISTS,则在数据库已存在时不会抛出错误。WITH子句用于指定数据库的额外属性。
二、创建表(CREATE TABLE)
- 语法:CREATE [TEMPORARY] TABLE [IF NOT EXISTS] [catalog_name.][db_name.]table_name ({<physical_column_definition>|<metadata_column_definition>|<computed_column_definition>}[ , ...n][<watermark_definition>][<table_constraint>[ , ...n]]) [COMMENT table_comment] [PARTITIONED BY (partition_column_name1, partition_column_name2, ...)] WITH (key1=val1, key2=val2, ...) [LIKE source_table[(<like_options>)]]
- 说明 :
- 物理列:定义了物理介质中存储的数据的字段名称、类型和顺序。
- 元数据列:允许访问数据源本身具有的一些元数据,由METADATA关键字标识。
- 计算列:使用表达式定义的虚拟列,不物理存储在表中。
- 水印列:用于处理事件时间,指定如何生成水印。
- 表约束:用于定义表的约束条件,如主键等。
- 分区:用于对表进行分区,以提高查询性能。
- WITH子句:用于指定表的连接器类型和配置项。
- LIKE子句:允许基于现有表创建新表,包括表的结构和配置。
三、创建视图(CREATE VIEW)
- 语法:CREATE VIEW [IF NOT EXISTS] [catalog_name.][db_name.]view_name AS select_statement
- 说明:用于创建一个基于SQL查询结果的视图。视图可以看作是一个虚拟表,它包含了查询结果的数据,并且可以根据需要进行过滤、转换等操作。
四、其他DDL操作
- 修改数据库:使用ALTER DATABASE语句修改数据库的某些属性。
- 删除数据库:使用DROP DATABASE语句删除数据库。如果数据库不为空,可以指定CASCADE来删除所有相关的表和函数。
- 修改表:使用ALTER TABLE语句修改表的结构,如添加、删除或修改列。
- 删除表:使用DROP TABLE语句删除表。
五、Connector
一、定义与功能
Flink SQL Connector是一种用于在Flink SQL与外部数据源或数据汇之间建立连接的插件。它允许Flink SQL从外部数据源读取数据,并将处理后的数据写入到外部数据汇中。通过Connector,Flink SQL能够轻松实现对各种类型数据的实时处理和分析。
二、类型与分类
Flink SQL Connector主要分为以下几类:
-
数据源Connector :用于从外部数据源读取数据。常见的数据源Connector包括Kafka、JDBC、FileSystem等。
-
数据汇Connector:用于将处理后的数据写入到外部数据汇中。常见的数据汇Connector也包括Kafka、JDBC、Elasticsearch等。
三、工作原理
Flink SQL Connector的工作原理如下:
- 连接配置:在创建表时,通过WITH子句指定Connector的类型和相关配置项,如数据源或数据汇的地址、用户名、密码等。
- 数据读取:对于数据源Connector,Flink SQL会根据配置的连接信息从外部数据源中读取数据,并将其转换为Flink内部的Row或Tuple数据类型。
- 数据处理:在Flink SQL中,用户可以对读取到的数据进行各种查询和处理操作,如过滤、聚合、连接等。
- 数据写入:对于数据汇Connector,Flink SQL会将处理后的数据写入到指定的外部数据汇中,如Kafka主题、数据库表等。
四、常用Connector示例
- Kafka Connector:
- Kafka作为流数据的代表,在Flink SQL中得到了广泛的支持。
- 用户可以通过Kafka Connector轻松地从Kafka主题中读取数据,并将处理后的数据写回到Kafka中。
- JDBC Connector:
- JDBC Connector允许Flink SQL与关系型数据库进行交互。
- 用户可以通过JDBC Connector从数据库中读取数据,或将数据写入到数据库中。
- FileSystem Connector:
- FileSystem Connector支持Flink SQL与文件系统(如HDFS、S3等)进行交互。
- 用户可以通过FileSystem Connector从文件中读取数据,或将数据写入到文件中。
五、自定义Connector
在某些特殊情况下,用户可能需要自定义Connector来满足特定的需求。Flink提供了丰富的API和工具来支持用户自定义Connector。自定义Connector通常涉及以下几个步骤:
-
定义Connector的工厂类 :实现Flink提供的DynamicTableSourceFactory或DynamicTableSinkFactory接口。
-
实现TableSource或TableSink :根据需求实现StreamTableSource、BatchTableSource、StreamTableSink或BatchTableSink接口。
-
配置参数 :在Connector的工厂类中定义并解析所需的参数。
-
注册Connector:将自定义的Connector注册到Flink的SPI(Service Provider Interface)机制中,以便在创建表时能够识别并使用它。
六 Format
Flink SQL Format指的是在Flink SQL中定义数据源和数据汇时所使用的数据序列化方式。在Flink中,数据通常以流的形式进行处理,而Format则定义了如何将这些流数据映射到Flink SQL的表结构中,以及如何将处理后的数据序列化为特定的格式输出到外部系统。
一、Format的类型
Flink SQL支持多种Format类型,包括但不限于以下几种:
- CSV:逗号分隔值格式,适用于简单的文本数据。CSV Format允许基于CSV schema读写CSV格式的数据,目前CSV schema通常来源于表schema定义。
- JSON:JavaScript对象表示法格式,适用于复杂的数据结构。JSON Format允许基于JSON schema读写JSON格式的数据,目前JSON schema通常也派生于表schema。
- Avro:Apache Avro是一种紧凑的、快速的二进制数据格式,适用于需要高效序列化和反序列化的场景。Avro Format允许基于Avro schema读写Avro格式的数据。
- Raw:原始数据格式,适用于不需要对数据进行任何解析或格式化的场景。在Flink SQL中,使用Raw Format时,Kafka消息会直接将原始数据读取为纯字符串。
二、Format的配置
在Flink SQL中,Format的配置通常通过WITH子句中的参数来指定。以下是一些常见的Format配置参数:
- 'format' = 'xxx':指定使用的Format类型,如CSV、JSON、Avro等。
- 'csv.ignore-parse-errors' = 'true':对于CSV Format,忽略解析错误。
- 'json.fail-on-missing-field' = 'false':对于JSON Format,当缺少字段时不抛出异常。
- 其他与特定Format相关的配置参数,如Avro的schema定义等。
三、Format的使用示例
以下是一个使用Kafka连接器和JSON Format创建表的示例:
sql
CREATE TABLE user_behavior (
user_id BIGINT,
item_id BIGINT,
category_id BIGINT,
behavior STRING,
ts TIMESTAMP(3)
) WITH (
'connector' = 'kafka',
'topic' = 'user_behavior',
'properties.bootstrap.servers' = 'localhost:9092',
'properties.group.id' = 'testGroup',
'format' = 'json',
'json.fail-on-missing-field' = 'false',
'json.ignore-parse-errors' = 'true'
);
在这个示例中,我们创建了一个名为user_behavior的表,它从一个Kafka主题中读取数据,数据格式为JSON。我们还指定了Kafka连接器的配置项和JSON Format的相关参数。
七、示例
以下是一个简单的Flink SQL DDL示例,演示了如何创建一个表、视图以及执行一些基本的DDL操作:
sql
-- 创建一个名为user_table的表,包含id、name和age字段,并使用Kafka作为数据源
CREATE TABLE user_table (
id INT,
name STRING,
age INT
) WITH (
'connector' = 'kafka',
'topic' = 'user_topic',
'properties.bootstrap.servers' = 'localhost:9092',
'format' = 'json'
);
-- 创建一个名为user_view的视图,包含user_table表中年龄大于18岁的用户的name和age字段
CREATE VIEW user_view AS
SELECT name, age
FROM user_table
WHERE age > 18;
-- 修改user_table表,添加一个新的字段email
ALTER TABLE user_table
ADD COLUMNS (email STRING);
-- 删除user_view视图
DROP VIEW user_view;
-- 删除user_table表
DROP TABLE user_table;
注意事项
- 在执行DDL操作时,请确保具有相应的权限。
- 不同的Flink版本可能支持不同的DDL语法和特性,请参考官方文档以获取最新信息。
- 在创建表和视图时,请确保指定了正确的连接器类型和配置项,以便Flink能够正确地连接到数据源。
DML
WITH...AS
WITH ... AS语法是一种定义公用表表达式(Common Table Expressions, CTEs)的方式,它允许在查询中定义一个或多个临时的结果集,这些结果集可以在后续的查询中被引用。CTE在复杂查询中特别有用,因为它们可以帮助将查询分解为更小、更易于管理的部分。
语法详解
WITH ... AS语法的基本形式如下:
sql
WITH cte_name (column1, column2, ...) AS (
-- 这里是CTE的定义,可以是一个子查询
SELECT ...
FROM ...
WHERE ...
GROUP BY ...
HAVING ...
ORDER BY ...
)
-- 后续的查询,可以引用上面定义的CTE
SELECT ...
FROM cte_name
-- 还可以加入其他表或CTE进行连接、过滤等操作
- cte_name:公用表表达式的名称,可以在后续的查询中引用。
- (column1, column2, ...):CTE中定义的列名(可选,但在某些情况下有助于明确CTE的结构)。
- AS:关键字,用于引入CTE的定义。
- 子查询:定义了CTE的内容,可以包含SELECT、FROM、WHERE、GROUP BY、HAVING、ORDER BY等子句。
样例
以下是一个使用WITH ... AS语法的Flink SQL DML样例,它演示了如何定义一个CTE来计算每个商品在每个小时内的销售额,并将销售额超过一定金额的商品信息插入到另一个表中。
sql
-- 定义公用表表达式,计算每个商品在每个小时内的销售额
WITH product_sales AS (
SELECT
product_id,
product_name,
TUMBLE_START(sale_time, INTERVAL '1' HOUR) AS window_start,
TUMBLE_END(sale_time, INTERVAL '1' HOUR) AS window_end,
SUM(sale_amount) AS total_sales
FROM sales_stream
GROUP BY product_id, product_name, TUMBLE(sale_time, INTERVAL '1' HOUR)
)
-- 插入销售额超过1000的商品信息到结果表中
INSERT INTO product_sales_summary
SELECT
product_id,
product_name,
window_start,
window_end,
total_sales
FROM product_sales
WHERE total_sales > 1000;
在这个样例中:
- product_sales是一个CTE,它计算了每个商品在每个小时内的销售额。这里使用了Flink的窗口函数TUMBLE来按小时划分时间窗口,并计算了每个窗口内的销售总额。
- INSERT INTO product_sales_summary SELECT ... FROM product_sales WHERE ...是一个DML语句,它将销售额超过1000的商品信息从product_salesCTE插入到product_sales_summary表中。
SELECT & WHERE:ETL、字段标准化
ELECT和WHERE子句是数据提取(Extract)、转换(Transform)、加载(Load,简称ETL)过程中的核心组件。它们被广泛应用于从数据流或数据表中筛选和转换数据。字段标准化,作为ETL流程的一个重要环节,通常涉及数据清洗、格式统一和值映射等任务。
语法详解
SELECT
SELECT子句用于指定查询要返回的列。它可以包含原始列、表达式计算结果、别名列等。
sql
SELECT column1, column2, ..., expression AS alias_name
FROM source_table
- column1, column2, ...:要查询的原始列名。
- expression AS alias_name:计算表达式及其别名。
WHERE
WHERE子句用于过滤数据,只返回满足特定条件的记录。
sql
SELECT ...
FROM source_table
WHERE condition
- condition:筛选条件,可以是简单的比较操作、逻辑运算或复杂的子查询。
样例
以下是一个Flink SQL DML样例,展示了如何使用SELECT和WHERE子句进行ETL和字段标准化。
sql
-- 假设有一个名为`raw_data`的数据流,包含以下字段:user_id, raw_age, raw_salary, city_code
-- ETL和字段标准化查询
SELECT
user_id,
-- 数据清洗:去除无效年龄(例如,负数或超过120岁的年龄)
CASE
WHEN raw_age IS NULL OR raw_age < 0 OR raw_age > 120 THEN NULL
ELSE raw_age
END AS standardized_age,
-- 格式统一:将薪资转换为整数(假设原始薪资为字符串格式)
CAST(raw_salary AS INT) AS standardized_salary,
-- 值映射:将城市代码转换为城市名称(这里使用简单的CASE语句作为示例)
CASE city_code
WHEN '001' THEN 'Beijing'
WHEN '002' THEN 'Shanghai'
WHEN '003' THEN 'Guangzhou'
ELSE 'Unknown'
END AS city_name
FROM raw_data
-- 数据过滤:只选择年龄和薪资都有效的记录
WHERE
(raw_age IS NOT NULL AND raw_age >= 0 AND raw_age <= 120)
AND (raw_salary IS NOT NULL AND LENGTH(raw_salary) > 0 AND TRY_CAST(raw_salary AS INT) IS NOT NULL);
在这个样例中:
- 使用CASE语句进行数据清洗和值映射。
- 使用CAST函数将薪资字段从字符串转换为整数,实现格式统一。
- 在WHERE子句中,通过多个条件组合来过滤无效数据。
- TRY_CAST函数(如果Flink SQL支持)用于安全地尝试类型转换,避免转换失败导致的查询错误。如果不支持TRY_CAST,可以使用其他方式处理类型转换错误,例如使用嵌套的CASE语句。
SELECT DISTINCT:去重
在Flink SQL中,SELECT DISTINCT语句用于从数据集中选择唯一的记录,即去除重复的行。这个操作在处理大量数据时非常有用,尤其是当你需要确保结果集中不包含重复条目时。
语法
sql
SELECT DISTINCT column1, column2, ...
FROM source_table;
- column1, column2, ...:你想要选择并确保唯一的列。
- source_table:包含数据的表或数据流。
工作原理
当你执行SELECT DISTINCT查询时,Flink SQL引擎会对指定的列进行去重操作。这通常涉及到排序和哈希等算法,以确保所有返回的行在指定的列组合上都是唯一的。
样例
假设你有一个名为users的数据流,它包含以下字段:user_id, name, email。你想要选择所有唯一的用户(基于user_id和email的组合):
sql
SELECT DISTINCT user_id, email
FROM users;
注意事项
- 性能:SELECT DISTINCT可能会增加查询的复杂性和执行时间,特别是在处理大型数据集时。因此,在使用时应该权衡其带来的好处和可能的性能开销。
- NULL值:在SQL中,NULL值被视为不同的值。因此,如果两行在指定的列上除了NULL值之外都相同,但它们在不同的列上有NULL值,那么这两行仍然会被视为不同的记录。然而,当两行在所有指定的列上都有NULL值时,它们会被视为相同的记录,并只会在结果集中出现一次。
- 列的顺序:在SELECT DISTINCT中,列的顺序很重要。不同的列顺序可能会导致不同的去重结果。
- 组合索引:如果表上有适当的索引,SELECT DISTINCT的性能可能会得到提高。然而,在Flink中,索引的使用和效果可能因具体的执行计划和配置而异。
- 数据类型:确保你选择的列具有兼容的数据类型。如果列的数据类型不兼容,Flink SQL可能会在执行时抛出错误。
窗口聚合
在Flink SQL中,窗口聚合是一种强大的功能,它允许你对数据流中的数据进行分组,并在指定的时间窗口内执行聚合操作。这对于处理实时数据流并计算诸如滚动平均值、总和、计数等统计信息非常有用。
窗口类型
Flink SQL支持多种类型的窗口,包括:
- 滚动窗口(Tumbling Window):固定长度的窗口,每个窗口之间不重叠。例如,每5分钟一个窗口。
- 滑动窗口(Sliding Window):固定长度的窗口,但窗口之间可以重叠。例如,每30秒计算一次过去5分钟的聚合。
- 会话窗口(Session Window):基于活动间隔的窗口,当没有数据到达时窗口会关闭。例如,用户活动间隔超过10分钟则视为会话结束。
- 渐进式窗口(CUMULATE):渐进式窗口在其实就是固定窗口间隔内提前触发的的滚动窗口,其实就是Tumble Window + early-fire的一个事件时间的版本。
- 全局窗口(Global Window):对整个数据流进行聚合,通常与其他触发机制(如处理时间、事件时间的时间戳水印)结合使用。
- Window TVF:表值函数(table-valued function, TVF),顾名思义就是指返回值是一张表的函数,在Oracle、SQL Server等数据库中屡见不鲜。
- GROUPING SETS:Grouping Sets 可以通过一个标准的 GROUP BY 语句来描述更复杂的分组操作。数据按每个指定的 Grouping Sets 分别分组,并像简单的 group by 子句一样为每个组进行聚合。
语法
窗口聚合的语法通常涉及GROUP BY子句和窗口函数。以下是一个基本的滚动窗口聚合示例:
sql
SELECT
window_start,
window_end,
user_id,
SUM(amount) AS total_amount,
COUNT(*) AS transaction_count
FROM
transactions
GROUP BY
TUMBLE(event_time, INTERVAL '5' MINUTE),
user_id;
在这个示例中:
- TUMBLE(event_time, INTERVAL '5' MINUTE)定义了一个滚动窗口,窗口长度为5分钟。
- user_id是另一个分组键。
- SUM(amount)和COUNT(*)是聚合函数。
- window_start和window_end是窗口的开始和结束时间(这些通常需要额外的步骤或函数来提取,因为Flink SQL的默认输出并不总是包括这些时间戳)。
时间属性
在Flink SQL中,窗口聚合通常依赖于时间属性,如处理时间(processing time)或事件时间(event time)。事件时间是数据本身携带的时间戳,而处理时间是数据被Flink处理的时间。
- **事件时间**:需要为数据流指定时间戳和水印(watermark),以便Flink能够正确理解和处理事件时间。
- **处理时间**:不需要额外的配置,因为处理时间是基于Flink集群的本地时钟。
Group聚合
Apache Flink 是一个用于处理无界和有界数据流的分布式流处理框架。Flink SQL 是 Flink 提供的一种高级 API,允许用户使用 SQL 查询来处理数据流。在 Flink SQL 中,GROUP BY 子句用于对数据进行分组聚合。
以下是一些常见的 Flink SQL 聚合操作以及如何使用 GROUP BY 子句的示例:
基本示例
假设你有一个数据流 orders,其中包含以下字段:user_id、order_amount 和 order_time。你想要计算每个用户的总订单金额。
sql
SELECT
user_id,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
user_id;
多字段分组
你也可以根据多个字段进行分组。例如,假设你希望按 user_id 和 order_time 的日期部分进行分组,以计算每个用户每天的总订单金额。
sql
SELECT
user_id,
TUMBLE_START(order_time, INTERVAL '1' DAY) AS window_start,
TUMBLE_END(order_time, INTERVAL '1' DAY) AS window_end,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
user_id,
TUMBLE(order_time, INTERVAL '1' DAY);
在这个示例中,TUMBLE 是一个窗口函数,用于将数据按时间窗口分组。这里的时间窗口是每天。
其他聚合函数
除了 SUM 之外,Flink SQL 还支持许多其他聚合函数,如 COUNT、AVG、MIN、MAX 等。例如,计算每个用户的订单数量:
sql
SELECT
user_id,
COUNT(*) AS order_count
FROM
orders
GROUP BY
user_id;
HAVING 子句
HAVING 子句用于对聚合结果进行过滤。例如,只选择订单总金额大于 1000 的用户:
sql
SELECT
user_id,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
user_id
HAVING
SUM(order_amount) > 1000;
窗口聚合
除了基本的 GROUP BY 聚合,Flink SQL 还支持窗口聚合。窗口聚合允许你根据时间或行数的窗口对数据进行分组。
滑动窗口
假设你希望计算每个用户每两小时(滑动间隔为 1 小时)的总订单金额:
sql
SELECT
user_id,
HOP_START(order_time, INTERVAL '2' HOUR, INTERVAL '1' HOUR) AS window_start,
HOP_END(order_time, INTERVAL '2' HOUR, INTERVAL '1' HOUR) AS window_end,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
user_id,
HOP(order_time, INTERVAL '2' HOUR, INTERVAL '1' HOUR);
会话窗口
假设你希望计算每个用户每次会话(会话间隔为 30 分钟)的总订单金额:
sql
SELECT
user_id,
SESSION_START(order_time, INTERVAL '30' MINUTE) AS session_start,
SESSION_END(order_time, INTERVAL '30' MINUTE) AS session_end,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
user_id,
SESSION(order_time, INTERVAL '30' MINUTE);
GROUPING SETS
GROUPING SETS 允许你指定多个分组集,每个分组集都可以包含不同的维度组合。这样,你可以在一个查询中同时计算多个不同维度的聚合结果。
sql
SELECT
user_id,
product_id,
SUM(order_amount) AS total_amount,
GROUPING(user_id, product_id) AS grouping_id
FROM
orders
GROUP BY
GROUPING SETS (
(user_id),
(product_id),
(user_id, product_id),
() -- 空的分组集会计算全局总计
);
在这个示例中,查询将返回四个聚合结果集:按 user_id 分组、按 product_id 分组、按 user_id 和 product_id 同时分组,以及全局总计。
ROLLUP
ROLLUP 是 GROUPING SETS 的一个特例,它会自动生成从指定的维度组合到所有维度都包括以及一个全局总计的所有可能分组集。
sql
SELECT
user_id,
product_id,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
ROLLUP(user_id, product_id);
这个查询将返回以下分组集的结果:
- (user_id, product_id)
- (user_id, NULL)(对 user_id 进行分组,不考虑 product_id)
- (NULL, NULL)(全局总计)
CUBE
CUBE 是 GROUPING SETS 的另一个特例,它会生成所有可能的维度组合,包括每个维度的单独组合、所有维度的组合以及一个全局总计。
sql
SELECT
user_id,
product_id,
SUM(order_amount) AS total_amount
FROM
orders
GROUP BY
CUBE(user_id, product_id);
这个查询将返回以下分组集的结果:
- (user_id, product_id)
- (user_id, NULL)
- (NULL, product_id)
- (NULL, NULL)(全局总计)
Over聚合
Flink SQL中的Over聚合是一种使用Over子句的开窗函数,用于对数据按照不同的维度和范围进行分组和聚合。以下是对Flink SQL Over聚合的详细解释:
一、Over聚合的基本概念
Over聚合可以保留原始字段,不像分组聚合(Group By)只能输出聚合结果和分组字段。它通过对每行数据进行窗口内聚合,能够计算出如移动平均、累计和等统计指标。
二、Over聚合的语法
Over聚合的语法结构如下:
sql
SELECT agg_func(agg_col) OVER (
[PARTITION BY col1 [, col2, ...]]
ORDER BY time_col
[range_definition]
), ...
FROM ...
其中:
- **agg_func(agg_col)**:聚合函数和要聚合的列。
- **PARTITION BY**:可选,用于指定分区的键,类似于Group By的分组。
- **ORDER BY**:必须,指定数据基于哪个字段排序,通常是时间戳列。
- **range_definition**:定义聚合窗口的数据范围,有两种指定方式:按时间区间聚合和按行数聚合。
三、Over聚合的窗口范围
- 按时间区间聚合:
使用RANGE BETWEEN INTERVAL ... PRECEDING AND CURRENT ROW来指定一个向前的时间范围。例如,计算最近一小时的订单金额总和:
sql
SELECT product, order_time, amount,
SUM(amount) OVER (
PARTITION BY product
ORDER BY order_time
RANGE BETWEEN INTERVAL '1' HOUR PRECEDING AND CURRENT ROW
) AS one_hour_prod_amount_sum
FROM Orders;
- 按行数聚合:
使用ROWS BETWEEN ... PRECEDING AND CURRENT ROW来指定一个向前的行数范围。例如,计算当前行及其前5行的订单金额总和:
sql
SELECT product, order_time, amount,
SUM(amount) OVER (
PARTITION BY product
ORDER BY order_time
ROWS BETWEEN 5 PRECEDING AND CURRENT ROW
) AS five_rows_prod_amount_sum
FROM Orders;
四、Over聚合的应用场景
Over聚合在数据分析中有着广泛的应用,如:
计算最近一段滑动窗口的聚合结果数据,如最近一小时或最近五行的数据。
计算累计或移动平均等指标,如累计销售额或移动平均价格。