来自 ODBC blob 的数据与来自 SQL 查询的 return 不匹配
Data from ODBC blob not matching return from SQL query
我正在从 ODBC 数据连接中读取 BLOB 字段(BLOB 字段是一个文件)。我连接并查询数据库,返回 blob 和文件名。但是,blob 本身不包含与我在数据库中找到的数据相同的数据。我的代码如下以及数据库中返回的数据。
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…
文件名匹配,content/blob 的大小也匹配。
您采用的确切方法可能因您的 ODBC 驱动程序而异。我将演示如何在 MS SQL 服务器上执行此操作,希望您可以根据需要进行调整。
我将在名为 InsertFile
的数据库中使用 table,其定义如下:
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_write
和 conn_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")
大危险警告
您需要注意文件的大小。我通常将文件存储为 VARBINARY(MAX)
,而 SQL 服务器对于通过 ODBC 导出这些文件不是很友好(我不确定其他 SQL 引擎;请参阅 RODBC sqlQuery() returns varchar(255) when it should return varchar(MAX)了解更多详情)
我发现解决这个问题的唯一方法是将 VARBINARY(MAX)
重铸为 VARBINARY(8000)
。如果您的文件中有超过 8000 个字节,那显然是一个糟糕的解决方案。当我需要解决这个问题时,我不得不遍历 VARBINARY(MAX)
列并创建多个新列,每个列的长度为 8000,然后将它们全部粘贴到 R 中。(查看: )
到目前为止,我还没有想出一个通用的解决方案来解决这个问题。不过,也许这是我应该花更多时间的事情。
8000 的限制是由 ODBC 驱动程序强加的,而不是由 RODBC、DBI 或 odbc 包强加的。
使用最新的驱动程序来消除限制:ODBC Driver 17 for SQL Server
使用这个最新的驱动程序不需要将列转换为 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
)
我正在从 ODBC 数据连接中读取 BLOB 字段(BLOB 字段是一个文件)。我连接并查询数据库,返回 blob 和文件名。但是,blob 本身不包含与我在数据库中找到的数据相同的数据。我的代码如下以及数据库中返回的数据。
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…
文件名匹配,content/blob 的大小也匹配。
您采用的确切方法可能因您的 ODBC 驱动程序而异。我将演示如何在 MS SQL 服务器上执行此操作,希望您可以根据需要进行调整。
我将在名为 InsertFile
的数据库中使用 table,其定义如下:
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_write
和 conn_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")
大危险警告
您需要注意文件的大小。我通常将文件存储为 VARBINARY(MAX)
,而 SQL 服务器对于通过 ODBC 导出这些文件不是很友好(我不确定其他 SQL 引擎;请参阅 RODBC sqlQuery() returns varchar(255) when it should return varchar(MAX)了解更多详情)
我发现解决这个问题的唯一方法是将 VARBINARY(MAX)
重铸为 VARBINARY(8000)
。如果您的文件中有超过 8000 个字节,那显然是一个糟糕的解决方案。当我需要解决这个问题时,我不得不遍历 VARBINARY(MAX)
列并创建多个新列,每个列的长度为 8000,然后将它们全部粘贴到 R 中。(查看:
到目前为止,我还没有想出一个通用的解决方案来解决这个问题。不过,也许这是我应该花更多时间的事情。
8000 的限制是由 ODBC 驱动程序强加的,而不是由 RODBC、DBI 或 odbc 包强加的。
使用最新的驱动程序来消除限制:ODBC Driver 17 for SQL Server
使用这个最新的驱动程序不需要将列转换为 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
)