来自ODBC blob的数据不匹配从SQL查询返回

时间:2016-09-19 20:08:15

标签: r rodbc

我正在从ODBC数据连接读取BLOB字段(BLOB字段是文件)。我连接并查询数据库,返回blob和文件名。然而,blob本身不包含我在数据库中找到的相同数据。我的代码如下,以及DB中返回的数据。

library(RODBC)

sqlret<-odbcConnect('ODBCConnection')
qry<-'select content,Filename from document with(nolock) where documentid = \'xxxx\'' 
df<-sqlQuery(sqlret,qry)
close(sqlret)

rootpath<-paste0(getwd(),'/DocTest/')

dir.create(rootpath,showWarnings = FALSE)

content<-unlist(df$content)
fileout<-file(paste0(rootpath,df$Filename),"w+b")
writeBin(content, fileout)
close(fileout)

数据库blob是

0x50726F642050434E203A0D0A35363937313533320D0A33383335323133320D0A42463643453335380D0A0D0A574C4944203A0D0A0D0…

数据框的内容是

00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b020000000000000d0000f1000000008807840200000000d0f60c0c0000…

文件名匹配,内容/ blob的大小也匹配。

2 个答案:

答案 0 :(得分:3)

您采取的确切方法可能因ODBC驱动程序而异。我将演示如何在MS SQL Server上执行此操作,并希望您可以根据自己的需要进行调整。

我将在我的数据库中使用名为InsertFile的表,其定义如下:

CREATE TABLE [dbo].[InsertFile](
  [OID] [int] IDENTITY(1,1) NOT NULL,
  [filename] [varchar](50) NULL,
  [filedata] [varbinary](max) NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

现在让我们创建一个我们将推入数据库的文件。

file <- "hello_world.txt"
write("Hello world", file)

我需要做一些工作来准备这个文件的字节代码以进入SQL。我使用这个功能。

prep_file_for_sql <- function(filename){
  bytes <- 
    mapply(FUN = readBin,
           con = filename,
           what = "raw",
           n = file.info(filename)[["size"]],
           SIMPLIFY = FALSE) 
  chars <- 
    lapply(X = bytes,
           FUN = as.character)
  vapply(X = bytes,
         FUN = paste,
         collapse = "",
         FUN.VALUE = character(1))

}

现在,这有点奇怪,但SQL Server ODBC驱动程序非常擅长编写VARBINARY列,但读取它们很糟糕。

巧合的是,SQL Server Native Client 11.0 ODBC驱动程序在编写VARBINARY列时非常糟糕,但是阅读它们还可以。

所以我将有两个RODBC对象,conn_writeconn_read

conn_write <- 
  RODBC::odbcDriverConnect(
    paste0("driver=SQL Server; server=[server_name]; database=[database_name];",
           "uid=[user_name]; pwd=[password]")
  )

conn_read <- 
  RODBC::odbcDriverConnect(
    paste0("driver=SQL Server Native Client 11.0; server=[server_name]; database=[database_name];",
           "uid=[user_name]; pwd=[password]")
  )

现在我要使用参数化查询将文本文件插入数据库。

sqlExecute(
  channel = conn_write,
  query = "INSERT INTO dbo.InsertFile (filename, filedata) VALUES (?, ?)",
  data = list(file,
              prep_file_for_sql(file)),
  fetch = FALSE
)

现在使用参数化查询将其读回来。在这里使用的令人不快的诀窍是将VARBINARY属性重新命名为VARBINARY(不要问我原因,但它有效)。

X <- sqlExecute(
  channel = conn_read,
  query = paste0("SELECT OID, filename, ",
                 "CAST(filedata AS VARBINARY(8000)) AS filedata ",
                 "FROM dbo.InsertFile WHERE filename = ?"),
  data = list("hello_world.txt"),
  fetch = TRUE,
  stringsAsFactors = FALSE
)

现在您可以使用

查看内容
unlist(X$filedata)

编写文件
writeBin(unlist(X$filedata),
         con = "hello_world2.txt")

BIG DANGEROUS CAVEAT

您需要了解文件的大小。我通常将文件存储为VARBINARY(MAX),SQL Server对通过ODBC导出文件不是很友好(我不确定其他SQL引擎;有关详细信息,请参阅RODBC sqlQuery() returns varchar(255) when it should return varchar(MAX)

我发现解决此问题的唯一方法是将VARBINARY(MAX)重新设为VARBINARY(8000)。如果你的文件中有超过8000个字节,那显然是一个糟糕的解决方案。当我需要解决这个问题时,我必须遍历VARBINARY(MAX)列并创建多个长度为8000的新列,然后将它们全部粘贴在R中。(请查看:Reconstitute PNG file stored as RAW in SQL Database

到目前为止,我还没有想出这个问题的通用解决方案。也许这是我应该花更多时间的事情。

答案 1 :(得分:0)

8000 的限制是由 ODBC 驱动程序强加的,而不是由 RODBC、DBI 或 odbc 包强加的。

使用最新的驱动来解除限制:ODBC Driver 17 for SQL Server

https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server?view=sql-server-2017

使用此最新驱动程序无需将列转换为 VARBINARY。

以下应该有效

X <- sqlExecute(
  channel = conn_read,
  query = paste0("SELECT OID, filename, ",
                 "filedata ",
                 "FROM dbo.InsertFile WHERE filename = ?"),
  data = list("hello_world.txt"),
  fetch = TRUE,
  stringsAsFactors = FALSE
)