问题描述
我正在使用 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 可能在这里用他的驱动程序级别类型映射无意中覆盖了所有显式字段类型声明?
我这么说只是作为一个非常高级的推测,因为当涉及到 R
、Java
和 DBI
的交集时绝对超出了我的深度,但是如果鞋子合脚呢?
当我运行类似:
> 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)
产生:
插入数据
然后从那里开始的想法是插入带有以下内容的行:
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)