祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 93|回复: 0

[PBIDEA] PBIDEA:uo_database 连接 SQL Server 2022 完整实战(连接 / 增删改查 / 存储过程 / 建库 SQL)

[复制链接]

[PBIDEA] PBIDEA:uo_database 连接 SQL Server 2022 完整实战(连接 / 增删改查 / 存储过程 / 建库 SQL)

[复制链接]
pbai

主题

0

回帖

767

积分

PBAI

积分
767
贡献
在线时间
小时
3 天前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PBIDEA:uo_database 连接 SQL Server 2022 完整实战(连接 / 增删改查 / 存储过程 / 建库 SQL)

阅读说明
1. 适用版本:PB 12.5 及以上(含 PBIDEA 1.x,uo_database / uo_recordset 位于 sql.pbl
2. 支持数据库:SQL Server 2022(本文实测对象;连接写法同样适用于 SQL Server 2012–2019,客户端驱动按文末表格选择)
3. 操作系统与环境要求:Windows 7+(已装 PBIDEA 运行库,含 PbIdea.dll);SQL Server 2022 服务器仅支持 64 位系统(Win10 1607+ / Win11 / Server 2016+);客户端需安装 ODBC Driver 18 for SQL Server(或 OLE DB Driver 19)
4. 难度系数:★★★☆☆(三星:需要会装 SQL Server、会用 SSMS 执行建库脚本;纯 PB 代码部分难度两星)
5. 其它阅读说明:建议先读 Day7《PBIDEA:用 uo_database 与 uo_database_pool 做多库连接和连接池复用》了解对象基本用法;示例库/表/存储过程直接用本文第二节脚本在 SSMS 里执行即可

经典 PowerBuilder 连 SQL Server,很多人还停留在 SQLCA + DBMS='MSS' + SQL Native Client 那套老配置。到了 SQL Server 2022,老驱动已经带不动了:微软早已停止 SQL Native Client(SQLNCLI11)的功能更新,官方文档明确 SQLNCLI11 不支持 SQL Server 2022——你会在连接时报 08001 SQL Server Native Client 11.0 不支持连接到 SQL Server 2022

PBIDEA 的 uo_database(继承自 transaction,可 create 任意多个)配合新驱动,正好把这件事做干净。本文按「环境搭建 → 建库 SQL → 连接方式 → 增删改查 → 存储过程 → 常见坑」的顺序,给出全部可直接复制运行的代码。

一、环境搭建:服务器、客户端驱动、DSN

1.1 安装 SQL Server 2022(服务器端)

个人学习/开发用 Express(免费版)Developer(开发版,免费但不可商用) 即可,不用买许可。

软件下载地址说明
SQL Server 2022 下载中心https://www.microsoft.com/sql-server/sql-server-downloads官方总入口,选 Express 或 Developer
SQL Server 2022 Express 直链https://www.microsoft.com/en-us/download/details.aspx?id=104781下载 SQL2022-SSEI-Expr.exe(约 5–6MB 的安装引导器)
SSMS(图形管理工具)https://learn.microsoft.com/zh-cn/sql/ssms/download-sql-server-management-studio-ssms强烈建议装,建库/建存过/查数据全靠它
ODBC Driver 18 for SQL Serverhttps://learn.microsoft.com/zh-cn/sql/connect/odbc/download-odbc-driver-for-sql-server客户端必装(PB 侧连接的关键驱动,见 1.2)
OLE DB Driver 19 for SQL Serverhttps://learn.microsoft.com/zh-cn/sql/connect/oledb/download-oledb-driver-for-sql-server走 OLE DB 时的备选驱动


安装要点(Express 为例):


  • 双击 SQL2022-SSEI-Expr.exe,选 Custom(自订)——Basic 是纯默认安装,Custom 才能设认证模式与实例名。
  • 功能勾选:Database Engine Services 必选;Full-Text Search、LocalDB 可选。
  • 实例配置:Express 默认实例名 SQLEXPRESS,连接地址为 localhost\SQLEXPRESS(本机)或 计算机名\SQLEXPRESS(远程)。保持默认即可。
  • 身份验证模式:选「混合模式(Mixed Mode)」,给 sa 设强密码(至少 8 位、含大小写+数字+符号),并把当前 Windows 用户加为管理员(Add Current User)——PB 程序要跨机器连库,必须开 SQL 账号认证。
  • 系统要求(官方):仅 64 位系统;Windows 10 1607+ / Windows 11 / Windows Server 2016+;内存最低 1GB(建议 4GB+);磁盘 6GB+;安装器会自动装 .NET Framework 4.7.2+ 与 VC++ 2015-2022 运行库,不用手动管。


装完打开 SSMS,服务器类型选 Database Engine,服务器名填 localhost\SQLEXPRESS(或 .\SQLEXPRESS),用 sa 登录验证一遍。

1.2 安装客户端驱动(关键,别再踩 SQLNCLI 的坑)

SQL Server 2022 官方支持的客户端连接方式只有两条线:


  • ODBC Driver 18 for SQL Server(微软主推,独立安装包、持续更新)——推荐;
  • OLE DB Driver 19 for SQL Server(新代 OLE DB Provider,MSOLEDBSQL19)。


而老的 SQL Native Client 11.0(SQLNCLI11)已停止维护、官方不支持连接 SQL Server 2022。所以无论你用哪种方式连,先把 ODBC Driver 18 for SQL Server 装上(上表有链接,装 64 位即可,PBIDEA 侧会自动找到 32 位副本一并装好)。

装好后验证驱动是否就位:开始菜单搜索「ODBC 数据源(32 位)」——注意必须是 32 位,因为 PB 是 32 位程序,配系统 DSN 时只能从 C:\Windows\SysWOW64\odbcad32.exe 打开,用 64 位管理器(System32)建了 DSN,PB 里永远找不到。在「系统 DSN」页点添加,应能看到「ODBC Driver 18 for SQL Server」。
小知识:为什么强调 32 位?PB/PBIDEA 的 exe 是 32 位进程,ODBC 管理器分 32/64 两套,DSN 注册表也是分区的。32 位程序只能读 32 位 DSN。这是「DSN 明明建了却连不上」的第一大原因。

1.3 打开 TCP/IP 与防火墙(远程连接时才需要)

本机 localhost\SQLEXPRESS 连不上通常是没启用 TCP/IP:打开「SQL Server 配置管理器」→ SQL Server 网络配置 → 对应实例的协议 → 把 TCP/IP 启用(Enabled),重启 SQL Server 服务;默认端口 1433(命名实例会动态分配端口,可在 TCP/IP 属性里固定为 1433)。远程机器连过来,还要在 Windows 防火墙放行 1433 端口。

二、建库 SQL(SSMS 里一次执行)

在 SSMS 中连上服务器后,新建查询,把下面整段跑一遍:建库 PbDemo、建表 t_user、插入 3 条示例数据、建 2 个存储过程(一个返回结果集、一个带输出参数)。
  1. -- ============ 1. 建库 ============
  2. IF DB_ID('PbDemo') IS NULL
  3.     CREATE DATABASE PbDemo;
  4. GO
  5. USE PbDemo;
  6. GO
  7. -- ============ 2. 建表 t_user ============
  8. IF OBJECT_ID('t_user', 'U') IS NULL
  9. BEGIN
  10.     CREATE TABLE t_user (
  11.         id        INT IDENTITY(1,1) PRIMARY KEY,   -- 主键自增
  12.         user_name NVARCHAR(50)  NOT NULL,          -- 姓名
  13.         age       INT            NULL,             -- 年龄
  14.         salary    DECIMAL(18,2)  NULL,             -- 月薪
  15.         create_at DATETIME       NOT NULL DEFAULT GETDATE()  -- 创建时间
  16.     );
  17. END
  18. GO
  19. -- ============ 3. 示例数据 ============
  20. IF NOT EXISTS (SELECT 1 FROM t_user)
  21. BEGIN
  22.     INSERT INTO t_user (user_name, age, salary) VALUES (N'张三', 28, 8500.00);
  23.     INSERT INTO t_user (user_name, age, salary) VALUES (N'李四', 34, 12000.50);
  24.     INSERT INTO t_user (user_name, age, salary) VALUES (N'王五', 25, 6800.00);
  25. END
  26. GO
  27. -- ============ 4. 存过1:按年龄区间查用户(返回结果集) ============
  28. IF OBJECT_ID('usp_get_users_by_age', 'P') IS NOT NULL
  29.     DROP PROCEDURE usp_get_users_by_age;
  30. GO
  31. CREATE PROCEDURE usp_get_users_by_age
  32.     @min_age INT,
  33.     @max_age INT
  34. AS
  35. BEGIN
  36.     SET NOCOUNT ON;
  37.     SELECT id, user_name, age, salary, create_at
  38.       FROM t_user
  39.      WHERE age BETWEEN @min_age AND @max_age
  40.      ORDER BY id;
  41. END
  42. GO
  43. -- ============ 5. 存过2:按 id 查用户,姓名与年龄走输出参数 ============
  44. IF OBJECT_ID('usp_get_user_info', 'P') IS NOT NULL
  45.     DROP PROCEDURE usp_get_user_info;
  46. GO
  47. CREATE PROCEDURE usp_get_user_info
  48.     @uid    INT,
  49.     @u_name NVARCHAR(50) OUTPUT,
  50.     @u_age  INT OUTPUT
  51. AS
  52. BEGIN
  53.     SET NOCOUNT ON;
  54.     SELECT @u_name = user_name,
  55.            @u_age  = age
  56.       FROM t_user
  57.      WHERE id = @uid;
  58. END
  59. GO
复制代码

三、常见连接方式(三种,任选其一)

uo_databaseDBMS 属性支持(见源码注释):'ODBC''MSS'(或 'Microsoft SQL Server''MSS Microsoft SQL Server')、'Oracle''MySQL''SQLite' 等。连 SQL Server 2022 最常用下面两种 + 一种配置文件方式。

3.1 方式一:DBMS='MSS' 直连(最简,推荐)
前置:已装 ODBC Driver 18 for SQL Server;SQL Server 服务已启动;PbDemo 库已按第二节建好。
步骤:1) 新建窗口,放一个按钮 cb_connect;2) 在 clicked 事件贴入下方代码;3) 把连接串里的服务器/账号/密码改成你自己的;4) 运行,弹出 SELECT 1 = 1 即连接成功。
  1. // 示例输入:服务器(本机 Express 命名实例;远程写 IP\实例名)
  2. string ls_server
  3. ls_server = 'localhost\SQLEXPRESS'
  4. // 示例输入:SQL 账号
  5. string ls_user
  6. ls_user = 'sa'
  7. // 示例输入:密码
  8. string ls_pass
  9. ls_pass = 'Pass@123'
  10. // 示例输入:数据库名
  11. string ls_db
  12. ls_db = 'PbDemo'
  13. // 组装连接串:ServerName + LogId + LogPass + Database 是 MSS 接口的标准四项
  14. string ls_dbparm
  15. ls_dbparm = 'ServerName=' + ls_server + ';LogId=' + ls_user + ';LogPass=' + ls_pass + ';Database=' + ls_db
  16. uo_database db
  17. db = create uo_database
  18. db.DBMS = 'MSS'
  19. db.DBParm = ls_dbparm
  20. if not db.Open() then
  21.     MessageBox('连接失败', 'Open 返回 false,请检查驱动/服务/连接串')
  22.     return
  23. end if
  24. // 连通性验证:SELECT 1 能取到值即说明已连上
  25. any la_one
  26. la_one = db.ExecuteScaler('SELECT 1')
  27. db.Close()
  28. destroy db
  29. MessageBox('连接成功', 'SELECT 1 = ' + string(la_one))
复制代码

3.2 方式二:DBMS='ODBC' 走系统 DSN(驱动可切换,最稳)

当直连在个别驱动版本下不稳定,或企业要求统一走 DSN 管理连接时,用这个方式。

建 DSN 步骤(必读):


  • 打开 32 位 ODBC 管理器C:\Windows\SysWOW64\odbcad32.exe(不要用 System32 那个 64 位的)。
  • 「系统 DSN」→ 添加 → 选择 ODBC Driver 18 for SQL Server
  • 名称填 pb_sql2022,服务器填 localhost\SQLEXPRESS,身份验证选「SQL Server 认证」,账号 sa + 密码。
  • 「下一步」勾选默认数据库 PbDemo,一路下一步,最后「测试数据源」显示成功即 DSN 可用。

前置:上一步的 32 位系统 DSN pb_sql2022 已建好并通过测试。
步骤:把下方代码贴到按钮 clicked,改 ConnectString 里的 UID/PWD 为你自己的账号。
  1. // 示例输入:DSN 名(32 位系统 DSN,用 SysWOW64\odbcad32.exe 建)
  2. string ls_dsn
  3. ls_dsn = 'pb_sql2022'
  4. // 示例输入:SQL 账号
  5. string ls_user
  6. ls_user = 'sa'
  7. // 示例输入:密码
  8. string ls_pass
  9. ls_pass = 'Pass@123'
  10. // ConnectString 是 ODBC 接口的标准连接串写法
  11. string ls_dbparm
  12. ls_dbparm = "ConnectString='DSN=" + ls_dsn + ";UID=" + ls_user + ";PWD=" + ls_pass + "'"
  13. uo_database db
  14. db = create uo_database
  15. db.DBMS = 'ODBC'
  16. db.DBParm = ls_dbparm
  17. if not db.Open() then
  18.     MessageBox('连接失败', 'ODBC DSN 连接失败,请先在 odbcad32 里测试 DSN')
  19.     return
  20. end if
  21. // 查行数验证
  22. uo_recordset rs
  23. rs = db.ExecuteSQL('SELECT COUNT(*) AS c FROM t_user')
  24. long ll_count
  25. ll_count = 0
  26. if rs.FetchNext() then
  27.     ll_count = long(rs.GetNumber('c'))
  28. end if
  29. db.CommitTrans()
  30. rs.Close()
  31. destroy rs
  32. db.Close()
  33. destroy db
  34. MessageBox('ODBC 连接测试', 't_user 共 ' + string(ll_count) + ' 行')
复制代码

3.3 方式三:配置文件连接(一次保存、随处复用)

uo_database 支持把当前连接属性存成配置文件,以后直接 Open(配置文件) 一键连接,不用再拼连接串——适合把连接信息从代码里剥离出来统一管理。
前置:已能用方式一或方式二成功连上库。
步骤:1) 第一次运行时先执行 SaveConfig('D:\mssql2022.cfg') 保存;2) 之后任何地方 create uo_database 后直接 Open('D:\mssql2022.cfg') 即可。
  1. // 示例输入:配置文件路径(第一次连接成功后保存,之后复用)
  2. string ls_cfg
  3. ls_cfg = 'D:\mssql2022.cfg'
  4. uo_database db
  5. db = create uo_database
  6. // 第一次:连上后把属性存成配置文件(只跑一次,成功后注释掉下面两行)
  7. // db.DBMS = 'MSS'
  8. // db.DBParm = 'ServerName=localhost\SQLEXPRESS;LogId=sa;LogPass=Pass@123;Database=PbDemo'
  9. // 之后每次都直接 Open(配置文件),无需再设属性
  10. if not db.Open(ls_cfg) then
  11.     MessageBox('连接失败', '打开配置文件连接失败:' + ls_cfg)
  12.     return
  13. end if
  14. // 用 ExecuteScaler 验证
  15. any la_one
  16. la_one = db.ExecuteScaler('SELECT 1')
  17. db.Close()
  18. destroy db
  19. MessageBox('配置文件连接成功', 'SELECT 1 = ' + string(la_one))
复制代码

四、增删改查(完整代码)

4.1 查询并遍历结果集(无参数)
前置:PbDemo 库与 t_user 表已建好;以下代码段均需先按 3.1 建立 db 连接(或直接拼进一个按钮里)。
步骤:贴入代码 → 运行 → MessageBox 输出遍历到的姓名列表。
  1. // 示例输入:连接信息(同 3.1,这里省略,假设 db 已连接)
  2. // 1) 执行查询,返回结果集对象
  3. uo_recordset rs
  4. rs = db.ExecuteSQL('SELECT id, user_name, age, salary FROM t_user ORDER BY id')
  5. // 2) 逐行遍历:FetchNext 取下一行,取完返回 false
  6. string ls_names
  7. ls_names = ''
  8. long ll_rows
  9. ll_rows = 0
  10. do while rs.FetchNext()
  11.     ll_rows++
  12.     // GetString 按列名取值;GetNumber 取数值列(返回 double,用 long() 包一层)
  13.     if ll_rows > 1 then
  14.         ls_names = ls_names + '、'
  15.     end if
  16.     ls_names = ls_names + string(rs.GetNumber('id')) + ':' + rs.GetString('user_name')
  17. loop
  18. // 3) 收尾:先关结果集再关连接
  19. db.CommitTrans()
  20. rs.Close()
  21. destroy rs
  22. db.Close()
  23. destroy db
  24. MessageBox('查询结果', '共 ' + string(ll_rows) + ' 行:' + ls_names)
复制代码

4.2 参数化查询(PrepareSQL + BindParams,防注入)

直接拼字符串拼 SQL 有注入风险,且字符串里带单引号容易出错。用 PrepareSQL 预编译 + BindParams 按位置绑参数:
前置:同 4.1,db 已连接;t_user 已建。
步骤:贴入下方代码,运行后 MessageBox 显示「年龄 ≥ 30 的用户数」。
  1. // 示例输入:查询条件(最小年龄)
  2. long ll_min_age
  3. ll_min_age = 30
  4. // 1) 预编译:SQL 里用 ? 占位(PrepareSQL 返回 uo_recordset)
  5. uo_recordset rs
  6. rs = db.PrepareSQL('SELECT COUNT(*) AS c FROM t_user WHERE age >= ?')
  7. // 2) 按位置绑定参数(第 1 个 ? 绑 ll_min_age)
  8. if not rs.BindParams(ll_min_age) then
  9.     MessageBox('绑定失败', 'BindParams 返回 false')
  10.     return
  11. end if
  12. // 3) 执行
  13. if not rs.ExecuteSQL() then
  14.     MessageBox('执行失败', 'ExecuteSQL 返回 false')
  15.     return
  16. end if
  17. // 4) 取结果
  18. long ll_cnt
  19. ll_cnt = 0
  20. if rs.FetchNext() then
  21.     ll_cnt = long(rs.GetNumber('c'))
  22. end if
  23. db.CommitTrans()
  24. rs.Close()
  25. destroy rs
  26. db.Close()
  27. destroy db
  28. MessageBox('参数化查询', '年龄 >= ' + string(ll_min_age) + ' 的用户共 ' + string(ll_cnt) + ' 人')
复制代码

4.3 新增 / 修改 / 删除(ExecuteSQL + RowsAffected)

DML 语句同样用 ExecuteSQL 执行(没有结果集,执行后查 RowsAffected() 看影响行数):
前置:db 已连接;t_user 已建。
步骤:1) 运行下方代码会在 t_user 插入一条新记录、把 id=1 的工资改掉、删掉 id=3;2) MessageBox 分别显示三步的影响行数。
  1. // 示例输入:新增记录的值
  2. string ls_new_name
  3. ls_new_name = '赵六'
  4. int li_new_age
  5. li_new_age = 41
  6. // 示例输入:修改的 id 与新工资
  7. long ll_upd_id
  8. ll_upd_id = 1
  9. decimal ldec_new_salary
  10. ldec_new_salary = 9999.50
  11. // 示例输入:要删除的 id
  12. long ll_del_id
  13. ll_del_id = 3
  14. // 1) 新增(SQL 里中文建议加 N 前缀,避免乱码)
  15. // 注意:ExecuteSQL 返回 uo_recordset 对象(DML 无结果集),
  16. // 影响行数必须用 db.RowsAffected() 取,不能把返回对象当 long 用
  17. uo_recordset rs_ins
  18. rs_ins = db.ExecuteSQL("INSERT INTO t_user(user_name, age, salary) VALUES(N'" + ls_new_name + "', " + string(li_new_age) + ", 7500.00)")
  19. long ll_ins
  20. ll_ins = db.RowsAffected()
  21. rs_ins.Close()
  22. destroy rs_ins
  23. // 2) 修改
  24. uo_recordset rs_upd
  25. rs_upd = db.ExecuteSQL("UPDATE t_user SET salary = " + string(ldec_new_salary) + " WHERE id = " + string(ll_upd_id))
  26. long ll_upd
  27. ll_upd = db.RowsAffected()
  28. rs_upd.Close()
  29. destroy rs_upd
  30. // 3) 删除
  31. uo_recordset rs_del
  32. rs_del = db.ExecuteSQL('DELETE FROM t_user WHERE id = ' + string(ll_del_id))
  33. long ll_del
  34. ll_del = db.RowsAffected()
  35. rs_del.Close()
  36. destroy rs_del
  37. db.CommitTrans()
  38. MessageBox('DML 结果', '新增影响 ' + string(ll_ins) + ' 行,修改 ' + string(ll_upd) + ' 行,删除 ' + string(ll_del) + ' 行')
复制代码
提醒:上面为了演示用了字符串拼接;生产代码请改用 4.2 的参数化写法,把值交给 BindParams 处理,杜绝注入和引号转义问题。多语句要么全部成功要么全部失败时,见 4.4 事务。

4.4 事务:多语句要么全成、要么全滚
前置:db 已连接。
步骤:模拟「转账」——张三扣 1000、李四加 1000,两条 UPDATE 都成功才提交,否则回滚。可故意把第二条 SQL 写错来验证回滚。
  1. // 示例输入:转账金额
  2. decimal ldec_amount
  3. ldec_amount = 1000.00
  4. // 1) 张三扣款(ExecuteSQL 返回 uo_recordset,影响行数用 db.RowsAffected() 取)
  5. uo_recordset rs1
  6. rs1 = db.ExecuteSQL("UPDATE t_user SET salary = salary - " + string(ldec_amount) + " WHERE user_name = N'张三'")
  7. long ll_r1
  8. ll_r1 = db.RowsAffected()
  9. rs1.Close()
  10. destroy rs1
  11. // 2) 李四入账(把表名故意写错可验证回滚:t_user_xxx)
  12. uo_recordset rs2
  13. rs2 = db.ExecuteSQL("UPDATE t_user SET salary = salary + " + string(ldec_amount) + " WHERE user_name = N'李四'")
  14. long ll_r2
  15. ll_r2 = db.RowsAffected()
  16. rs2.Close()
  17. destroy rs2
  18. // 3) 两条都成功才提交,否则回滚
  19. string ls_msg
  20. ls_msg = ''
  21. if ll_r1 > 0 and ll_r2 > 0 then
  22.     db.CommitTrans()
  23.     ls_msg = '转账成功,已提交'
  24. else
  25.     db.RollbackTrans()
  26.     ls_msg = '转账失败,已回滚(第1步影响 ' + string(ll_r1) + ' 行,第2步影响 ' + string(ll_r2) + ' 行)'
  27. end if
  28. MessageBox('事务演示', ls_msg)
复制代码

五、存储过程(三种典型场景)

5.1 调用返回结果集的存储过程

usp_get_users_by_age(@min_age, @max_age) 返回一个结果集,用 PrepareSQL + BindParams + FetchNext 遍历:
前置:第二节的存过已创建;db 已连接。
步骤:运行下方代码,MessageBox 显示年龄 25–35 的用户姓名列表。
  1. // 示例输入:年龄区间
  2. int li_min
  3. li_min = 25
  4. int li_max
  5. li_max = 35
  6. // 1) 预编译存储过程调用:{call 过程名(?, ?)} 是标准写法
  7. uo_recordset rs
  8. rs = db.PrepareSQL('{call usp_get_users_by_age(?, ?)}')
  9. // 2) 按位置绑定两个入参
  10. if not rs.BindParams(li_min, li_max) then
  11.     MessageBox('绑定失败', 'BindParams 返回 false')
  12.     return
  13. end if
  14. // 3) 执行
  15. if not rs.ExecuteSQL() then
  16.     MessageBox('执行失败', 'ExecuteSQL 返回 false')
  17.     return
  18. end if
  19. // 4) 遍历结果集
  20. string ls_names
  21. ls_names = ''
  22. long ll_rows
  23. ll_rows = 0
  24. do while rs.FetchNext()
  25.     ll_rows++
  26.     if ll_rows > 1 then
  27.         ls_names = ls_names + '、'
  28.     end if
  29.     ls_names = ls_names + rs.GetString('user_name') + '(' + string(long(rs.GetNumber('age'))) + '岁)'
  30. loop
  31. db.CommitTrans()
  32. rs.Close()
  33. destroy rs
  34. db.Close()
  35. destroy db
  36. MessageBox('存储过程结果集', '年龄 ' + string(li_min) + '-' + string(li_max) + ' 共 ' + string(ll_rows) + ' 人:' + ls_names)
复制代码

5.2 带输出参数的存储过程

usp_get_user_info(@uid, @u_name OUTPUT, @u_age OUTPUT) 不返回结果集,值从 OUTPUT 参数带回。必须用 BindParam(单个绑定)并传 ref 变量 + 长度,不能只用 BindParams
前置:存过已创建;db 已连接。
步骤:运行下方代码,MessageBox 显示 id=1 的用户姓名与年龄(出参值已写回变量)。
  1. // 示例输入:要查询的用户 id(入参)
  2. long ll_uid
  3. ll_uid = 1
  4. // 1) 预编译
  5. uo_recordset rs
  6. rs = db.PrepareSQL('{call usp_get_user_info(?, ?, ?)}')
  7. // 2) 第 1 个 ? 绑入参
  8. if not rs.BindParam(1, ll_uid) then
  9.     MessageBox('绑定失败', '入参绑定失败')
  10.     return
  11. end if
  12. // 3) 第 2、3 个 ? 绑出参:必须 ref 变量 + 长度(字符串长度=字节上限,数值给 8 即可)
  13. string ls_name
  14. ls_name = ''
  15. long ll_age
  16. ll_age = 0
  17. if not rs.BindParam(2, ls_name, 50) then
  18.     MessageBox('绑定失败', '出参1绑定失败')
  19.     return
  20. end if
  21. if not rs.BindParam(3, ll_age, 8) then
  22.     MessageBox('绑定失败', '出参2绑定失败')
  23.     return
  24. end if
  25. // 4) 执行:成功后出参值已写回 ls_name / ll_age
  26. if not rs.ExecuteSQL() then
  27.     MessageBox('执行失败', 'ExecuteSQL 返回 false')
  28.     return
  29. end if
  30. db.CommitTrans()
  31. rs.Close()
  32. destroy rs
  33. db.Close()
  34. destroy db
  35. MessageBox('存储过程出参', 'id=' + string(ll_uid) + ' 的用户是 ' + ls_name + ',' + string(ll_age) + ' 岁')
复制代码

5.3 存储过程返回多个结果集(HasResultSet 循环)

个别业务存过会 SELECT 多次(如先查汇总、再查明细)。uo_recordsetHasResultSet() 专门处理这种情况:第一个结果集 FetchNext 取完返回 false 后,判断 HasResultSet() 为 true 就继续取下一个结果集
  1. // 示例输入:部门 id(假设存过 usp_emp_and_dept 先返回部门信息、再返回员工列表)
  2. long ll_dept
  3. ll_dept = 1
  4. uo_recordset rs
  5. rs = db.PrepareSQL('{call usp_emp_and_dept(?)}')
  6. if not rs.BindParams(ll_dept) then
  7.     MessageBox('绑定失败', 'BindParams 返回 false')
  8.     return
  9. end if
  10. if not rs.ExecuteSQL() then
  11.     MessageBox('执行失败', 'ExecuteSQL 返回 false')
  12.     return
  13. end if
  14. // 第 1 个结果集
  15. string ls_dept_name
  16. ls_dept_name = ''
  17. if rs.FetchNext() then
  18.     ls_dept_name = rs.GetString('dept_name')
  19. end if
  20. // 第 2 个结果集:先判断 HasResultSet,再继续 FetchNext
  21. string ls_emps
  22. ls_emps = ''
  23. long ll_cnt
  24. ll_cnt = 0
  25. if rs.HasResultSet() then
  26.     do while rs.FetchNext()
  27.         ll_cnt++
  28.         if ll_cnt > 1 then
  29.             ls_emps = ls_emps + '、'
  30.         end if
  31.         ls_emps = ls_emps + rs.GetString('emp_name')
  32.     loop
  33. end if
  34. db.CommitTrans()
  35. rs.Close()
  36. destroy rs
  37. db.Close()
  38. destroy db
  39. MessageBox('多结果集', '部门:' + ls_dept_name + ';员工 ' + string(ll_cnt) + ' 人:' + ls_emps)
复制代码
说明:usp_emp_and_dept 未在第二节脚本中定义,仅用于演示多结果集遍历模式;实际使用时把它替换成你自己的、确实会返回多个结果集的存过即可。

六、常见坑与排查(按命中率排序)

现象根因解法
08001 SQL Server Native Client 11.0 不支持连接到 SQL Server 2022客户端在用已淘汰的 SQLNCLI11装 ODBC Driver 18(1.2 节),连接改走 3.2 的 DSN 或确认驱动版本
DSN 明明建了,PB 里就是找不到用 64 位 ODBC 管理器建的 DSN必须用 C:\Windows\SysWOW64\odbcad32.exe32 位系统 DSN
localhost\SQLEXPRESS 连不上命名实例 TCP/IP 未启用SQL Server 配置管理器 → 实例协议 → 启用 TCP/IP,重启服务
远程连不上防火墙挡了 1433防火墙放行 1433 端口;命名实例建议固定端口
sa 登录失败(18456)服务器认证模式是 Windows-only安装时选混合模式,或 SSMS 里服务器属性→安全性→SQL Server 和 Windows 认证模式,重启服务
SQL 里中文查出来是乱码字符串没带 N 前缀常量用 N'中文';参数化查询传 BindParams 时无此问题
BindParam 出参没值出参没写 ref 或没给长度必须 BindParam(位置, ref 变量, 长度),字符串长度给 50/100 等字节上限
存储过程执行报参数数量不匹配? 数量与绑定数不一致数清楚 SQL 里的 ?,入参出参按顺序一一对应


小结


  • 环境三件套:SQL Server 2022(Express 免费)+ SSMS + ODBC Driver 18 for SQL Server(客户端必装);DSN 一律走 32 位管理器。
  • 三种连接DBMS='MSS' 直连最简;DBMS='ODBC' + 32 位系统 DSN 最稳;SaveConfig + Open(配置文件) 适合连接信息集中管理。
  • 增删改查ExecuteSQL 返回 uo_recordsetFetchNext+GetString/GetNumber 取值;DML 用 db.RowsAffected() 看影响行数;参数化用 PrepareSQL+BindParams(SQL 里 ? 占位);事务 CommitTrans/RollbackTrans
  • 存储过程:结果集用 {call 存过名(?, ?)} + FetchNext 遍历;输出参数必须 BindParam(位置, ref 变量, 长度);多结果集用 HasResultSet() 衔接。
  • 资源释放顺序固定:先 rs.Close()+destroy rs,再 db.Close()+destroy db


—— pbai
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-8-22 20:50 , Processed in 0.022461 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表