Spark-SQL之DataFrame基本操作

1、創建DataFrame

本文所使用的DataFrame是通過讀取mysql數據庫獲得的,代碼如下:

val spark = SparkSession
      .builder()
      .appName("Spark SQL basic example")
      .enableHiveSupport()
      //.config("spark.some.config.option", "some-value")
      .getOrCreate()
    import spark.implicits._
    val url = "jdbc:mysql://localhost:3306/test"
    val df = spark.read
      .format("jdbc")
      .option("url", url)
      .option("dbtable", "pivot")
      .option("user", "root")
      .option("password", "admin")
      .load()

2、DataFrame基本動作運算

2.1 show展示數據

可以用show() 方法來展示數據,show有以下幾種不同的使用方式:
show():顯示所有數據
show(n) :顯示前n條數據
show(true): 最多顯示20個字符,默認爲true
show(false): 去除最多顯示20個字符的限制
show(n, true):顯示前n條並最多顯示20個自負

代碼爲:

df.show()
df.show(3)
df.show(true)
df.show(false)
df.show(3,true)

上面的輸出爲:

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
|  4|   2| 助手1|2017-08-07 13:44:...|
|  5|   3|APP1|2017-08-02 13:44:...|
|  6|   3|APP1|2017-08-01 13:44:...|
|  7|   3| 助手2|2017-08-14 13:44:...|
|  8|   3|APP2|2017-08-03 13:44:...|
|  9|   2|APP2|2017-08-11 13:44:...|
| 10|   2| 助手1|2017-07-14 13:44:...|
| 11|   1|APP1|2017-07-15 13:45:...|
| 12|   1| 助手2|2017-07-07 13:45:...|
+---+----+----+--------------------+

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
+---+----+----+--------------------+
only showing top 3 rows

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
|  4|   2| 助手1|2017-08-07 13:44:...|
|  5|   3|APP1|2017-08-02 13:44:...|
|  6|   3|APP1|2017-08-01 13:44:...|
|  7|   3| 助手2|2017-08-14 13:44:...|
|  8|   3|APP2|2017-08-03 13:44:...|
|  9|   2|APP2|2017-08-11 13:44:...|
| 10|   2| 助手1|2017-07-14 13:44:...|
| 11|   1|APP1|2017-07-15 13:45:...|
| 12|   1| 助手2|2017-07-07 13:45:...|
+---+----+----+--------------------+

+---+----+----+---------------------+
|id |user|type|visittime            |
+---+----+----+---------------------+
|1  |1   |助手1 |2017-08-10 13:44:19.0|
|2  |1   |APP1|2017-08-04 13:44:26.0|
|3  |2   |助手1 |2017-08-05 13:44:29.0|
|4  |2   |助手1 |2017-08-07 13:44:32.0|
|5  |3   |APP1|2017-08-02 13:44:38.0|
|6  |3   |APP1|2017-08-01 13:44:41.0|
|7  |3   |助手2 |2017-08-14 13:44:48.0|
|8  |3   |APP2|2017-08-03 13:44:45.0|
|9  |2   |APP2|2017-08-11 13:44:53.0|
|10 |2   |助手1 |2017-07-14 13:44:57.0|
|11 |1   |APP1|2017-07-15 13:45:03.0|
|12 |1   |助手2 |2017-07-07 13:45:08.0|
+---+----+----+---------------------+

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
+---+----+----+--------------------+
only showing top 3 rows

2.2 collect獲取所有數據到數組

不同於前面的show方法,這裏的collect方法會將df中的所有數據都獲取到,並返回一個Array對象。

df.collect().foreach(println)

輸出爲:

[1,1,助手1,2017-08-10 13:44:19.0]
[2,1,APP1,2017-08-04 13:44:26.0]
[3,2,助手1,2017-08-05 13:44:29.0]
[4,2,助手1,2017-08-07 13:44:32.0]
[5,3,APP1,2017-08-02 13:44:38.0]
[6,3,APP1,2017-08-01 13:44:41.0]
[7,3,助手2,2017-08-14 13:44:48.0]
[8,3,APP2,2017-08-03 13:44:45.0]
[9,2,APP2,2017-08-11 13:44:53.0]
[10,2,助手1,2017-07-14 13:44:57.0]
[11,1,APP1,2017-07-15 13:45:03.0]
[12,1,助手2,2017-07-07 13:45:08.0]

2.3 collectAsList:獲取所有數據到List

功能和collect類似,只不過將返回結構變成了List對象,使用方法如下:

println(df.collectAsList())

輸出爲:

[[1,1,助手1,2017-08-10 13:44:19.0], [2,1,APP1,2017-08-04 13:44:26.0], [3,2,助手1,2017-08-05 13:44:29.0], [4,2,助手1,2017-08-07 13:44:32.0], [5,3,APP1,2017-08-02 13:44:38.0], [6,3,APP1,2017-08-01 13:44:41.0], [7,3,助手2,2017-08-14 13:44:48.0], [8,3,APP2,2017-08-03 13:44:45.0], [9,2,APP2,2017-08-11 13:44:53.0], [10,2,助手1,2017-07-14 13:44:57.0], [11,1,APP1,2017-07-15 13:45:03.0], [12,1,助手2,2017-07-07 13:45:08.0]]

2.4describe(cols: String*):獲取指定字段的統計信息

這個方法可以動態的傳入一個或多個String類型的字段名,結果仍然爲DataFrame對象,用於統計數值類型字段的統計值,比如count, mean, stddev, min, max等。

df .describe("user" ).show()

輸出爲:

+-------+------------------+
|summary|              user|
+-------+------------------+
|  count|                12|
|   mean|               2.0|
| stddev|0.8528028654224418|
|    min|                 1|
|    max|                 3|
+-------+------------------+

2.5first, head, take, takeAsList:獲取若干行記錄

這裏列出的四個方法比較類似,其中
(1)first獲取第一行記錄
(2)head獲取第一行記錄,head(n: Int)獲取前n行記錄
(3)take(n: Int)獲取前n行數據
(4)takeAsList(n: Int)獲取前n行數據,並以List的形式展現
以Row或者Array[Row]的形式返回一行或多行數據。first和head功能相同。
take和takeAsList方法會將獲得到的數據返回到Driver端,所以,使用這兩個方法時需要注意數據量,以免Driver發生OutOfMemoryError

3、單個DataFrame操作

3.1 使用where篩選條件

where(conditionExpr: String):SQL語言中where關鍵字後的條件 ,傳入篩選條件表達式,可以用and和or。得到DataFrame類型的返回結果, 比如我們想得到用戶1或者使用助手1的操作記錄:

df.where("user=1 or type ='助手1'").show()

輸出爲

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
|  4|   2| 助手1|2017-08-07 13:44:...|
| 10|   2| 助手1|2017-07-14 13:44:...|
| 11|   1|APP1|2017-07-15 13:45:...|
| 12|   1| 助手2|2017-07-07 13:45:...|
+---+----+----+--------------------+

3.2 filter:根據字段進行篩選

傳入篩選條件表達式,得到DataFrame類型的返回結果。和where使用條件相同,比如我們想得到用戶1或者使用助手1的操作記錄:

df.filter("user=1 or type ='助手1'").show()

結果和上面相同:

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
|  4|   2| 助手1|2017-08-07 13:44:...|
| 10|   2| 助手1|2017-07-14 13:44:...|
| 11|   1|APP1|2017-07-15 13:45:...|
| 12|   1| 助手2|2017-07-07 13:45:...|
+---+----+----+--------------------+

3.3 select:獲取指定字段值

根據傳入的String類型字段名,獲取指定字段的值,以DataFrame類型返回,比如我們想要查找user和type兩列:

df.select("user","type").show()

結果爲:

+----+----+
|user|type|
+----+----+
|   1| 助手1|
|   1|APP1|
|   2| 助手1|
|   2| 助手1|
|   3|APP1|
|   3|APP1|
|   3| 助手2|
|   3|APP2|
|   2|APP2|
|   2| 助手1|
|   1|APP1|
|   1| 助手2|
+----+----+

還有一個重載的select方法,不是傳入String類型參數,而是傳入Column類型參數,Column類型即DataFrame中的一列。可以實現select id, id+1 from pivot這種邏輯。

df.select(df("user"),df("user")+1).show()

輸出爲

+----+----------+
|user|(user + 1)|
+----+----------+
|   1|       2.0|
|   1|       2.0|
|   2|       3.0|
|   2|       3.0|
|   3|       4.0|
|   3|       4.0|
|   3|       4.0|
|   3|       4.0|
|   2|       3.0|
|   2|       3.0|
|   1|       2.0|
|   1|       2.0|
+----+----------+

3.4selectExpr:可以對指定字段進行特殊處理

可以直接對指定字段調用UDF函數,或者指定別名等。傳入String類型參數,得到DataFrame對象。 比如,將type字段重新命名爲visittype,同時截取visittime的date:

df.selectExpr("user","type as visittype","to_date(visittime)").show()

輸出爲:

+----+---------+--------------------------------+
|user|visittype|to_date(CAST(visittime AS DATE))|
+----+---------+--------------------------------+
|   1|      助手1|                      2017-08-10|
|   1|     APP1|                      2017-08-04|
|   2|      助手1|                      2017-08-05|
|   2|      助手1|                      2017-08-07|
|   3|     APP1|                      2017-08-02|
|   3|     APP1|                      2017-08-01|
|   3|      助手2|                      2017-08-14|
|   3|     APP2|                      2017-08-03|
|   2|     APP2|                      2017-08-11|
|   2|      助手1|                      2017-07-14|
|   1|     APP1|                      2017-07-15|
|   1|      助手2|                      2017-07-07|
+----+---------+--------------------------------+

3.5 col/apply:獲取指定字段

只能獲取一個字段,返回對象爲Column類型。 示例略

3.6 drop:去除指定字段,保留其他字段

返回一個新的DataFrame對象,其中不包含去除的字段,一次只能去除一個字段。比如我們去除type字段:

df.drop("type").show()

輸出爲:

+---+----+--------------------+
| id|user|           visittime|
+---+----+--------------------+
|  1|   1|2017-08-10 13:44:...|
|  2|   1|2017-08-04 13:44:...|
|  3|   2|2017-08-05 13:44:...|
|  4|   2|2017-08-07 13:44:...|
|  5|   3|2017-08-02 13:44:...|
|  6|   3|2017-08-01 13:44:...|
|  7|   3|2017-08-14 13:44:...|
|  8|   3|2017-08-03 13:44:...|
|  9|   2|2017-08-11 13:44:...|
| 10|   2|2017-07-14 13:44:...|
| 11|   1|2017-07-15 13:45:...|
| 12|   1|2017-07-07 13:45:...|
+---+----+--------------------+

3.7 limit

limit方法獲取指定DataFrame的前n行記錄,得到一個新的DataFrame對象。和take與head不同的是,limit方法不是Action操作。比如獲得前3條記錄:

df.limit(3).show()

輸出爲

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  1|   1| 助手1|2017-08-10 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
+---+----+----+--------------------+

3.8 orderBy和sort

orderBy和sort:按指定字段排序,默認爲升序 ,例如,按照時間字段進行排序:

df.orderBy("visittime").show(false)

輸出爲:

+---+----+----+---------------------+
|id |user|type|visittime            |
+---+----+----+---------------------+
|12 |1   |助手2 |2017-07-07 13:45:08.0|
|10 |2   |助手1 |2017-07-14 13:44:57.0|
|11 |1   |APP1|2017-07-15 13:45:03.0|
|6  |3   |APP1|2017-08-01 13:44:41.0|
|5  |3   |APP1|2017-08-02 13:44:38.0|
|8  |3   |APP2|2017-08-03 13:44:45.0|
|2  |1   |APP1|2017-08-04 13:44:26.0|
|3  |2   |助手1 |2017-08-05 13:44:29.0|
|4  |2   |助手1 |2017-08-07 13:44:32.0|
|1  |1   |助手1 |2017-08-10 13:44:19.0|
|9  |2   |APP2|2017-08-11 13:44:53.0|
|7  |3   |助手2 |2017-08-14 13:44:48.0|
+---+----+----+---------------------+

如果想要降序排序,可以使用如下的方法:

df.orderBy(df("visittime").desc).show(false)

輸出爲:

+---+----+----+---------------------+
|id |user|type|visittime            |
+---+----+----+---------------------+
|7  |3   |助手2 |2017-08-14 13:44:48.0|
|9  |2   |APP2|2017-08-11 13:44:53.0|
|1  |1   |助手1 |2017-08-10 13:44:19.0|
|4  |2   |助手1 |2017-08-07 13:44:32.0|
|3  |2   |助手1 |2017-08-05 13:44:29.0|
|2  |1   |APP1|2017-08-04 13:44:26.0|
|8  |3   |APP2|2017-08-03 13:44:45.0|
|5  |3   |APP1|2017-08-02 13:44:38.0|
|6  |3   |APP1|2017-08-01 13:44:41.0|
|11 |1   |APP1|2017-07-15 13:45:03.0|
|10 |2   |助手1 |2017-07-14 13:44:57.0|
|12 |1   |助手2 |2017-07-07 13:45:08.0|
+---+----+----+---------------------+

3.9 group by數據分組

groupBy方法有兩種調用方式,可以傳入String類型的字段名,也可傳入Column類型的對象。
使用方法如下:

df.groupBy("user")
df.groupBy(df("user"))

groupBy方法之後得到的是GroupedData類型對象,不能直接接show方法來展示DataFrame,還需要跟一些分組統計函數,常用的統計函數有:
max(colNames: String)方法,獲取分組中指定字段或者所有的數字類型字段的最大值,只能作用於數字型字段
min(colNames: String
)方法,獲取分組中指定字段或者所有的數字類型字段的最小值,只能作用於數字型字段
mean(colNames: String)方法,獲取分組中指定字段或者所有的數字類型字段的平均值,只能作用於數字型字段
sum(colNames: String
)方法,獲取分組中指定字段或者所有的數字類型字段的和值,只能作用於數字型字段
count()方法,獲取分組中的元素個數
例如下面的例子:

df.groupBy("user").max("id").show()
df.groupBy(df("user")).max("id").show()

輸出爲:

+----+-------+
|user|max(id)|
+----+-------+
|   3|      8|
|   1|     12|
|   2|     10|
+----+-------+

我們還經常想要實現一個類似excel數據透視表的功能,這裏就需要用到pivot函數,比如要統計每個用戶通過各種渠道下單的次數:

df.groupBy(df("user")).pivot("type").count().show()

輸出爲:

+----+----+----+----+----+
|user|APP1|APP2| 助手1| 助手2|
+----+----+----+----+----+
|   3|   2|   1|null|   1|
|   1|   2|null|   1|   1|
|   2|null|   1|   3|null|
+----+----+----+----+----+

3.10 distinct數據去重

使用distinct:返回當前DataFrame中不重複的Row記錄。該方法和接下來的dropDuplicates()方法不傳入指定字段時的結果相同。

3.11 dropDuplicates:根據指定字段去重

跟distinct方法不同的是,此方法可以根據指定字段去重。例如我們想要去掉相同用戶通過相同渠道下單的數據:

df.dropDuplicates("user","type").show()

輸出爲:

+---+----+----+--------------------+
| id|user|type|           visittime|
+---+----+----+--------------------+
|  8|   3|APP2|2017-08-03 13:44:...|
|  1|   1| 助手1|2017-08-10 13:44:...|
|  7|   3| 助手2|2017-08-14 13:44:...|
| 12|   1| 助手2|2017-07-07 13:45:...|
|  3|   2| 助手1|2017-08-05 13:44:...|
|  5|   3|APP1|2017-08-02 13:44:...|
|  9|   2|APP2|2017-08-11 13:44:...|
|  2|   1|APP1|2017-08-04 13:44:...|
+---+----+----+--------------------+

3.11 agg方法實現聚合操作

聚合操作調用的是agg方法,該方法有多種調用方式。一般與groupBy方法配合使用。
比如我們查找最大的id,並把所有的user值相加,這裏只是爲了演示代碼的作用:

df.agg("id"->"max","user"->"sum").show()

輸出爲:

+-------+---------+
|max(id)|sum(user)|
+-------+---------+
|     12|     24.0|
+-------+---------+

3.12 withColumn添加新的一列

我們可以使用withColumn方法爲DataFrame添加新的一列,這個方法指定兩個參數,一個是列名,一個是值,值需要是Column對象:

df.withColumn("sex",df("user")%2).show()

輸出爲

+---+----+----+--------------------+---+
| id|user|type|           visittime|sex|
+---+----+----+--------------------+---+
|  1|   1| 助手1|2017-08-10 13:44:...|1.0|
|  2|   1|APP1|2017-08-04 13:44:...|1.0|
|  3|   2| 助手1|2017-08-05 13:44:...|0.0|
|  4|   2| 助手1|2017-08-07 13:44:...|0.0|
|  5|   3|APP1|2017-08-02 13:44:...|1.0|
|  6|   3|APP1|2017-08-01 13:44:...|1.0|
|  7|   3| 助手2|2017-08-14 13:44:...|1.0|
|  8|   3|APP2|2017-08-03 13:44:...|1.0|
|  9|   2|APP2|2017-08-11 13:44:...|0.0|
| 10|   2| 助手1|2017-07-14 13:44:...|0.0|
| 11|   1|APP1|2017-07-15 13:45:...|1.0|
| 12|   1| 助手2|2017-07-07 13:45:...|1.0|
+---+----+----+--------------------+---+

4、兩個DataFrame操作

首先,我們先來創建一個用戶性別表,並讀入新的DataFrame中。

val df2 = spark.read
      .format("jdbc")
      .option("url", url)
      .option("dbtable", "user")
      .option("user", "root")
      .option("password", "admin")
      .load()

df2.show()
+----+---+
|user|sex|
+----+---+
|   1||
|   2||
|   5||
+----+---+

4.1 join鏈接

首先,我們可以通過join函數實現兩個DataFrame的鏈接操作,並要指定鏈接字段:

df.join(df2,"user").show()

輸出爲:

+----+---+----+--------------------+---+
|user| id|type|           visittime|sex|
+----+---+----+--------------------+---+
|   1|  1| 助手1|2017-08-10 13:44:...||
|   1|  2|APP1|2017-08-04 13:44:...||
|   1| 11|APP1|2017-07-15 13:45:...||
|   1| 12| 助手2|2017-07-07 13:45:...||
|   2|  3| 助手1|2017-08-05 13:44:...||
|   2|  4| 助手1|2017-08-07 13:44:...||
|   2|  9|APP2|2017-08-11 13:44:...||
|   2| 10| 助手1|2017-07-14 13:44:...||
+----+---+----+--------------------+---+

如果我們有多個字段,可以使用:

df.join(df2,Seq("id","user"))

上面兩個指定鏈接字段的形式稱爲using形式,因爲類似於a join b using column1的形式,當然也可以使用Column類型來join,注意是三個等號:

df.join(df2,df("user")===df2("user"))

我們可以看到,默認的鏈接方式是內鏈接,當然我們已可以使用其他的方式,通過第三個參數來指定。我們可以指定的類型有inner, outer, left_outer, right_outer, leftsemi類型,不過只有using形式指定兩個及以上字段以及使用Column類型來鏈接的時候可以指定鏈接方式。

比如下面的方式是錯誤的:

df.join(df2,"user","outer").show()

比如我們使用外鏈接:

df.join(df2,df("user")===df2("user"),"outer").show()

結果爲:

+----+----+----+--------------------+----+----+
|  id|user|type|           visittime|user| sex|
+----+----+----+--------------------+----+----+
|   1|   1| 助手1|2017-08-10 13:44:...|   1||
|   2|   1|APP1|2017-08-04 13:44:...|   1||
|  11|   1|APP1|2017-07-15 13:45:...|   1||
|  12|   1| 助手2|2017-07-07 13:45:...|   1||
|   5|   3|APP1|2017-08-02 13:44:...|null|null|
|   6|   3|APP1|2017-08-01 13:44:...|null|null|
|   7|   3| 助手2|2017-08-14 13:44:...|null|null|
|   8|   3|APP2|2017-08-03 13:44:...|null|null|
|   3|   2| 助手1|2017-08-05 13:44:...|   2||
|   4|   2| 助手1|2017-08-07 13:44:...|   2||
|   9|   2|APP2|2017-08-11 13:44:...|   2||
|  10|   2| 助手1|2017-07-14 13:44:...|   2||
|null|null|null|                null|   5||
+----+----+----+--------------------+----+----+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章