【问题标题】:Writing Unicode from R to SQL Server将 Unicode 从 R 写入 SQL Server
【发布时间】:2018-06-14 18:35:21
【问题描述】:

我正在尝试将 Unicode 字符串从 R 写入 SQL,然后使用该 SQL 表为 Power BI 仪表板提供动力。不幸的是,Unicode 字符似乎只在我将表加载回 R 时才起作用,而不是在我在 SSMS 或 Power BI 中查看表时。

require(odbc)
require(DBI)
require(dplyr)
con <- DBI::dbConnect(odbc::odbc(),
                      .connection_string = "DRIVER={ODBC Driver 13 for SQL Server};SERVER=R9-0KY02L01\\SQLEXPRESS;Database=Test;trusted_connection=yes;")
testData <- data_frame(Characters = "❤")
dbWriteTable(con,"TestUnicode",testData,overwrite=TRUE)
result <- dbReadTable(con, "TestUnicode")
result$Characters

成功产出:

> result$Characters
[1] "❤"

但是,当我在 SSMS 中拉出该表时:

SELECT * FROM TestUnicode

我得到两个不同的字符:

Characters
~~~~~~~~~~
â¤

这些字符也出现在 Power BI 中。如何正确地将心脏字符拉到 R 之外?

【问题讨论】:

  • 您在 SSMS 中尝试过 result as text 吗?
  • 是的,当我将结果另存为文本时也会出现同样的问题。

标签: sql-server r unicode


【解决方案1】:

我发现上一个答案非常有用,但遇到了字符向量的问题,这些字符向量具有另一种编码,例如“latin1”而不是 UTF-8。由于不间断空格等特殊字符,这会导致数据库列中出现随机 NULL。

为了避免这些编码问题,我进行了以下修改以检测字符向量编码,或者在转换为 UTF-16LE 之前默认返回 UTF-8:

library(rlist)

convertToUTF16_df <- function(df){
  output <- cbind(df[sapply(df, typeof) != "character"]
                  , list.cbind(apply(df[sapply(df, typeof) == "character"], 2, function(x){
                    return(lapply(x, function(y) {
                        if (Encoding(y)=="unknown") {
                          unlist(iconv(enc2utf8(y), from = "UTF-8", to = "UTF-16LE", toRaw = TRUE))
                        } else {
                          unlist(iconv(y, from = Encoding(y), to = "UTF-16LE", toRaw = TRUE))
                        }
                      }))
                  }))
  )[colnames(df)]

  return(output)
}

field_types <- function(df){

  output <- list()
  output[colnames(df)[sapply(df, typeof) == "character"]] <- "nvarchar(max)"

  return(output)
}

DBI::dbWriteTable(odbc_connect
                  , name = SQL("database.schema.table")
                  , value = convertToUTF16_df(df)
                  , overwrite = TRUE
                  , row.names = FALSE
                  , field.types = field_types(df)
)

理想情况下,我仍然会修改它以删除 rlist 依赖项,但它现在似乎可以工作了。

【讨论】:

    【解决方案2】:

    受最后一个答案的启发,我还尝试找到一种将数据帧写入 SQL Server 的自动化方法。我无法确认 nvarchar(max) 错误,所以我最终得到了这些函数:

    convertToUTF16_df <- function(df){
      output <- cbind(df[sapply(df, typeof) != "character"]
        , list.cbind(apply(df[sapply(df, typeof) == "character"], 2, function(x){
          return(lapply(x, function(y) unlist(iconv(y, from = "UTF-8", to = "UTF-16LE", toRaw = TRUE))))
        }))
    
      )[colnames(df)]
    
      return(output)
    }
    
    field_types <- function(df){
    
      output <- list()
      output[colnames(df)[sapply(df, typeof) == "character"]] <- "nvarchar(max)"
    
      return(output)
    }
    
    DBI::dbWriteTable(odbc_connect
                      , name = SQL("database.schema.table")
                      , value = convertToUTF16_df(df)
                      , overwrite = TRUE
                      , row.names = FALSE
                      , field.types = field_types(df)
    )
    

    【讨论】:

    • 将所有字符列设置为 varchar(max) 可以避免手动创建表或更改 dbWriteTable 中的单个列长度。正是我要找的
    【解决方案3】:

    灵感来自 last answergithub: r-dbi/DBI#215: Storing unicode characters in SQL Server

    跟随field.types = c(Char = "NVARCHAR(MAX)"),但由于错误dbReadTable/dbGetQuery returns Invalid Descriptor Index ....,向量和计算最大值为:

    
    vector_nvarchar<-c(Filter(Negate(is.null), 
                                  (
                                    lapply(testData,function(x){
                                      if (is.character(x) ) c(
                                        names(x),
                                        paste0("NVARCHAR(", 
                                               max(
                                                 # nvarchar(max) gave error dbReadTable/dbGetQuery returns Invalid Descriptor Index error on SQL server 
                                                 # https://github.com/r-dbi/odbc/issues/112  
                                                 # so we compute the max                                           
                                                 nchar(
                                                   iconv( #nchar doesn't work for UTF-8 :  help (nchar)
                                                     Filter(Negate(is.null),x)
                                                     ,"UTF-8","ASCII",sub ="x" 
                                                   )
                                                 )
                                                 ,na.rm = TRUE)
                                               ,")"
                                        )
                                      )
                                    })
                                  )
        ))
    
    con= DBI::dbConnect(odbc::odbc(),.connection_string=xxxxt, encoding = 'UTF-8')
    
    DBI::dbWriteTable(con,"UnicodeExample",testData, overwrite= TRUE, append=FALSE, field.types= vector_nvarchar)
    
     DBI::dbGetQuery(con,iconv('select * from UnicodeExample'))
    

    【讨论】:

      【解决方案4】:

      事实证明,这是 R/DBI/ODBC 驱动程序中某处的错误。问题是 R 将字符串存储为 UTF-8 编码,而 SQL Server 将它们存储为 UTF-16LE 编码。此外,当 dbWriteTable 创建表时,默认情况下它会为甚至不能保存 Unicode 字符的字符串创建一个 VARCHAR 列。因此,您需要两者:

      1. 将 R 数据框中的列从字符串列更改为 UTF-16LE 原始字节的列表列。
      2. 使用 dbWriteTable 时,将字段类型指定为 NVARCHAR(MAX)

      这似乎仍然应该由 DBI 或 ODBC 或其他东西来处理。

      require(odbc)
      require(DBI)
      
      # This function takes a string vector and turns it into a list of raw UTF-16LE bytes. 
      # These will be needed to load into SQL Server
      convertToUTF16 <- function(s){
        lapply(s, function(x) unlist(iconv(x,from="UTF-8",to="UTF-16LE",toRaw=TRUE)))
      }
      
      # create a connection to a sql table
      connectionString <- "[YOUR CONNECTION STRING]"
      con <- DBI::dbConnect(odbc::odbc(),
                            .connection_string = connectionString)
      
      # our example data
      testData <- data.frame(ID = c(1,2,3), Char = c("I", "❤","Apples"), stringsAsFactors=FALSE)
      
      # we adjust the column with the UTF-8 strings to instead be a list column of UTF-16LE bytes
      testData$Char <- convertToUTF16(testData$Char)
      
      # write the table to the database, specifying the field type
      dbWriteTable(con, 
                   "UnicodeExample", 
                   testData, 
                   append=TRUE, 
                   field.types = c(Char = "NVARCHAR(MAX)"))
      
      dbDisconnect(con)
      

      【讨论】:

        猜你喜欢
        • 2018-03-12
        • 2022-06-11
        • 1970-01-01
        • 1970-01-01
        • 2020-03-28
        • 2021-12-09
        • 2020-12-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多