十、 RDD編程和Spark SQL

@Author : By Runsen
@Date : 2020/6/21

作者介紹:Runsen目前大三下學期,專業化學工程與工藝,大學沉迷日語,Python, Java和一系列數據分析軟件。導致翹課嚴重,專業排名中下。.在大學60%的時間,都在CSDN。

在一月到四月都沒怎麼寫博客,因爲決定寫書,結果出書方說大學生就是一個菜鳥,看我確實還是一個菜鳥,就更新到博客算了。

我把第九章更新到博客上。

9.6 Spark

9.6.2 RDD編程

(1)Spark核心

Hadoop是集數據存儲、運算、調度於一體的框架,使用HDFS進行分佈式存儲,MapReduce進行分佈式計算,YARN進行資源調度。

但是,MapReduce存在缺陷。因爲MapReduce只提供 Map 和 Reduce 兩個操作,很多現實的數據處理場景並不適合用這個模型來描述。舉個例子,兩個數據集的 Join 是很基本而且常用的功能,但是在 MapReduce 的世界中,需要對這兩個數據集做一次 Map 和 Reduce 才能得到結果。

因此,爲了優化Hadoop,Spark應之而生。Spark 最基本的數據結構叫作彈性分佈式數據集(Resilient Distributed Dataset, RDD),Spark 提供了很多對 RDD 的操作,如 Map、Filter、flatMap、groupByKey 和 Union 等等,極大地提升了對各種複雜場景的支持。

Spark不是取代Hadoop,而是取代MapReduce。

下面是有關Spark核心術語:

  • Application :Spark的應用程序,其實是用戶基於Spark API開發的程序,通過一個有main方法的類執行的,比如Java或Scala開發的Spark程序,在Eclipse或IDEA等開發工具裏其實是一個工程。
  • Application Jar:把寫好的spark工程,打包成一個jar或者war包,比如連接數據庫的驅動等。
  • Driver Program:指我們編寫的Spark程序中main方法的裏的邏輯,這也叫driver進程。
  • Cluster Manager:集羣管理器。集羣管理器的作用是爲每個Application,在集羣中調度和分配資源,比如Spark Standalone(單機)模式下是Master、Spark On YARN模式下是ResourcesManager
  • Deploy Mode:部署模式。Spark作業部署提供了兩種運行模式,分別是client和cluster。client模式下driver運行在提交spark作業的機器上。cluster模式下,提交給集羣管理器由集羣管理器決定driver的運行節點。
  • Worker Node:集羣中的工作節點(slave節點),能夠運行executor進程,運行作業代碼的節點。在Standalone模式下是Worker節點,在Spark On YARN模式下是NodeManager節點。
  • Executor:集羣管理器爲Application分配的進程,運行在worker節點上,負責執行作業的任務,並將數據保存在內存或磁盤中,每個Application都有自己的executor進程。
  • Job:在一個Application會根據代碼中的action算子操作來劃分Job,可以有一個或多個Job。
  • Stage:在一個Job中依據Shuffle算子爲依據來劃分Stage,一個Shuffle算子劃分兩個Stage,一個Job可以有一個或多個Stage,每個stage都會有對應的一批task,分配到executor上去執行。
  • Task:driver發送到executor執行的計算單元,是運行在Executor上的線程,每個task負責在一個階段(stage),處理一小片數據,計算出對應的結果。

在瞭解Spark的核心術語後,我們結合術語來了解一下Spark作業的提交執行過程,如下圖9-19所示。

在Spark中,存在Spark作業提交的官方例子,在examples文件夾中。下面我們運行官方的Spark示例,我們可以在官方文檔中查看:http://spark.apache.org/examples.html

[root@node01 spark]# cd examples/src/main
[root@node01 main]# ls
java  python  r  resources  scala  scripts

其中examples目錄下提供了java,scala,python,R語言的各種例子。點進src目錄可以看到官方示例的源代碼,

回到安裝目錄下,執行RDD官方示例,計算圓周率pi

[root@node01 spark]# #計算圓周率pi
[root@node01 spark]# bin/spark-submit --class org.apache.spark.examples.SparkPi --master local examples/jars/spark-examples_2.12-3.0.0-preview2.jar 
Pi is roughly 3.146035730178651
[root@node01 spark]# bin/spark-submit examples/src/main/python/pi.py
Pi is roughly 3.144120

(2)創建RDD

創建RDD的基本方式有兩種,第一種是使用textFile加載本地或者集羣文件系統中的數據。第二種是使用parallelize方法將Driver中的數據結構轉化成RDD。

# 從集羣中讀取數據
scala> val rdd = sc.textFile("hdfs://node01:9000/input.txt")

# parallelize將Driver中的數據結構轉化成RDD,參數2指定分區數
scala> val rdd = sc.parallelize(1 to 10 ,2)

scala> rdd.collect
res2: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 

scale> val rdd = sc.parallelize(List("spark","i like spark"))

scala> rdd.collect
res3: Array[String] = Array(spark, i like spark)

(3)Action操作

RDD中的提供了action(執行)和transformation(轉換)兩種常用的操作。下面依次介紹Action和Transformation操作

RDD提供了大量的Action操作,分別有collect,count,countByValue,coutByKey,take, lookup,top,takeOrdered,takeSample,reduce,aggregate,fold,foreach,和saveAsFile。

scala> var rdd1 = sc.makeRDD(Array(("A","1"),("B","2"),("C","3")),2)

scala> rdd1.collect
res6: Array[(String, String)] = Array((A,1), (B,2), (C,3))   

scala> rdd1.count()
res7 Long = 3

# countByValue 統計RDD中出現的次數
scala> rdd1.countByValue()
res8: scala.collection.Map[(String, String),Long] = Map((B,2) -> 1, (C,3) -> 1, (A,1) -> 1)

scala> rdd1.countByKey()
res9: scala.collection.Map[String,Long] = Map(B -> 1, A -> 1, C -> 1)

# take(n)返回第n個前幾個元素 
scala> rdd1.take(2)
res10: Array[(String, String)] = Array((A,1), (B,2))


# lookup用於(K,V)類型的RDD,指定K值,返回RDD中該K對應的所有V值。
scala> rdd1.lookup("A")
res11: Seq[String] = WrappedArray(1)


scala> val rdd2 = sc.parallelize(List(1,2,3,3,4,5))

# top(n) 降序返回前n個元素 
scala> rdd2.top(2)
res13: Array[Int] = Array(5, 4)

# takeOrdered(n) 升序返回前n個元素
scala> rdd2.takeOrdered(2)
res14: Array[Int] = Array(1, 2)

# takeSample(false,3) 隨機抽樣 false不可以重複抽樣
scala> rdd2.takeSample(false,3)
res15: Array[Int] = Array(3, 1, 5)

# true 可以重複抽樣
scala> rdd2.takeSample(true,3)
res16: Array[Int] = Array(3, 1, 3)

# reduce並行整合RDD中所有數據
scala> rdd2.reduce((x,y)=>x+y)
res17: Int = 18

# 可以簡寫成 _+_
scala> rdd2.reduce(_+_)
res18: Int = 18

scala> val rdd3 = sc.parallelize(List(1,2,3,4,5,6),2)

scala> rdd3.partitions.length
res20: Int = 2

# aggregate(0) : 給 (zeroValue: U) 指定一個默認值0
# 每個分區操作的時候都需要加上此默認值,也就是 分區數 * 默認值
# 計算:(n + 1) * 默認值 ,n :代表分區數,這是個可變參數;
# (2 + 1)* 0 + (第一個分區和)+(第二個分區和) = 0 +(1+2+3)+(4+5+6)=21
scala> rdd3.aggregate(0)(_+_,_+_)
res21: Int = 21

# (2+1)*1 +(1+2+3)+(4+5+6)=24
scala> rdd3.aggregate(1)(_+_,_+_)
res22: Int = 24

# (2+1)*10 +(1+2+3)+(4+5+6)=51
scala> rdd3.aggregate(10)(_+_,_+_)
res23: Int = 51

# 第一個分區最大值3 + 第二個分區最大值 6 = 9
scala> rdd3.aggregate(0)(math.max(_,_),_+_)
res24: Int = 9

# 3+9+1=10
scala> rdd3.aggregate(1)(math.max(_,_),_+_)
res25: Int = 10

# 默認值4 大於第一個分區數最大值3 因此4+4+6=14
scala> rdd3.aggregate(4)(math.max(_,_),_+_)
res26: Int = 14

# 默認值10 大於第一個分區數最大值3和第二個分區數最大值6,因此10+10+10
scala> rdd3.aggregate(10)(math.max(_,_),_+_)
res27: Int = 33

# 同理 100+100+100
scala> rdd3.aggregate(100)(math.max(_,_),_+_)
res28: Int = 300

# fold計算方法aggregate一樣
scala> rdd3.fold(0)(_+_)
res289 Int = 21

scala> rdd3.fold(1)(_+_)
res30: Int = 24

# foreach用於遍歷RDD,
# var和val區別:val就不能再賦值了,var可以重複給變量複製,相當Java中的靜態變量
scala> var accum = sc.longAccumulator("sumAccum")

scala> rdd2.foreach(x => accum.add(x))

# 1+2+3+3+4+5=18
scala> accum.value
res33: Long = 18

# saveAsFile保存rdd成text文件到hdfs
scala> val file = "hdfs://node01:9000/rdddata"
file: String = hdfs://node01:9000/rdddata

scala> val rdd4 = sc.parallelize(1 to 10)

scala> rdd4.saveAsTextFile(file)

scala> val data = sc.textFile(file)

scala> data.collect.foreach(print)
12345678910

(4)Transformation操作

Transformation轉換操作具有懶惰執行的特性,指的是在被調用行動操作之前 Spark 不會開始計算。只有當Action操作觸發到該依賴的時候,它才被運行。

RDD提供了大量的Transformation操作,分別有map, filter,flatMap,sample,distinct,distinct

,subtract,union,intersection,cartesian和sortBy

# map操作對每個元素進行映射轉換
scala> val rdd = sc.parallelize(1 to 10,2)

scala> rdd.map(_+1).collect
res1: Array[Int] = Array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11) 

#filter應用過濾條件對每個元素進行過濾
scala> rdd.filter(_>5).collect
res2: Array[Int] = Array(6, 7, 8, 9, 10)  

#fiatmap實際上是先進行map,然後再進行一次平滑flat處理
scala> val rdd1 = sc.parallelize(Array("hello spark","hello hadoop"))

scala> rdd1.flatMap(_.split(" ")).collect
res4: Array[String] = Array(hello, spark, hello, hadoop)

scala> rdd1.map(_.split(" ")).collect
res5: Array[Array[String]] = Array(Array(hello, spark), Array(hello, hadoop))

# sample(withReplacement,fraction,seed):給定的隨機種子seed,隨機抽樣出數量爲frac的數據。
# withReplacement:是否放回抽樣;fraction:比例,0.1表示10% ;
> rdd.sample(false,0.1,0).collect
res6: Array[Int] = Array(2)

# distinct去重
> val rdd2 = sc.parallelize(Array(1,1,2,3,3,4))

> rdd.distinct.collect
res8: Array[Int] = Array(1, 2, 3, 4) 

# subtract找到上一個rdd不屬於下一個rdd的元素,rdd:1~10,rdd2:1,2,3,4
scala> rdd.subtract(rdd2).collect
res9: Array[Int] = Array(6, 8, 10, 5, 7, 9)

# union合併數據
scala> val a = sc.parallelize(1 to 3)
scala> val b = sc.parallelize(4 to 6)

scala> a.union(b).collect
res12: Array[Int] = Array(1, 2, 3, 4, 5, 6)

#intersection求交集
scala> val a = sc.parallelize(1 to 3)
scala> val b = sc.parallelize(2 to 4)

scala> a.intersection(b).collect
res15: Array[Int] = Array(2, 3)

#cartesian進行笛卡兒計算
scala> a.cartesian(b).collect
res16: Array[(Int, Int)] = Array((1,2), (1,3), (1,4), (2,2), (2,3), (2,4), (3,2), (3,3), (3,4))

#sortBy排序
scala> val rdd3 = sc.parallelize(Array((1,2,3),(3,2,1),(2,3,1)))

#按照第一個元素排序
scala> rdd3.sortBy(_._1).collect
res18: Array[(Int, Int, Int)] = Array((1,2,3), (2,3,1), (3,2,1))

#按照第二個元素排序
scala> rdd3.sortBy(_._2).collect
res19: Array[(Int, Int, Int)] = Array((3,2,1), (1,2,3), (2,3,1))

(5)PairRDD

Spark操作中經常會用到“鍵值對RDD”(Pair RDD),用於完成聚合計算。普通RDD裏面存儲的數據類型是Int、String等,而“鍵值對RDD”裏面存儲的數據類型是“鍵值對”。

scala> val rdd = sc.parallelize(List("Hadoop","Spark","Hive","Spark"))

#普通RDD轉PairRDD主要使用map()函數來實現
scala> val mapRDD = rdd.map(word=>(word,1))

scala> mapRDD.keys.collect.foreach(println)
Hadoop
Spark
Hive
Spark

scala> mapRDD.collect.foreach(println)
(Hadoop,1)
(Spark,1)
(Hive,1)
(Spark,1)

#reduceByKey歸併操作
scala> mapRDD.reduceByKey(_+_).collect.foreach(println)
(Hive,1)                                                                        
(Spark,2)
(Hadoop,1)

#groupByKey收集成一個Iterator迭代器
scala> mapRDD.groupByKey().collect.foreach(println)
(Hive,CompactBuffer(1))
(Spark,CompactBuffer(1, 1))
(Hadoop,CompactBuffer(1))


#按照key排序
scala> mapRDD.sortByKey().collect.foreach(println)
(Hadoop,1)
(Hive,1)
(Spark,1)
(Spark,1)

#鍵值對RDD中的每個value都應用一個函數
scala> mapRDD.mapValues(_+1).collect.foreach(println)
(Hadoop,2)
(Spark,2)
(Hive,2)
(Spark,2)

9.6.3 Spark SQL

Spark SQL從shark發展而來。在Hadoop前身,HQL可以說是SQL on Hadoop的唯一選擇。Databricks爲了實現Hive兼容,開發了Shark,在HQL方面重用了Hive中HQL的解析、邏輯執行計劃翻譯、執行計劃優化等邏輯。但是在2014年7月1日的Spark Summit上,Databricks宣佈終止對Shark的開發,將重點放到Spark SQL上。

Spark SQL提供了一個稱爲DataFrame的編程抽象,並且可以充當分佈式SQL查詢引擎,DataFrame參照了Pandas的思想,在RDD基礎上增加了schma,能夠獲取列名信息。筆者認爲,學習Spark SQL無疑就是學習Python模塊中的pandas。

(1)創建DataFrame

scala val seq = Seq((1, "First", java.sql.Date.valueOf("2020-03-02")),(2, "Second", java.sql.Date.valueOf("2020-03-02")))
scala> val df = seq.toDF("int_column","string_column","date_column")
scala> df.show
+----------+-------------+-----------+
|int_column|string_column|date_column|
+----------+-------------+-----------+
|         1|        First| 2020-03-02|
|         2|       Second| 2020-03-02|
+----------+-------------+-----------+

scala> df.printSchema
root
 |-- int_column: integer (nullable = false)
 |-- string_column: string (nullable = true)
 |-- date_column: date (nullable = true)
 
# list -> DataFrame
scala> val list = List(("Runsen",20,100),("Zhangsan",21,99),("Lisi",22,98))
scala> var df = list.toDF("name","age","score")
scala> df.show
+--------+---+-----+
|    name|age|score|
+--------+---+-----+
|  Runsen| 20|  100|
|Zhangsan| 21|   99|
|    Lisi| 22|   98|
+--------+---+-----+

# RDD -> DataFrame
scala> val rdd = sc.parallelize(List(("Runsen",20),("Zhangsan",21),("Lisi",22)),2)
scala> val df = rdd.toDF("name","age")
scala> df.show 
+--------+---+                                                                  
|    name|age|
+--------+---+
|  Runsen| 20|
|Zhangsan| 21|
|    Lisi| 22|
+--------+---+

(2)讀取保存文件

# 讀取mysql數據表生成DataFrame,需要將jdbc的驅動jar包複製到spark安裝目錄中的jar文件夾中
scala> val url = "jdbc:mysql://192.168.92.90:3306/persons"
url: String = jdbc:mysql://192.168.92.90:3306/persons
scala> val df = spark.read.format("jdbc").option("url", url).option("dbtable", "persons").option("user", "root").option("password", "123456").load()
scala> df.show
+---+--------+---+---------+                                                    
| id|    name|sex|     city|
+---+--------+---+---------+
|  1|  Runsen|  1|Guangzhou|
|  2|Zhangsan|  1|  Beijing|
|  3|    Lisi|  2| Shanghai|
+---+--------+---+---------+

# 讀取csv文件,需要iris.csv上傳到hdfs中
scala> val df = spark.sqlContext.read.format("csv").option("header","true") .option("inferSchema","true").option("delimiter", ",").load("/iris.csv")
scala> df.show(5)
+-----------+----------+-----------+----------+-----+                           
|sepallength|sepalwidth|petallength|petalwidth|label|
+-----------+----------+-----------+----------+-----+
|        5.1|       3.5|        1.4|       0.2|    0|
|        4.9|       3.0|        1.4|       0.2|    0|
|        4.7|       3.2|        1.3|       0.2|    0|
|        4.6|       3.1|        1.5|       0.2|    0|
|        5.0|       3.6|        1.4|       0.2|    0|
+-----------+----------+-----------+----------+-----+
only showing top 5 rows

# 保存txt文件到hdfs
scala> df.rdd.saveAsTextFile("/iris.txt")

# 讀取Parquet。Parquet格式經常在Hadoop生態圈中被使用,它也支持Spark SQL的全部數據類型
scala> val df = spark.read.parquet("/persons.parquet")
scala> df.show
+---+--------+---+---------+                                                    
| id|    name|sex|     city|
+---+--------+---+---------+
|  1|  Runsen|  1|Guangzhou|
|  2|Zhangsan|  1|  Beijing|
|  3|    Lisi|  2| Shanghai|
+---+--------+---+---------+

(3)創建保存DataSet

DataSet主要通過toDS方法從Seq,List或者RDD數據類型轉換得到,或者從DataFrame通過as方法轉換得到。

#toDS方法轉換DataSet
scala> case class Student(name:String,age:Int)
defined class Student
scala> val seq = Seq(Student("Runsen",20),Student("Zhangsan",21),Student("Lisi",22))
scala> val ds = seq.toDS
scala> ds.show
+--------+---+
|    name|age|
+--------+---+
|  Runsen| 20|
|Zhangsan| 21|
|    Lisi| 22|
+--------+---+

#保存DataSet
scala> ds.toDF.rdd.saveAsTextFile("/Student.txt")

# 將RDD轉換成DataSet
scala> val rdd = sc.parallelize(List(Student("Runsen",20),Student("Zhangsan",21),Student("Lisi",22)))
scala> val ds = rdd.toDS
scala> ds.show 
+--------+---+                                                                  
|    name|age|
+--------+---+
|  Runsen| 20|
|Zhangsan| 21|
|    Lisi| 22|
+--------+---+

# 讀取csv文件並轉換得到DataSet
scala> case class Flower(sepallength:Double,sepalwidth:Double,petallength:Double,petalwidth:Double,label:Int
scala> val ds = spark.sqlContext.read.format("csv").option("header","true").option("inferSchema","true").option("delimiter", ",").load("/iris.csv").as[Flower]

scala> ds.show(3)
+-----------+----------+-----------+----------+-----+
|sepallength|sepalwidth|petallength|petalwidth|label|
+-----------+----------+-----------+----------+-----+
|        5.1|       3.5|        1.4|       0.2|    0|
|        4.9|       3.0|        1.4|       0.2|    0|
|        4.7|       3.2|        1.3|       0.2|    0|
+-----------+----------+-----------+----------+-----+
only showing top 3 rows

scala> ds.show(3)

(4)DataFrame中的RDD編程

scala> df.show()
+---+--------+---+---------+                                                    
| id|    name|sex|     city|
+---+--------+---+---------+
|  1|  Runsen|  1|Guangzhou|
|  2|Zhangsan|  1|  Beijing|
|  3|    Lisi|  2| Shanghai|
+---+--------+---+---------+
only showing top 3 rows

scala> df.count
res14: Long = 3

scala> df.take(2)
res15: Array[org.apache.spark.sql.Row] = Array([1,Runsen,1,Guangzhou], [2,Zhangsan,1,Beijing])

# map
scala> df.map(x=>x(1).toString.toUpperCase).show()
+--------+
|   value|
+--------+
|  RUNSEN|
|ZHANGSAN|
|    LISI|
+--------+

# filte
scala> df.filter(s=>s(1).toString.endsWith("n")).show()
+---+--------+---+---------+
| id|    name|sex|     city|
+---+--------+---+---------+
|  1|  Runsen|  1|Guangzhou|
|  2|Zhangsan|  1|  Beijing|
+---+--------+---+---------+

scala> val df1 = Seq("Hello World","Hello Scala","Hello Spark","Hello Spark").toDF("value")

scala> df1.show()
+-----------+
|      value|
+-----------+
|Hello World|
|Hello Scala|
|Hello Spark|
|Hello Spark|
+-----------+

#flatMap
scala> df1.flatMap(x=>x(0).toString.split(" ")).show()
+-----+
|value|
+-----+
|Hello|
|World|
|Hello|
|Scala|
|Hello|
|Spark|
|Hello|
|Spark|
+-----+

#distinct
df1.flatMap(x=>x(0).toString.split(" ")).distinct.show()
+-----+                                                                         
|value|
+-----+
|World|
|Hello|
|Scala|
|Spark|
+-----+

#sample
scala> df1.sample(false,0.6,0).show()
+-----------+
|      value|
+-----------+
|Hello Spark|
|Hello Spark|
+-----------+

scala> val df2 = Seq("Hello World","Hello java","Hello python","Hello Hadoop").toDF("value")
scala> df2.show()
+------------+
|       value|
+------------+
| Hello World|
|  Hello java|
|Hello python|
|Hello Hadoop|
+------------+

#intersect
scala> df1.intersect(df2).show()
+-----------+                                                                   
|      value|
+-----------+
|Hello World|
+-----------+

#except
scala> df1.except(df2).show()
+-----------+                                                                   
|      value|
+-----------+
|Hello Spark|
|Hello Scala|
+-----------+

(5)DataFrame常用操作

scala> val df = List((1,"Runsen",1,null),(2,"Zhangsan",1,"Beijing"),(3,"Lisi",2,"Shanghai")).toDF("id","name","sex","city")

scala> df.show
+---+--------+---+--------+
| id|    name|sex|    city|
+---+--------+---+--------+
|  1|  Runsen|  1|    null|
|  2|Zhangsan|  1| Beijing|
|  3|    Lisi|  2|Shanghai|
+---+--------+---+--------+

#增加列
scala> df.withColumn("Number",df("id")+202000).show()
+---+--------+---+--------+------+
| id|    name|sex|    city|Number|
+---+--------+---+--------+------+
|  1|  Runsen|  1|    null|202001|
|  2|Zhangsan|  1| Beijing|202002|
|  3|    Lisi|  2|Shanghai|202003|
+---+--------+---+--------+------+

#刪除列
scala> df.drop("city").show()
+---+--------+---+
| id|    name|sex|
+---+--------+---+
|  1|  Runsen|  1|
|  2|Zhangsan|  1|
|  3|    Lisi|  2|
+---+--------+---+

#重命名列
scala> df.withColumnRenamed("sex","gender").show()
+---+--------+------+--------+
| id|    name|gender|    city|
+---+--------+------+--------+
|  1|  Runsen|     1|    null|
|  2|Zhangsan|     1| Beijing|
|  3|    Lisi|     2|Shanghai|
+---+--------+------+--------+

#降序排名
scala> df.sort($"name".desc).show()
+---+--------+---+--------+
| id|    name|sex|    city|
+---+--------+---+--------+
|  2|Zhangsan|  1| Beijing|
|  1|  Runsen|  1|    null|
|  3|    Lisi|  2|Shanghai|
+---+--------+---+--------+

# 去掉nan值
scala> df.na.drop.show()
+---+--------+---+--------+
| id|    name|sex|    city|
+---+--------+---+--------+
|  2|Zhangsan|  1| Beijing|
|  3|    Lisi|  2|Shanghai|
+---+--------+---+--------+

# 填充nan值
scala> df.na.fill("Guangzhou").show()
+---+--------+---+---------+
| id|    name|sex|     city|
+---+--------+---+---------+
|  1|  Runsen|  1|Guangzhou|
|  2|Zhangsan|  1|  Beijing|
|  3|    Lisi|  2| Shanghai|
+---+--------+---+---------+

在本節中,很簡單的介紹了Spark SQL中的DataFrame,但是並沒有涉及編程Java和scala編程API,因此建議讀者閱讀Spark SQL的官方文檔:https://spark.apache.org/docs/latest/sql-programming-guide.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章