spark从oracle库抽取数据时,Number类型的数据小数位数变多

项目中涉及到从oracle库抽取数据到hive库,出现了这样一个bug,抽取到hive库的数字小数位数格外的多,甚至有些出现了科学计数问题。
oracle的数据
oracle的数据
从oracle抽取到hive库的数据
spark从oracle库抽取数据时,Number类型的数据小数位数变多_第1张图片
刚开始以为是代码中map部分的转换造成的
spark从oracle库抽取数据时,Number类型的数据小数位数变多_第2张图片
于是将转换的部分注释了起来,直接取oracle的值总归是没错的吧。信誓旦旦的就把bug返了回去。
结果……没过多久bug又回来了,仔细一看,问题依旧如故。怎么会出现这样的问题呢?明明是直接从oracle查了数据直接插入hive,中间没有其他的转换。
于是在代码中将查询的oracle数据show()了一下,才发现,原来查出来的dataset中就已经是多了很多位了(以后不亲眼看到执行完的数据,坚决不返bug)。苦思不得,问问度娘先。
参考1:https://blog.csdn.net/qq_14950717/article/details/51323679
从这篇博客中得知
在1.6以前的版本中,当Oracle表字段为Number时,对应DataType为decimal
但是如何解决呢?
参考2:https://bbs.aliyun.com/detail/337340.html?page=e
这篇博客讲到了spark 读取oracle,字段类型为Date的处理
和我的问题很相似,于是乎看了看源码org.apache.spark.sql.jdbc.OracleDialect

private case object OracleDialect extends JdbcDialect {

  override def canHandle(url: String): Boolean = url.startsWith("jdbc:oracle")

  override def getCatalystType(
      sqlType: Int, typeName: String, size: Int, md: MetadataBuilder): Option[DataType] = {
    if (sqlType == Types.NUMERIC) {
      val scale = if (null != md) md.build().getLong("scale") else 0L
      size match {
        // Handle NUMBER fields that have no precision/scale in special way
        // because JDBC ResultSetMetaData converts this to 0 precision and -127 scale
        // For more details, please see
        // https://github.com/apache/spark/pull/8780#issuecomment-145598968
        // and
        // https://github.com/apache/spark/pull/8780#issuecomment-144541760
        case 0 => Option(DecimalType(DecimalType.MAX_PRECISION, 10))
        // Handle FLOAT fields in a special way because JDBC ResultSetMetaData converts
        // this to NUMERIC with -127 scale
        // Not sure if there is a more robust way to identify the field as a float (or other
        // numeric types that do not specify a scale.
        case _ if scale == -127L => Option(DecimalType(DecimalType.MAX_PRECISION, 10))
        case _ => None
      }
    } else {
      None
    }
  }

  override def getJDBCType(dt: DataType): Option[JdbcType] = dt match {
    // For more details, please see
    // https://docs.oracle.com/cd/E19501-01/819-3659/gcmaz/
    case BooleanType => Some(JdbcType("NUMBER(1)", java.sql.Types.BOOLEAN))
    case IntegerType => Some(JdbcType("NUMBER(10)", java.sql.Types.INTEGER))
    case LongType => Some(JdbcType("NUMBER(19)", java.sql.Types.BIGINT))
    case FloatType => Some(JdbcType("NUMBER(19, 4)", java.sql.Types.FLOAT))
    case DoubleType => Some(JdbcType("NUMBER(19, 4)", java.sql.Types.DOUBLE))
    case ByteType => Some(JdbcType("NUMBER(3)", java.sql.Types.SMALLINT))
    case ShortType => Some(JdbcType("NUMBER(5)", java.sql.Types.SMALLINT))
    case StringType => Some(JdbcType("VARCHAR2(255)", java.sql.Types.VARCHAR))
    case _ => None
  }

  override def isCascadingTruncateTable(): Option[Boolean] = Some(false)
}

原来小数点后多出来的十位数字是从这里来的。
小数位数变多的问题只会出现在Number格式,它的size为0,
Number(4)这种类型,它的size为4不会多小数位数,它按照默认的配置会转化为decimal(4,0)

private[this] var dialects = List[JdbcDialect]()

  registerDialect(MySQLDialect)
  registerDialect(PostgresDialect)
  registerDialect(DB2Dialect)
  registerDialect(MsSqlServerDialect)
  registerDialect(DerbyDialect)
  registerDialect(OracleDialect)

知道原委之后,便好处理了。从oracle中取出的数据无需进行其他的改变,主要的目的是原模原样的把它存到hive库,好拿出来做计算,于是乎便按照[参考2]中的代码尝试了一下,将oracle中的Number类型对应为StringType,这样一来就不用考虑展现形式会发生变化了。

 public void oracleInit() {
        JdbcDialect dialect = new JdbcDialect() {

            //判断是否为oracle库
            @Override
            public boolean canHandle(String url) {
                return url.startsWith("jdbc:oracle");
            }

            //用于读取Oracle数据库时数据类型的转换
            @Override
            public Option getCatalystType(int sqlType, String typeName, int size, MetadataBuilder md){
               /* if (sqlType == Types.DATE && typeName.equals("DATE") && size == 0)
                    return Option.apply(DataTypes.TimestampType);*/
                if (sqlType == Types.NUMERIC && typeName.equals("NUMBER"))
                    return Option.apply(DataTypes.StringType);

                return Option.empty();
            }
        };
        JdbcDialects.registerDialect(dialect);
    }

测试了一下,果然成了

难得一次突破封装多看了点东西解决了问题,开心得赶紧记录一下。对程序猿之路越发感兴趣了~

使用的sparksql版本为spark-sql_2.11-2.2.1.jar

你可能感兴趣的:(spark)