【问题标题】:Scala Spark Structured Streaming Filter by TimestampType within Struct FieldScala Spark结构化流过滤器按Struct字段中的TimestampType
【发布时间】:2020-10-13 03:02:26
【问题描述】:

我在定义的架构中有多种数据类型。试图找到一种按 TimestampType 过滤的好方法,将所有 TimeStampType 字段从 long 转换为 datetime。我可以在 StringType 的流中使用 .dtypes 进行过滤,但在尝试使用 StructFields 和 StructTypes 的 .dtypes 进行过滤时遇到问题。 有没有办法只过滤 Struct 中的 TimestampType? 下面是我在 Scala 2.11 中使用 spark 结构化流的 sudo 代码

val isoDateFormatter = "yyyy-MM-dd'T'HH:mm:ss'Z'"

val ExampleDataFrameLoad = spark
    .readStream
    .format("kafka")
    .option("subscribe", topics.keys.mkString(","))
    .options(kafkaConfig)
    .load()
    .select($"key".cast(StringType), $"value".cast(StringType), $"topic")
    // Convert untyped dataframe to dataset
    .as[(String, String, String)]
    // Merge all manifests for vehicle in minibatch
    .groupByKey(_._1)
    //Start of merge
    .flatMapGroupsWithState(OutputMode.Append, GroupStateTimeout.ProcessingTimeTimeout)(mergeGroup)
    // .select($"key".cast(StringType),from_json($"value",schema).as("manifest"))
    .select($"_1".alias("key"), $"_2".alias("jsonvalues"))
    .select("key", "jsonvalues.*")

val ExampleDataFrame = ExampleDataFrameLoad
 ExampleDataFrame.dtypes.foreach(println)
/* Returns      
(key,StringType)
(contractVersion,StringType)
(metaData,StructType(StructField(Test,StringType,true), StructField(DateUtc,TimestampType,true) 
*/

*Uses the following objects
   import java.sql.Timestamp

 object ManifestClasses {
 
 final case class ProductManifestDocument(
                                        contractVersion: Option[String],
                                        metaData: DocumentMetaData 
                                      )
                                      
final case class DocumentMetaData(
                                 Test: Option[String]
                                 DateUtc: Timestamp
                               ) 
*/
 

 ExampleDataFrame
    //brings back data fields with types
    .dtypes
   //Currently returning empty but works for StringType 
    .filter(_._2 == "TimestampType")

    .map(_._1)
    //Tranforms all timestamp longs to yyyy-MM-dd'T'HH:mm:ss'Z' format
    .foldLeft(ExampleDataFrame)((df, colName) => df.withColumn(colName, date_format(col(colName), isoDateFormatter)))

【问题讨论】:

    标签: scala apache-spark spark-streaming


    【解决方案1】:

    你可以像下面这样转换structType -

    就地更改时间戳类型的日期格式

    加载测试数据

    val ExampleDataFrame = spark.sql("select key, contractVersion, metaData from values " +
          "('k1', 'v1', named_struct('Test', 'test1', 'DateUtc', cast(unix_timestamp() as timestamp))) " +
          "T(key, contractVersion, metaData)")
        ExampleDataFrame.show(false)
        ExampleDataFrame.printSchema()
        ExampleDataFrame.dtypes.foreach(println)
        /**
          * +---+---------------+----------------------------+
          * |key|contractVersion|metaData                    |
          * +---+---------------+----------------------------+
          * |k1 |v1             |[test1, 2020-06-23 14:39:55]|
          * +---+---------------+----------------------------+
          *
          * root
          * |-- key: string (nullable = false)
          * |-- contractVersion: string (nullable = false)
          * |-- metaData: struct (nullable = false)
          * |    |-- Test: string (nullable = false)
          * |    |-- DateUtc: timestamp (nullable = true)
          *
          * (key,StringType)
          * (contractVersion,StringType)
          * (metaData,StructType(StructField(Test,StringType,false), StructField(DateUtc,TimestampType,true)))
          */
    

    将时间戳从结构转换为特定日期格式

    
        val isoDateFormatter = "yyyy-MM-dd'T'HH:mm:ss'Z'"
        val processedDF = ExampleDataFrame.withColumn("metaData", struct($"metaData.Test",
          date_format($"metaData.DateUtc", isoDateFormatter)))
          processedDF.show(false)
    
        /**
          * +---+---------------+-----------------------------+
          * |key|contractVersion|metaData                     |
          * +---+---------------+-----------------------------+
          * |k1 |v1             |[test1, 2020-06-23T14:51:17Z]|
          * +---+---------------+-----------------------------+
          */
    

    Update-1(基于 cmets)

    从 structType 中提取 timestamptype 作为单独的列

    ExampleDataFrame.schema
          .filter(_.dataType.isInstanceOf[StructType])
          .flatMap(s => s.dataType.asInstanceOf[StructType]
            .filter(_.dataType == TimestampType)
            .map(f => s"${s.name}.${f.name}")
          )
          .foldLeft(ExampleDataFrame)((df, colName) => df.withColumn(colName, date_format(col(colName), isoDateFormatter)))
          .show(false)
    
        /**
          * +---+---------------+----------------------------+--------------------+
          * |key|contractVersion|metaData                    |metaData.DateUtc    |
          * +---+---------------+----------------------------+--------------------+
          * |k1 |v1             |[test1, 2020-06-23 21:40:36]|2020-06-23T21:40:36Z|
          * +---+---------------+----------------------------+--------------------+
          */
    // use df.select("`metaData.DateUtc`") to select the columns having dot(.)
    

    【讨论】:

    • 选择选项很好,唯一的问题是我有大约 100 列可供选择,这就是我使用 manifest.* 选项的原因。我也有 .withcolumn 转换工作,但问题是,一旦添加了新字段,它就会从架构中删除现有列。这可行,但会删除架构中的现有列 .withColumn("metaData", struct(lit(date_format(col("metaData.DateUtc"), isoDateFormatter)) 为 "collectionDateUtc" ) )
    • 有没有一种好方法可以更改 .dtypes 以列出分解的 StructField,然后过滤器会选择时间戳类型 (key,StringType) (contractVersion,StringType) (DateUtc,TimestampType,true)
    猜你喜欢
    • 2018-11-27
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 2017-05-04
    • 2019-02-17
    相关资源
    最近更新 更多