如何解决Spark中的AnalysisException:resolved属性

时间:2017-08-16 12:14:31

标签: java scala spark-dataframe

val rdd = sc.parallelize(Seq(("vskp", Array(2.0, 1.0, 2.1, 5.4)),("hyd",Array(1.5, 0.5, 0.9, 3.7)),("hyd", Array(1.5, 0.5, 0.9, 3.2)),("tvm", Array(8.0, 2.9, 9.1, 2.5))))
val df1= rdd.toDF("id", "vals")
val rdd1 = sc.parallelize(Seq(("vskp","ap"),("hyd","tel"),("bglr","kkt")))
val df2 = rdd1.toDF("id", "state")
val df3 = df1.join(df2,df1("id")===df2("id"),"left")

联接操作正常 但是当我重用df2时,我面临着未解决的属性错误

val rdd2 = sc.parallelize(Seq(("vskp", "Y"),("hyd", "N"),("hyd", "N"),("tvm", "Y")))
val df4 = rdd2.toDF("id","existance")
val df5 = df4.join(df2,df4("id")===df2("id"),"left")
  

错误:org.apache.spark.sql.AnalysisException:已解析的属性ID#426

13 个答案:

答案 0 :(得分:19)

正如我的评论所述,它与https://issues.apache.org/jira/browse/SPARK-10925相关,更具体地说是https://issues.apache.org/jira/browse/SPARK-14948。重复使用引用会在命名中产生歧义,因此您必须克隆df - 请参阅https://issues.apache.org/jira/browse/SPARK-14948中的最后一条注释。

答案 1 :(得分:3)

这个问题确实浪费了我很多时间,我终于找到了一个简单的解决方案。

在PySpark中,对于有问题的列,例如colA,我们可以简单地使用

import pyspark.sql.functions as F

df = df.select(F.col("colA").alias("colA"))

df中使用join之前。

我认为这也应该适用于Scala / Java Spark。

答案 2 :(得分:2)

如果您有df1和df2衍生自df1,请尝试重命名df2中的所有列,以便连接后没有两个列具有相同的名称。所以在加入之前:

所以不是df1.join(df2...

# Step 1 rename shared column names in df2.
df2_renamed = df2.withColumnRenamed('columna', 'column_a_renamed').withColumnRenamed('columnb', 'column_b_renamed')

# Step 2 do the join on the renamed df2 such that no two columns have same name.
df1.join(df2_renamed)

答案 3 :(得分:1)

尝试在两个连续的联接中使用一个DataFrame时遇到相同的问题。

这是问题所在:DataFrame A有2列(我们称它们为x和y),DataFrame B也有2列(我们称它们为w和z)。我需要在x = z上将A与B联接在一起,然后在y = z上将它们联接在一起。

(A join B on A.x=B.z) as C join B on C.y=B.z

我得到的确切错误是,在第二个联接中它抱怨“ 已解决的属性B.z#1234 ... ”。

在@Erik提供的链接以及一些其他博客和问题之后,我收集了一个B的副本。

这是我所做的:

val aDF = ...
val bDF = ...
val bCloned = spark.createDataFrame(bDF.rdd, bDF.schema)
aDF.join(bDF, aDF("x") === bDF("z")).join(bCloned, aDF("y") === bCloned("z"))

答案 4 :(得分:1)

在我的情况下,此错误在同一表的自我联接期间出现。 我遇到了Spark SQL而不是数据框API的以下问题:

org.apache.spark.sql.AnalysisException: Resolved attribute(s) originator#3084,program_duration#3086,originator_locale#3085 missing from program_duration#1525,guid#400,originator_locale#1524,EFFECTIVE_DATETIME_UTC#3157L,device_timezone#2366,content_rpd_id#734L,originator_sublocale#2355,program_air_datetime_utc#3155L,originator#1523,master_campaign#735,device_provider_id#2352 in operator !Deduplicate [guid#400, program_duration#3086, device_timezone#2366, originator_locale#3085, originator_sublocale#2355, master_campaign#735, EFFECTIVE_DATETIME_UTC#3157L, device_provider_id#2352, originator#3084, program_air_datetime_utc#3155L, content_rpd_id#734L]. Attribute(s) with the same name appear in the operation: originator,program_duration,originator_locale. Please check if the right attribute(s) are used.;;

我之前使用的是以下查询

    SELECT * FROM DataTable as aext
             INNER JOIN AnotherDataTable LAO 
ON aext.device_provider_id = LAO.device_provider_id 

在加入前仅选择必需的列可以为我解决问题。

      SELECT * FROM (
    select distinct EFFECTIVE_DATE,system,mso_Name,EFFECTIVE_DATETIME_UTC,content_rpd_id,device_provider_id 
from DataTable 
) as aext
         INNER JOIN AnotherDataTable LAO ON aext.device_provider_id = LAO.device_provider_id 

答案 5 :(得分:1)

@Json_Chans 的回答非常好,因为它不需要任何资源密集型操作。无论如何,在处理大量列时,您需要一些通用函数来动态处理这些内容,而不是手动编码数百列。

幸运的是,您可以从 Dataframe 本身派生该函数,因此除了单行代码(至少在 Python 和 pySpark 中)之外,您不需要任何其他代码:

import pyspark.sql.functions as f

df # Some Dataframe you have the "resolve(d) attribute(s)" error with

df = df.select([ f.col( column_name ).alias( column_name) for column_name in df.columns])

由于列的正确字符串表示仍存储在 Dataframe(df.columns: list) 的列属性中,因此您可以将其重置为自身 - 这是通过 {{1} } (注意:这仍然会产生一个新的 .alias(),因为 Dataframe 是不可变的,这意味着它们不能被更改)。

答案 6 :(得分:0)

对于Java开发人员,请尝试调用此方法:

private static Dataset<Row> cloneDataset(Dataset<Row> ds) {
    List<Column> filterColumns = new ArrayList<>();
    List<String> filterColumnsNames = new ArrayList<>();
    scala.collection.Iterator<StructField> it = ds.exprEnc().schema().toIterator();
    while (it.hasNext()) {
        String columnName = it.next().name();
        filterColumns.add(ds.col(columnName));
        filterColumnsNames.add(columnName);
    }
    ds = ds.select(JavaConversions.asScalaBuffer(filterColumns).seq()).toDF(scala.collection.JavaConverters.asScalaIteratorConverter(filterColumnsNames.iterator()).asScala().toSeq());
    return ds;
}

在连接之前的两个数据集上,它将数据集克隆到新数据集中:

df1 = cloneDataset(df1); 
df2 = cloneDataset(df2);
Dataset<Row> join = df1.join(df2, col("column_name"));
// if it didn't work try this
final Dataset<Row> join = cloneDataset(df1.join(df2, columns_seq)); 

答案 7 :(得分:0)

如果您执行以下操作,它将起作用。

假设您有一个数据框。 df1,如果您想交叉加入同一数据框,则可以使用以下

df1.toDF("ColA","ColB").as("f_df").join(df1.toDF("ColA","ColB").as("t_df"), 
   $"f_df.pcmdty_id" === 
   $"t_df.assctd_pcmdty_id").select($"f_df.pcmdty_id",$"f_df.assctd_pcmdty_id")

答案 8 :(得分:0)

根据我的经验,我们有2个解决方案 1)克隆DF 2)在连接表之前重命名具有歧义的列。 (不要忘记删除重复的加入键)

我个人更喜欢第二种方法,因为用第一种方法克隆DF需要时间,尤其是在数据量很大的情况下。

答案 9 :(得分:0)

[TLDR]

通过将中间DataFrame写入文件系统并再次读取,来中断父DataFrame和派生DataFrame中的列之间共享的 AttributeReference

例如:

val df1 = spark.read.parquet("file1")
df1.createOrReplaceTempView("df1")
val df2 = spark.read.parquet("file2")
df2.createOrReplaceTempView("df2")

val df12 = spark.sql("""SELECT * FROM df1 as d1 JOIN df2 as d2 ON d1.a = d2.b""")
df12.createOrReplaceTempView("df12")

val df12_ = spark.sql(""" -- some transformation -- """)
df12_.createOrReplaceTempView("df12_")

val df3 = spark.read.parquet("file3")
df3.createOrReplaceTempView("df3")

val df123 = spark.sql("""SELECT * FROM df12_ as d12_ JOIN df3 as d3 ON d12_.a = d3.c""")
df123.createOrReplaceTempView("df123")

现在与顶级DataFrame一起加入将导致“无法解决的属性错误”

val df1231 = spark.sql("""SELECT * FROM df123 as d123 JOIN df1 as d1 ON d123.a = d1.a""") 

解决方案:d123.a和d1.a共享相同的AttributeReference,将其打破 将中间表df123写入文件系统并再次读取。现在df123write.a和d1.a不共享AttributeReference

val df123 = spark.sql("""SELECT * FROM df12 as d12 JOIN df3 as d3 ON d12.a = d3.c""")
df123.createOrReplaceTempView("df123")

df123.write.parquet("df123.par")
val df123write = spark.read.parquet("df123.par")
spark.catalog.dropTempView("df123")
df123write.createOrReplaceTempView("df123")

val df1231 = spark.sql("""SELECT * FROM df123 as d123 JOIN df1 as d1 ON d123.a = d1.a""") 

长话短说

我们拥有复杂的ETL,这些ETL具有在多个级别执行的DataFrame的转换和自连接。我们经常遇到“无法解析的属性”错误,我们通过选择必需的属性并在顶层表上执行联接来解决此问题,而不是直接与顶层表进行联接,这暂时解决了该问题,但是当我们对这些DataFrame应用更多转换并加入对于任何顶级DataFrame,“ unresolved attribute”错误都再次引起人们的注意。

之所以发生这种情况,是因为底层的DataFrame与来自其的顶层DataFrame共享相同的AttributeReference [more details]

因此,通过仅编写1个中间转换的DataFrame并再次读取并继续我们的ETL,我们打破了此引用共享。这中断了底部数据框和顶部数据框之间的AttributeReference共享,并且我们再也不会遇到“未解决的属性”错误。

这对我们很有用,因为当我们从顶层DataFrame转移到执行效果最差的底层数据并加入比开始的初始DataFrames收缩的数据时,由于数据量更小且火花不必回溯,它也提高了我们的性能DAG一直到最后一个持久化的DataFrame。

答案 10 :(得分:0)

只需重命名您的列并输入相同的名称。 在pyspark中: 为我在df.columns中:     df = df.withColumnRenamed(i,i)

答案 11 :(得分:0)

感谢Tomer's Answer

对于scala-当我尝试使用self-join子句中的列时,出现了问题,使用方法来解决该问题

// To `and` all the column conditions
def andAll(cols: Iterable[Column]): Column =
   if (cols.isEmpty) lit(true)
   else cols.tail.foldLeft(cols.head) { case (soFar, curr) => soFar.and(curr) }

// To perform join different col name
def renameColAndJoin(leftDf: DataFrame, joinCols: Seq[String], joinType: String = "inner")(rightDf: DataFrame): DataFrame = {

   val renamedCols: Seq[String]          = joinCols.map(colName => s"${colName}_renamed")
   val zippedCols: Seq[(String, String)] = joinCols.zip(renamedCols)

   val renamedRightDf: DataFrame = zippedCols.foldLeft(rightDf) {
     case (df, (origColName, renamedColName)) => df.withColumnRenamed(origColName, renamedColName)
   }

   val joinExpr: Column = andAll(zippedCols.map {
     case (origCol, renamedCol) => renamedRightDf(renamedCol).equalTo(rightDf(origCol))
   })

   leftDf.join(renamedRightDf, joinExpr, joinType)

}

答案 12 :(得分:0)

就我而言,检查点指向原始数据框解决了该问题。