J'aime les jeux de données Spark, car ils me donnent des erreurs d'analyse et de syntaxe lors de la compilation et me permettent également de travailler avec des getters au lieu de noms/nombres codés en dur. La plupart des calculs peuvent être effectués avec les API de haut niveau de Dataset. Par exemple, il est beaucoup plus simple d’effectuer les opérations agg, select, sum, avg, map, filter ou groupBy en accédant à un objet de type Dataset plutôt qu’à utiliser les champs de données des lignes RDD.
Cependant l'opération de jointure est absente de ceci, j'ai lu que je peux faire une jointure comme ceci
ds1.joinWith(ds2, ds1.toDF().col("key") === ds2.toDF().col("key"), "inner")
Mais ce n’est pas ce que je veux, car je préférerais le faire via l’interface de cas, alors quelque chose de plus semblable à celui-ci.
ds1.joinWith(ds2, ds1.key === ds2.key, "inner")
La meilleure alternative pour le moment semble créer un objet à côté de la classe de cas et donner à cette fonction la possibilité de me fournir le bon nom de colonne sous forme de chaîne. Donc, je voudrais utiliser la première ligne de code, mais mettre une fonction au lieu d'un nom de colonne codé en dur. Mais cela ne semble pas assez élégant ..
Quelqu'un peut-il me conseiller sur d'autres options ici? Le but est d’obtenir une abstraction à partir des noms de colonnes et de travailler de préférence via les accesseurs de la classe de cas.
J'utilise Spark 1.6.1 et Scala 2.10
Spark SQL ne peut optimiser la jointure que si la condition de jointure est basée sur l'opérateur d'égalité. Cela signifie que nous pouvons considérer les équijointes et les non-équijoins séparément.
Equijoin peut être implémenté de manière sécurisée en mappant les deux Datasets
sur les n-uplets (clé, valeur), en effectuant une jointure basée sur des clés et en remodelant le résultat:
import org.Apache.spark.sql.Encoder
import org.Apache.spark.sql.Dataset
def safeEquiJoin[T, U, K](ds1: Dataset[T], ds2: Dataset[U])
(f: T => K, g: U => K)
(implicit e1: Encoder[(K, T)], e2: Encoder[(K, U)], e3: Encoder[(T, U)]) = {
val ds1_ = ds1.map(x => (f(x), x))
val ds2_ = ds2.map(x => (g(x), x))
ds1_.joinWith(ds2_, ds1_("_1") === ds2_("_1")).map(x => (x._1._2, x._2._2))
}
Peut être exprimé en utilisant des opérateurs d’algèbre relationnelle sous la forme R ⋈θ S = σθ (R × S) et converti directement en code.
Activez crossJoin
et utilisez joinWith
avec un prédicat trivialement égal:
spark.conf.set("spark.sql.crossJoin.enabled", true)
def safeNonEquiJoin[T, U](ds1: Dataset[T], ds2: Dataset[U])
(p: (T, U) => Boolean) = {
ds1.joinWith(ds2, lit(true)).filter(p.tupled)
}
Utilisez la méthode crossJoin
:
def safeNonEquiJoin[T, U](ds1: Dataset[T], ds2: Dataset[U])
(p: (T, U) => Boolean)
(implicit e1: Encoder[Tuple1[T]], e2: Encoder[Tuple1[U]], e3: Encoder[(T, U)]) = {
ds1.map(Tuple1(_)).crossJoin(ds2.map(Tuple1(_))).as[(T, U)].filter(p.tupled)
}
case class LabeledPoint(label: String, x: Double, y: Double)
case class Category(id: Long, name: String)
val points1 = Seq(LabeledPoint("foo", 1.0, 2.0)).toDS
val points2 = Seq(
LabeledPoint("bar", 3.0, 5.6), LabeledPoint("foo", -1.0, 3.0)
).toDS
val categories = Seq(Category(1, "foo"), Category(2, "bar")).toDS
safeEquiJoin(points1, categories)(_.label, _.name)
safeNonEquiJoin(points1, points2)(_.x > _.x)
Il convient de noter que ces méthodes diffèrent qualitativement d'une application directe joinWith
et nécessitent des transformations coûteuses DeserializeToObject
/SerializeFromObject
(par rapport à ce que joinWith
peut utiliser des opérations logiques sur les données).
Ceci est similaire au comportement décrit dans Spark 2.0 Dataset vs DataFrame .
Si vous n'êtes pas limité à l'API Spark SQL frameless
fournit des extensions sûres de types intéressants pour Datasets
(à ce jour, il ne prend en charge que Spark 2.0):
import frameless.TypedDataset
val typedPoints1 = TypedDataset.create(points1)
val typedPoints2 = TypedDataset.create(points2)
typedPoints1.join(typedPoints2, typedPoints1('x), typedPoints2('x))
L'API Dataset
n'est pas stable dans la version 1.6, donc je ne pense pas qu'il soit logique de l'utiliser ici.
Bien sûr, cette conception et les noms descriptifs ne sont pas nécessaires. Vous pouvez facilement utiliser la classe type pour ajouter implicitement ces méthodes à Dataset
. Il n'y a pas de conflit avec les signatures intégrées et les deux peuvent donc s'appeler joinWith
.
En outre, un autre problème plus important pour l'API Spark non sécurisée est que lorsque vous joignez deux Datasets
, cela vous donnera une DataFrame
. Et ensuite, vous perdez des types de vos deux jeux de données d'origine.
val a: Dataset[A]
val b: Dataset[B]
val joined: Dataframe = a.join(b)
// what would be great is
val joined: Dataset[C] = a.join(b)(implicit func: (A, B) => C)