目前我有一个输入文件(数百万条记录),其中所有记录都包含2个字符的标识符。此输入文件中的多行将连接到输出文件中的一个记录,以及如何根据标识符的连续顺序确定如何确定
例如,记录将从下面开始
1A
1B
1C
2A
2B
2C
的 1A
1C
2B
2C
的 1A
1B
1C
1A表示新记录的开头,因此在这种情况下输出文件将有3条记录。 " 1A"之间的所有内容将合并为一个记录
1A + 1B + 1C + 2A + 2B + 2C
1A + 1C + 2B + 2C
1A + 1B + 1C
" 1A"之间的记录数量各不相同,因此我必须迭代并检查标识符。
我不确定如何使用scala / spark来解决这种情况。 我的策略是:
将输入文件加载到数据框中。
根据记录的子字符串创建标识符列。
创建一个新列TempID和一个设置为0的变量x
迭代数据框
如果Identifier = 1A,x = x + 1
TempID =变量x
然后创建一个UDF以使用相同的TempID连接记录。
总结我的问题: 如何遍历数据帧,检查Identifier列的值,然后分配tempID(如果identifier列的值为1A,则其值增加1)
答案 0 :(得分:1)
这很危险。问题是火花不能保证在元素之间保持相同的顺序,特别是因为它们可能跨越分区边界。因此,当您迭代它们时,您可以获得不同的订单。这也必须完全按顺序发生,所以在这一点上,为什么不只是完全跳过spark并将其作为常规scala代码运行,作为前处理步骤之前的火花。
我的建议是要么考虑编写自定义数据输入格式/数据源,要么可以使用“1A”作为类似于this question.的记录分隔符
答案 1 :(得分:1)
首先 - 通常“迭代”DataFrame
(或Spark的其他分布式集合抽象,如RDD
和Dataset
)错误或不可能。该术语根本不适用。你应该使用Spark的函数转换这些集合,而不是试图迭代它们。
您可以使用窗口函数来实现目标(或 - 几乎,要遵循的详细信息)。这里的想法是(1)添加“id”列进行排序,(2)使用Window函数(基于该排序)计数以前的“1A”实例数,然后(3)使用这些“计数”作为“组ID”,将每个组的所有记录连接在一起,并按其分组:
import functions._
import spark.implicits._
// sample data:
val df = Seq("1A", "1B", "1C", "2A", "2B", "2C", "1A", "1C", "2B", "2C", "1A", "1B", "1C").toDF("val")
val result = df.withColumn("id", monotonically_increasing_id()) // add row ID
.withColumn("isDelimiter", when($"val" === "1A", 1).otherwise(0)) // add group "delimiter" indicator
.withColumn("groupId", sum("isDelimiter").over(Window.orderBy($"id"))) // add groupId using Window function
.groupBy($"groupId").agg(collect_list($"val") as "list") // NOTE: order of list might not be guaranteed!
.orderBy($"groupId").drop("groupId") // removing groupId
result.show(false)
// +------------------------+
// |list |
// +------------------------+
// |[1A, 1B, 1C, 2A, 2B, 2C]|
// |[1A, 1C, 2B, 2C] |
// |[1A, 1B, 1C] |
// +------------------------+
(如果将结果作为列表不符合您的需求,我会留给您将此列转换为您需要的任何内容)
此处的主要警告是collect_list
不一定保证保留订单 - 一旦您使用groupBy
,订单可能会丢失。所以 - 每个结果列表中的顺序可能是错误的(但是,与组的分离必然是正确的)。如果这对您很重要,可以通过收集包含"id"
列的列列表并稍后使用它来对这些列表进行排序来解决此问题。
编辑:如果不解决这个警告就意识到这个答案是不完整的,并且意识到这不是微不足道的 - 这就是你如何解决它:
定义以下UDF:
val getSortedValues = udf { (input: mutable.Seq[Row]) => input
.map { case Row (id: Long, v: String) => (id, v) }
.sortBy(_._1)
.map(_._2)
}
然后,将上面建议的解决方案中的行.groupBy($"groupId").agg(collect_list($"val") as "list")
替换为以下行:
.groupBy($"groupId")
.agg(collect_list(struct($"id" as "_1", $"val" as "_2")) as "list")
.withColumn("list", getSortedValues($"list"))
这样我们必须保留订单(以及对这些小清单进行排序的价格)。