J'utilise Spark SQL (je mentionne qu'il est en Spark au cas où cela affecte la syntaxe SQL - je ne suis pas encore assez familier pour en être sûr)) et j'ai une table que j'essaie de restructurer, mais je suis coincé en essayant de transposer plusieurs colonnes en même temps.
Fondamentalement, j'ai des données qui ressemblent à:
userId someString varA varB
1 "example1" [0,2,5] [1,2,9]
2 "example2" [1,20,5] [9,null,6]
et je voudrais exploser simultanément varA et varB (la longueur sera toujours cohérente) - de sorte que la sortie finale ressemble à ceci:
userId someString varA varB
1 "example1" 0 1
1 "example1" 2 2
1 "example1" 5 9
2 "example2" 1 9
2 "example2" 20 null
2 "example2" 5 6
mais je ne peux obtenir qu'une seule instruction explode (var) pour fonctionner dans une seule commande, et si j'essaye de les enchaîner (c'est-à-dire créer une table temporaire après la première commande explode), alors j'obtiens évidemment un grand nombre de doublons, inutile Lignes.
Merci beaucoup!
Spark> = 2,4
Vous pouvez ignorer Zip
udf
et utiliser arrays_Zip
une fonction:
df.withColumn("vars", explode(arrays_Zip($"varA", $"varB"))).select(
$"userId", $"someString",
$"vars.varA", $"vars.varB").show
Spark <2,4
Ce que vous voulez n'est pas possible sans un UDF personnalisé. Dans Scala vous pourriez faire quelque chose comme ceci:
val data = sc.parallelize(Seq(
"""{"userId": 1, "someString": "example1",
"varA": [0, 2, 5], "varB": [1, 2, 9]}""",
"""{"userId": 2, "someString": "example2",
"varA": [1, 20, 5], "varB": [9, null, 6]}"""
))
val df = spark.read.json(data)
df.printSchema
// root
// |-- someString: string (nullable = true)
// |-- userId: long (nullable = true)
// |-- varA: array (nullable = true)
// | |-- element: long (containsNull = true)
// |-- varB: array (nullable = true)
// | |-- element: long (containsNull = true)
Maintenant, nous pouvons définir Zip
udf:
import org.Apache.spark.sql.functions.{udf, explode}
val Zip = udf((xs: Seq[Long], ys: Seq[Long]) => xs.Zip(ys))
df.withColumn("vars", explode(Zip($"varA", $"varB"))).select(
$"userId", $"someString",
$"vars._1".alias("varA"), $"vars._2".alias("varB")).show
// +------+----------+----+----+
// |userId|someString|varA|varB|
// +------+----------+----+----+
// | 1| example1| 0| 1|
// | 1| example1| 2| 2|
// | 1| example1| 5| 9|
// | 2| example2| 1| 9|
// | 2| example2| 20|null|
// | 2| example2| 5| 6|
// +------+----------+----+----+
Avec SQL brut:
sqlContext.udf.register("Zip", (xs: Seq[Long], ys: Seq[Long]) => xs.Zip(ys))
df.registerTempTable("df")
sqlContext.sql(
"""SELECT userId, someString, explode(Zip(varA, varB)) AS vars FROM df""")
Vous pouvez également essayer
case class Input(
userId: Integer,
someString: String,
varA: Array[Integer],
varB: Array[Integer])
case class Result(
userId: Integer,
someString: String,
varA: Integer,
varB: Integer)
def getResult(row : Input) : Iterable[Result] = {
val user_id = row.user_id
val someString = row.someString
val varA = row.varA
val varB = row.varB
val seq = for( i <- 0 until varA.size) yield {Result(user_id,someString,varA(i),varB(i))}
seq
}
val obj1 = Input(1, "string1", Array(0, 2, 5), Array(1, 2, 9))
val obj2 = Input(2, "string2", Array(1, 3, 6), Array(2, 3, 10))
val input_df = sc.parallelize(Seq(obj1, obj2)).toDS
val res = input_df.flatMap{ row => getResult(row) }
res.show
// +------+----------+----+-----+
// |userId|someString|varA|varB |
// +------+----------+----+-----+
// | 1| string1 | 0| 1 |
// | 1| string1 | 2| 2 |
// | 1| string1 | 5| 9 |
// | 2| string2 | 1| 2 |
// | 2| string2 | 3| 3 |
// | 2| string2 | 6| 10|
// +------+----------+----+-----+