关系数据模型中Spark中的树/嵌套结构

时间:2019-03-17 19:10:30

标签: apache-spark apache-spark-dataset

如果我正确理解,我可以将Spark数据集视为类型为T的对象列表。如何以父级包含子级列表的方式连接两个数据集?但是一个孩子也会有自己的孩子清单...

一种解决方法是根据密钥对groupBy个孩子进行操作,但是collect_list仅返回一列,我想有一种更好的方法。

想要的结果基本上是Customer类型的数据集(客户对象列表?),但有附加内容:

  • 每个客户都有一张发票清单。
  • 每张发票都有自己的属性,但是里面还有项目列表...
  • ...这可以继续(一棵树)

最终结果将类似于

case class Customer(customer_id: Int, name: String, address: String, age: Int, invoices: List[Invoices])
case class Invoice(invoice_id: Int, customer_id: Int, invoice_num:String, date: Int, invoice_type: String, items: List[Items])

要达到这个结果,我需要来自以下输入:

case class Customer(customer_id: Int, name: String, address: String, age: Int)
case class Invoice(invoice_id: Int, customer_id: Int, invoice_num:String, date: Int, invoice_type: String)
case class InvoiceItem(item_id: Int, invoice_id: Int, num_of_items: Int, price: Double, total: Double)

    val customers_df = Seq(
       (11,"customer1", "address1", 10, "F")
      ,(12,"customer2", "address2", 20, "M")
      ,(13,"customer3", "address3", 30, "F")
    ).toDF("customer_id", "name", "address", "age", "sex")
    val customers_ds = customers_df.as[Customer].as("c")

    customers_ds.show

    val invoices_df = Seq(
       (21,11, "10101/1", 20181105, "manual")
      ,(22,11, "10101/2", 20181105, "manual")
      ,(23,11, "10101/3", 20181105, "manual")
      ,(24,12, "10101/4", 20181105, "generated")
      ,(25,12, "10101/5", 20181105, "pos")
    ).toDF("invoice_id", "customer_id", "invoice_num", "date", "invoice_type")
    val invoices_ds = invoices_df.as[Invoice].as("i")

    invoices_ds.show

    val invoice_items_df = Seq(
       (31, 21, 5, 10.0, 50.0)
      ,(32, 21, 3, 15.0, 45.0)
      ,(33, 22, 6, 11.0, 66.0)
      ,(34, 22, 7, 2.0, 14.0)
      ,(35, 23, 1, 100.0, 100.0)
      ,(36, 24, 4, 4.0, 16.0)
    ).toDF("item_id", "invoice_id", "num_of_items", "price", "total")
    val invoice_items_ds = invoice_items_df.as[InvoiceItem].as("ii")

    invoice_items_ds.show

在表中看起来像这样:

+-----------+---------+--------+---+---+
|customer_id|     name| address|age|sex|
+-----------+---------+--------+---+---+
|         11|customer1|address1| 10|  F|
|         12|customer2|address2| 20|  M|
|         13|customer3|address3| 30|  F|
+-----------+---------+--------+---+---+

+----------+-----------+-----------+--------+------------+
|invoice_id|customer_id|invoice_num|    date|invoice_type|
+----------+-----------+-----------+--------+------------+
|        21|         11|    10101/1|20181105|      manual|
|        22|         11|    10101/2|20181105|      manual|
|        23|         11|    10101/3|20181105|      manual|
|        24|         12|    10101/4|20181105|   generated|
|        25|         12|    10101/5|20181105|         pos|
+----------+-----------+-----------+--------+------------+

+-------+----------+------------+-----+-----+
|item_id|invoice_id|num_of_items|price|total|
+-------+----------+------------+-----+-----+
|     31|        21|           5| 10.0| 50.0|
|     32|        21|           3| 15.0| 45.0|
|     33|        22|           6| 11.0| 66.0|
|     34|        22|           7|  2.0| 14.0|
|     35|        23|           1|100.0|100.0|
|     36|        24|           4|  4.0| 16.0|
+-------+----------+------------+-----+-----+

2 个答案:

答案 0 :(得分:1)

似乎您正在尝试将标准化数据读入Scala对象树中。您当然可以使用Spark来做到这一点,但Spark可能不是最佳的工具。如果数据足够小以适合内存,我认为从您的问题是正确的,那么对象关系映射(ORM)库可能更适合此工作。

如果您仍然想使用Spark,那么您在groupBycollect_list的正确位置上。您缺少的是struct()函数。

case class Customer(id: Int)
case class Invoice(id: Int, customer_id: Int)

val customers = spark.createDataset(Seq(Customer(1))).as("customers")
val invoices = spark.createDataset(Seq(Invoice(1, 1), Invoice(2, 1)))

case class CombinedCustomer(id: Int, invoices: Option[Seq[Invoice]])

customers
  .join(
    invoices
      .groupBy('customer_id)
      .agg(collect_list(struct('*)).as("invoices"))
      .withColumnRenamed("customer_id", "id"), 
    Seq("id"), "left_outer")
  .as[CombinedCustomer]
  .show

struct('*)从整个行中构建一个StructType列。您还可以选择任何列,例如struct('x.as("colA"), 'colB)

这产生

+---+----------------+
| id|        invoices|
+---+----------------+
|  1|[[1, 1], [2, 1]]|
+---+----------------+

现在,在预计客户数据无法容纳在内存中的情况下,即不能使用简单的collect,可以采取许多不同的策略。

最简单的一种方法,而不是收集到驱动程序中,应该考虑,它要求可以独立处理每个客户的数据。在这种情况下,请尝试使用map并将按客户的处理逻辑分配给工作人员。

如果不能接受客户进行独立处理,则一般策略如下:

  1. 使用上述方法根据需要将数据聚合到结构化行中。

  2. 对数据进行分区,以确保您需要处理的所有内容都在单个分区中。

  3. (可选)sortWithinPartitions,以确保分区中的数据按需要排序。

  4. 使用mapPartitions

答案 1 :(得分:0)

您可以使用Spark-SQL,并且每个客户,发票和物料都有一个数据集。 然后,您可以简单地在这些数据集之间使用联接和聚合函数来获得所需的输出。

Spark SQL在sql样式和编程方式之间的性能差异可以忽略不计。