R DBI SQL Server:dbWriteTable truncates rows / field.types 参数不起作用 RJDBC列和类型插入数据

问题描述

我正在使用 sql Server 数据库和 JDBC 以及带有 sqljdbc42.jar 驱动程序的池。

代码

library(DBI)
library(RJDBC)
library(pool)

jar.path.ms.sql.driver <- "./www/base/sql_drivers/sqljdbc42.jar"

jdbc.sql.driver <- JDBC(
  driverClass = "com.microsoft.sqlserver.jdbc.sqlServerDriver",classpath   = jar.path.ms.sql.driver
)
sql.url <- "jdbc:sqlserver://sql01" # our server url
sql.dname <-  "my_projects"
sql.username <- "user"
sql.password <- "password"

pool <- dbPool(
  drv      = jdbc.sql.driver,url      = sql.url,dname    = sql.dname,username = sql.username,password = sql.password
)

到目前为止,一切都很好。我可以使用 dbReadTable数据库中读取表。我还可以使用 dbWriteTable 将表写入数据库。对于写入情况,我在数据框中有列要用很长的字符串写入。在某些情况下,字符串长度超过 255 个字符。

我曾希望使用 field.types 函数dbWriteTable 参数来正确配置 sql Server 列(comments 是我要写入数据库的数据框):

conn <- poolCheckout(pool)

DBI::dbWriteTable(conn = conn,DBI::Id(cluster = "my_projects",schema = "dbo",table = "comments"),value = comments,overwrite = TRUE,row.names = FALSE,field.types = c(
               STRATEGIC_AREA = "varchar(255)",OBJECTIVE = "varchar(255)",METHOD_OF_MEASURE = "varchar(MAX)",TARGET = "float",UNIT = "varchar(255)",MIN_MAX = "varchar(255)",JUL = "varchar(1024)",AUG = "varchar(MAX)",SEP = "varchar(MAX)",OCT = "varchar(MAX)",NOV = "varchar(MAX)",DEC = "varchar(MAX)",JAN = "varchar(MAX)",FEB = "varchar(MAX)",MAR = "varchar(MAX)",APR = "varchar(MAX)",MAY = "varchar(MAX)",JUN = "varchar(MAX)"
  ))
poolReturn(conn)
poolClose(pool)

问题是 sql Server 数据库忽略了列类,并与 varchar(255) 列一致创建。 varchar(1024) 中的 varchar(MAX)field.types 等列类将被忽略。该函数似乎遵循数据框列类的映射:

  • char -> varchar(255)
  • num -> float

如果我用 DBI::Id(cluster = "my_projects",table = "comments") 替换 "my_projects.dbo.comments" 似乎没有什么区别。如果我引用传递给 field.types 的命名字符向量的左侧也没关系。

因此,包含长度超过 255 个字符的字符串的数据框行被截断(这些行被跳过),并且与数据框相比,sql Server 表中的行最终更少。错误消息(非常长的字符串位于 FEB 列中):

.local(conn,statement,...) 中的错误
在 dbSendUpdate 中执行 JDBC 更新查询失败(字符串或二进制数据将在表“my_projects.dbo.comments”,列“FEB”中被截断。截断的值:

我错过了什么?

更新

根据下面 sgoley 的更新,我将代码更改如下:

dbCreateTable(
  conn = conn,Id(
    database = "my_projects",table = "comments"
  ),fields = c(
    STRATEGIC_AREA = "varchar(255)",METHOD_OF_MEASURE = "varchar(255)",JUL = "varchar(MAX)",JUN = "varchar(MAX)"
  ),row.names = NULL
)


values <- DBI::sqlAppendTable(con = conn,table = Id(database = "my_projects",values = comments,row.names = FALSE)
RJDBC::dbSendUpdate(conn,values)

这现在可以正常工作,没有任何问题和错误消息。使用 Microsoft sql Server Management Studio 进行的验证确认列现在是正确的类并遵循 fields 规范。

解决方法

尝试构建尽可能完整的答案 - 让我们先从 RJDBC 项目开始


RJDBC

回购:https://github.com/s-u/RJDBC

在我看来,Simon 可能在这里用他的驱动程序级别类型映射无意中覆盖了所有显式字段类型声明?

maptypes.R

我这么说只是作为一个非常高级的推测,因为当涉及到 RJavaDBI 的交集时绝对超出了我的深度,但是如果鞋子合脚呢?

当我运行类似:

> dbDataType(jdbc.sql.driver,titanic)
   Class   Sex      Age      Survived  Freq 
  "TEXT"   "TEXT"   "TEXT"   "TEXT"    "DOUBLE" 

似乎驱动程序将这些类型映射到 varchar(255),这就是为什么您会看到默认情况下所有创建的列都设置为该类型。

无论如何。只是猜测原因,然后继续解决问题。


列和类型

利用以上所有评论和建议,到目前为止,对我来说是在 sql server 中实现这些数据类型的最好也是唯一的方法(无需通过 dbExecute 你可以 ) 是:

pool <- dbPool(
  drv      = jdbc.sql.driver,url      = sql.url,dname    = sql.dname,schema   = sql.schema,username = sql.username,password = sql.password
)

conn <- poolCheckout(pool)

dbCreateTable(conn= conn,Id(database = "my_project",schema = "dbo",table = "titanic"),fields = c( Class="nvarchar(max)",Sex="varchar(max)",Age="ntext",Survived="text",Freq="integer"
                            ),row.names = NULL)

产生:

SSMS Table Screenshot


插入数据

然后从那里开始的想法是插入带有以下内容的行:

dbAppendTable(conn= conn,name= Id(database = "my_project",value = titanic)

但我收到以下错误:

Error in .verify.JDBC.result(r,"Unable to retrieve JDBC result set",: 
  Unable to retrieve JDBC result set
  JDBC ERROR: The value is not set for the parameter number 2.
  Statement: INSERT INTO "my_project"."dbo"."titanic"
  ("Class","Sex","Age","Survived","Freq")
VALUES
  (?,?,?)
In addition: Warning message:
In if (is.na(v)) { :
  the condition has length > 1 and only the first element will be used

所以现在更好的选择是使用:

values <- DBI::sqlAppendTable(con = conn,table = Id(database = "my_project",values = titanic)
DBI::dbExecute(conn = conn,values)

产生具有请求列类型的预期行: Data values with correct column types