如何使用具有相同架构的Scala在Spark中比较2个文件

时间:2019-05-07 11:15:26

标签: scala apache-spark dataframe

所以我有这2个文件(获取hive表的hdfs文件位置),具有历史记录的file1和具有当前日期的file2。它们都具有相同的架构。现在,我想对两个文件进行比较后,执行CDC流程以获取更新的/新插入的记录。 多个列中可能会有更改,因此我们想一次提取所有更改的列。 假设这些列是:-Customer_ID,名称,地址,国家/地区。 现在,Customer_ID是主键,而其余3列可能会更改。

文件1

12343| John| Rear exit market| SanFrancisco
45656| Bobs| Knewbound Road PD| Seattle
54345| Fersi| Dallas Road Pnth| Newyork
86575| Persa| Roman Building Path| Kirkland
64565| Camy| Olympus Ground 3rd| NewJersey

文件2

12343| John| World Centre Phase| SanFrancisco
54345| Posi| Dallas Road Pnth| Newyork

我希望最终结果看起来像:-

12343|Rear exit market| World Centre Phase
54345| Fersi| Posi

所以我想要主键,更改前的先前记录,新的更新记录,这些记录在我的最终答案中得到了更新。

1 个答案:

答案 0 :(得分:0)

这是一个可能的解决方案。正如我的评论中提到的那样,这几乎是3或4班轮的价格,但我提供了一些替代方案。

// Load the data into 2 dataframes
val df1 = spark.read.option("sep","|").csv("file1a.txt")
val df2 = spark.read.option("sep","|").csv("file2a.txt")

// Next join the two dataframes using an INNER JOIN on the key as follows:
val joined = df1.joinWith(df2, df1.col("_c0") === df2.col("_c0"))

文件中没有标题信息,因此列将获得默认名称。联接的模式基本上是一个Tuple2,每个Tuple2包含联接每一侧的列列表。

以下是架构:

scala> df1.printSchema
root
 |-- _c0: string (nullable = true)
 |-- _c1: string (nullable = true)
 |-- _c2: string (nullable = true)
 |-- _c3: string (nullable = true)


scala> joined.printSchema
root
 |-- _1: struct (nullable = false)
 |    |-- _c0: string (nullable = true)
 |    |-- _c1: string (nullable = true)
 |    |-- _c2: string (nullable = true)
 |    |-- _c3: string (nullable = true)
 |-- _2: struct (nullable = false)
 |    |-- _c0: string (nullable = true)
 |    |-- _c1: string (nullable = true)
 |    |-- _c2: string (nullable = true)
 |    |-- _c3: string (nullable = true)

最后一步是(我认为)您想要生产的东西的外推法。我认为您想显示哪些列具有不同的值。您显示的输出格式,恕我直言,有两个潜在的问题。我认为您只是想在两个输出列中显示一个不同的值。恕我直言,这有两个挑战:

  1. 如果一条记录中有两列具有不同的值-那么您需要为每个输出记录显示4个(或更多)值怎么办?
  2. 如果有很多具有差异的记录怎么办,这将使查找不同的原始列变得非常困难(因为在输出中丢失了所有具有差异的列的标识)-这将更加困难带有大量列的记录。
  3. 当您在结果集中随机排列列时,解决方案可能会更加复杂。

以下输出格式通过显示所有列以及一个指示符来显示哪些列具有不同的值,从而解决了上述问题。指标是关键,因为它使差异更容易发现。 这是“强力”方法,其中列出了每一列,并且手动确定了每个差异。

joined.select($"_1._C0".as("id"), $"_1._c1", $"_2._c1", when(col("_1._c1") === col("_2._c1"), "").otherwise("ne").as("c1 Ind"),
  $"_1._c2", $"_2._c2", when(col("_1._c2") === col("_2._c2"), "").otherwise("ne").as("c2 Ind"),
  $"_1._c3", $"_2._c3", when(col("_1._c3") === col("_2._c3"), "").otherwise("ne").as("c3 Ind")).show(false)

哪个会产生:

+-----+-----+----+------+----------------+------------------+------+------------+------------+------+
|id   |_c1  |_c1 |c1 Ind|_c2             |_c2               |c2 Ind|_c3         |_c3         |c3 Ind|
+-----+-----+----+------+----------------+------------------+------+------------+------------+------+
|12343|John |John|      |Rear exit market|World Centre Phase|ne    |SanFrancisco|SanFrancisco|      |
|54345|Fersi|Posi|ne    |Dallas Road Pnth|Dallas Road Pnth  |      |Newyork     |Newyork     |      |
+-----+-----+----+------+----------------+------------------+------+------------+------------+------+

蛮力法很乏味且难以输入-特别是对于较大的结果集。因此,我们可以使用一些Scala魔术使它更加优雅。

// Define a helper function that takes a column name and returns the three parts needed
// to generate the output for that column. i.e. select the column from the two sides of the joined result set
// and generate the case statement to generate the "ne" indicator if the two values
// are unequal.
def genComp(colName:String) = List(s"_1.$colName", s"_2.$colName", s"case when _1.$colName = _2.$colName then '' else 'ne' end as ${colName}_ind")

// Run the query to produce the results:
joined.selectExpr(
    (List("_1._C0 as id") ++ genComp("_c1") ++ genComp("_c2") ++ genComp("_c3")) : _*
  ).show(false)

运行时,其产生的结果与“强力”方法相同。

这是如何工作的?好在第二行是魔术,它是selectExpr方法的功能。

selectExpr方法具有以下签名:def selectExpr(exprs: String*): org.apache.spark.sql.DataFrame。这意味着它可以接受可变数量的字符串参数。

要生成传递给selectExpr的参数,我使用此构造List (strings) : _*。这是Scala的“魔术”,它接收字符串列表并将其转换为可变数量的参数参数列表。

其余的非常简单。基本上,genComp函数会返回一个字符串列表,这些字符串用于标识联接的DataFrame的每一侧中的列以及不等式指示符生成逻辑。将它们连接在一起。结果转换为传递给selectExpr的参数列表,该列表最终运行与“蛮力”方法相同的查询。

这是一个有趣的想法,我将作为练习:df1的架构使用genComp生成要输出的列的列表(而不是像我所示的那样简单地手动串联它们)。

这是一个 big 提示:

val cols = df1.schema.filter(c => c.name != "_c0").map(c => List(c.name)).flatten
cols.foreach(println)