【问题标题】:Data from ODBC blob not matching return from SQL query来自 ODBC Blob 的数据与来自 SQL 查询的返回不匹配
【发布时间】:2016-09-19 20:08:14
【问题描述】:

我正在从 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…

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

【问题讨论】:

    标签: r rodbc


    【解决方案1】:

    您采用的具体方法可能因您的 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")
    

    大危险警告

    您需要注意文件的大小。我通常将文件存储为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

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

    【讨论】:

    • 这太神奇了,非常感谢您的回答。 RODBC 是唯一一个有 8000 个限制的包还是做所有的 sql 包?
    • 我认为没有明确的答案。我相信部分问题在于某些 ODBC 驱动程序中的VARCHAR(MAX)VARBINARY(MAX) 变量报告了0 的长度,因此RODBC 不知道如何分配空间。因此,虽然可能存在与RODBC 相关的一些限制,但您使用的 SQL 风格提供的 ODBC 驱动程序也存在限制。
    【解决方案2】:

    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
    )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-20
      • 1970-01-01
      • 2019-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多