Delta Lake

1.Delta Lake 简介
Delta Lake 是可提高数据湖可靠性的开源存储层。Delta Lake 提供 ACID 事务和可缩放的元数据处理,并统一流和批数据处理。 Delta Lake 在现有 Data Lake 的顶层运行,与 Apache Spark API 完全兼容。

具体来说,Delta Lake提供:

Spark ACID事务:可序列化的隔离级别确保用户永远不会看到不一致的数据。

可扩展的元数据处理:利用Spark的分布式处理能力,可以轻松处理数十亿个文件的PB级表的所有元数据。

流式处理和批处理统一:Delta Lake中的表既是批处理表,又是也是流式处理源和接收器。流式处理数据引入、批处理历史回填、交互式查询功能都是现成的。

架构强制:自动处理架构变体,以防在引入过程中插入错误的记录。

按时间顺序查看:数据版本控制支持回滚、完整的历史审核线索和可重现的机器学习试验

更新插入和删除:支持合并、更新和删除操作,以启用复杂用例,如更改数据捕获、渐变维度 (SCD) 操作、流式处理更新插入等。

Delta Engine 优化使 Delta Lake 操作具有高性能,并支持各种工作负载,从大规模 ETL 处理到临时交互式查询均可。有关Delta Engine的信息,请参阅Delta Engine的相关文档。
2.Delta Lake快速入门概述了使用Delta Lake的基础知识。此快速入门演示如何生成管道,以便将JSON数据读入Delta表、修改表、读取表、显示表历史记录,以及优化表。
2.1创建表
若要创建一个delta表,可以使用现有的Apache Spark SQL代码,也可以将parquet、csv、json等数据格式转换为delta。
对于所有文件类型,您将文件读入DataFrame并将格式转为delta
%pyspark
events = spark.read.json("/xz/events_data.json")
events.write.format("delta").save("/xz/delta/events")
spark.sql("CREATE TABLE events USING DELTA LOCATION '/xz/delta/events/'")
SQL
%sql
CREATE TABLE events
USING delta
AS SELECT *
FROM json.`/data/events/`
如果源文件是Parquet格式,则可以使用SQL Convert to Delta语句就地转换文件,以创建非托管表
SQL
%sql
CONVERT TO DELTA parquet.`/mnt/delta/events`

2.2分区数据
要加快包含涉及分区列的谓词查询,可以对数据进行分区。
Python
%pyspark
events = spark.read.json("/databricks-datasets/structured-streaming/events/")
events.write.partitionBy("date").format("delta").save("/mnt/delta/events")
spark.sql("CREATE TABLE events USING DELTA LOCATION '/mnt/delta/events/'")
SQL
%sql
CREATE TABLE events (
  date DATE,
  eventId STRING,
  eventType STRING,
  data STRING)
USING delta
PARTITIONED BY (date)
2.3 修改表格
Delta Lake支持一组丰富的操作来修改表。
2.3.1 对流写入表
您可以使用结构化流式处理将数据写入Delta表。即使有其他流或批查询同时运行表,Delta Lake事务日志也可以保证一次性处理。默认情况下,流在附加模式下运行,这会将新记录添加到表中。
Python
%pyspark
from pyspark.sql.types import *
inputPath = "/databricks-datasets/structured-streaming/events/"
jsonSchema = StructType([ StructField("time", TimestampType(), True), StructField("action", StringType(), True) ])
eventsDF = (
  spark
    .readStream
    .schema(jsonSchema) # Set the schema of the JSON data
    .option("maxFilesPerTrigger", 1) # Treat a sequence of files as a stream by picking one file at a time
    .json(inputPath)
)

(eventsDF.writeStream
  .outputMode("append")
  .option("checkpointLocation", "/mnt/delta/events/_checkpoints/etl-from-json")
  .table("events")
)

2.3.2批处理更新

要将一组更新和插入合并到现有表中,请使用merge-into语句。例如,以下语句获取更新流并将其合并到表中。当已经存在具有相同事件ID的事件时,Delta Lake将使用给定的表达式更新数据列。如果没有匹配事件时,Delta Lake将添加一个新行。
SQL
%sql
MERGE INTO events
USING updates
ON events.eventId = updates.eventId
WHEN MATCHED THEN
  UPDATE SET
    events.data = updates.data
WHEN NOT MATCHED
  THEN INSERT (date, eventId, data) VALUES (date, eventId, data)
执行INSERT时(例如,当现有数据集中没有匹配的行时),必须为表中的每一列指定一个值。但是,您不需要更新所有值。
2.3.3读一个表
在这个部分:
显示表格历史记录
查询表的早期版本(时间行程)
您可以通过在DBFS("/mnt/delta/events")或表名("event")上指定路径来访问Delta表中的数据:
Scala
%spark
SELECT * FROM delta.`/mnt/delta/events`

%spark
val events = spark.table("events")
SQL
%sql
SELECT * FROM delta.`/mnt/delta/events`

%sql
SELECT * FROM events
2.3.4 显示表的历史记录
使用DESCRIBE HISTORY语句,查看表的历史记录。该语句提供每次为写入表出处信息,包括表版本、操作、用户等。检索增量表历史记录
2.3.5 查询表的早期版本(按时间顺序查询)
使用Delta Lake时间行程,您可以查询Delta表的旧快照。
对于timestamp_string,只接受日期或时间戳字符串。例如,“2019-01-01”和“2019-01-01'T'00:00:00.000Z”。
若要查询较早版本的表,请在语句中指定版本或时间戳SELECT。例如,若要从上述历史记录中查询版本0,请使用
SQL
%sql
SELECT * FROM events VERSION AS OF 0
or
%sql
SELECT * FROM events VERSION AS OF 0
使用DataFrameReader选项,您可以通过Delta table中一个固定的特定版本表创建DataFrame。
Python
%pyspark
df1 = spark.read.format("delta").option("timestampAsOf", timestamp_string).load("/mnt/delta/events")
df2 = spark.read.format("delta").option("versionAsOf", version).load("/mnt/delta/events")
2.3.6 优化表
对表执行多次更改后,可能会有很多小文件。为了提高读取查询的速度,可以使用OPTIMIZE将小文件折叠为较大的文件:
SQL
%sql
OPTIMIZE delta.`/mnt/delta/events`

%sql
OPTIMIZE events
2.3.7 Z-order排序
为了进一步提高读取性能,可以通过Z-Ordering在同一组文件中共同定位相关信息。Delta Lake数据跳过算法会自动使用这种共区域性来显著减少需要读取的数据量。对于Z-Order数据,您可以在子句中指定要排序的列。例如:要通过共同定位,请运行:ZORDER BY Clause
SQL
%sql
OPTIMIZE events
ZORDER BY (eventType)
2.3.8 清理快照
Delta Lake为读取提供快照隔离,这意味着即使其他用户或作业正在查询表时,也可以安全地运行OPTIMIZE。但是最终,您应该清理旧快照。您可以通过运行以下VACUUM命令来执行此操作
SQL
%sql
VACUUM events
您可以使用 RETAIN<N>HOURS 选项来控制最新快照的保留时间:
SQL
%sql
VACUUM events RETAIN 24 HOURS

3. Python notebook
3.1 Read Databricks dataset  
# Define the input format and path.
read_format = 'delta'
load_path = '/databricks-datasets/learning-spark-v2/people/people-10m.delta'
 
# Load the data from its source.
people = spark.read \
  .format(read_format) \
  .load(load_path)
 
# Show the results.
display(people)

3.2 Write out DataFrame as Databricks Delta data
# Define the output format, output mode, columns to partition by, and the output path.
write_format = 'delta'
write_mode = 'overwrite'
partition_by = 'gender'
save_path = '/mnt/delta/people-10m'
 
# Write the data to its target.
people.write \
  .format(write_format) \
  .partitionBy(partition_by) \
  .mode(write_mode) \
  .save(save_path)
  

3.3 Query the data file path
# Load the data from the save location.
people_delta = spark.read.format(read_format).load(save_path)
display(people_delta)

3.4 Create table
table_name = 'people10m'
display(spark.sql("DROP TABLE IF EXISTS " + table_name))
display(spark.sql("CREATE TABLE " + table_name + " USING DELTA LOCATION '" + save_path + "'"))

3.5 Query the table
display(spark.table(table_name).select('id', 'salary').orderBy('salary', ascending = False))

3.6 Count rows
people_delta.count()

3.7 Show partitions and contents of a partition
display(spark.sql("SHOW PARTITIONS " + table_name))
dbutils.fs.ls('dbfs:/mnt/delta/people-10m/gender=M/')

3.8 Optimize table 
display(spark.sql("OPTIMIZE " + table_name))


3.9 Show table history
display(spark.sql("DESCRIBE HISTORY " + table_name))

3.10 Show table details
display(spark.sql("DESCRIBE DETAIL " + table_name))

3.11 Show the table format
display(spark.sql("DESCRIBE FORMATTED " + table_name))

3.12 Clean up
# Delete the table.
spark.sql("DROP TABLE " + table_name)
# Delete the Delta files.
dbutils.fs.rm(save_path, True)

4.表批读写
Delta Lake支持Apache Spark DataFrame读写API提供的大多数选项,用于对表执行批量读写。
4.1 建立表格
Delta Lake支持使用DataFrameWriter(Scala/Java / Python)直接基于路径创建表。Delta Lake还支持使用标准DDL CREATE TABLE在元存储中创建表。 使用Delta Lake在元存储中创建表时,它将表数据的位置存储在元存储中。此方式使其他用户更容易发现和引用数据,而无需担心数据存储的准确位置。但是,元存储不是表中有效内容的真实来源。数据内容仍然由Delta Lake负责存储。
SQL
%sql
-- Create table in the metastore
CREATE TABLE events (
  date DATE,
  eventId STRING,
  eventType STRING,
  data STRING)
USING DELTA

Python
%pyspark
df = spark.createDataFrame([("case21", '2020-10-12', 21, 'INFO'),("case22", '2020-10-13', 22, 'INFO')], ['data', 'date', 'eventId', 'eventType'])
df.write.format("delta").saveAsTable("events2")      # create table in the metastore
df.write.format("delta").save("/mnt/delta/events3")  # create table by path

4.2 分区数据
您可以对数据进行分区以加快其谓词及分区列的查询和DML。要在创建Delta表时对数据进行分区,请按列指定分区。常见的模式是按日期划分,例如:
SQL
%sql
-- Create table in the metastore
CREATE TABLE events (
 date DATE,
 eventId STRING,
 eventType STRING,
 data STRING)
USING DELTA
PARTITIONED BY (date)
LOCATION '/mnt/delta/events'

Python
%pyspark
df = spark.createDataFrame([("case21", '2020-10-12', 21, 'INFO'),("case22", '2020-10-13', 22, 'INFO')], ['data', 'date', 'eventId', 'eventType'])
df.write.format("delta").partitionBy("date").saveAsTable("events1")      # create table in the metastore
df.write.format("delta").partitionBy("date").save("/mnt/delta/events2")  # create table by path

4.3 控制数据位置
要控制Delta表文件的位置,可以选择将LOCATION指定为OSS上的路径。与不指定路径的内部表不同,当您使用DROP表时,不会删除外部表的文件
如果运行CREATE TABLE的位置已经包含使用Delta Lake存储的数据,Delta Lake将执行以下操作:
如果只指定表名和位置,例如:

SQL
%sql
CREATE TABLE events
USING DELTA
LOCATION '/mnt/delta/events'
Hive Metastore中的表会自动继承现有数据的Schema,分区和表属性。此功能可用于将数据“导入”到元存储中。
如果指定任何配置(Schema,分区或表属性),则Delta Lake会验证该规范与现有数据的配置是否完全匹配。

4.4读取表
您可以通过指定表名或路径将Delta表加载到DataFrame中:
SQL
%sql
SELECT * FROM events   -- query table in the metastore
SELECT * FROM delta.`/mnt/delta/events`  -- query table by path
Python
%pyspark
spark.table("events")    # query table in the metastore
spark.read.format("delta").load("/mnt/delta/events")   # query table by path
返回的DataFrame会自动读取表中之前查询结果的最新快照;您不需要运行REFRESH TABLE。当查询中有适用的谓词时,Delta-Lake会自动使用分区和统计来读取最小数量的数据。

4.5 查询表的旧快照(按时间顺序查看)
Delta Lake按时间顺序查看允许您查询Delta表的旧快照。按时间顺序查看有很多用例,包括:
重新创建分析,报告或输出(例如,机器学习模型的输出)。这对于排查问题或者审计尤其有用,特别是在管控行业中。
编写复杂的时间查询。
修正数据中的错误。
在快速变更的表中,提供一系列的快照查询功能。
本节介绍了旧版本表和数据保存相关的查询方法,并提供了示例。
语法
本节说明如何查询较旧版sql本的Delta表。
SQL AS OF 语法
%sql
SELECT * FROM  
events 
 TIMESTAMP AS OF timestamp_expression
SELECT * FROM events VERSION AS OF version

4.6 数据保存
默认情况下,Delta表将提交历史记录保留30天。这意味着您可以声明一个30天以内的版本。但是,有以下注意事项:
您没有在Delta表上运行VACUUM。如果运行VACUUM,您将无法恢复到默认的7天数据保留期之前的版本。
您可以使用以下表属性来配置保留期:
delta.logRetentionDuration = "interval <interval>":控制表的历史记录保留时间长度。每次写入一个检查点时,Databricks都会自动清除早于保留间隔的日志条目。如果将此配置设置为足够大的值,则会保留许多日志条目。这不会影响性能,因为针对日志的操作恒定时间。历史记录的操作是并行的(但是随着日志大小的增加,它将变得更加昂贵)。默认值为interval 30 days
delta.deletedFileRetentionDuration = "interval <interval>":控制选择的文件必须选择时间段,默认值为间隔7天。若要访问 30 天的历史数据,请设置 delta.deletedFileRetentionDuration = "interval 30 days"。此设置可能会导致您的存储成本上升。
注意 VACUUM 不清理日志文件;写入检查点后,日志文件将自动清除。
按时间顺序查看到以前的版本,必须保留日志文件和该版本的数据文件。
案例
修复用户111表的意外删除问题:
SQL
%sql
INSERT INTO my_table
  SELECT * FROM my_table TIMESTAMP AS OF date_sub(current_date(), 1)
  WHERE userId = 111
修复对表的意外错误更新:
SQL
%sql
MERGE INTO my_table target
  USING my_table TIMESTAMP AS OF date_sub(current_date(), 1) source
  ON source.userId = target.userId
  WHEN MATCHED THEN UPDATE SET *
查询过去一周增加的新客户数量。

SQL
%sql
SELECT count(distinct userId) - (
  SELECT count(distinct userId)
  FROM my_table TIMESTAMP AS OF date_sub(current_date(), 7))

4.7 写入表格
4.7.1 追加:使用append模式,可以将新数据以原子的方式添加到现有的Delta表中:
SQL
%sql
INSERT INTO events SELECT * FROM newEvents
Python
%pyspark
df.write.format("delta").mode("append").save("/mnt/delta/events")
df.write.format("delta").mode("append").saveAsTable("events")
4.7.2 覆盖:要原子式地替换表中的所有数据,可以使用overwrite模式:
SQL
%sql
INSERT OVERWRITE TABLE events SELECT * FROM newEvents
插入表
Python
%pyspark
from pyspark.sql.functions import  to_date
df = spark.createDataFrame([("case21", '2020-10-12', 23, 'INFO'),("case22", '2020-10-13', 24, 'INFO')], ['data', 'date', 'eventId', 'eventType'])
df1 = df.select('data', to_date('date', 'yyyy-MM-dd').alias('date'), 'eventId', 'eventType')
df1.write.format("delta").mode("append").save("/mnt/delta/events")
df1.write.format("delta").mode("append").saveAsTable("events")
4.8 Schema验证
Delta Lake自动验证正在写入的DataFrame的Schema与表的Schema兼容。Delta Lake使用以下规则来确定从DataFrame到表的写入是否兼容:
所有DataFrame列都必须存在于目标表中。如DataFrame中有表中不存在列,则会抛出异常。表中存在但DataFrame中不存在的列设置为NULL。
DataFrame列数据类型必须与目标表中的列数据类型匹配。如果它们不匹配,则会抛出异常。
DataFrame列名称不能仅通过大小写来区分。这意味着您不能在同一表中定义诸如“ Foo”和“ foo”之类的列。虽然可以在区分大小写或不区分大小写(默认)模式下使用Spark,但在存储和返回列信息时,Parquet区分大小写。在存储Schema时,Delta Lake 保留但不区分大小写,并采用此限制来避免潜在的错误、数据损坏或丢失问题。
Delta Lake支持DDL显式添加新列,并具有自动更新Schema的功能。
如果您指定其他选项(例如partitionBy与附加模式结合使用),则Delta Lake会验证它们是否匹配,并在任何不匹配项时发生错误。如果分区不存在,会在对现有数据分区之后自动进行追加。

5.表流读写
Delta Lake通过readStream和writeStream与Spark结构化流式处理深度集成。Delta Lake克服了许多流式处理系统和文件相关的常见限制,例如:
合并低延迟引入产生的小文件
保持多个流(或并发批处理作业)执行“仅一次”处理
使用文件作为流源时,可以有效地发现哪些文件是新文件

5.1 Delta表作为流源
当您将Delta表加载为流源并在流式查询中使用它时,该查询将处理表中存在的所有数据以及流启动后到达的所有新数据。
您可以将路径和表都作为流加载。
Scala
%spark
spark.readStream.format("delta").load("/mnt/delta/events")
通过设置maxFilesPerTrigger选项,控制Delta Lake提供给流的任何微批处理的最大大小。这指定了每个触发器中要考虑的新文件的最大数量。默认值为1000。
通过设置maxBytesPerTrigger选项来限制每个微批处理的数据量的速率。这将设置一个“软最大值”,这意味着批处理大约此数量的数据,并可能处理超过该限制的数据量。如果你使用Trigger。如果Trigger.Once用于流式传输,则忽略此选项。如果将此选项与maxFilesPerTrigger结合使用,则微批处理将处理数据,直到达到maxFilesPerTrigger或maxBytesPerTrigger限制。

5.2 忽略更新和删除
结构化流式处理不处理非追加的输入,如果在用作源的表上进行了任何修改,则引发异常。有两种主要策略可以处理无法自动向下游传播的更改:
您可以删除输出和检查点,并从头开始重新启动流。
您可以设置以下两个选项之一:
ignoreDeletes:忽略在分区边界删除数据的事务。
ignoreChanges:如果由于更新、合并、删除(在分区内)或覆盖等数据更改操作而必须重写源表中的文件,则重新处理更新。未更改的行可能仍会发出,因此您的下游使用者应该能够处理重复项。删除不会传播到下游。ignoreChanges包含ignoreDeletes。因此,如果使用ignoreChanges,则源表的删除或更新不会中断流。
Scala
%spark
events.readStream
  .format("delta")
  .option("ignoreDeletes", "true")
  .load("/mnt/delta/user_events")

5.3 指定初始位置
您可以使用以下选项来指定Delta Lake流式处理源的起点,而无需处理整个表。
startingVersion:Delta Lake版本开始。从该版本(包括该版本)开始的所有表更改都将由流式处理源读取。您可以从命令“ DESCRIBE HISTORY events”输出的version列中获取提交版本。
startingTimestamp:开始的时间戳。流式处理源将读取在时间戳(包括时间戳)或之后提交的所有表更改。可以是以下任何一项:
'2018-10-18T22:15:12.013Z',即可以转换为时间戳的字符串
cast('2018-10-18 13:36:32 CEST' as timestamp)
'2018-10-18',即日期字符串
本身就是时间戳或可强制转换为时间的任何其他表达式,如:current_timestamp() - interval 12 hour sdate_sub(current_date(), 1)

您不能同时设置两个选项。您只需要使用其中之一个选项即可。
%spark
events.readStream
  .format("delta")
  .option("startingVersion", "5")
  .load("/mnt/delta/user_events")

%spark
events.readStream
  .format("delta")
  .option("startingTimestamp", "2018-10-18")
  .load("/mnt/delta/user_events")
  
5.4 用作接收器的Delta表
您也可以使用结构化流将数据写入Delta表。事务日志使Delta Lake能够保证"仅一次"处理,即使针对该表同时运行其他流或批查询。
5.4.1 追加模式
默认情况下,流以追加模式运行,这会将新记录添加到表中。
您可以使用路径方法:
Python
%pyspark
events.writeStream
  .format("delta")
  .outputMode("append")
  .option("checkpointLocation", "/delta/events/_checkpoints/etl-from-json")
  .start("/delta/events")
5.4.2 完整模式
您还可以使用结构化流式处理技术将整个批替换为每个批。一个示例用例是使用聚合来计算摘要:
Scala
%spark
spark.readStream
  .format("delta")
  .load("/mnt/delta/events")
  .groupBy("customerId")
  .count()
  .writeStream
  .format("delta")
  .outputMode("complete")
  .option("checkpointLocation", "/mnt/delta/eventsByCustomer/_checkpoints/streaming-agg")
  .start("/mnt/delta/eventsByCustomer")

6.表删除,更新和合并1.Delta Lake 简介
Delta Lake 是可提高数据湖可靠性的开源存储层。Delta Lake 提供 ACID 事务和可缩放的元数据处理,并统一流和批数据处理。 Delta Lake 在现有 Data Lake 的顶层运行,与 Apache Spark API 完全兼容。

具体来说,Delta Lake提供:

Spark ACID事务:可序列化的隔离级别确保用户永远不会看到不一致的数据。

可扩展的元数据处理:利用Spark的分布式处理能力,可以轻松处理数十亿个文件的PB级表的所有元数据。

流式处理和批处理统一:Delta Lake中的表既是批处理表,又是也是流式处理源和接收器。流式处理数据引入、批处理历史回填、交互式查询功能都是现成的。

架构强制:自动处理架构变体,以防在引入过程中插入错误的记录。

按时间顺序查看:数据版本控制支持回滚、完整的历史审核线索和可重现的机器学习试验

更新插入和删除:支持合并、更新和删除操作,以启用复杂用例,如更改数据捕获、渐变维度 (SCD) 操作、流式处理更新插入等。

Delta Engine 优化使 Delta Lake 操作具有高性能,并支持各种工作负载,从大规模 ETL 处理到临时交互式查询均可。有关Delta Engine的信息,请参阅Delta Engine的相关文档。
2.Delta Lake快速入门概述了使用Delta Lake的基础知识。此快速入门演示如何生成管道,以便将JSON数据读入Delta表、修改表、读取表、显示表历史记录,以及优化表。
2.1创建表
若要创建一个delta表,可以使用现有的Apache Spark SQL代码,也可以将parquet、csv、json等数据格式转换为delta。
对于所有文件类型,您将文件读入DataFrame并将格式转为delta
%pyspark
events = spark.read.json("/xz/events_data.json")
events.write.format("delta").save("/xz/delta/events")
spark.sql("CREATE TABLE events USING DELTA LOCATION '/xz/delta/events/'")
SQL
%sql
CREATE TABLE events
USING delta
AS SELECT *
FROM json.`/data/events/`
如果源文件是Parquet格式,则可以使用SQL Convert to Delta语句就地转换文件,以创建非托管表
SQL
%sql
CONVERT TO DELTA parquet.`/mnt/delta/events`

2.2分区数据
要加快包含涉及分区列的谓词查询,可以对数据进行分区。
Python
%pyspark
events = spark.read.json("/databricks-datasets/structured-streaming/events/")
events.write.partitionBy("date").format("delta").save("/mnt/delta/events")
spark.sql("CREATE TABLE events USING DELTA LOCATION '/mnt/delta/events/'")
SQL
%sql
CREATE TABLE events (
  date DATE,
  eventId STRING,
  eventType STRING,
  data STRING)
USING delta
PARTITIONED BY (date)
2.3 修改表格
Delta Lake支持一组丰富的操作来修改表。
2.3.1 对流写入表
您可以使用结构化流式处理将数据写入Delta表。即使有其他流或批查询同时运行表,Delta Lake事务日志也可以保证一次性处理。默认情况下,流在附加模式下运行,这会将新记录添加到表中。
Python
%pyspark
from pyspark.sql.types import *
inputPath = "/databricks-datasets/structured-streaming/events/"
jsonSchema = StructType([ StructField("time", TimestampType(), True), StructField("action", StringType(), True) ])
eventsDF = (
  spark
    .readStream
    .schema(jsonSchema) # Set the schema of the JSON data
    .option("maxFilesPerTrigger", 1) # Treat a sequence of files as a stream by picking one file at a time
    .json(inputPath)
)

(eventsDF.writeStream
  .outputMode("append")
  .option("checkpointLocation", "/mnt/delta/events/_checkpoints/etl-from-json")
  .table("events")
)

2.3.2批处理更新

要将一组更新和插入合并到现有表中,请使用merge-into语句。例如,以下语句获取更新流并将其合并到表中。当已经存在具有相同事件ID的事件时,Delta Lake将使用给定的表达式更新数据列。如果没有匹配事件时,Delta Lake将添加一个新行。
SQL
%sql
MERGE INTO events
USING updates
ON events.eventId = updates.eventId
WHEN MATCHED THEN
  UPDATE SET
    events.data = updates.data
WHEN NOT MATCHED
  THEN INSERT (date, eventId, data) VALUES (date, eventId, data)
执行INSERT时(例如,当现有数据集中没有匹配的行时),必须为表中的每一列指定一个值。但是,您不需要更新所有值。
2.3.3读一个表
在这个部分:
显示表格历史记录
查询表的早期版本(时间行程)
您可以通过在DBFS("/mnt/delta/events")或表名("event")上指定路径来访问Delta表中的数据:
Scala
%spark
SELECT * FROM delta.`/mnt/delta/events`

%spark
val events = spark.table("events")
SQL
%sql
SELECT * FROM delta.`/mnt/delta/events`

%sql
SELECT * FROM events
2.3.4 显示表的历史记录
使用DESCRIBE HISTORY语句,查看表的历史记录。该语句提供每次为写入表出处信息,包括表版本、操作、用户等。检索增量表历史记录
2.3.5 查询表的早期版本(按时间顺序查询)
使用Delta Lake时间行程,您可以查询Delta表的旧快照。
对于timestamp_string,只接受日期或时间戳字符串。例如,“2019-01-01”和“2019-01-01'T'00:00:00.000Z”。
若要查询较早版本的表,请在语句中指定版本或时间戳SELECT。例如,若要从上述历史记录中查询版本0,请使用
SQL
%sql
SELECT * FROM events VERSION AS OF 0
or
%sql
SELECT * FROM events VERSION AS OF 0
使用DataFrameReader选项,您可以通过Delta table中一个固定的特定版本表创建DataFrame。
Python
%pyspark
df1 = spark.read.format("delta").option("timestampAsOf", timestamp_string).load("/mnt/delta/events")
df2 = spark.read.format("delta").option("versionAsOf", version).load("/mnt/delta/events")
2.3.6 优化表
对表执行多次更改后,可能会有很多小文件。为了提高读取查询的速度,可以使用OPTIMIZE将小文件折叠为较大的文件:
SQL
%sql
OPTIMIZE delta.`/mnt/delta/events`

%sql
OPTIMIZE events
2.3.7 Z-order排序
为了进一步提高读取性能,可以通过Z-Ordering在同一组文件中共同定位相关信息。Delta Lake数据跳过算法会自动使用这种共区域性来显著减少需要读取的数据量。对于Z-Order数据,您可以在子句中指定要排序的列。例如:要通过共同定位,请运行:ZORDER BY Clause
SQL
%sql
OPTIMIZE events
ZORDER BY (eventType)
2.3.8 清理快照
Delta Lake为读取提供快照隔离,这意味着即使其他用户或作业正在查询表时,也可以安全地运行OPTIMIZE。但是最终,您应该清理旧快照。您可以通过运行以下VACUUM命令来执行此操作
SQL
%sql
VACUUM events
您可以使用 RETAIN<N>HOURS 选项来控制最新快照的保留时间:
SQL
%sql
VACUUM events RETAIN 24 HOURS

3. Python notebook
3.1 Read Databricks dataset  
# Define the input format and path.
read_format = 'delta'
load_path = '/databricks-datasets/learning-spark-v2/people/people-10m.delta'
 
# Load the data from its source.
people = spark.read \
  .format(read_format) \
  .load(load_path)
 
# Show the results.
display(people)

3.2 Write out DataFrame as Databricks Delta data
# Define the output format, output mode, columns to partition by, and the output path.
write_format = 'delta'
write_mode = 'overwrite'
partition_by = 'gender'
save_path = '/mnt/delta/people-10m'
 
# Write the data to its target.
people.write \
  .format(write_format) \
  .partitionBy(partition_by) \
  .mode(write_mode) \
  .save(save_path)
  

3.3 Query the data file path
# Load the data from the save location.
people_delta = spark.read.format(read_format).load(save_path)
display(people_delta)

3.4 Create table
table_name = 'people10m'
display(spark.sql("DROP TABLE IF EXISTS " + table_name))
display(spark.sql("CREATE TABLE " + table_name + " USING DELTA LOCATION '" + save_path + "'"))

3.5 Query the table
display(spark.table(table_name).select('id', 'salary').orderBy('salary', ascending = False))

3.6 Count rows
people_delta.count()

3.7 Show partitions and contents of a partition
display(spark.sql("SHOW PARTITIONS " + table_name))
dbutils.fs.ls('dbfs:/mnt/delta/people-10m/gender=M/')

3.8 Optimize table 
display(spark.sql("OPTIMIZE " + table_name))


3.9 Show table history
display(spark.sql("DESCRIBE HISTORY " + table_name))

3.10 Show table details
display(spark.sql("DESCRIBE DETAIL " + table_name))

3.11 Show the table format
display(spark.sql("DESCRIBE FORMATTED " + table_name))

3.12 Clean up
# Delete the table.
spark.sql("DROP TABLE " + table_name)
# Delete the Delta files.
dbutils.fs.rm(save_path, True)

4.表批读写
Delta Lake支持Apache Spark DataFrame读写API提供的大多数选项,用于对表执行批量读写。
4.1 建立表格
Delta Lake支持使用DataFrameWriter(Scala/Java / Python)直接基于路径创建表。Delta Lake还支持使用标准DDL CREATE TABLE在元存储中创建表。 使用Delta Lake在元存储中创建表时,它将表数据的位置存储在元存储中。此方式使其他用户更容易发现和引用数据,而无需担心数据存储的准确位置。但是,元存储不是表中有效内容的真实来源。数据内容仍然由Delta Lake负责存储。
SQL
%sql
-- Create table in the metastore
CREATE TABLE events (
  date DATE,
  eventId STRING,
  eventType STRING,
  data STRING)
USING DELTA

Python
%pyspark
df = spark.createDataFrame([("case21", '2020-10-12', 21, 'INFO'),("case22", '2020-10-13', 22, 'INFO')], ['data', 'date', 'eventId', 'eventType'])
df.write.format("delta").saveAsTable("events2")      # create table in the metastore
df.write.format("delta").save("/mnt/delta/events3")  # create table by path

4.2 分区数据
您可以对数据进行分区以加快其谓词及分区列的查询和DML。要在创建Delta表时对数据进行分区,请按列指定分区。常见的模式是按日期划分,例如:
SQL
%sql
-- Create table in the metastore
CREATE TABLE events (
 date DATE,
 eventId STRING,
 eventType STRING,
 data STRING)
USING DELTA
PARTITIONED BY (date)
LOCATION '/mnt/delta/events'

Python
%pyspark
df = spark.createDataFrame([("case21", '2020-10-12', 21, 'INFO'),("case22", '2020-10-13', 22, 'INFO')], ['data', 'date', 'eventId', 'eventType'])
df.write.format("delta").partitionBy("date").saveAsTable("events1")      # create table in the metastore
df.write.format("delta").partitionBy("date").save("/mnt/delta/events2")  # create table by path

4.3 控制数据位置
要控制Delta表文件的位置,可以选择将LOCATION指定为OSS上的路径。与不指定路径的内部表不同,当您使用DROP表时,不会删除外部表的文件
如果运行CREATE TABLE的位置已经包含使用Delta Lake存储的数据,Delta Lake将执行以下操作:
如果只指定表名和位置,例如:

SQL
%sql
CREATE TABLE events
USING DELTA
LOCATION '/mnt/delta/events'
Hive Metastore中的表会自动继承现有数据的Schema,分区和表属性。此功能可用于将数据“导入”到元存储中。
如果指定任何配置(Schema,分区或表属性),则Delta Lake会验证该规范与现有数据的配置是否完全匹配。

4.4读取表
您可以通过指定表名或路径将Delta表加载到DataFrame中:
SQL
%sql
SELECT * FROM events   -- query table in the metastore
SELECT * FROM delta.`/mnt/delta/events`  -- query table by path
Python
%pyspark
spark.table("events")    # query table in the metastore
spark.read.format("delta").load("/mnt/delta/events")   # query table by path
返回的DataFrame会自动读取表中之前查询结果的最新快照;您不需要运行REFRESH TABLE。当查询中有适用的谓词时,Delta-Lake会自动使用分区和统计来读取最小数量的数据。

4.5 查询表的旧快照(按时间顺序查看)
Delta Lake按时间顺序查看允许您查询Delta表的旧快照。按时间顺序查看有很多用例,包括:
重新创建分析,报告或输出(例如,机器学习模型的输出)。这对于排查问题或者审计尤其有用,特别是在管控行业中。
编写复杂的时间查询。
修正数据中的错误。
在快速变更的表中,提供一系列的快照查询功能。
本节介绍了旧版本表和数据保存相关的查询方法,并提供了示例。
语法
本节说明如何查询较旧版sql本的Delta表。
SQL AS OF 语法
%sql
SELECT * FROM  
events 
 TIMESTAMP AS OF timestamp_expression
SELECT * FROM events VERSION AS OF version

4.6 数据保存
默认情况下,Delta表将提交历史记录保留30天。这意味着您可以声明一个30天以内的版本。但是,有以下注意事项:
您没有在Delta表上运行VACUUM。如果运行VACUUM,您将无法恢复到默认的7天数据保留期之前的版本。
您可以使用以下表属性来配置保留期:
delta.logRetentionDuration = "interval <interval>":控制表的历史记录保留时间长度。每次写入一个检查点时,Databricks都会自动清除早于保留间隔的日志条目。如果将此配置设置为足够大的值,则会保留许多日志条目。这不会影响性能,因为针对日志的操作恒定时间。历史记录的操作是并行的(但是随着日志大小的增加,它将变得更加昂贵)。默认值为interval 30 days
delta.deletedFileRetentionDuration = "interval <interval>":控制选择的文件必须选择时间段,默认值为间隔7天。若要访问 30 天的历史数据,请设置 delta.deletedFileRetentionDuration = "interval 30 days"。此设置可能会导致您的存储成本上升。
注意 VACUUM 不清理日志文件;写入检查点后,日志文件将自动清除。
按时间顺序查看到以前的版本,必须保留日志文件和该版本的数据文件。
案例
修复用户111表的意外删除问题:
SQL
%sql
INSERT INTO my_table
  SELECT * FROM my_table TIMESTAMP AS OF date_sub(current_date(), 1)
  WHERE userId = 111
修复对表的意外错误更新:
SQL
%sql
MERGE INTO my_table target
  USING my_table TIMESTAMP AS OF date_sub(current_date(), 1) source
  ON source.userId = target.userId
  WHEN MATCHED THEN UPDATE SET *
查询过去一周增加的新客户数量。

SQL
%sql
SELECT count(distinct userId) - (
  SELECT count(distinct userId)
  FROM my_table TIMESTAMP AS OF date_sub(current_date(), 7))

4.7 写入表格
4.7.1 追加:使用append模式,可以将新数据以原子的方式添加到现有的Delta表中:
SQL
%sql
INSERT INTO events SELECT * FROM newEvents
Python
%pyspark
df.write.format("delta").mode("append").save("/mnt/delta/events")
df.write.format("delta").mode("append").saveAsTable("events")
4.7.2 覆盖:要原子式地替换表中的所有数据,可以使用overwrite模式:
SQL
%sql
INSERT OVERWRITE TABLE events SELECT * FROM newEvents
插入表
Python
%pyspark
from pyspark.sql.functions import  to_date
df = spark.createDataFrame([("case21", '2020-10-12', 23, 'INFO'),("case22", '2020-10-13', 24, 'INFO')], ['data', 'date', 'eventId', 'eventType'])
df1 = df.select('data', to_date('date', 'yyyy-MM-dd').alias('date'), 'eventId', 'eventType')
df1.write.format("delta").mode("append").save("/mnt/delta/events")
df1.write.format("delta").mode("append").saveAsTable("events")
4.8 Schema验证
Delta Lake自动验证正在写入的DataFrame的Schema与表的Schema兼容。Delta Lake使用以下规则来确定从DataFrame到表的写入是否兼容:
所有DataFrame列都必须存在于目标表中。如DataFrame中有表中不存在列,则会抛出异常。表中存在但DataFrame中不存在的列设置为NULL。
DataFrame列数据类型必须与目标表中的列数据类型匹配。如果它们不匹配,则会抛出异常。
DataFrame列名称不能仅通过大小写来区分。这意味着您不能在同一表中定义诸如“ Foo”和“ foo”之类的列。虽然可以在区分大小写或不区分大小写(默认)模式下使用Spark,但在存储和返回列信息时,Parquet区分大小写。在存储Schema时,Delta Lake 保留但不区分大小写,并采用此限制来避免潜在的错误、数据损坏或丢失问题。
Delta Lake支持DDL显式添加新列,并具有自动更新Schema的功能。
如果您指定其他选项(例如partitionBy与附加模式结合使用),则Delta Lake会验证它们是否匹配,并在任何不匹配项时发生错误。如果分区不存在,会在对现有数据分区之后自动进行追加。

5.表流读写
Delta Lake通过readStream和writeStream与Spark结构化流式处理深度集成。Delta Lake克服了许多流式处理系统和文件相关的常见限制,例如:
合并低延迟引入产生的小文件
保持多个流(或并发批处理作业)执行“仅一次”处理
使用文件作为流源时,可以有效地发现哪些文件是新文件

5.1 Delta表作为流源
当您将Delta表加载为流源并在流式查询中使用它时,该查询将处理表中存在的所有数据以及流启动后到达的所有新数据。
您可以将路径和表都作为流加载。
Scala
%spark
spark.readStream.format("delta").load("/mnt/delta/events")
通过设置maxFilesPerTrigger选项,控制Delta Lake提供给流的任何微批处理的最大大小。这指定了每个触发器中要考虑的新文件的最大数量。默认值为1000。
通过设置maxBytesPerTrigger选项来限制每个微批处理的数据量的速率。这将设置一个“软最大值”,这意味着批处理大约此数量的数据,并可能处理超过该限制的数据量。如果你使用Trigger。如果Trigger.Once用于流式传输,则忽略此选项。如果将此选项与maxFilesPerTrigger结合使用,则微批处理将处理数据,直到达到maxFilesPerTrigger或maxBytesPerTrigger限制。

5.2 忽略更新和删除
结构化流式处理不处理非追加的输入,如果在用作源的表上进行了任何修改,则引发异常。有两种主要策略可以处理无法自动向下游传播的更改:
您可以删除输出和检查点,并从头开始重新启动流。
您可以设置以下两个选项之一:
ignoreDeletes:忽略在分区边界删除数据的事务。
ignoreChanges:如果由于更新、合并、删除(在分区内)或覆盖等数据更改操作而必须重写源表中的文件,则重新处理更新。未更改的行可能仍会发出,因此您的下游使用者应该能够处理重复项。删除不会传播到下游。ignoreChanges包含ignoreDeletes。因此,如果使用ignoreChanges,则源表的删除或更新不会中断流。
Scala
%spark
events.readStream
  .format("delta")
  .option("ignoreDeletes", "true")
  .load("/mnt/delta/user_events")

5.3 指定初始位置
您可以使用以下选项来指定Delta Lake流式处理源的起点,而无需处理整个表。
startingVersion:Delta Lake版本开始。从该版本(包括该版本)开始的所有表更改都将由流式处理源读取。您可以从命令“ DESCRIBE HISTORY events”输出的version列中获取提交版本。
startingTimestamp:开始的时间戳。流式处理源将读取在时间戳(包括时间戳)或之后提交的所有表更改。可以是以下任何一项:
'2018-10-18T22:15:12.013Z',即可以转换为时间戳的字符串
cast('2018-10-18 13:36:32 CEST' as timestamp)
'2018-10-18',即日期字符串
本身就是时间戳或可强制转换为时间的任何其他表达式,如:current_timestamp() - interval 12 hour sdate_sub(current_date(), 1)

您不能同时设置两个选项。您只需要使用其中之一个选项即可。
%spark
events.readStream
  .format("delta")
  .option("startingVersion", "5")
  .load("/mnt/delta/user_events")

%spark
events.readStream
  .format("delta")
  .option("startingTimestamp", "2018-10-18")
  .load("/mnt/delta/user_events")
  
5.4 用作接收器的Delta表
您也可以使用结构化流将数据写入Delta表。事务日志使Delta Lake能够保证"仅一次"处理,即使针对该表同时运行其他流或批查询。
5.4.1 追加模式
默认情况下,流以追加模式运行,这会将新记录添加到表中。
您可以使用路径方法:
Python
%pyspark
events.writeStream
  .format("delta")
  .outputMode("append")
  .option("checkpointLocation", "/delta/events/_checkpoints/etl-from-json")
  .start("/delta/events")
5.4.2 完整模式
您还可以使用结构化流式处理技术将整个批替换为每个批。一个示例用例是使用聚合来计算摘要:
Scala
%spark
spark.readStream
  .format("delta")
  .load("/mnt/delta/events")
  .groupBy("customerId")
  .count()
  .writeStream
  .format("delta")
  .outputMode("complete")
  .option("checkpointLocation", "/mnt/delta/eventsByCustomer/_checkpoints/streaming-agg")
  .start("/mnt/delta/eventsByCustomer")

6.表删除,更新和合并

猜你喜欢

转载自blog.csdn.net/victory0508/article/details/122042341