【问题标题】:Create file system file from file stored in Microsoft SQL database从存储在 Microsoft SQL 数据库中的文件创建文件系统文件
【发布时间】:2021-09-01 01:06:35
【问题描述】:

标题听起来很混乱,但我不知道如何更好地描述它。

我的任务是进行从程序 A 到程序 B 的数据库迁移。
程序 A 使用 MSSQL 数据库并将其所有文件存储在数据库中。
程序 B 处理以“正常”方式存储的文件,即在文件系统中。

现在我必须使用 PHP 将数据库存储的文件提取、转换和下载到文件系统文件,但我尝试转换它们没有成功。

出于测试目的,我创建了一个简单的.txt 文件,内容为Test document for migration,程序A 将其存储在数据库中:

0x5465737420646F63756D656E7420666F72206D6967726174696F6E'

那是什么格式,如何将其转换为普通的document.txt 文件?

编辑

非常感谢@PanagiotisKanavos。这现在确实适用于流:

$query = "select top(1) DESCRIPTION, FILETYPE, DOCUMENT from dbo.Documents;";
$stmt = sqlsrv_query($this->sqlsrv_conn, $query);
if (sqlsrv_fetch($stmt)) {
    $document = sqlsrv_get_field($stmt, 2, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
    $fileName = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
    $ext = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
    file_put_contents(
        $fileName . '.' . $ext,
        stream_get_contents($document),
    );
} 

现在对所有文件执行此操作的最有效方法是什么?我必须对每一行都执行查询吗?

有了 PDO,我可以使用 $stmt->fetchAll(FETCH_ASSOC),它给了我一个很好的 assoc 数组,里面有数据。
sqlsrv 有一个类似的函数 sqlsrv_fetch_array 解释了 php.net 和 [docs.microsoft]下面的例子:

while( $row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC) ) {
      echo $row['LastName'].", ".$row['FirstName']."<br />";
}

但尽我所能搜索,我找不到一种方法来循环遍历结果集而不获取它,然后单独获取每一行,同时混合流和字符串类型。 sqlsrv_fetch_array 只接受SQLSRV_FETCH_NUMERICSQLSRV_FETCH_ASSOCSQLSRV_FETCH_BOTH 然后结果集已经获取,我不能使用sqlsrv_get_field 来设置每个字段的类型。
我不可能是第一个需要这样的东西的人,但我找不到任何关于它的东西。可能我搜索错误,或者我误解了一个概念。

【问题讨论】:

  • 你为什么假设它应该是一个txt文件?它可以是任何二进制格式。我假设数据库中的其他地方必须有元数据(通常在同一个表或相关表中),它告诉您存储在其中的每个文件的名称和可能的 mime 类型。否则也很难看出原始应用程序如何利用这些数据。
  • 我自己创建了这个文本文件,所以我知道,但它们有许多不同的文件类型,你说得对。他们确实有文档的扩展名和名称以及其他元数据,如日期时间和页数,但我找不到 mime 类型。但是我找到了解决方案,我会尽快写一篇文章
  • @SamuelGfeller 解决方案是错误的。这不是“格式”,这就是 SSMS 显示二进制数据的方式。您的数据访问库已经有办法做到这一点。指定您的库并添加实际的数据访问代码
  • @PanagiotisKanavos 为不清楚的问题道歉。如果我分享我尝试实现的目标可能会更好,即将存储在 SQL Server 2008 数据库(大约 9 个表)中的所有文件“转换”为文件系统文件。最好使用 PHP,因为我知道该语言,但是任何语言的小脚本都可以使用任何库。 (如果是 PHP,它必须能够在 Windows 上运行)。你会怎么做?

标签: php file hex filesystems


【解决方案1】:

我找到了解决方案! 我测试了多个在线工具来解码字符串并找出编码是什么。 甚至尝试过hex2bin(),但所有工具都告诉我它不是有效的十六进制字符串。

直到我偶然发现了 this godsent 工具,该工具将无效的十六进制转换为 ?,但将其余部分转换为:

?Test document for migration

从那时起,很容易推断出0x 是麻烦制造者。删除它后,转换就像一个魅力,我可以“转换”更复杂的文件,比如.doc。代码如下:

file_put_contents(
    // 'DESCRIPTION' is the file name
    'files/' . $dbDocument['DESCRIPTION'] . 
    // 'FILETYPE' is the extension
    '.' . mb_strtolower($dbDocument['FILETYPE']),
    // 'DOCUMENT' is the document content hex encoded with prepended '0x'
    hex2bin(str_replace('0x', '', $dbDocument['DOCUMENT']))
);

【讨论】:

  • 首先没有要转换的格式。您发布的是您的数据库客户端如何显示二进制数据,而不是文本。无论您使用什么数据库,都已经可以读取二进制数据
  • 显然不是 odbc。我以前使用“sqlsrv”作为库,它检索了已解码二进制数据的文件。问题是 20 个文件需要 25 秒。 ODBC 需要大约 5 秒来处理 20 个文件,但它们仍然是十六进制格式,然后使用上面的 hex2bin 快速解码(比 sqlsrv 快很多)所以我选择了这个选项
  • 微软的 SQL Server PHP 示例 show how to use LOBs with SQLDriverPDO
  • 非常感谢您的链接!我已经尝试过 PDO LOBS,但无法使其正常工作,但我会使用您发送的链接再试一次。如果我还有问题,你介意我联系你吗?
【解决方案2】:

这不是一种格式,它是一些数据库管理工具(例如 SSMS)显示二进制数据的方式。数据已经是二进制的,不需要转换。

读取大对象 (LOB) 就像它们是数字或字符串一样确实很慢,将整个文档缓存在服务器和客户端内存中,即使对象不会被重用,并且甚至不需要保存在内存中。毕竟,SQL Server 中的BLOB 可以是 2GB 甚至更多。这就是为什么几乎所有数据库和数据访问库都允许将 LOB 作为文件流处理。

Microsoft 的 PHP 文档示例展示了如何将 LOB 读取为 PDOSQLSRV 的文件流。

从示例中复制,这个参数化查询将搜索用户的图片:

/* Get the product picture for a given product ID. */  
$tsql = "SELECT LargePhoto   
         FROM Production.ProductPhoto AS p  
         JOIN Production.ProductProductPhoto AS q  
         ON p.ProductPhotoID = q.ProductPhotoID  
         WHERE ProductID = ?";  
  
$params = array(&$_REQUEST['productId']);  
  
/* Execute the query. */  
$stmt = sqlsrv_query($conn, $tsql, $params);  

虽然不是将整个图片作为单个值读取,而是作为文件流加载:

$getAsType = SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY);  
if ( sqlsrv_fetch( $stmt ) )  
{  
   $image = sqlsrv_get_field( $stmt, 0, $getAsType);  
   fpassthru($image);  
}  
else  
{  
     echo "Error in retrieving data.</br>";  
     die(print_r( sqlsrv_errors(), true));  
}  

$getAsType = SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY); 指定数据将作为流检索。

$image = sqlsrv_get_field( $stmt, 0, $getAsType); 使用指定类型检索第一个字段,在本例中为流。这不会加载实际内容。

fpassthru 将流内容直接复制到输出。图片可能是 2GB,但它永远不会保存在 Web 服务器的内存中。

【讨论】:

  • 成功了!我又尝试了一次 PDO,但它返回了一个字符串 (bugs.php.net/bug.php?id=40913),我现在遇到了与 sqlsrv 函数相同的问题。我添加了一个带有详细信息的编辑,但我现在如何才能最好地检索每一行?
猜你喜欢
  • 1970-01-01
  • 2011-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-04
  • 2023-04-04
  • 2021-05-31
相关资源
最近更新 更多