【发布时间】:2019-04-08 20:08:53
【问题描述】:
假设我有以下 DataFrame:
+---+--------+---+----+----+
|grp|null_col|ord|col1|col2|
+---+--------+---+----+----+
| 1| null| 3|null| 11|
| 2| null| 2| xxx| 22|
| 1| null| 1| yyy|null|
| 2| null| 7|null| 33|
| 1| null| 12|null|null|
| 2| null| 19|null| 77|
| 1| null| 10| s13|null|
| 2| null| 11| a23|null|
+---+--------+---+----+----+
这是带有 cmets 的同一个示例 DF,按 grp 和 ord 排序:
scala> df.orderBy("grp", "ord").show
+---+--------+---+----+----+
|grp|null_col|ord|col1|col2|
+---+--------+---+----+----+
| 1| null| 1| yyy|null|
| 1| null| 3|null| 11| # grp:1 - last value for `col2` (11)
| 1| null| 10| s13|null| # grp:1 - last value for `col1` (s13)
| 1| null| 12|null|null| # grp:1 - last values for `null_col`, `ord`
| 2| null| 2| xxx| 22|
| 2| null| 7|null| 33|
| 2| null| 11| a23|null| # grp:2 - last value for `col1` (a23)
| 2| null| 19|null| 77| # grp:2 - last values for `null_col`, `ord`, `col2`
+---+--------+---+----+----+
我想压缩它。 IE。按"grp" 列对它进行分组,对于每个组,按"ord" 列对行进行排序,并在每列中取最后一个not null 值(如果有的话)。
+---+--------+---+----+----+
|grp|null_col|ord|col1|col2|
+---+--------+---+----+----+
| 1| null| 12| s13| 11|
| 2| null| 19| a23| 77|
+---+--------+---+----+----+
我见过以下类似的问题:
- How to select the first row of each group?
- How to find first non-null values in groups? (secondary sorting using dataset api)
但我真正的 DataFrame 有超过 250 列,所以我需要一个解决方案,我不必明确指定所有列。
我无法理解它......
MCVE:如何创建示例 DataFrame:
- 创建本地文件“/tmp/data.txt”并复制并粘贴 DataFrame 的上下文(如上面发布的那样)
- 定义function
readSparkOutput(): -
将“/tmp/data.txt”解析为DataFrame:
val df = readSparkOutput("file:///tmp/data.txt")
UPDATE:我觉得应该类似于下面的SQL:
SELECT
grp, ord, null_col, col1, col2
FROM (
SELECT
grp,
ord,
FIRST(null_col) OVER (PARTITION BY grp ORDER BY ord DESC) as null_col,
FIRST(col1) OVER (PARTITION BY grp ORDER BY ord DESC) as col1,
FIRST(col2) OVER (PARTITION BY grp ORDER BY ord DESC) as col2,
ROW_NUMBER() OVER (PARTITION BY grp ORDER BY ord DESC) as rn
FROM table_name) as v
WHERE v.rn = 1;
我们如何动态地生成这样的 Spark 查询?
我尝试了以下简化的方法:
import org.apache.spark.sql.expressions.Window
val win = Window
.partitionBy("grp")
.orderBy($"ord".desc)
val cols = df.columns.map(c => first(c, ignoreNulls=true).over(win).as(c))
产生:
scala> cols
res23: Array[org.apache.spark.sql.Column] = Array(first(grp, true) OVER (PARTITION BY grp ORDER BY ord DESC NULLS LAST UnspecifiedFrame) AS `grp`, first(null_col, true) OVER (PARTITION BY grp ORDER BY ord DESC NULLS LAST UnspecifiedFrame) AS `null_col`, first(ord, true) OVER (PARTITION BY grp ORDER BY ord DESC NULLS LAST UnspecifiedFrame) AS `ord`, first(col1, true) OVER (PARTITION BY grp ORDER BY ord DESC NULLS LAST UnspecifiedFrame) AS `col1`, first(col2, true) OVER (PARTITION BY grp ORDER BY ord DESC NULLS LAST UnspecifiedFrame) AS `col2`)
但我无法将其传递给df.select:
scala> df.select(cols.head, cols.tail: _*).show
<console>:34: error: no `: _*' annotation allowed here
(such annotations are only allowed in arguments to *-parameters)
df.select(cols.head, cols.tail: _*).show
又一次尝试:
scala> df.select(cols.map(col): _*).show
<console>:34: error: type mismatch;
found : String => org.apache.spark.sql.Column
required: org.apache.spark.sql.Column => ?
df.select(cols.map(col): _*).show
【问题讨论】:
-
好吧,我猜你可以编写一个函数来接受一个 DF 和一个按列名分组的函数,然后你可以通过从 DF 模式中获取剩余的列来动态创建一个选择语句,该模式选择每个列的最大值列。
-
@TerryDactyl,感谢您的建议!不幸的是
max()不会给我我需要的东西。我需要最后一个(就“ord”列而言)而不是空值。而且我仍然不知道如何动态地制作它。如果你能把它放在答案中,我将不胜感激 -
@eliasah,感谢您的评论。您知道如何动态获取所需的数据集吗?
-
如果你有时间,请考虑在我的帖子中权衡:stackoverflow.com/questions/53927460/… 谢谢 :)
-
@coldspeed,当然,这是一项很棒的工作! :)
标签: scala apache-spark aggregate-functions aggregation