창 기능 :
이와 같은 것이 트릭을 수행해야합니다.
import org.apache.spark.sql.functions.{row_number, max, broadcast}
import org.apache.spark.sql.expressions.Window
val df = sc.parallelize(Seq(
(0,"cat26",30.9), (0,"cat13",22.1), (0,"cat95",19.6), (0,"cat105",1.3),
(1,"cat67",28.5), (1,"cat4",26.8), (1,"cat13",12.6), (1,"cat23",5.3),
(2,"cat56",39.6), (2,"cat40",29.7), (2,"cat187",27.9), (2,"cat68",9.8),
(3,"cat8",35.6))).toDF("Hour", "Category", "TotalValue")
val w = Window.partitionBy($"hour").orderBy($"TotalValue".desc)
val dfTop = df.withColumn("rn", row_number.over(w)).where($"rn" === 1).drop("rn")
dfTop.show
// +----+--------+----------+
// |Hour|Category|TotalValue|
// +----+--------+----------+
// | 0| cat26| 30.9|
// | 1| cat67| 28.5|
// | 2| cat56| 39.6|
// | 3| cat8| 35.6|
// +----+--------+----------+
이 방법은 데이터 왜곡이 심할 경우 비효율적입니다.
일반 SQL 집계 다음에join
:
또는 집계 된 데이터 프레임과 결합 할 수 있습니다.
val dfMax = df.groupBy($"hour".as("max_hour")).agg(max($"TotalValue").as("max_value"))
val dfTopByJoin = df.join(broadcast(dfMax),
($"hour" === $"max_hour") && ($"TotalValue" === $"max_value"))
.drop("max_hour")
.drop("max_value")
dfTopByJoin.show
// +----+--------+----------+
// |Hour|Category|TotalValue|
// +----+--------+----------+
// | 0| cat26| 30.9|
// | 1| cat67| 28.5|
// | 2| cat56| 39.6|
// | 3| cat8| 35.6|
// +----+--------+----------+
중복 값을 유지합니다 (동일한 총 값으로 시간당 둘 이상의 범주가있는 경우). 다음과 같이 제거 할 수 있습니다.
dfTopByJoin
.groupBy($"hour")
.agg(
first("category").alias("category"),
first("TotalValue").alias("TotalValue"))
이상 주문 사용structs
:
잘 테스트되지는 않았지만 깔끔하거나 조인이나 창 기능이 필요없는 트릭 :
val dfTop = df.select($"Hour", struct($"TotalValue", $"Category").alias("vs"))
.groupBy($"hour")
.agg(max("vs").alias("vs"))
.select($"Hour", $"vs.Category", $"vs.TotalValue")
dfTop.show
// +----+--------+----------+
// |Hour|Category|TotalValue|
// +----+--------+----------+
// | 0| cat26| 30.9|
// | 1| cat67| 28.5|
// | 2| cat56| 39.6|
// | 3| cat8| 35.6|
// +----+--------+----------+
DataSet API (Spark 1.6 이상, 2.0 이상) :
스파크 1.6 :
case class Record(Hour: Integer, Category: String, TotalValue: Double)
df.as[Record]
.groupBy($"hour")
.reduce((x, y) => if (x.TotalValue > y.TotalValue) x else y)
.show
// +---+--------------+
// | _1| _2|
// +---+--------------+
// |[0]|[0,cat26,30.9]|
// |[1]|[1,cat67,28.5]|
// |[2]|[2,cat56,39.6]|
// |[3]| [3,cat8,35.6]|
// +---+--------------+
Spark 2.0 이상 :
df.as[Record]
.groupByKey(_.Hour)
.reduceGroups((x, y) => if (x.TotalValue > y.TotalValue) x else y)
마지막 두 가지 방법은 맵 측 결합을 활용할 수 있으며 전체 셔플이 필요하지 않으므로 대부분의 경우 창 기능 및 조인에 비해 성능이 향상됩니다. 이 지팡이는 또한 completed
출력 모드 에서 구조적 스트리밍과 함께 사용 됩니다.
사용하지 마십시오 :
df.orderBy(...).groupBy(...).agg(first(...), ...)
그것은 (특히 local
모드에서) 작동하는 것처럼 보이지만 신뢰할 수 없습니다 ( SPARK-16207 , 관련 JIRA 문제 를 연결 하기위한 Tzach Zohar의 크레딧 및 SPARK-30335 참조 ).
같은 메모가 적용됩니다
df.orderBy(...).dropDuplicates(...)
내부적으로 동등한 실행 계획을 사용합니다.