目录

简介

SQL

数据集和数据框

入门

起点:SparkSession

Scala语言

Java语言

Python语言

R语言

创建DataFrame

Scala语言

Java语言

Python语言

R语言

未类型化的数据集操作(也称为DataFrame操作)

Scala语言

Java语言

Python语言

R语言

以编程方式运行SQL查询

Scala语言

Java语言

Python语言

R语言

全局临时视图

Scala语言

Java语言

Python语言

SQL

创建DataSet

Scala语言

Java语言

与RDD互操作

使用反射推断架构

以编程方式指定架构

标量函数

汇总功能

数据源

性能调优

在内存中缓存数据

其他配置选项

加入针对SQL查询的策略提示

Scala语言

Java语言

Python语言

R语言

SQL

SQL查询的合并提示

自适应查询执行

合并后的Shuffle分区

将排序合并联接转换为广播联接

优化偏斜连接

分布式SQL引擎

运行Thrift JDBC / ODBC服务器

运行Spark SQL CLI

PySpark使用Apache Arrow的熊猫使用指南

PySpark中的Apache Arrow

确保已安装PyArrow

启用与熊猫之间的转换

熊猫UDF(又名矢量化UDF)

系列到系列

系列迭代器到系列迭代器

多个系列的迭代器到系列的迭代器

系列到标量

熊猫函数API

分组Map

Map操作

共同Map

使用说明

支持的SQL类型

设置箭头批处理大小

带时区语义的时间戳

推荐的Pandas和PyArrow版本

PyArrow> = 0.15.0和Spark 2.3.x,2.4.x的兼容性设置

迁移指南

SQL参考


简介

Spark SQL是用于结构化数据处理的Spark模块。与基本的Spark RDD API不同,Spark SQL提供的接口为Spark提供了有关数据结构和正在执行的计算的更多信息。在内部,Spark SQL使用这些额外的信息来执行额外的优化。与Spark SQL交互的方法有几种,包括SQL和Dataset API。计算结果时,将使用相同的执行引擎,而与要用来表达计算的API /语言无关。这种统一意味着开发人员可以轻松地在不同的API之间来回切换,从而提供最自然的方式来表达给定的转换。

此页面上的所有示例均使用Spark发行版中包含的示例数据,并且可以在spark-shellpysparkshell或sparkRshell中运行。

SQL

Spark SQL的一种用途是执行SQL查询。Spark SQL还可以用于从现有的Hive安装中读取数据。有关如何配置此功能的更多信息,请参考Hive Tables部分。从另一种编程语言运行SQL时,结果将作为Dataset / DataFrame返回。您还可以使用命令行 或通过JDBC / ODBC与SQL接口进行交互。

数据集和数据框

数据集是数据的分布式集合。数据集是Spark 1.6中添加的新接口,它具有RDD的优点(强类型输入,使用强大的Lambda函数的能力)以及Spark SQL的优化执行引擎的优点。数据集可以被构造从JVM对象,然后使用功能性的转换(操作mapflatMapfilter等等)。Dataset API在Scala和 Java中可用。Python不支持Dataset API。但是由于Python的动态特性,Dataset API的许多优点已经可用(即,您可以自然地通过名称访问行的字段 row.columnName)。R的情况类似。

DataFrame是组织为命名列的数据集。从概念上讲,它等效于关系数据库中的表或R / Python中的数据框,但是在后台进行了更丰富的优化。可以从多种资源构造DataFrame,例如:结构化数据文件,Hive中的表,外部数据库或现有的RDD。DataFrame API在Scala,Java,Python和R中可用。在Scala和Java中,DataFrame由的数据集表示Row。在Scala API中,DataFrame只是类型别名Dataset[Row]。而在Java API中,用户需要使用Dataset<Row>来代表DataFrame

在整个文档中,我们通常将的Scala / Java数据集Row称为DataFrames。

入门

起点:SparkSession

Scala语言

SparkSession类是Spark中所有功能的入口点。要创建一个基本的SparkSession,只需使用SparkSession.builder()

import org.apache.spark.sql.SparkSessionval spark = SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option", "some-value").getOrCreate()// For implicit conversions like converting RDDs to DataFrames
import spark.implicits._

在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

SparkSessionSpark 2.0中的内置支持Hive功能,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表读取数据的功能。要使用这些功能,您不需要现有的Hive设置。

Java语言

SparkSession类是Spark中所有功能的入口点。要创建一个基本的SparkSession,只需使用SparkSession.builder()

import org.apache.spark.sql.SparkSession;SparkSession spark = SparkSession.builder().appName("Java Spark SQL basic example").config("spark.some.config.option", "some-value").getOrCreate();

在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

SparkSessionSpark 2.0中的内置支持Hive功能,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表读取数据的功能。要使用这些功能,您不需要现有的Hive设置。

Python语言

SparkSession类是Spark中所有功能的入口点。要创建一个基本的SparkSession,只需使用SparkSession.builder

from pyspark.sql import SparkSessionspark = SparkSession \.builder \.appName("Python Spark SQL basic example") \.config("spark.some.config.option", "some-value") \.getOrCreate()

在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

SparkSessionSpark 2.0中的内置支持Hive功能,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表读取数据的功能。要使用这些功能,您不需要现有的Hive设置。

R语言

SparkSession类是Spark中所有功能的入口点。要初始化一个基本的SparkSession,只需调用sparkR.session()

sparkR.session(appName = "R Spark SQL basic example", sparkConfig = list(spark.some.config.option = "some-value"))

在Spark存储库中的“ examples / src / main / r / RSparkSQLExample.R”中找到完整的示例代码。

请注意,首次调用时,sparkR.session()将初始化全局SparkSession单例实例,并始终为连续调用返回对此实例的引用。这样,用户只需初始化SparkSession一次,然后SparkR之类的函数read.df就可以隐式访问此全局实例,并且用户无需传递该SparkSession实例。

SparkSessionSpark 2.0中的内置支持Hive功能,包括使用HiveQL编写查询,访问Hive UDF以及从Hive表读取数据的功能。要使用这些功能,您不需要现有的Hive设置。

创建DataFrame

Scala语言

使用SparkSession,应用程序可以从现有的RDD,Hive表的或Spark数据源创建DataFrame 。

例如,以下内容基于JSON文件的内容创建一个DataFrame:

val df = spark.read.json("examples/src/main/resources/people.json")// Displays the content of the DataFrame to stdout
df.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

Java语言

使用SparkSession,应用程序可以从现有的RDD,Hive表的或Spark数据源创建DataFrame 。

例如,以下内容基于JSON文件的内容创建一个DataFrame:

import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;Dataset<Row> df = spark.read().json("examples/src/main/resources/people.json");// Displays the content of the DataFrame to stdout
df.show();
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

Python语言

使用SparkSession,应用程序可以从现有的RDD,Hive表的或Spark数据源创建DataFrame 。

例如,以下内容基于JSON文件的内容创建一个DataFrame:

# spark is an existing SparkSession
df = spark.read.json("examples/src/main/resources/people.json")
# Displays the content of the DataFrame to stdout
df.show()
# +----+-------+
# | age|   name|
# +----+-------+
# |null|Michael|
# |  30|   Andy|
# |  19| Justin|
# +----+-------+
在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

R语言

使用SparkSession,应用程序可以从本地R data.frame,Hive表或Spark数据源创建DataFrame 。

例如,以下内容基于JSON文件的内容创建一个DataFrame:

df <- read.json("examples/src/main/resources/people.json")# Displays the content of the DataFrame
head(df)
##   age    name
## 1  NA Michael
## 2  30    Andy
## 3  19  Justin# Another method to print the first few rows and optionally truncate the printing of long values
showDF(df)
## +----+-------+
## | age|   name|
## +----+-------+
## |null|Michael|
## |  30|   Andy|
## |  19| Justin|
## +----+-------+
在Spark存储库中的“ examples / src / main / r / RSparkSQLExample.R”中找到完整的示例代码。

未类型化的数据集操作(也称为DataFrame操作)

DataFrames为Scala,Java,Python和R中的结构化数据操作提供了一种特定于域的语言。

如上所述,在Spark 2.0中,DataFrames只是RowScala和Java API中的的数据集。与强类型的Scala / Java数据集附带的“类型转换”相反,这些操作也称为“非类型转换”。

这里我们包括一些使用数据集进行结构化数据处理的基本示例:

Scala语言

// This import is needed to use the $-notation
import spark.implicits._
// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)// Select only the "name" column
df.select("name").show()
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+// Select everybody, but increment the age by 1
df.select($"name", $"age" + 1).show()
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+// Select people older than 21
df.filter($"age" > 21).show()
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+// Count people by age
df.groupBy("age").count().show()
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+
在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

有关可对数据集执行的操作类型的完整列表,请参阅API文档。

除了简单的列引用和表达式外,数据集还具有丰富的函数库,包括字符串处理,日期算术,通用数学运算等。完整列表可在DataFrame Function Reference中获得。

Java语言

// col("...") is preferable to df.col("...")
import static org.apache.spark.sql.functions.col;// Print the schema in a tree format
df.printSchema();
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)// Select only the "name" column
df.select("name").show();
// +-------+
// |   name|
// +-------+
// |Michael|
// |   Andy|
// | Justin|
// +-------+// Select everybody, but increment the age by 1
df.select(col("name"), col("age").plus(1)).show();
// +-------+---------+
// |   name|(age + 1)|
// +-------+---------+
// |Michael|     null|
// |   Andy|       31|
// | Justin|       20|
// +-------+---------+// Select people older than 21
df.filter(col("age").gt(21)).show();
// +---+----+
// |age|name|
// +---+----+
// | 30|Andy|
// +---+----+// Count people by age
df.groupBy("age").count().show();
// +----+-----+
// | age|count|
// +----+-----+
// |  19|    1|
// |null|    1|
// |  30|    1|
// +----+-----+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

有关可对数据集执行的操作类型的完整列表,请参阅API文档。

除了简单的列引用和表达式外,数据集还具有丰富的函数库,包括字符串处理,日期算术,通用数学运算等。完整列表可在DataFrame Function Reference中获得。

Python语言

在Python中,可以通过属性(df.age)或通过索引(df['age'])访问DataFrame的列。尽管前者便于交互式数据探索,但强烈建议用户使用后者形式,这是将来的证明,并且不会与列名保持一致,列名也是DataFrame类的属性。

# spark, df are from the previous example
# Print the schema in a tree format
df.printSchema()
# root
# |-- age: long (nullable = true)
# |-- name: string (nullable = true)# Select only the "name" column
df.select("name").show()
# +-------+
# |   name|
# +-------+
# |Michael|
# |   Andy|
# | Justin|
# +-------+# Select everybody, but increment the age by 1
df.select(df['name'], df['age'] + 1).show()
# +-------+---------+
# |   name|(age + 1)|
# +-------+---------+
# |Michael|     null|
# |   Andy|       31|
# | Justin|       20|
# +-------+---------+# Select people older than 21
df.filter(df['age'] > 21).show()
# +---+----+
# |age|name|
# +---+----+
# | 30|Andy|
# +---+----+# Count people by age
df.groupBy("age").count().show()
# +----+-----+
# | age|count|
# +----+-----+
# |  19|    1|
# |null|    1|
# |  30|    1|
# +----+-----+
在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

有关可在DataFrame上执行的操作类型的完整列表,请参阅API文档。

除了简单的列引用和表达式外,DataFrames还具有丰富的函数库,包括字符串处理,日期算术,通用数学运算等。完整列表可在DataFrame Function Reference中获得。

R语言

# Create the DataFrame
df <- read.json("examples/src/main/resources/people.json")# Show the content of the DataFrame
head(df)
##   age    name
## 1  NA Michael
## 2  30    Andy
## 3  19  Justin# Print the schema in a tree format
printSchema(df)
## root
## |-- age: long (nullable = true)
## |-- name: string (nullable = true)# Select only the "name" column
head(select(df, "name"))
##      name
## 1 Michael
## 2    Andy
## 3  Justin# Select everybody, but increment the age by 1
head(select(df, df$name, df$age + 1))
##      name (age + 1.0)
## 1 Michael          NA
## 2    Andy          31
## 3  Justin          20# Select people older than 21
head(where(df, df$age > 21))
##   age name
## 1  30 Andy# Count people by age
head(count(groupBy(df, "age")))
##   age count
## 1  19     1
## 2  NA     1
## 3  30     1
在Spark存储库中的“ examples / src / main / r / RSparkSQLExample.R”中找到完整的示例代码。

有关可在DataFrame上执行的操作类型的完整列表,请参阅API文档。

除了简单的列引用和表达式外,DataFrames还具有丰富的函数库,包括字符串处理,日期算术,通用数学运算等。完整列表可在DataFrame Function Reference中获得。

以编程方式运行SQL查询

Scala语言

上的sql函数SparkSession使应用程序能够以编程方式运行SQL查询,并以形式返回结果DataFrame

// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

Java语言

上的sql函数SparkSession使应用程序能够以编程方式运行SQL查询,并以形式返回结果Dataset<Row>

import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people");Dataset<Row> sqlDF = spark.sql("SELECT * FROM people");
sqlDF.show();
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

Python语言

上的sql函数SparkSession使应用程序能够以编程方式运行SQL查询,并以形式返回结果DataFrame

# Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
# +----+-------+
# | age|   name|
# +----+-------+
# |null|Michael|
# |  30|   Andy|
# |  19| Justin|
# +----+-------+
在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

R语言

sql函数使应用程序能够以编程方式运行SQL查询,并以形式返回结果SparkDataFrame

df <- sql("SELECT * FROM table")
在Spark存储库中的“ examples / src / main / r / RSparkSQLExample.R”中找到完整的示例代码。

全局临时视图

Spark SQL中的临时视图是会话作用域的,如果创建它的会话终止,它将消失。如果要在所有会话之间共享一个临时视图并保持活动状态,直到Spark应用程序终止,则可以创建全局临时视图。全局临时视图与系统保留的数据库相关联global_temp,我们必须使用限定名称来引用它,例如SELECT * FROM global_temp.view1

Scala语言

// Register the DataFrame as a global temporary view
df.createGlobalTempView("people")// Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+// Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

Java语言

// Register the DataFrame as a global temporary view
df.createGlobalTempView("people");// Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show();
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+// Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show();
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

Python语言

# Register the DataFrame as a global temporary view
df.createGlobalTempView("people")# Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
# +----+-------+
# | age|   name|
# +----+-------+
# |null|Michael|
# |  30|   Andy|
# |  19| Justin|
# +----+-------+# Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
# +----+-------+
# | age|   name|
# +----+-------+
# |null|Michael|
# |  30|   Andy|
# |  19| Justin|
# +----+-------+
在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

SQL

CREATE GLOBAL TEMPORARY VIEW temp_view AS SELECT a + 1, b * 2 FROM tblSELECT * FROM global_temp.temp_view

创建DataSet

数据集与RDD相似,但是它们不是使用Java序列化或Kryo,而是使用专用的Encoder对对象进行序列化以进行网络处理或传输。虽然编码器和标准序列化都负责将对象转换为字节,但是编码器是动态生成的代码,并使用一种格式,该格式允许Spark执行许多操作,如过滤,排序和哈希处理,而无需将字节反序列化为对象。

Scala语言

case class Person(name: String, age: Long)// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

Java语言

import java.util.Arrays;
import java.util.Collections;
import java.io.Serializable;import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.Encoder;
import org.apache.spark.sql.Encoders;public static class Person implements Serializable {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}// Create an instance of a Bean class
Person person = new Person();
person.setName("Andy");
person.setAge(32);// Encoders are created for Java beans
Encoder<Person> personEncoder = Encoders.bean(Person.class);
Dataset<Person> javaBeanDS = spark.createDataset(Collections.singletonList(person),personEncoder
);
javaBeanDS.show();
// +---+----+
// |age|name|
// +---+----+
// | 32|Andy|
// +---+----+// Encoders for most common types are provided in class Encoders
Encoder<Integer> integerEncoder = Encoders.INT();
Dataset<Integer> primitiveDS = spark.createDataset(Arrays.asList(1, 2, 3), integerEncoder);
Dataset<Integer> transformedDS = primitiveDS.map((MapFunction<Integer, Integer>) value -> value + 1,integerEncoder);
transformedDS.collect(); // Returns [2, 3, 4]// DataFrames can be converted to a Dataset by providing a class. Mapping based on name
String path = "examples/src/main/resources/people.json";
Dataset<Person> peopleDS = spark.read().json(path).as(personEncoder);
peopleDS.show();
// +----+-------+
// | age|   name|
// +----+-------+
// |null|Michael|
// |  30|   Andy|
// |  19| Justin|
// +----+-------+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

与RDD互操作

Spark SQL支持两种将现有RDD转换为数据集的方法。第一种方法使用反射来推断包含特定对象类型的RDD的架构。这种基于反射的方法可以使代码更简洁,当您在编写Spark应用程序时已经了解架构时,可以很好地工作。

创建数据集的第二种方法是通过编程界面,该界面允许您构造模式,然后将其应用于现有的RDD。尽管此方法较为冗长,但可以在运行时才知道列及其类型的情况下构造数据集。

使用反射推断架构

Scala语言

Spark SQL的Scala接口支持自动将包含案例类的RDD转换为DataFrame。案例类定义表的架构。案例类的参数名称使用反射读取,并成为列的名称。Case类也可以嵌套或包含Seqs或Arrays之类的复杂类型。可以将该RDD隐式转换为DataFrame,然后将其注册为表。表可以在后续的SQL语句中使用。

// For implicit conversions from RDDs to DataFrames
import spark.implicits._// Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.sparkContext.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(attributes => Person(attributes(0), attributes(1).trim.toInt)).toDF()
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by Spark
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")// The columns of a row in the result can be accessed by field index
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+// or by field name
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+// No pre-defined encoders for Dataset[Map[K,V]], define explicitly
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
// Primitive types and case classes can be also defined as
// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()
// Array(Map("name" -> "Justin", "age" -> 19))
在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

Java语言

Spark SQL支持将JavaBean的RDD自动转换 为DataFrame。的BeanInfo,使用反射得到,定义了表的模式。当前,Spark SQL不支持包含Map字段的JavaBean 。 不过,支持嵌套JavaBean和ListArray字段。您可以通过创建一个实现Serializable且其所有字段具有getter和setter的类来创建JavaBean。

import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.MapFunction;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.Encoder;
import org.apache.spark.sql.Encoders;// Create an RDD of Person objects from a text file
JavaRDD<Person> peopleRDD = spark.read().textFile("examples/src/main/resources/people.txt").javaRDD().map(line -> {String[] parts = line.split(",");Person person = new Person();person.setName(parts[0]);person.setAge(Integer.parseInt(parts[1].trim()));return person;});// Apply a schema to an RDD of JavaBeans to get a DataFrame
Dataset<Row> peopleDF = spark.createDataFrame(peopleRDD, Person.class);
// Register the DataFrame as a temporary view
peopleDF.createOrReplaceTempView("people");// SQL statements can be run by using the sql methods provided by spark
Dataset<Row> teenagersDF = spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19");// The columns of a row in the result can be accessed by field index
Encoder<String> stringEncoder = Encoders.STRING();
Dataset<String> teenagerNamesByIndexDF = teenagersDF.map((MapFunction<Row, String>) row -> "Name: " + row.getString(0),stringEncoder);
teenagerNamesByIndexDF.show();
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+// or by field name
Dataset<String> teenagerNamesByFieldDF = teenagersDF.map((MapFunction<Row, String>) row -> "Name: " + row.<String>getAs("name"),stringEncoder);
teenagerNamesByFieldDF.show();
// +------------+
// |       value|
// +------------+
// |Name: Justin|
// +------------+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

Python语言

Spark SQL可以将Row对象的RDD转换为DataFrame,从而推断数据类型。通过将键/值对列表作为kwargs传递给Row类来构造行。该列表的键定义表的列名,并且通过对整个数据集进行采样来推断类型,类似于对JSON文件执行的推断。

from pyspark.sql import Rowsc = spark.sparkContext# Load a text file and convert each line to a Row.
lines = sc.textFile("examples/src/main/resources/people.txt")
parts = lines.map(lambda l: l.split(","))
people = parts.map(lambda p: Row(name=p[0], age=int(p[1])))# Infer the schema, and register the DataFrame as a table.
schemaPeople = spark.createDataFrame(people)
schemaPeople.createOrReplaceTempView("people")# SQL can be run over DataFrames that have been registered as a table.
teenagers = spark.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19")# The results of SQL queries are Dataframe objects.
# rdd returns the content as an :class:`pyspark.RDD` of :class:`Row`.
teenNames = teenagers.rdd.map(lambda p: "Name: " + p.name).collect()
for name in teenNames:print(name)
# Name: Justin
在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

以编程方式指定架构

Scala语言

如果无法提前定义案例类(例如,记录的结构编码为字符串,或者将解析文本数据集,并且针对不同的用户对字段进行不同的投影),DataFrame则可以通过三个步骤以编程方式创建a 。

  1. Row从原始RDD 创建一个的RDD;
  2. 在步骤1中创建的RDD中,创建StructTypeRows 的结构匹配 的模式。
  3. Row通过createDataFrame提供的方法将架构应用于的RDD SparkSession

例如:

import org.apache.spark.sql.Rowimport org.apache.spark.sql.types._// Create an RDD
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")// The schema is encoded in a string
val schemaString = "name age"// Generate the schema based on the string of schema
val fields = schemaString.split(" ").map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)// Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD.map(_.split(",")).map(attributes => Row(attributes(0), attributes(1).trim))// Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)// Creates a temporary view using the DataFrame
peopleDF.createOrReplaceTempView("people")// SQL can be run over a temporary view created using DataFrames
val results = spark.sql("SELECT name FROM people")// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
results.map(attributes => "Name: " + attributes(0)).show()
// +-------------+
// |        value|
// +-------------+
// |Name: Michael|
// |   Name: Andy|
// | Name: Justin|
// +-------------+

在Spark存储库中的“ examples / src / main / scala / org / apache / spark / examples / sql / SparkSQLExample.scala”中找到完整的示例代码。

Java语言

当无法提前定义JavaBean类时(例如,记录的结构编码为字符串,或者将解析文本数据集,并且为不同的用户设计不同的字段),Dataset<Row>可以通过三个步骤以编程方式创建a 。

  1. Row从原始RDD 创建一个的RDD;
  2. 在步骤1中创建的RDD中,创建StructTypeRows 的结构匹配 的模式。
  3. Row通过createDataFrame提供的方法将架构应用于的RDD SparkSession

例如:

import java.util.ArrayList;
import java.util.List;import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.function.Function;import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructField;
import org.apache.spark.sql.types.StructType;// Create an RDD
JavaRDD<String> peopleRDD = spark.sparkContext().textFile("examples/src/main/resources/people.txt", 1).toJavaRDD();// The schema is encoded in a string
String schemaString = "name age";// Generate the schema based on the string of schema
List<StructField> fields = new ArrayList<>();
for (String fieldName : schemaString.split(" ")) {StructField field = DataTypes.createStructField(fieldName, DataTypes.StringType, true);fields.add(field);
}
StructType schema = DataTypes.createStructType(fields);// Convert records of the RDD (people) to Rows
JavaRDD<Row> rowRDD = peopleRDD.map((Function<String, Row>) record -> {String[] attributes = record.split(",");return RowFactory.create(attributes[0], attributes[1].trim());
});// Apply the schema to the RDD
Dataset<Row> peopleDataFrame = spark.createDataFrame(rowRDD, schema);// Creates a temporary view using the DataFrame
peopleDataFrame.createOrReplaceTempView("people");// SQL can be run over a temporary view created using DataFrames
Dataset<Row> results = spark.sql("SELECT name FROM people");// The results of SQL queries are DataFrames and support all the normal RDD operations
// The columns of a row in the result can be accessed by field index or by field name
Dataset<String> namesDS = results.map((MapFunction<Row, String>) row -> "Name: " + row.getString(0),Encoders.STRING());
namesDS.show();
// +-------------+
// |        value|
// +-------------+
// |Name: Michael|
// |   Name: Andy|
// | Name: Justin|
// +-------------+
在Spark存储库中的“ examples / src / main / java / org / apache / spark / examples / sql / JavaSparkSQLExample.java”中找到完整的示例代码。

Python语言

如果无法提前定义kwarg的字典(例如,记录的结构编码为字符串,或者将解析文本数据集,并且为不同的用户投影不同的字段),则DataFrame可以通过编程方式创建三个脚步。

  1. 从原始RDD创建元组或列表的RDD;
  2. StructType在步骤1中创建的RDD中创建由元组或列表结构的匹配表示的模式。
  3. 通过createDataFrame提供的方法将架构应用于RDD SparkSession

例如:

# Import data types
from pyspark.sql.types import *sc = spark.sparkContext# Load a text file and convert each line to a Row.
lines = sc.textFile("examples/src/main/resources/people.txt")
parts = lines.map(lambda l: l.split(","))
# Each line is converted to a tuple.
people = parts.map(lambda p: (p[0], p[1].strip()))# The schema is encoded in a string.
schemaString = "name age"fields = [StructField(field_name, StringType(), True) for field_name in schemaString.split()]
schema = StructType(fields)# Apply the schema to the RDD.
schemaPeople = spark.createDataFrame(people, schema)# Creates a temporary view using the DataFrame
schemaPeople.createOrReplaceTempView("people")# SQL can be run over DataFrames that have been registered as a table.
results = spark.sql("SELECT name FROM people")results.show()
# +-------+
# |   name|
# +-------+
# |Michael|
# |   Andy|
# | Justin|
# +-------+
在Spark存储库中的“ examples / src / main / python / sql / basic.py”中找到完整的示例代码。

标量函数

标量函数是每行返回一个值的函数,聚合函数则返回一组行的值,而标量函数则是每行返回一个值。Spark SQL支持多种内置标量函数。它还支持用户定义的标量函数。

汇总功能

聚合函数是在一组行上返回单个值的函数。该内置聚合函数提供通用聚合如count()countDistinct()avg()max()min(),等用户不限于预定义的聚集功能,可以创建自己的。有关用户定义的聚合函数的更多详细信息,请参阅用户定义的聚合函数的文档 。

数据源

Spark SQL支持通过DataFrame接口对各种数据源进行操作。DataFrame可以使用关系转换进行操作,也可以用于创建临时视图。将DataFrame注册为临时视图使您可以对其数据运行SQL查询。本节介绍了使用Spark数据源加载和保存数据的一般方法,然后介绍了可用于内置数据源的特定选项。

  • 通用加载/保存功能

    • 手动指定选项
    • 直接在文件上运行SQL
    • 保存模式
    • 保存到永久表
    • 桶,分类和分区
  • 通用文件源选项
    • 忽略损坏的文件
    • 忽略丢失的文件
    • 路径全局过滤器
    • 递归文件查找
  • 实木复合地板文件
    • 以编程方式加载数据
    • 分区发现
    • 模式合并
    • Hive Metastore Parquet表转换
    • 组态
  • ORC文件
  • JSON文件
  • 蜂巢表
    • 指定Hive表的存储格式
    • 与Hive Metastore的不同版本进行交互
  • JDBC到其他数据库
  • Avro文件
    • 部署中
    • 加载和保存功能
    • to_avro()和from_avro()
    • 数据源选项
    • 组态
    • 与Databricks spark-avro的兼容性
    • Avro-> Spark SQL转换支持的类型
    • Spark SQL支持的类型-> Avro转换
  • 整个二进制文件
  • 故障排除

性能调优

对于某些工作负载,可以通过在内存中缓存数据或打开某些实验选项来提高性能。

在内存中缓存数据

Spark SQL可以通过调用spark.catalog.cacheTable("tableName")或使用内存列式格式缓存表dataFrame.cache()。然后,Spark SQL将仅扫描必需的列,并将自动调整压缩以最小化内存使用和GC压力。您可以调用spark.catalog.uncacheTable("tableName")从内存中删除表。

可以使用setConfon方法SparkSessionSET key=value使用SQL 运行 命令来完成内存中缓存的配置。

物业名称 默认 含义 自版本
spark.sql.inMemoryColumnarStorage.compressed 真正 设置为true时,Spark SQL将根据数据统计信息自动为每一列选择一个压缩编解码器。 1.0.1
spark.sql.inMemoryColumnarStorage.batchSize 10000 控制用于列式缓存的批处理的大小。较大的批处理大小可以提高内存利用率和压缩率,但是在缓存数据时会出现OOM。 1.1.1

其他配置选项

以下选项也可以用于调整查询执行的性能。随着自动执行更多优化,将来的发行版中可能会弃用这些选项。

物业名称 默认 含义 自版本
spark.sql.files.maxPartitionBytes 134217728(128 MB) 读取文件时打包到单个分区中的最大字节数。仅当使用基于文件的源(例如Parquet,JSON和ORC)时,此配置才有效。 2.0.0
spark.sql.files.openCostInBytes 4194304(4 MB) 可以同时扫描以字节数衡量的打开文件的估计成本。将多个文件放入分区时使用。最好高估一下,然后具有较小文件的分区将比具有较大文件的分区(首先安排)更快。仅当使用基于文件的源(例如Parquet,JSON和ORC)时,此配置才有效。 2.0.0
spark.sql.broadcastTimeout 300

广播加入中广播等待时间的秒数超时

1.3.0
spark.sql.autoBroadcastJoinThreshold 10485760(10 MB) 配置表的最大大小(以字节为单位),该表在执行联接时将广播到所有工作程序节点。通过将此值设置为-1,可以禁用广播。请注意,当前仅ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan运行命令的Hive Metastore表支持统计信息 。 1.1.0
spark.sql.shuffle.partitions 200 配置在对联接或聚合进行数据混排时要使用的分区数。 1.1.0

加入针对SQL查询的策略提示

连接策略提示,即BROADCASTMERGESHUFFLE_HASHSHUFFLE_REPLICATE_NL,指导星火与其他关系结合时,他们使用暗示策略上的每个特定关系。例如,当BROADCAST在表't1'上使用提示时,Spark将优先考虑以't1'作为生成端的广播连接(广播哈希连接或广播嵌套循环连接,取决于是否有任何等连接键)。即使统计信息建议的表't1'的大小在配置之上spark.sql.autoBroadcastJoinThreshold

当在连接的两侧指定了不同的连接策略提示时,Spark会优先于BROADCAST提示而不是MERGE提示优先 SHUFFLE_HASHSHUFFLE_REPLICATE_NL 提示。当使用BROADCAST提示或SHUFFLE_HASH提示指定双方时,Spark将根据联接类型和关系的大小选择构建方。

请注意,由于特定策略可能不支持所有联接类型,因此不能保证Spark将选择提示中指定的联接策略。

Scala语言

spark.table("src").join(spark.table("records").hint("broadcast"), "key").show()

有关更多详细信息,请参阅Join Hints的文档。

Java语言

spark.table("src").join(spark.table("records").hint("broadcast"), "key").show();

有关更多详细信息,请参阅Join Hints的文档。

Python语言

spark.table("src").join(spark.table("records").hint("broadcast"), "key").show()

有关更多详细信息,请参阅Join Hints的文档。

R语言

src <- sql("SELECT * FROM src")
records <- sql("SELECT * FROM records")
head(join(src, hint(records, "broadcast"), src$key == records$key))

有关更多详细信息,请参阅Join Hints的文档。

SQL

-- We accept BROADCAST, BROADCASTJOIN and MAPJOIN for broadcast hint
SELECT /*+ BROADCAST(r) */ * FROM records r JOIN src s ON r.key = s.key

有关更多详细信息,请参阅Join Hints的文档。

SQL查询的合并提示

Coalesce提示使Spark SQL用户可以像一样控制输出文件的数量 coalescerepartition并且repartitionByRange在Dataset API中,它们可以用于性能调整和减少输出文件的数量。“ COALESCE”提示仅具有分区号作为参数。“ REPARTITION”提示具有分区号和/或列作为参数。“ REPARTITION_BY_RANGE”提示必须具有列名,并且分区号是可选的。

<span style="color:#1d1f22"><span style="color:#333333"><code>SELECT /*+ COALESCE(3) */ * FROM t
SELECT /*+ REPARTITION(3) */ * FROM t
SELECT /*+ REPARTITION(c) */ * FROM t
SELECT /*+ REPARTITION(3, c) */ * FROM t
SELECT /*+ REPARTITION_BY_RANGE(c) */ * FROM t
SELECT /*+ REPARTITION_BY_RANGE(3, c) */ * FROM t
</code></span></span>

有关更多详细信息,请参阅“ 分区提示 ”文档。

自适应查询执行

自适应查询执行(AQE)是Spark SQL中的一种优化技术,它利用运行时统计信息来选择最有效的查询执行计划。默认情况下禁用AQE。Spark SQL可以使用的伞形配置spark.sql.adaptive.enabled来控制是否打开/关闭它。从Spark 3.0开始,AQE具有三个主要功能,包括合并后混洗分区,将排序合并联接转换为广播联接以及倾斜联接优化。

合并后的Shuffle分区

spark.sql.adaptive.enabledspark.sql.adaptive.coalescePartitions.enabled配置均为true 时,此功能将根据地图输出统计信息合并后混洗分区。此功能简化了运行查询时对混洗分区号的调整。您无需设置适当的随机播放分区号即可适合您的数据集。一旦通过spark.sql.adaptive.coalescePartitions.initialPartitionNum配置设置了足够大的初始shuffle分区数量,Spark便可以在运行时选择正确的shuffle分区编号。

物业名称 默认 含义 自版本
spark.sql.adaptive.coalescePartitions.enabled 真正 如果为true和spark.sql.adaptive.enabledtrue,Spark将根据目标大小(由指定spark.sql.adaptive.advisoryPartitionSizeInBytes)合并连续的shuffle分区,以避免执行过多的小任务。 3.0.0
spark.sql.adaptive.coalescePartitions.minPartitionNum 默认并行 合并后的最小混洗分区数。如果未设置,则默认值为Spark集群的默认并行度。仅当spark.sql.adaptive.enabledspark.sql.adaptive.coalescePartitions.enabled都启用时,此配置才有效。 3.0.0
spark.sql.adaptive.coalescePartitions.initialPartitionNum 200 合并之前的洗牌分区的初始数量。默认情况下,它等于spark.sql.shuffle.partitions。仅当spark.sql.adaptive.enabledspark.sql.adaptive.coalescePartitions.enabled都启用时,此配置才有效。 3.0.0
spark.sql.adaptive.advisoryPartitionSizeInBytes 64兆字节 自适应优化过程中(随机数)随机播放分区的建议大小(以字节为单位spark.sql.adaptive.enabled)。当Spark合并较小的shuffle分区或拆分倾斜的shuffle分区时,此命令才会生效。 3.0.0

将排序合并联接转换为广播联接

当任何联接端的运行时统计信息小于广播哈希联接阈值时,AQE会将排序合并联接转换为广播哈希联接。这不像首先计划广播哈希连接那样有效,但是比继续进行排序合并连接要好,因为我们可以保存两个连接端的排序,并在本地读取随机文件以节省网络流量(如果spark.sql.adaptive.localShuffleReader.enabled是真的)

优化偏斜连接

数据偏斜会严重降低联接查询的性能。此功能通过将倾斜的任务拆分(按需复制)为大小大致相等的任务来动态处理排序合并联接中的倾斜。同时启用spark.sql.adaptive.enabledspark.sql.adaptive.skewJoin.enabled配置时,此选项才生效。

物业名称 默认 含义 自版本
spark.sql.adaptive.skewJoin.enabled 真正 在true和spark.sql.adaptive.enabledtrue时,Spark通过拆分(如果需要,可以复制)歪斜的分区来动态处理排序合并联接中的歪斜。 3.0.0
spark.sql.adaptive.skewJoin.skewedPartitionFactor 10 如果分区的大小大于此因子乘以分区中位数的乘积,并且也大于,则认为该分区是倾斜的spark.sql.adaptive.skewedPartitionThresholdInBytes 3.0.0
spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes 256MB 如果分区的字节大小大于此阈值,并且大于spark.sql.adaptive.skewJoin.skewedPartitionFactor分区中位数的乘积,则认为该分区是歪斜的。理想情况下,此配置应设置为大于spark.sql.adaptive.advisoryPartitionSizeInBytes

分布式SQL引擎

  • 运行Thrift JDBC / ODBC服务器
  • 运行Spark SQL CLI

Spark SQL也可以使用其JDBC / ODBC或命令行界面充当分布式查询引擎。在这种模式下,最终用户或应用程序可以直接与Spark SQL交互以运行SQL查询,而无需编写任何代码。

运行Thrift JDBC / ODBC服务器

此处实现的Thrift JDBC / ODBC服务器对应HiveServer2 于内置的Hive。您可以使用Spark或兼容的Hive随附的beeline脚本测试JDBC服务器。

要启动JDBC / ODBC服务器,请在Spark目录中运行以下命令:

./sbin/start-thriftserver.sh

该脚本接受所有bin/spark-submit命令行选项,以及--hiveconf用于指定Hive属性的选项。您可以运行./sbin/start-thriftserver.sh --help以获取所有可用选项的完整列表。默认情况下,服务器在localhost:10000上侦听。您可以通过任一环境变量来覆盖此行为,即:

export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh \--master <master-uri> \...

或系统属性:

./sbin/start-thriftserver.sh \--hiveconf hive.server2.thrift.port=<listening-port> \--hiveconf hive.server2.thrift.bind.host=<listening-host> \--master <master-uri>...

现在,您可以使用beeline测试Thrift JDBC / ODBC服务器:

./bin/beeline

通过以下方式直线连接到JDBC / ODBC服务器:

beeline> !connect jdbc:hive2://localhost:10000

Beeline会要求您提供用户名和密码。在非安全模式下,只需在计算机上输入用户名和空白密码即可。对于安全模式,请遵循beeline文档中给出的 说明。

蜂巢的结构是通过将您做hive-site.xmlcore-site.xmlhdfs-site.xml文件conf/

您还可以使用Hive随附的beeline脚本。

Thrift JDBC服务器还支持通过HTTP传输发送Thrift RPC消息。使用以下设置可以将HTTP模式作为系统属性或在hive-site.xml文件中启用conf/

hive.server2.transport.mode - Set this to value: http
hive.server2.thrift.http.port - HTTP port number to listen on; default is 10001
hive.server2.http.endpoint - HTTP endpoint; default is cliservice

要进行测试,请使用beeline通过以下方式以http模式连接到JDBC / ODBC服务器:

beeline> !connect jdbc:hive2://<host>:<port>/<database>?hive.server2.transport.mode=http;hive.server2.thrift.http.path=<http_endpoint>

如果您关闭了会话并执行CTAS,则必须在中将其设置fs.%s.impl.disable.cache为true hive-site.xml。在[SPARK-21067]中查看更多详细信息。

运行Spark SQL CLI

Spark SQL CLI是一种方便的工具,可以在本地模式下运行Hive Metastore服务并执行从命令行输入的查询。请注意,Spark SQL CLI无法与Thrift JDBC服务器对话。

要启动Spark SQL CLI,请在Spark目录中运行以下命令:

./bin/spark-sql

蜂巢的结构是通过将您做hive-site.xmlcore-site.xmlhdfs-site.xml文件conf/。您可以运行./bin/spark-sql --help以获取所有可用选项的完整列表。

PySpark使用Apache Arrow的熊猫使用指南

PySpark中的Apache Arrow

Apache Arrow是一种内存中的列式数据格式,在Spark中使用它来在JVM和Python进程之间有效地传输数据。目前,这对于使用Pandas / NumPy数据的Python用户最为有利。它的使用不是自动的,可能需要对配置或代码进行一些小的更改才能充分利用并确保兼容性。本指南将对如何在Spark中使用Arrow进行高层描述,并突出显示使用启用了Arrow的数据时的所有差异。

确保已安装PyArrow

要在PySpark中使用Apache Arrow, 应安装推荐版本的PyArrow。如果使用pip安装PySpark,则可以使用命令引入PyArrow作为SQL模块的额外依赖项pip install pyspark[sql]。否则,您必须确保PyArrow已安装并在所有群集节点上可用。您可以从conda-forge频道使用pip或conda进行安装。有关详细信息,请参见PyArrow 安装。

启用与熊猫之间的转换

使用调用将Spark DataFrame转换为Pandas DataFrame并使用toPandas()时从Pandas DataFrame创建Spark DataFrame时, 可以使用Arrow作为优化createDataFrame(pandas_df)。要在执行这些调用时使用Arrow,用户需要首先将Spark配置设置spark.sql.execution.arrow.pyspark.enabledtrue。默认情况下禁用。

此外,spark.sql.execution.arrow.pyspark.enabled如果在Spark内的实际计算之前发生错误,启用的优化可能会自动回退到非箭头优化实现。可以通过控制spark.sql.execution.arrow.pyspark.fallback.enabled

import numpy as np
import pandas as pd# Enable Arrow-based columnar data transfers
spark.conf.set("spark.sql.execution.arrow.pyspark.enabled", "true")# Generate a Pandas DataFrame
pdf = pd.DataFrame(np.random.rand(100, 3))# Create a Spark DataFrame from a Pandas DataFrame using Arrow
df = spark.createDataFrame(pdf)# Convert the Spark DataFrame back to a Pandas DataFrame using Arrow
result_pdf = df.select("*").toPandas()

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

将上述优化与Arrow一起使用将产生与未启用Arrow时相同的结果。请注意,即使使用Arrow,也会toPandas()导致将DataFrame中的所有记录收集到驱动程序中,并且应该对数据的一小部分进行处理。当前尚不支持所有的Spark数据类型,如果列的类型不受支持,则会引发错误,请参阅支持的SQL类型。如果在期间发生错误createDataFrame(),Spark将回退以创建不带箭头的DataFrame。

熊猫UDF(又名矢量化UDF)

熊猫UDF是用户定义的函数,由Spark使用Arrow来传输数据,并通过Pandas与数据一起使用来进行矢量化操作。熊猫UDF是使用pandas_udf修饰符或包装功能定义的,不需要其他配置。熊猫UDF通常表现为常规的PySpark函数API。

在Spark 3.0之前,Pandas UDF以前是通过定义的PandasUDFType。在带有Python 3.6+的Spark 3.0中,您还可以使用Python类型提示。首选使用Python类型提示PandasUDFType,在将来的版本中将不推荐使用。

请注意,pandas.Series在所有情况下都应使用pandas.DataFrame类型提示,但当输入或输出列为of时,应为其输入或输出类型提示使用一种变体StructType。以下示例显示了一个Pandas UDF,它使用长列,字符串列和结构列,并输出一个结构列。它需要的功能,以指定的类型提示pandas.Seriespandas.DataFrame如下:

import pandas as pdfrom pyspark.sql.functions import pandas_udf@pandas_udf("col1 string, col2 long")
def func(s1: pd.Series, s2: pd.Series, s3: pd.DataFrame) -> pd.DataFrame:s3['col2'] = s1 + s2.str.len()return s3# Create a Spark DataFrame that has three columns including a sturct column.
df = spark.createDataFrame([[1, "a string", ("a nested string",)]],"long_col long, string_col string, struct_col struct<col1:string>")df.printSchema()
# root
# |-- long_column: long (nullable = true)
# |-- string_column: string (nullable = true)
# |-- struct_column: struct (nullable = true)
# |    |-- col1: string (nullable = true)df.select(func("long_col", "string_col", "struct_col")).printSchema()
# |-- func(long_col, string_col, struct_col): struct (nullable = true)
# |    |-- col1: string (nullable = true)
# |    |-- col2: long (nullable = true)

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

在以下各节中,它描述了受支持的类型提示的组合。为了简单起见, pandas.DataFrame省略了variant。

系列到系列

类型提示可以表示为pandas.Series…-> pandas.Series

通过pandas_udf与上面具有这种类型提示的函数一起使用,它将创建一个Pandas UDF,其中给定的函数将采用一个或多个pandas.Series并输出一个pandas.Series。函数的输出应始终与输入具有相同的长度。在内部,PySpark将列拆分成批处理并为每个批处理调用函数作为数据的子集,然后将结果串联在一起,从而执行Pandas UDF。

以下示例显示了如何创建此熊猫UDF来计算2列乘积。

import pandas as pdfrom pyspark.sql.functions import col, pandas_udf
from pyspark.sql.types import LongType# Declare the function and create the UDF
def multiply_func(a: pd.Series, b: pd.Series) -> pd.Series:return a * bmultiply = pandas_udf(multiply_func, returnType=LongType())# The function for a pandas_udf should be able to execute with local Pandas data
x = pd.Series([1, 2, 3])
print(multiply_func(x, x))
# 0    1
# 1    4
# 2    9
# dtype: int64# Create a Spark DataFrame, 'spark' is an existing SparkSession
df = spark.createDataFrame(pd.DataFrame(x, columns=["x"]))# Execute function as a Spark vectorized UDF
df.select(multiply(col("x"), col("x"))).show()
# +-------------------+
# |multiply_func(x, x)|
# +-------------------+
# |                  1|
# |                  4|
# |                  9|
# +-------------------+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅 pyspark.sql.functions.pandas_udf

系列迭代器到系列迭代器

类型提示可以表示为Iterator[pandas.Series]-> Iterator[pandas.Series]

通过pandas_udf与上面具有此类类型提示的函数配合使用,它将创建Pandas UDF,其中给定函数采用的迭代器pandas.Series并输出的迭代器pandas.Series。该函数的整个输出的长度应与整个输入的长度相同。因此,只要长度相同,它就可以从输入迭代器中预取数据。在这种情况下,调用熊猫UDF时,创建的熊猫UDF需要一个输入列。要使用多个输入列,需要不同的类型提示。请参阅多序列的迭代器到序列的迭代器。

当UDF执行需要初始化一些状态时,它也很有用,尽管在内部它与“系列到系列”的情况相同。下面的伪代码说明了该示例。

@pandas_udf("long")
def calculate(iterator: Iterator[pd.Series]) -> Iterator[pd.Series]:# Do some expensive initialization with a state
    state = very_expensive_initialization()for x in iterator:# Use that state for whole iterator.
        yield calculate_with_state(x, state)df.select(calculate("value")).show()

以下示例显示了如何创建此Pandas UDF:

from typing import Iteratorimport pandas as pdfrom pyspark.sql.functions import pandas_udfpdf = pd.DataFrame([1, 2, 3], columns=["x"])
df = spark.createDataFrame(pdf)# Declare the function and create the UDF
@pandas_udf("long")
def plus_one(iterator: Iterator[pd.Series]) -> Iterator[pd.Series]:for x in iterator:yield x + 1df.select(plus_one("x")).show()
# +-----------+
# |plus_one(x)|
# +-----------+
# |          2|
# |          3|
# |          4|
# +-----------+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅 pyspark.sql.functions.pandas_udf

多个系列的迭代器到系列的迭代器

类型提示可以表示为Iterator[Tuple[pandas.Series, ...]]-> Iterator[pandas.Series]

通过pandas_udf与上面具有此类类型提示的函数配合使用,它将创建一个Pandas UDF,其中给定函数采用一个为multiple的元组pandas.Series的迭代器,并输出为的迭代器pandas.Series。在这种情况下,当调用Pandas UDF时,创建的Pandas UDF需要多个输入列,其数量与元组中的序列数相同。否则,它具有与系列迭代器到系列迭代器案例相同的特性和限制。

以下示例显示了如何创建此Pandas UDF:

from typing import Iterator, Tupleimport pandas as pdfrom pyspark.sql.functions import pandas_udfpdf = pd.DataFrame([1, 2, 3], columns=["x"])
df = spark.createDataFrame(pdf)# Declare the function and create the UDF
@pandas_udf("long")
def multiply_two_cols(iterator: Iterator[Tuple[pd.Series, pd.Series]]) -> Iterator[pd.Series]:for a, b in iterator:yield a * bdf.select(multiply_two_cols("x", "x")).show()
# +-----------------------+
# |multiply_two_cols(x, x)|
# +-----------------------+
# |                      1|
# |                      4|
# |                      9|
# +-----------------------+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅 pyspark.sql.functions.pandas_udf

系列到标量

类型提示可以表示为pandas.Series…-> Any

通过pandas_udf与上面具有此类类型提示的函数一起使用,它会创建一个类似于PySpark的聚合函数的Pandas UDF。给定的函数采用pandas.Series并返回标量值。返回类型应该是原始数据类型,并且返回的标量可以是python原始类型(例如)intfloatnumpy数据类型(例如numpy.int64或)numpy.float64。 Any理想情况下应相应地为特定的标量类型。

该UDF也可以与groupBy().agg()和一起使用pyspark.sql.Window。它定义了从一个或多个pandas.Series到标量值的聚合,其中每个都pandas.Series 代表组或窗口中的一列。

请注意,这种类型的UDF不支持部分聚合,并且组或窗口的所有数据都将被加载到内存中。另外,分组熊猫聚合UDF当前仅支持无边界窗口。下面的示例显示如何使用此类UDF通过group-by和window操作来计算均值

import pandas as pdfrom pyspark.sql.functions import pandas_udf
from pyspark.sql import Windowdf = spark.createDataFrame([(1, 1.0), (1, 2.0), (2, 3.0), (2, 5.0), (2, 10.0)],("id", "v"))# Declare the function and create the UDF
@pandas_udf("double")
def mean_udf(v: pd.Series) -> float:return v.mean()df.select(mean_udf(df['v'])).show()
# +-----------+
# |mean_udf(v)|
# +-----------+
# |        4.2|
# +-----------+df.groupby("id").agg(mean_udf(df['v'])).show()
# +---+-----------+
# | id|mean_udf(v)|
# +---+-----------+
# |  1|        1.5|
# |  2|        6.0|
# +---+-----------+w = Window \.partitionBy('id') \.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)
df.withColumn('mean_v', mean_udf(df['v']).over(w)).show()
# +---+----+------+
# | id|   v|mean_v|
# +---+----+------+
# |  1| 1.0|   1.5|
# |  1| 2.0|   1.5|
# |  2| 3.0|   6.0|
# |  2| 5.0|   6.0|
# |  2|10.0|   6.0|
# +---+----+------+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅 pyspark.sql.functions.pandas_udf

熊猫函数API

熊猫函数API可以DataFrame通过使用熊猫实例直接将Python本机函数应用于整个函数。在内部,它与Pandas UDF的工作方式类似,方法是使用Arrow传输数据,使用Pandas处理数据,从而实现矢量化操作。但是,Pandas Function API在PySpark下的行为类似于常规API,DataFrame而不是Column,并且Pandas Functions API中的Python类型提示是可选的,尽管将来可能会需要它们,但暂时不会影响它在内部的工作方式。

从Spark 3.0开始,分组地图熊猫UDF现在被归类为单独的熊猫功能API DataFrame.groupby().applyInPandas()。它仍然有可能与使用它PandasUDFType ,并DataFrame.groupby().apply()为它; 但是,最好DataFrame.groupby().applyInPandas()直接使用 。使用PandasUDFType将在未来被废弃。

分组Map

支持具有Pandas实例的分组地图操作,DataFrame.groupby().applyInPandas() 该操作要求Python函数接受a pandas.DataFrame并返回另一个pandas.DataFrame。它将每个组映射到pandas.DataFramePython函数中的每个组。

该API实现了“ split-apply-combine”模式,该模式包括三个步骤:

  • 通过使用将数据分成几组DataFrame.groupBy
  • 在每个组上应用功能。函数的输入和输出均为pandas.DataFrame。输入数据包含每个组的所有行和列。
  • 将结果合并到新的PySpark中DataFrame

要使用groupBy().applyInPandas(),用户需要定义以下内容:

  • 一个Python函数,用于定义每个组的计算。
  • StructType对象或定义输出PySpark的模式的字符串DataFrame

pandas.DataFrame如果指定为字符串,则返回的列标签必须与定义的输出模式中的字段名称匹配,或者如果不是字符串,则必须按位置匹配字段数据类型,例如整数索引。有关 在构造时如何标记列的信息,请参见pandas.DataFramepandas.DataFrame

请注意,在应用该功能之前,组的所有数据将被加载到内存中。这可能会导致内存不足异常,尤其是在组大小偏斜的情况下。maxRecordsPerBatch的配置 不适用于组,并且由用户决定是否将分组的数据放入可用内存中。

以下示例说明如何使用groupby().applyInPandas()从组中的每个值减去平均值

df = spark.createDataFrame([(1, 1.0), (1, 2.0), (2, 3.0), (2, 5.0), (2, 10.0)],("id", "v"))def subtract_mean(pdf):# pdf is a pandas.DataFramev = pdf.vreturn pdf.assign(v=v - v.mean())df.groupby("id").applyInPandas(subtract_mean, schema="id long, v double").show()
# +---+----+
# | id|   v|
# +---+----+
# |  1|-0.5|
# |  1| 0.5|
# |  2|-3.0|
# |  2|-1.0|
# |  2| 4.0|
# +---+----+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅pyspark.sql.GroupedData.applyInPandas

Map操作

支持使用Pandas实例进行Map操作,DataFrame.mapInPandas()该操作将pandas.DataFrames 的迭代器映射到s的另一个迭代器,pandas.DataFrame该迭代器表示当前PySpark DataFrame并将结果作为PySpark返回DataFrame。该函数接受并输出的迭代器pandas.DataFrame。与某些Pandas UDF相比,它可以返回任意长度的输出,尽管在内部它与Series to Series Pandas UDF类似。

以下示例显示如何使用mapInPandas()

df = spark.createDataFrame([(1, 21), (2, 30)], ("id", "age"))def filter_func(iterator):for pdf in iterator:yield pdf[pdf.id == 1]df.mapInPandas(filter_func, schema=df.schema).show()
# +---+---+
# | id|age|
# +---+---+
# |  1| 21|
# +---+---+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅pyspark.sql.DataFrame.mapsInPandas

共同Map

支持与Pandas实例的联合分组Map操作,DataFrame.groupby().cogroup().applyInPandas()该操作允许DataFrame通过一个公用密钥将两个PySpark 联合分组,然后将Python函数应用于每个联合分组。它包括以下步骤:

  • 对数据进行混洗,以使共享密钥的每个数据帧的组共同分组。
  • 将一个功能应用于每个共同组。该函数的输入为2 pandas.DataFrame(带有一个可选的表示键的元组)。该函数的输出为pandas.DataFrame
  • pandas.DataFrame所有组中的合并到新的PySpark中DataFrame

要使用groupBy().cogroup().applyInPandas(),用户需要定义以下内容:

  • 一个Python函数,用于定义每个协同组的计算。
  • StructType对象或定义输出PySpark的模式的字符串DataFrame

pandas.DataFrame如果指定为字符串,则返回的列标签必须与定义的输出模式中的字段名称匹配,或者如果不是字符串,则必须按位置匹配字段数据类型,例如整数索引。有关 在构造时如何标记列的信息,请参见pandas.DataFramepandas.DataFrame

请注意,在应用该功能之前,一个cogroup的所有数据将被加载到内存中。这可能会导致内存不足异常,尤其是在组大小偏斜的情况下。maxRecordsPerBatch的配置 未应用,并且取决于用户,以确保共同分组的数据将适合可用内存。

以下示例显示了如何用于groupby().cogroup().applyInPandas()在两个数据集之间执行asof连接。

import pandas as pddf1 = spark.createDataFrame([(20000101, 1, 1.0), (20000101, 2, 2.0), (20000102, 1, 3.0), (20000102, 2, 4.0)],("time", "id", "v1"))df2 = spark.createDataFrame([(20000101, 1, "x"), (20000101, 2, "y")],("time", "id", "v2"))def asof_join(l, r):return pd.merge_asof(l, r, on="time", by="id")df1.groupby("id").cogroup(df2.groupby("id")).applyInPandas(asof_join, schema="time int, id int, v1 double, v2 string").show()
# +--------+---+---+---+
# |    time| id| v1| v2|
# +--------+---+---+---+
# |20000101|  1|1.0|  x|
# |20000102|  1|3.0|  x|
# |20000101|  2|2.0|  y|
# |20000102|  2|4.0|  y|
# +--------+---+---+---+

在Spark存储库中的“ examples / src / main / python / sql / arrow.py”中找到完整的示例代码。

有关详细用法,请参阅pyspark.sql.PandasCogroupedOps.applyInPandas()

使用说明

支持的SQL类型

目前,所有Spark SQL数据类型是基于箭转换,除了支持MapType, ArrayTypeTimestampType和嵌套StructType

设置箭头批处理大小

Spark中的数据分区将转换为Arrow记录批,这可能会暂时导致JVM中的内存使用率很高。为避免可能的内存不足异常,可以通过将conf“ spark.sql.execution.arrow.maxRecordsPerBatch”设置为整数来调整Arrow记录批的大小,该整数将确定每个批处理的最大行数。默认值为每批10,000条记录。如果列数很大,则应相应地调整该值。使用此限制,每个数据分区将被分为1个或多个记录批次以进行处理。

带时区语义的时间戳

Spark在内部将时间戳存储为UTC值,并且在没有指定时区的情况下传入的时间戳数据将作为本地时间转换为具有微秒分辨率的UTC。当时间戳数据导出或在Spark中显示时,会话时区用于本地化时间戳值。会话时区使用配置“ spark.sql.session.timeZone”设置,如果未设置,则默认为JVM系统本地时区。Pandas使用datetime64具有纳秒分辨率的类型,datetime64[ns]每个列都具有可选的时区。

当时间戳数据从Spark传输到Pandas时,它将转换为纳秒,每列将转换为Spark会话时区,然后本地化到该时区,从而删除时区并将值显示为本地时间。这将在调用时toPandas()pandas_udf使用时间戳列时发生。

当时间戳数据从Pandas传输到Spark时,它将转换为UTC微秒。在createDataFrame使用Pandas DataFrame进行调用或从返回时间戳时, 会发生这种情况pandas_udf。这些转换是自动完成的,以确保Spark拥有预期格式的数据,因此您无需自己进行任何转换。任何毫微秒的值都会被截断。

请注意,标准UDF(非Pandas)会将时间戳数据加载为Python日期时间对象,这与Pandas时间戳不同。建议在使用pandas_udfs中的时间戳时使用Pandas时间序列功能以获得最佳性能,有关详细信息,请参见 此处。

推荐的Pandas和PyArrow版本

对于与pyspark.sql一起使用,支持的Pandas版本是0.24.2,而PyArrow是0.15.1。可以使用更高版本,但是不能保证兼容性和数据正确性,并且应由用户验证。

PyArrow> = 0.15.0和Spark 2.3.x,2.4.x的兼容性设置

从Arrow 0.15.0开始,二进制IPC格式的更改要求环境变量与Arrow <= 0.14.1的早期版本兼容。这仅对于版本2.3.x和2.4.x且已将PyArrow手动升级到0.15.0的PySpark用户需要执行。可以添加以下内容conf/spark-env.sh以使用旧版Arrow IPC格式:

ARROW_PRE_0_15_IPC_FORMAT=1

这将指示PyArrow> = 0.15.0将旧版IPC格式与Spark 2.3.x和2.4.x中的较旧Arrow Java一起使用。运行s或启用Arrow 时, 未设置此环境变量将导致类似SPARK-29367中所述的错误。有关Arrow IPC更改的更多信息,请参见Arrow 0.15.0版本博客。pandas_udftoPandas()

迁移指南

现在,迁移指南已存档在此页面上。

SQL参考

Spark SQL是Apache Spark的用于处理结构化数据的模块。本指南是结构化查询语言(SQL)的参考,包括语法,语义,关键字和常见SQL使用示例。它包含有关以下主题的信息:

  • 符合ANSI
  • 资料类型
  • 日期时间模式
  • 功能
    • 内建功能
    • 标量用户定义函数(UDF)
    • 用户定义的聚合函数(UDAF)
    • 与Hive UDF / UDAF / UDTF集成
  • 身份标识
  • 文字
  • 空语义
  • SQL语法
    • DDL陈述式
    • DML语句
    • 数据检索语句
    • 辅助声明

Apache Spark 3.0 SQL DataFrame和DataSet指南相关推荐

  1. 全方位掌握Apache Spark 2.0七步走(二)

    2019独角兽企业重金招聘Python工程师标准>>> 在上一篇普及过Spark的相关概念之后,让我们继续深入研究它的核心结构以及好用的API,本篇视频内容丰富,机(fan)智(qi ...

  2. Apache Spark 2.0预览: 机器学习模型持久化

    在即将发布的Apache Spark 2.0中将会提供机器学习模型持久化能力.机器学习模型持久化(机器学习模型的保存和加载)使得以下三类机器学习场景变得容易: \\ 数据科学家开发ML模型并移交给工程 ...

  3. Apache Spark 2.0: 机器学习模型持久化

    在即将发布的Apache Spark 2.0中将会提供机器学习模型持久化能力.机器学习模型持久化(机器学习模型的保存和加载)使得以下三类机器学习场景变得容易: 数据科学家开发ML模型并移交给工程师团队 ...

  4. .NET for Apache Spark 1.0 版本发布

    .NET for Apache Spark 1.0 现已发布,这是一个用于 Spark 大数据的 .NET 框架,可以让 .NET 开发者轻松地使用 Apache Spark. 该软件包由微软和 .N ...

  5. 云栖大会 | Apache Spark 3.0 和 Koalas 最新进展

    本资料来自2019-09-26在杭州举办的云栖大会的大数据 & AI 峰会分会.议题名称<New Developments in the Open Source Ecosystem: A ...

  6. Apache Spark 3.0 预览版正式发布,多项重大功能发布

    今天早上 06:53(2019年11月08日 06:53) 数砖的 Xingbo Jiang 大佬给社区发了一封邮件,宣布 Apache Spark 3.0预览版正式发布,这个版本主要是为了对即将发布 ...

  7. 微信团队回应“部分用户朋友圈无法刷新”;罗永浩:准备做综艺节目;Apache Spark 3.0 发布| 极客头条...

    整理 | 屠敏 头图 | CSDN 下载自东方 IC 快来收听极客头条音频版吧,智能播报由出门问问「魔音工坊」提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极 ...

  8. Apache Spark 3.0 DStreams-Streaming编程指南

    目录 总览 一个简单的例子 基本概念 连结中 初始化StreamingContext 离散流(DStreams) 输入DStreams和接收器 基本资料 进阶资源 自订来源 接收器可靠性 DStrea ...

  9. Apache Spark 3.0 结构化Streaming流编程指南

    目录 总览 快速范例 Scala语言 Java语言 Python语言 R语言 程式设计模型 基本概念 处理事件时间和延迟数据 容错语义 使用数据集和数据帧的API 创建流数据框架和流数据集 流数据帧/ ...

最新文章

  1. github 搜索_因为这个工具,我在 GitHub 搜索源码的时间缩短了 50%!
  2. vue开发黑科技--利用引用类型的值处理复杂数据的编辑
  3. can-utils源码解析cansend
  4. MyOffic(经理评分)
  5. Tiny Core Linux 4.5 发布,微型 Linux 操作系统
  6. 查看及修改MYSQL最大连接数
  7. python 循环 覆盖之前print内容_Python爬虫第二战---爬取500px图片
  8. java第二部分项目_Java_第二次作业:项目构思与实现
  9. java包管理之gradle安装
  10. 这些将在新一年改变你的风控内容
  11. ASP.NET Web Pages 和 WebMatrix (Razor Syntax、Forms、Data、Grid、Chart、Files、Images、Video)的学习资源...
  12. Ubuntu18.04报错:Aborted (core dumped) (classes.jar.toc.tmp ) ninja: build stopped: subcommand failed解决
  13. numpy在对数组进行“行过滤“的时候,使用集合运算函数比使用逻辑运算函数更简单
  14. SDCC和Keil之stc89c52资料(纪念51单片机40周年)
  15. pandas写入excel指定行_使用pandas操作excel
  16. DirSync: List of attributes that are synced by the Azure Active Directory Sync Tool
  17. MySQL的auto_increment使用
  18. 上市4天暴降1500元,iPhone14创下了苹果降价最快纪录
  19. 【答学员问】面试问题-毕业时候为什么没有选择开发
  20. LevelDB整体介绍

热门文章

  1. 介绍一款好用的flash播放器(Vcastr 3.0 – flash video(flv) player)
  2. CG原画绘画教程之人物-张聪-专题视频课程
  3. AIX存储LV PV VG
  4. [Excel] 用sumproduct函数实现数据透视表功能
  5. MLCC电容和电介质材料类别X5R,X7R,Y5V,COG等一些资料
  6. 截取固定大小图片css,css-使不同大小的图片在固定大小的容器中居中
  7. 微信公众号自定义回复文字菜单-----详细教程
  8. 小米手机 开发app python_python之小米应用商店爬虫
  9. 项目管理软件有哪些,哪个好用?
  10. Internet Download Manager2022完整版安装下载教程