pbai 发表于 2026-9-5 07:27:15

PowerBuilder 用 UPDATEBLOB 与 SELECTBLOB 存取图片等大字段

PowerBuilder 用 UPDATEBLOB 与 SELECTBLOB 存取图片等大字段


阅读说明
1. 适用版本:PowerBuilder 9 / 10 / 11 / 12.5(UPDATEBLOB / SELECTBLOB 是嵌入式 SQL 的标准语法,自 PB 6 起即有,所有版本通用;本文代码已在 PB 12.5 实机验证)
2. 支持数据库:SQLite / SQL Server / Oracle / MySQL 等支持二进制大对象(BLOB / IMAGE / VARBINARY(MAX))的数据库;本文示例以 SQLite 实机验证,Blob 语句一字不变适用于其它库
3. 操作系统与环境要求:Windows 7+;演示用 SQLite 需 PBIDEA 提供的 LIT SQLite 接口(PbIdea.dll + sqlite3.dll),标准项目换成你的 SQL Server / Oracle 只需改 SQLCA 连接串,Blob 语句不变
4. 难度系数:★★★☆☆
5. 其它阅读说明:示例用一张图片或任意二进制文件演示;流程是「文件 → Blob → 入库」,取回时是「库 → Blob → 文件」;先把文件读进 Blob 再操作最稳妥




为什么要专门学这两个语句

PowerBuilder 里,普通的 INSERT / SELECT 处理的是「字符串」和「数值」。但图片、PDF、Word 文档、压缩包这类二进制大对象(Binary Large Object,简称 Blob),有两个特点让普通 SQL 处理不了:


[*]体积大:一个图片可能几 MB,远超过 string 类型(以及嵌入 SQL 字符串绑定)的上限,塞不进 :ls_text 这种字符串变量。
[*]含特殊字节:二进制里有 0x00 等控制字节,当成字符串处理会被截断、乱码。


所以 PB 在嵌入式 SQL 里单独给了两条语句:


[*]UPDATEBLOB:把内存里的 blob 变量写进某张表的 Blob 列。
[*]SELECTBLOB:把表里 Blob 列的内容读进内存的 blob 变量。


它们和普通的 UPDATE / SELECT 完全平行,区别只在「操作的是 Blob 列、绑定的是 blob 类型变量」。



一、第一步:把文件读进 Blob(FileOpen + FileReadEx)

存库之前,先把磁盘上的文件读进一个 blob 变量。PB 用 FileOpen 以流模式(StreamMode!)打开,再用 FileReadEx 一次性读入。


前置:窗口上放一个按钮 cb_load,在其 clicked 事件里贴下方代码;先准备一个二进制文件(图片、PDF 都行),记下它的路径。
步骤:1) 打开窗口;2) 点击 cb_load;3) 在 MessageBox 里看到读入的字节数。



// 示例输入:要读入的文件路径(换成你自己的文件)
string ls_path
ls_path = 'C:\temp\photo.png'
// 接收文件内容的 blob 变量
blob lb_pic

int li_f
long ll_len
li_f = FileOpen(ls_path, StreamMode!, Read!, LockRead!)
if li_f < 0 then
        MessageBox('错误', '文件打开失败,请检查路径')
        return
end if
ll_len = FileReadEx(li_f, lb_pic)
FileClose(li_f)

MessageBox('已读入字节数', string(ll_len))


要点:

[*]StreamMode! 是「二进制流」模式,按字节读,不会像文本模式那样做换行转换——读二进制文件必须用这个模式。
[*]FileReadEx(fileno, blobvar) 返回实际读到的字节数(读失败返回 -1)。它一次把整个文件读进 blob,比老式的 FileRead 按行读要省事得多。
[*]读到的 lb_pic 现在在内存里,可以拿去存库了。




二、写入数据库:UPDATEBLOB

Blob 变量准备好后,用 UPDATEBLOB 写进表的 Blob 列。注意它必须单独成句,不能用普通 UPDATE 的 SET col = :lb_pic 写法(普通 UPDATE 的绑定变量只认字符串/数值)。


前置:已 CONNECT USING SQLCA; 成功;数据表已建好,例如 files(id integer, name varchar(50), content blob),并且已经有一行 id = 1。



// 示例输入:要写入的 blob(假设已由上一步读入)
blob lb_pic
// 示例输入:目标记录 id
long ll_id
ll_id = 1

UPDATEBLOB files
SET content = :lb_pic
WHERE id = :ll_id
USING SQLCA;
if SQLCA.SQLCode <> 0 then
        MessageBox('写入失败', SQLCA.SQLErrText)
else
        MessageBox('写入成功', '字节数=' + string(len(lb_pic)))
end if


要点:

[*]SET content = :lb_pic 右边必须是 :blob变量,冒号绑定。
[*]WHERE 条件可以带普通变量(如 :ll_id),和普通 UPDATE 一样。
[*]必须跟 USING SQLCA;(或你自己的事务对象)。
[*]写完一定要检查 SQLCA.SQLCode:0 成功,非 0 看 SQLCA.SQLErrText。




三、取回数据库:SELECTBLOB

取回时方向相反,用 SELECTBLOB ... INTO :blob变量。


// 示例输入:要取回的记录 id
long ll_id
ll_id = 1
// 接收读出的 blob
blob lb_back

SELECTBLOB content
INTO :lb_back
FROM files
WHERE id = :ll_id
USING SQLCA;
if SQLCA.SQLCode <> 0 then
        MessageBox('读取失败', SQLCA.SQLErrText)
else
        MessageBox('取回字节数', string(len(lb_back)))
end if


要点:

[*]INTO :lb_back 的变量必须是 blob 类型。
[*]一次 SELECTBLOB 只能取一个 Blob 列,且 INTO 后面只能跟这一个 blob 变量(不能像普通 SELECT 那样 INTO :a, :b 混着来)。
[*]同样必须 USING SQLCA; 并检查 SQLCA.SQLCode。




四、把 Blob 写回文件:FileWriteEx

取回的 blob 要还原成文件,用 FileOpen(流模式写)+ FileWriteEx。


// 示例输入:写出的目标路径
string ls_out
ls_out = 'C:\temp\photo_copy.png'
// 示例输入:要写出的 blob(已由上一步取回)
blob lb_back

int li_o
li_o = FileOpen(ls_out, StreamMode!, Write!, LockWrite!)
if li_o < 0 then
        MessageBox('错误', '无法创建目标文件')
        return
end if
FileWriteEx(li_o, lb_back)
FileClose(li_o)

MessageBox('已写出字节数', string(len(lb_back)))




五、完整可运行示例(一个函数串起来)

下面把「读文件 → 入库 → 取回 → 写回验证」完整串在一次调用里,也是本文在 PB 12.5 实机跑通的代码。它以 SQLite 做演示库(LIT SQLite 是 PBIDEA 提供的 SQLite 接口),换成 SQL Server / Oracle 只改 SQLCA 的连接串,UPDATEBLOB / SELECTBLOB 语句一字不变。


// 示例输入:源二进制文件路径(换成你自己的文件)
string ls_in
ls_in = 'C:\temp\sample.bin'
// 示例输入:写回验证用的目标路径
string ls_out
ls_out = 'C:\temp\sample_out.bin'

int li_f
long ll_len
blob lb_data

// 1) 文件 -> Blob
li_f = FileOpen(ls_in, StreamMode!, Read!, LockRead!)
if li_f < 0 then
        MessageBox('错误', '源文件打开失败')
        return
end if
ll_len = FileReadEx(li_f, lb_data)
FileClose(li_f)

// 2) 连接 SQLite 演示库(生产环境换成你的 MSSQL/Oracle 连接串)
SQLCA.DBMS = 'LIT SQLite'
SQLCA.Database = 'pblit_demo.db'
SQLCA.AutoCommit = true
CONNECT USING SQLCA;
if SQLCA.SQLCode <> 0 then
        MessageBox('连接失败', SQLCA.SQLErrText)
        return
end if

// 3) 建表并清空旧数据,保证可重复运行
string ls_sql
ls_sql = 'create table if not exists files(id integer primary key, name varchar(50), content blob)'
EXECUTE IMMEDIATE :ls_sql USING SQLCA;
ls_sql = 'delete from files where id = 1'
EXECUTE IMMEDIATE :ls_sql USING SQLCA;
INSERT INTO files(id, name) VALUES(1, 'sample.bin') USING SQLCA;

// 4) UPDATEBLOB 写入
UPDATEBLOB files SET content = :lb_data WHERE id = 1 USING SQLCA;
if SQLCA.SQLCode <> 0 then
        MessageBox('入库失败', SQLCA.SQLErrText)
        DISCONNECT USING SQLCA;
        return
end if

// 5) SELECTBLOB 取回
blob lb_back
SELECTBLOB content INTO :lb_back FROM files WHERE id = 1 USING SQLCA;
if SQLCA.SQLCode <> 0 then
        MessageBox('取回失败', SQLCA.SQLErrText)
        DISCONNECT USING SQLCA;
        return
end if

DISCONNECT USING SQLCA;

// 6) 写回文件做往返验证
int li_o
li_o = FileOpen(ls_out, StreamMode!, Write!, LockWrite!)
FileWriteEx(li_o, lb_back)
FileClose(li_o)

// 结果输出:三次字节数应一致,证明往返无损
MessageBox('Blob 往返验证', '读入=' + string(ll_len) + ' 入库=' + string(len(lb_data)) + ' 取回=' + string(len(lb_back)))



连接串差异(仅举例,Blob 语句不变):SQL Server 用 SQLCA.DBMS = 'SNC SQL Native Client' + SQLCA.ServerName / SQLCA.Database / SQLCA.LogId / SQLCA.LogPass;Oracle 用 SQLCA.DBMS = 'O73 Oracle7' 系列或 O10/O11 驱动。无论哪种,上面的 UPDATEBLOB / SELECTBLOB 四行原样照搬即可。




六、边界情况与常见坑


[*]普通 UPDATE 不能改 Blob 列:UPDATE files SET content = :lb_pic 会编译报错或运行失败。必须用 UPDATEBLOB。
[*]普通 SELECT INTO 不能取 Blob:SELECT content INTO :ls_x 把 blob 当字符串会截断/报错。必须用 SELECTBLOB。
[*]SELECTBLOB 一次只能一个 Blob 列:想同时取 Blob 和普通列,分开两条语句,或先把普通列用普通 SELECT 取,Blob 单独 SELECTBLOB。
[*]必须检查 SQLCA.SQLCode:Blob 操作出错(列不存在、事务没连、类型不匹配)时 PB 不会抛异常,只会把 SQLCode 置非 0。养成「写完必查」的习惯。
[*]WHERE 条件不能省且要能定位到行:UPDATEBLOB / SELECTBLOB 都靠 WHERE 锁定行,条件错会把别的行写坏或取空。
[*]Blob 变量要先有值再写:UPDATEBLOB 把一个空 blob(长度 0)写进去,库里就是空的——不是报错,是「存了个空」。读文件失败时 lb_data 可能为空,先确认 ll_len > 0。
[*]事务与提交:如果 SQLCA.AutoCommit = false,UPDATEBLOB 后要 COMMIT USING SQLCA; 才真正落库;AutoCommit = true 则自动提交。忘了提交,取回时可能读不到刚写的内容。
[*]FileOpen 模式要配对:读用 Read!,写用 Write!;二进制一律 StreamMode!,文本才用 LineMode!。用错模式文件会损坏。




七、与 DataWindow 的 Blob 列对比

如果你的数据已经在 DataWindow 里(比如 d_files 有一列 content 是 blob 类型),不必走嵌入式 SQL,直接用 DataWindow 的方法:


// 示例输入:行号
long ll_row
ll_row = 1
// 示例输入:blob 列名
string ls_col
ls_col = 'content'

// 从 DataWindow 取 blob 列
blob lb_col
lb_col = dw_1.GetItemBlob(ll_row, ls_col)
// 写回 blob 列
dw_1.SetItemBlob(ll_row, ls_col, lb_col)
// 真正落库仍需 Update()
dw_1.Update()

MessageBox('DataWindow Blob', '取回字节=' + string(len(lb_col)))


两种路线怎么选:

[*]嵌入式 UPDATEBLOB / SELECTBLOB:适合后台服务、脚本、不在界面上的批量处理,不依赖 DataWindow。
[*]DataWindow 的 GetItemBlob / SetItemBlob + Update():适合界面上直接编辑、随 DataWindow 一起提交,和界面绑定更自然。




八、进阶:PBIDEA 的 uo_blob 封装

PBIDEA 的 websuite.pbl 里有一套 uo_blob 对象,对上述「读文件 / 写文件 / 入库 / 取回」做了更顺手的封装(例如把文件直读直写、Base64 编解码、与 uo_database 联动等收进对象方法),适合不想手写 FileOpen/FileReadEx 细节的场景。它的方法签名以本机 PBIDEA/export_20260811/websuite.pbl/uo_blob.sru 为准,需要时对照源码取用即可。本文核心是标准嵌入式 SQL,掌握后接入 uo_blob 只是少写几行胶水代码。



九、小结


[*]二进制大字段不能用普通 INSERT / SELECT,必须用 UPDATEBLOB(写)和 SELECTBLOB(读)。
[*]标准流程:文件 → FileOpen/FileReadEx 读进 blob → UPDATEBLOB 入库 → SELECTBLOB 取回 → FileWriteEx 写回文件。
[*]三条铁律:绑定变量用 :blob、必须 USING SQLCA;、写完必查 SQLCA.SQLCode。
[*]界面场景可用 DataWindow 的 GetItemBlob / SetItemBlob 替代,后台/脚本场景用嵌入式 SQL 更直接。
[*]本文全部示例已在 PB 12.5 实机跑通(含 SQLite 上的 UPDATEBLOB / SELECTBLOB 往返验证)。
页: [1]
查看完整版本: PowerBuilder 用 UPDATEBLOB 与 SELECTBLOB 存取图片等大字段

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com