web-dev-qa-db-fra.com

Comment obtenir un équivalent SQL row_number pour un RDD Spark?

J'ai besoin de générer une liste complète de row_numbers pour une table de données comportant plusieurs colonnes.

En SQL, ceci ressemblerait à ceci:

select
   key_value,
   col1,
   col2,
   col3,
   row_number() over (partition by key_value order by col1, col2 desc, col3)
from
   temp
;

Maintenant, disons que dans Spark, j'ai un RDD de la forme (K, V), où V = (col1, col2, col3), donc mes entrées sont comme

(key1, (1,2,3))
(key1, (1,4,7))
(key1, (2,2,3))
(key2, (5,5,5))
(key2, (5,5,9))
(key2, (7,5,5))
etc.

Je souhaite les commander à l'aide de commandes telles que sortBy (), sortWith (), sortByKey (), zipWithIndex, etc., et disposer d'un nouveau RDD avec le numéro de ligne correct

(key1, (1,2,3), 2)
(key1, (1,4,7), 1)
(key1, (2,2,3), 3)
(key2, (5,5,5), 1)
(key2, (5,5,9), 2)
(key2, (7,5,5), 3)
etc.

(Je me moque des parenthèses, donc la forme peut aussi être (K, (col1, col2, col3, rownum)) à la place)

Comment puis-je faire cela?

Voici ma première tentative:

val sample_data = Seq(((3,4),5,5,5),((3,4),5,5,9),((3,4),7,5,5),((1,2),1,2,3),((1,2),1,4,7),((1,2),2,2,3))

val temp1 = sc.parallelize(sample_data)

temp1.collect().foreach(println)

// ((3,4),5,5,5)
// ((3,4),5,5,9)
// ((3,4),7,5,5)
// ((1,2),1,2,3)
// ((1,2),1,4,7)
// ((1,2),2,2,3)

temp1.map(x => (x, 1)).sortByKey().zipWithIndex.collect().foreach(println)

// ((((1,2),1,2,3),1),0)
// ((((1,2),1,4,7),1),1)
// ((((1,2),2,2,3),1),2)
// ((((3,4),5,5,5),1),3)
// ((((3,4),5,5,9),1),4)
// ((((3,4),7,5,5),1),5)

// note that this isn't ordering with a partition on key value K!

val temp2 = temp1.???

Notez également que la fonction sortBy ne peut pas être appliquée directement à un RDD, mais que vous devez d'abord exécuter collect (), puis la sortie n'est pas non plus un RDD, mais un tableau.

temp1.collect().sortBy(a => a._2 -> -a._3 -> a._4).foreach(println)

// ((1,2),1,4,7)
// ((1,2),1,2,3)
// ((1,2),2,2,3)
// ((3,4),5,5,5)
// ((3,4),5,5,9)
// ((3,4),7,5,5)

Voici un peu plus de progrès, mais toujours pas partitionné:

val temp2 = sc.parallelize(temp1.map(a => (a._1,(a._2, a._3, a._4))).collect().sortBy(a => a._2._1 -> -a._2._2 -> a._2._3)).zipWithIndex.map(a => (a._1._1, a._1._2._1, a._1._2._2, a._1._2._3, a._2 + 1))

temp2.collect().foreach(println)

// ((1,2),1,4,7,1)
// ((1,2),1,2,3,2)
// ((1,2),2,2,3,3)
// ((3,4),5,5,5,4)
// ((3,4),5,5,9,5)
// ((3,4),7,5,5,6)
22
Glenn Strycker

La fonctionnalité row_number() over (partition by ... order by ...) a été ajoutée à Spark 1.4. Cette réponse utilise PySpark/DataFrames.

Créez un test DataFrame:

from pyspark.sql import Row, functions as F

testDF = sc.parallelize(
    (Row(k="key1", v=(1,2,3)),
     Row(k="key1", v=(1,4,7)),
     Row(k="key1", v=(2,2,3)),
     Row(k="key2", v=(5,5,5)),
     Row(k="key2", v=(5,5,9)),
     Row(k="key2", v=(7,5,5))
    )
).toDF()

Ajoutez le numéro de la ligne partitionnée:

from pyspark.sql.window import Window

(testDF
 .select("k", "v",
         F.rowNumber()
         .over(Window
               .partitionBy("k")
               .orderBy("k")
              )
         .alias("rowNum")
        )
 .show()
)

+----+-------+------+
|   k|      v|rowNum|
+----+-------+------+
|key1|[1,2,3]|     1|
|key1|[1,4,7]|     2|
|key1|[2,2,3]|     3|
|key2|[5,5,5]|     1|
|key2|[5,5,9]|     2|
|key2|[7,5,5]|     3|
+----+-------+------+
22
dnlbrky

C'est un problème intéressant que vous abordez. Je vais y répondre en Python, mais je suis sûr que vous pourrez traduire de manière transparente en Scala.

Voici comment je le ferais:

1- Simplifiez vos données:

temp2 = temp1.map(lambda x: (x[0],(x[1],x[2],x[3])))

temp2 est maintenant une "vraie" paire clé-valeur. Ça ressemble à ça:

[
((3, 4), (5, 5, 5)),  
((3, 4), (5, 5, 9)),   
((3, 4), (7, 5, 5)),   
((1, 2), (1, 2, 3)),  
((1, 2), (1, 4, 7)),   
((1, 2), (2, 2, 3))

2- Ensuite, utilisez la fonction group-by pour reproduire l’effet de la partition en:

temp3 = temp2.groupByKey()

temp3 est maintenant un RDD avec 2 lignes:

[((1, 2), <pyspark.resultiterable.ResultIterable object at 0x15e08d0>),  
 ((3, 4), <pyspark.resultiterable.ResultIterable object at 0x15e0290>)]

3- Maintenant, vous devez appliquer une fonction de classement pour chaque valeur du RDD. En python, j'utiliserais la fonction triée simple (l'énumération créera votre colonne row_number):

 temp4 = temp3.flatMap(lambda x: Tuple([(x[0],(i[1],i[0])) for i in enumerate(sorted(x[1]))])).take(10)

Notez que pour implémenter votre commande particulière, vous devez alimenter le bon argument "clé" (en python, je créerais simplement une fonction lambda comme celle-ci:

lambda Tuple : (Tuple[0],-Tuple[1],Tuple[2])

A la fin (sans l'argument clé, ça ressemble à ça):

[
((1, 2), ((1, 2, 3), 0)), 
((1, 2), ((1, 4, 7), 1)), 
((1, 2), ((2, 2, 3), 2)), 
((3, 4), ((5, 5, 5), 0)), 
((3, 4), ((5, 5, 9), 1)), 
((3, 4), ((7, 5, 5), 2))

]

J'espère que cela pourra aider! 

Bonne chance.

4
Guillaume G
val test = Seq(("key1", (1,2,3)),("key1",(4,5,6)), ("key2", (7,8,9)), ("key2", (0,1,2)))

test: Seq [(String, (Int, Int, Int))] = Liste ((clé1, (1,2,3)), (clé1, (4,5,6)), (clé2, (7,8 , 9)), (clé2, (0,1,2)))

test.foreach(println)

(clé1, (1,2,3))

(clé1, (4,5,6))

(clé2, (7,8,9))

(clé2, (0,1,2))

val rdd = sc.parallelize(test, 2)

rdd: org.Apache.spark.rdd.RDD [(String, (Int, Int, Int))]] = ParallelCollectionRDD [41] à la parallélisation à: 26

val rdd1 = rdd.groupByKey.map(x => (x._1,x._2.toArray)).map(x => (x._1, x._2.sortBy(x => x._1).zipWithIndex))

rdd1: org.Apache.spark.rdd.RDD [(String, Array [((Int, Int, Int), Int), Int), Int))]]] = MapPartitionsRDD [44] sur la carte à l'adresse: 25

val rdd2 = rdd1.flatMap{ 
  elem =>
   val key = elem._1
   elem._2.map(row => (key, row._1, row._2))
 }

rdd2: org.Apache.spark.rdd.RDD [(String, (Int, Int, Int), Int), Int)] = MapPartitionsRDD [45] à flatMap à: 25

rdd2.collect.foreach(println)

(key1, (1,2,3), 0)

(touche 1, (4,5,6), 1)

(clé2, (0,1,2), 0)

(key2, (7,8,9), 1)

1
Wallace Huang

De spark sql 
Lire les fichiers de données ... 
val df = spark.read.json ("s3: // s3bukcet/clé/activité/année = 2018/mois = 12/date = 15/*"); 

Le fichier ci-dessus contient les champs user_id, pages vues et clics. 

Génère l'ID d'activité (numéro_ligne) partitionné par l'utilisateur et identifie par ordre 
val output = df.withColumn ("id_activité", functions.row_number (). sur (Window.partitionBy ("idutilisateur"). orderBy ("clics")). cast (DataTypes.IntegerType);

0
Dakshin