1. 程式人生 > >spark從oracle庫抽取資料時,Number型別的資料小數位數變多

spark從oracle庫抽取資料時,Number型別的資料小數位數變多

專案中涉及到從oracle庫抽取資料到hive庫,出現了這樣一個bug,抽取到hive庫的數字小數位數格外的多,甚至有些出現了科學計數問題。
oracle的資料
oracle的資料
從oracle抽取到hive庫的資料
從oracle抽取到hive庫的資料
剛開始以為是程式碼中map部分的轉換造成的
問題排查
於是將轉換的部分註釋了起來,直接取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<DataType> 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