web-dev-qa-db-fra.com

Différence entre === null et isNull dans Spark DataFrame

Je suis un peu confus avec la différence lorsque nous utilisons

 df.filter(col("c1") === null) and df.filter(col("c1").isNull) 

Même trame de données que je reçois des comptes dans === null mais zéro compte dans isNull. Veuillez m'aider à comprendre la différence. Merci

18
John

D'abord et avant tout, n'utilisez pas null dans votre code Scala sauf si vous en avez vraiment besoin pour des raisons de compatibilité.

Concernant votre question, c'est du SQL simple. col("c1") === null est interprété comme c1 = NULL et, parce que NULL marque des valeurs non définies, le résultat n'est pas défini pour toute valeur incluant NULL elle-même.

spark.sql("SELECT NULL = NULL").show
+-------------+
|(NULL = NULL)|
+-------------+
|         null|
+-------------+
spark.sql("SELECT NULL != NULL").show
+-------------------+
|(NOT (NULL = NULL))|
+-------------------+
|               null|
+-------------------+
spark.sql("SELECT TRUE != NULL").show
+------------------------------------+
|(NOT (true = CAST(NULL AS BOOLEAN)))|
+------------------------------------+
|                                null|
+------------------------------------+
spark.sql("SELECT TRUE = NULL").show
+------------------------------+
|(true = CAST(NULL AS BOOLEAN))|
+------------------------------+
|                          null|
+------------------------------+

Les seules méthodes valides pour vérifier NULL sont:

  • IS NULL:

    spark.sql("SELECT NULL IS NULL").show
    
    +--------------+
    |(NULL IS NULL)|
    +--------------+
    |          true|
    +--------------+
    
    spark.sql("SELECT TRUE IS NULL").show
    
    +--------------+
    |(true IS NULL)|
    +--------------+
    |         false|
    +--------------+
    
  • IS NOT NULL:

    spark.sql("SELECT NULL IS NOT NULL").show
    
    +------------------+
    |(NULL IS NOT NULL)|
    +------------------+
    |             false|
    +------------------+
    
    spark.sql("SELECT TRUE IS NOT NULL").show
    
    +------------------+
    |(true IS NOT NULL)|
    +------------------+
    |              true|
    +------------------+
    

implémenté dans DataFrame DSL comme Column.isNull et Column.isNotNull respectivement.

Remarque :

Pour NULL- des comparaisons sûres, utilisez IS DISTINCT/IS NOT DISTINCT:

spark.sql("SELECT NULL IS NOT DISTINCT FROM NULL").show
+---------------+
|(NULL <=> NULL)|
+---------------+
|           true|
+---------------+
spark.sql("SELECT NULL IS NOT DISTINCT FROM TRUE").show
+--------------------------------+
|(CAST(NULL AS BOOLEAN) <=> true)|
+--------------------------------+
|                           false|
+--------------------------------+

ou not(_ <=> _)/<=>

spark.sql("SELECT NULL AS col1, NULL AS col2").select($"col1" <=> $"col2").show
+---------------+
|(col1 <=> col2)|
+---------------+
|           true|
+---------------+
spark.sql("SELECT NULL AS col1, TRUE AS col2").select($"col1" <=> $"col2").show
+---------------+
|(col1 <=> col2)|
+---------------+
|          false|
+---------------+

en SQL et DataFrame DSL respectivement.

Connexes :

Inclusion de valeurs nulles dans un Apache Spark Join

35
zero323

Habituellement, la meilleure façon de faire la lumière sur des résultats inattendus dans Spark Dataframes est de regarder le plan d'explication. Prenons l'exemple suivant:

import org.Apache.spark.sql.{DataFrame, SparkSession}
import org.Apache.spark.sql.functions._

object Example extends App {

  val session = SparkSession.builder().master("local[*]").getOrCreate()
  case class Record(c1: String, c2: String)
  val data = List(Record("a", "b"), Record(null, "c"))
  val rdd = session.sparkContext.parallelize(data)
  import session.implicits._

  val df: DataFrame = rdd.toDF
  val filtered = df.filter(col("c1") === null)
  println(filtered.count()) // <-- outputs 0, not expected

  val filtered2 = df.filter(col("c1").isNull)
  println(filtered2.count())
  println(filtered2) // <- outputs 1, as expected

  filtered.explain(true)
  filtered2.explain(true)
}

Le premier plan d'explication montre:

== Physical Plan ==
*Filter (isnotnull(c1#2) && null)
+- Scan ExistingRDD[c1#2,c2#3]
== Parsed Logical Plan ==
'Filter isnull('c1)
+- LogicalRDD [c1#2, c2#3]

Cette clause de filtre semble absurde. Le && à null garantit que cela ne pourra jamais être résolu en true.

Le deuxième plan d'explication ressemble à ceci:

== Physical Plan ==
*Filter isnull(c1#2)
+- Scan ExistingRDD[c1#2,c2#3]

Ici, le filtre est ce que vous attendez et ce que vous voulez.

4
mattinbits