J'ai un largeDataFrame
(plusieurs colonnes et des milliards de lignes) et un smallDataFrame
(une seule colonne et 10 000 lignes).
Je voudrais filtrer toutes les lignes de la largeDataFrame
chaque fois que la colonne some_identifier
Dans la largeDataFrame
correspond à l'une des lignes de la smallDataFrame
.
Voici un exemple:
largeDataFrame
some_idenfitier,first_name
111,bob
123,phil
222,mary
456,sue
smallDataFrame
some_identifier
123
456
sortie désirée
111,bob
222,mary
Voici ma vilaine solution.
val smallDataFrame2 = smallDataFrame.withColumn("is_bad", lit("bad_row"))
val desiredOutput = largeDataFrame.join(broadcast(smallDataFrame2), Seq("some_identifier"), "left").filter($"is_bad".isNull).drop("is_bad")
Existe-t-il une solution plus propre?
Vous devrez utiliser un left_anti
joindre dans ce cas.
Le anti-jointure gauche est l'opposé d'un semi-jointure gauche.
Il filtre les données de la table de droite dans la table de gauche selon une clé donnée:
largeDataFrame
.join(smallDataFrame, Seq("some_identifier"),"left_anti")
.show
// +---------------+----------+
// |some_identifier|first_name|
// +---------------+----------+
// | 222| mary|
// | 111| bob|
// +---------------+----------+
Une version en pur Spark SQL (et en utilisant PySpark comme exemple, mais avec de petites modifications identiques est applicable pour Scala API)):
def string_to_dataframe (df_name, csv_string):
rdd = spark.sparkContext.parallelize(csv_string.split("\n"))
df = spark.read.option('header', 'true').option('inferSchema','true').csv(rdd)
df.registerTempTable(df_name)
string_to_dataframe("largeDataFrame", '''some_identifier,first_name
111,bob
123,phil
222,mary
456,sue''')
string_to_dataframe("smallDataFrame", '''some_identifier
123
456
''')
anti_join_df = spark.sql("""
select *
from largeDataFrame L
where NOT EXISTS (
select 1 from smallDataFrame S
WHERE L.some_identifier = S.some_identifier
)
""")
print(anti_join_df.take(10))
anti_join_df.explain()
sortira comme prévu mary et bob:
[Row (some_identifier = 222, first_name = 'mary'),
Ligne (some_identifier = 111, first_name = 'bob')]
et aussi Plan d'exécution physique montrera qu'il utilise
== Physical Plan ==
SortMergeJoin [some_identifier#252], [some_identifier#264], LeftAnti
:- *(1) Sort [some_identifier#252 ASC NULLS FIRST], false, 0
: +- Exchange hashpartitioning(some_identifier#252, 200)
: +- Scan ExistingRDD[some_identifier#252,first_name#253]
+- *(3) Sort [some_identifier#264 ASC NULLS FIRST], false, 0
+- Exchange hashpartitioning(some_identifier#264, 200)
+- *(2) Project [some_identifier#264]
+- Scan ExistingRDD[some_identifier#264]
Remarquer Sort Merge Join
est plus efficace pour joindre/anti-joindre des ensembles de données qui sont approximativement de la même taille. Puisque vous avez indiqué que la petite trame de données est plus petite, nous devons nous assurer que Spark optimizer choisit Broadcast Hash Join
qui sera beaucoup plus efficace dans ce scénario:
Je changerai NOT EXISTS
à NOT IN
clause pour cela:
anti_join_df = spark.sql("""
select *
from largeDataFrame L
where L.some_identifier NOT IN (
select S.some_identifier
from smallDataFrame S
)
""")
anti_join_df.explain()
Voyons ce que cela nous a donné:
== Physical Plan ==
BroadcastNestedLoopJoin BuildRight, LeftAnti, ((some_identifier#302 = some_identifier#314) || isnull((some_identifier#302 = some_identifier#314)))
:- Scan ExistingRDD[some_identifier#302,first_name#303]
+- BroadcastExchange IdentityBroadcastMode
+- Scan ExistingRDD[some_identifier#314]
Notez que Spark Optimizer a en fait choisi Broadcast Nested Loop Join
et pas Broadcast Hash Join
. Le premier est correct puisque nous n'avons que deux enregistrements à exclure du côté gauche.
Notez également que les deux plans d'exécution ont LeftAnti
, c'est donc similaire à la réponse @eliasah, mais il est implémenté à l'aide de SQL pur. De plus, cela montre que vous pouvez avoir plus de contrôle sur le plan d'exécution physique.
PS. Gardez également à l'esprit que si la trame de données de droite est beaucoup plus petite que la trame de données de gauche mais est plus grande que quelques enregistrements, vous voulez avoir Broadcast Hash Join
et pas Broadcast Nested Loop Join
ni Sort Merge Join
. Si cela ne se produit pas, vous devrez peut-être régler spark.sql.autoBroadcastJoinThreshold car il est par défaut de 10 Mo, mais il doit être plus grand que la taille du "smallDataFrame".