我正在使用Scala和Spark 2.1.1。这是我的DataFrame模式的一部分:
root
|-- id: string (nullable = true)
|-- op: string (nullable = true)
|-- before: struct (containsNull = true)
| |-- a: string (nullable = true)
| |-- b: string (nullable = true)
| |-- c: string (nullable = true)
| |-- d: string (nullable = true)
|-- after: struct (containsNull = true)
| |-- a: string (nullable = true)
| |-- c: string (nullable = true)
它描述了对外部数据库的操作。在更新操作的情况下,数据帧包含表示更新操作之前和之后的行状态的“之前”和“之后”列。 '之前'始终包含完整行,而'之后'仅包含更新的字段。我需要将它们合并到一个包含更新后的完整行的列(和最终的DataFrame)中(只需将'before'和将其字段的值更改为从'after'取出的值(如果存在))。我尝试了不同的方法来实现这一点(主要是通过在'之前'和'之后'执行UDF创建一个新列,但我无法实现它。
一个包含3行的示例(为方便起见,我将使用JSON表示法):
{... "before": {"a": "1", "b": "2", "c": "test", "d": true}, "after": {"b": "3"} ...}
{... "before": {"a": "2", "b": null, "c": "test2", "d": false}, "after": {"c": "test4", "d": true} ...}
{... "before": {"other": "4", "other2": "5"}, "after": {"other": "5"} ...}
我需要什么:
{... "fullAfter": {"a": "1", "b": "3", "c": "test", "d": true} ...}
{... "fullAfter": {"a": "2", "b": null, "c": "test4", "d": true} ...}
{... "fullAfter": {"other": "5", "other2": "5"} ...}
问题是DataFrame包含来自不同表的操作,因此'before'和'after'在每一行中可能有不同的模式。
我尝试通过将'before'和'after'转换为JSON(to_json)并基于它们创建新的JSON来在UDF中执行一些操作。不幸的是to_json方法导致具有空值的字段消失,因此我无法在没有完整的原始模式的情况下创建完整行:
{... "before": {"a": "2", "b": null, "c": "test2", "d": false}, "after": {"c": "test4", "d": true} ...}
{... "fullAfter": {"a": "2", "c": "test4", "d": true} ...} - "b" is missing
有没有任何有效,简单/有效的方法呢?
答案 0 :(得分:0)
我建议使用spark sql解决问题。 让我们将您的表格命名为table_with_updates(id,op,before [a,b,c,d],after [a,c])
<!-- language: sql -->
select id, op, struct(
coalesce(after.a,before.a) as a,
after.b as b,
coalesce(after.c,before.c) as c,
before.d as d) as fullAfter
from table_with_updates
显然,您也可以对b和d使用合并,但是我认为您的after模式中缺少该合并。合并仅采用第一个非null值。
使用UDF时,您必须输入它。您需要案例类Before(a:String,b:String,c:String,d:String)和After。这不适用于空值,并且需要大量的逻辑和编码。而且,甚至scala udfs都比spark sql函数要慢得多。
如果您需要更动态的代码,我通常要做的只是编写一些代码以根据列名生成sql。 Scala在这方面很棒。
val colsAfter = spark.table("table_with_updates").select($"after").columns()
val colsBefore = spark.table("table_with_updates").select($"before").columns()
.map(c => if colsAfter.contains(c) s"coalesce(after.$c,before.$c" else s"before.$c as $c")
val structFields = colsBefore.mkString(",")
sql(" select id, op, struct($structFields) as fullAfter from table_with_updates")