做U9二次开发这几年,总会被业务方问到同一个问题:U9里到底怎么查BP数据?这里的BP不是Back Propagation神经网络,而是Business Partner,也就是业务伙伴。在U9里,它统一涵盖了客户、供应商,甚至内部部门。之前我接到一个需求,要把三百多家客户的联系人和信用额度按地区批量导出来,用系统自带功能点得手酸不说,列表还经常转圈卡死。后来我从界面、接口、数据库三个层面把U9的BP查询路子整个趟了一遍,才算真正摸清。这篇就讲清楚我摸索出来的BP查询方法,对搞U9运维和二次开发的兄弟应该能省不少劲。
1. 先搞清楚U9里的BP是什么,查询场景有哪些
1.1 BP概念和U9的数据模型
U9是典型的SOA架构ERP,把企业的主数据统一建模。BP,也就是业务伙伴,在这个模型里是一个很核心的"大头"。客户、供应商、经销商、内部组织等都被纳入了BP这个逻辑实体内。这样做的好处是,销售、采购、财务模块引用主数据时,不需要区分到底是客户还是供应商,统一用BP编码就行了。但坏处也很明显,对业务人员来说,他们习惯分客户档案、供应商档案,U9却在一个大表里存,标准界面的查询逻辑就又绕又慢。
实际在数据库层面,BP主数据往往存放在以Base_BP为前缀的若干张表里。主表存最基本的编码、名称、状态,扩展表存联系人、地址、银行账号,分类表存客户/供应商的类型,还有信用、价格等衍生表。我第一次做这个需求时,光确认表对应关系就花了一上午。所以后面我会专门讲怎么用工具快速找表,而不是靠猜。
1.2 业务中最常见的BP查询需求
在一线碰到的BP查询,无非这么几大类。第一类是模糊搜"名称含某关键词",比如查所有名字带“科技”的客户。第二类是按归属关系过滤,比如某业务员名下有哪些分销商。第三类是组合条件,比如"华东区的、信用等级为A的、最近一年有交易的供应商"。第四类是反向查询,即通过销售订单或采购单据反查BP的联系人、税号、付款条件。
这些需求听起来很简单,但U9标准界面的查询往往只能处理前两类,第三类、第四类就需要动心思了。尤其是需要导出大量字段时,标准功能一次最多导出明细列表里显示的那些列,想要的联系人手机号、开户行账号可能根本不在列表里,于是就得想别的办法。
1.3 为什么不能指望标准查询
我见过很多实施顾问给业务人员的培训就是"点放大镜,输入编码,点确定"。这套流程在几十条数据时没问题,一旦数据量到了几十万,标准查询的缺点就全暴露了。U9标准列表查询默认会读取单据模板的栏目配置,很多隐藏字段即使没显示在界面上,也会被SELECT进临时表,导致查询缓慢。而且多条件过滤用的是模糊匹配,不走索引的情况时有发生。
另外,标准查询的"按编码模糊"会给业务人员示好,但后台可能生成了LIKE '%xxx%',这玩意儿在SQL Server里基本等于全表扫描。数据量一大,卡上几十秒很正常。所以在实际项目里,我发现,与其天天吐槽界面慢,不如直接给业务方做一个定制查询页面,这也就引出了下面要介绍的几种更实用的方法。
2. 方法一:压榨U9标准界面,至少解决80%日常查询
2.1 用"高级筛选"替代简单放大镜
大多数用户只知道U9列表界面左上角有个简单查询框,其实在"业务伙伴-客户档案"列表里,工具栏上还有一个"高级筛选"入口,只是藏得深。点开之后,就可以设置多个字段条件,比如"客户分类"等于"重点客户",并且"所属地区"包含"华东",并且"信用等级"等于"A"。这些条件之间是并且的关系,满足大多数组合过滤的场景。
具体操作上,先打开客户档案列表,点击工具栏上的"高级查询"按钮(有些版本叫"筛选""查询条件"),在弹出的窗口里选择字段名、操作符(等于、包含、大于等),输入值,然后点击"添加条件"。多个条件会形成一个条件组,还可以设置条件组间的"并且/或者"关系。我之前给业务人员培训时,专门把常用条件组合保存成方案,他们后续点一下就能复用。
2.2 把常用查询保存成个人方案
高级筛选窗口里填完一堆条件之后,底部有"保存方案"按钮。建议以业务语义命名,比如"华东大客户信用A级"、"近三个月活跃供应商"。保存后,下次在列表界面的"方案"下拉框里直接选择,系统自动带出条件。这个功能很多老用户用了五六年都不知道,其实对提高效率特别有帮助。
此外,U9的列表界面支持把当前查询结果"输出到Excel"。在列表上方的导出图标里,选"当前页"或"全部数据"(如果数据量不大)。导出格式可以选xls或xlsx。这里有个小坑,如果列表本身因为性能问题没显示全部数据,导出的也只是当前加载的数据。所以导出前最好先执行一次高级查询,缩小范围。
2.3 标准查询的局限和替代思路
即便用上高级筛选,依然解决不了"按联系人手机号搜索客户"这样的冷门条件,因为标准筛选面板里根本没有联系人手机号这个字段。这时候就得考虑二开,或者直接走数据库。还有,某些U9版本的权限控制较严,高级筛选里能选到的字段受制于当前用户的数据权限,如果发现字段找不到,多半是因为没有分配可见字段权限。
标准界面作为日常兜底工具还是够用的。我有一个习惯,凡是业务方说"这个查询条件界面上弄不出来"的,我第一反应不是急着写代码,而是先打开高级筛选面板看看,往往他们只是不会用。真的弄不出来了,再上后面的重武器。
3. 方法二:通过U9接口做精准BP查询,适合需要程序集成
3.1 认识U9的BP查询服务和SV代理
U9把业务操作封装成服务,BP的查询也有对应的标准服务,通常叫做BPQuery或者QueryBusinessPartner。在U9的二次开发架构里,外部程序不能直接连数据库,也不能直接碰业务逻辑,而应该调用U9的"服务代理"(SV)。SV是一个部署在IIS上的中间层,外部程序通过HTTP/HTTPS(SOAP或.NET Remoting)访问SV,SV再去跟U9的应用服务器通信,最后把结果返回。
我们在项目里最常见的做法,是先通过U9的"管理控制台"或者开发工具定位到BP查询服务,查看它的输入输出参数。输入一般是一个BPQueryDTO,里面包含查询条件(编码、名称、ID、分类、状态等)和一些分页配置。输出通常是一个DataSet或者对象列表,里面包含了BP的主表字段和扩展字段。有了这个接口,我们就能在自己的程序里像调用普通WebService一样去查BP了。
3.2 用Java写一个BP查询调用示例
我经常遇到企业里其他系统是Java写的,要跟U9对接查BP。下面这段代码是我在项目里整理出来的简化示例,核心是构造SOAP请求,通过HTTP调用U9的SV服务,解析返回内容。
// BP查询请求参数 Map<String, Object> queryParams = new HashMap<>(); queryParams.put("BPType", "Customer"); queryParams.put("BpCode", "C%"); // 编码模糊查询 queryParams.put("BpName", null); queryParams.put("State", "1"); // 生效状态下 queryParams.put("PageIndex", 1); queryParams.put("PageSize", 100); // 将参数组织成SOAP Envelope String soapEnvelope = buildSoapEnvelope("QueryBP", queryParams); // 通过HttpClient调用SV服务地址 HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://u9sv-server/U9Service/BPSV.svc")) .header("Content-Type", "application/soap+xml; charset=utf-8") .POST(HttpRequest.BodyPublishers.ofString(soapEnvelope)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); // 解析SOAP响应中的DataSet String result = parseSoapResult(response.body()); System.out.println("查到BP数据: " + result);实际项目里,我们一般会用工具(比如SoapUI)先调通,再生成客户端代理类,代码会更简洁。调用接口时要特别注意,U9的SV服务默认有会话和权限校验,通常需要在HTTP头里带上身份票据,或者设置Windows集成认证,否则会报"未授权"。
3.3 接口查询的性能和可靠性优化
接口查询虽然灵活,但性能受制于U9服务器本身。如果业务方要求的是"一次性拉全量BP并同步到外部系统",建议不要直接用查询接口一次取几十万条,应使用U9提供的"BP导出"专用服务,或者做分页循环。分页时用PageIndex配合PageSize,每次取1000或2000条,测试下来比一次性取出快得多,而且不容易超时。
还有一个容易踩的坑是货币、日期格式的转换。U9接口返回的日期往往是标准DateTime,但SOAP序列化后可能带有时区信息,解析不对会导致数据差一天。我的经验是,在解析层统一转换成"yyyy-MM-dd HH:mm:ss"字符串再处理,不要直接用对象类型去后续操作。
3.4 什么时候该用接口,什么时候不该用
接口适合两种场景:一是外部系统(OA、BI)需要实时查U9的BP数据;二是二次开发页面需要和U9做事务联动,比如在自定义表单里选择BP后要校验其信用额度。如果只是给业务人员多写几个查询报表,完全没有必要用接口,直接走数据库或者U9报表工具就行,接口的维护成本高,而且U9升级时接口不保证完全兼容。
我自己经历过一次U9升级,从2017版升到2022版,旧版本里能直接调用的某个BP查询服务在新版本里被标记为废弃,不得不改代码。所以,开发接口前一定要先去U9的"服务管理"里确认版本支持,项目文档里也要记录所基于的U9版本,否则过半年回头改会很难受。
4. 方法三:直连数据库,一条SQL搞定复杂BP查询
4.1 用数据库监控工具找到U9界面背后的SQL
如果不想跟接口较劲,最直接的方法是连U9的数据库查。很多项目里允许只读账号访问U9的数据库,这种模式做报表和临时查询非常高效。问题是U9的数据表这么多,怎么知道哪张表存的是BP呢?我的土办法是用SQL Server Profiler(如果是Oracle就用审计功能)跟踪一次U9界面的BP查询,把实际执行的SQL捞出来。
操作步骤并不复杂:先启动SQL Profiler,新建跟踪,选择事件"SQL:BatchCompleted"和"RPC:Completed",过滤DatabaseName为U9的库,然后在U9界面里执行一次客户档案查询。跟踪停止后,搜索SQL语句里的From关键词,很快就能看到主表名和关联的表。比如常见的是Base_BP_Main关联Base_BP_Extend,其中客户类型过滤会关联Base_BP_Customer或分类表。
用这个办法,不止能查BP,任何U9界面的查询都能用它顺藤摸瓜找到底层表。找到表之后,我们就可以抛开界面,直接写SQL了。
4.2 BP核心表结构的通用套路
根据我在多个U9项目中的观察,BP相关的表一般以Base_BP开头,核心是主表和扩展表。
- 主表:
Base_BP_Main,存储BP编码、名称、状态、简称、所属组织等最基础字段,BP_ID是其主键。 - 扩展表:
Base_BP_Extend,存储联系信息、地址、电话、邮箱、税务登记号等,通过BP_ID关联主表。 - 分类表:
Base_BP_Category,存储BP的分类(客户/供应商/内部),通过BP_ID与分类ID关联。 - 银行信息:
Base_BP_Bank,存储开户行、账号等。 - 编码规则:
Base_BP_CodeRule或类似表,控制自动编号。
具体表名可能因U9版本不同有差异,但套路是统一的:先找主表,再通过外键找扩展表和子表。如果忘记了,可以用系统的"数据库关系图"或查询系统表sys.foreign_keys来发现关联关系。
4.3 一个可复用的BP查询SQL模板
下面这段SQL是我经常改一改就用的通用模板,实现了按编码、名称、分类、组织、状态和关键联系人过滤:
-- 查询生效中的、名称含'科技'的客户型BP,并带出联系人信息 SELECT main.BP_ID, main.BP_Code AS 编码, main.BP_Name AS 名称, main.BP_State AS 状态, ext.Contact_Person AS 联系人, ext.Contact_Mobile AS 手机号, ext.EMail AS 邮箱, c.Category_Name AS BP分类 FROM Base_BP_Main main LEFT JOIN Base_BP_Extend ext ON main.BP_ID = ext.BP_ID LEFT JOIN Base_BP_Relation rel ON main.BP_ID = rel.BP_ID -- 业务伙伴分类关系 LEFT JOIN Base_BP_Category c ON rel.Category_ID = c.Category_ID WHERE main.BP_State = 1 AND main.BP_Name LIKE N'%科技%' AND c.Category_Name = N'客户' AND (ext.Contact_Mobile LIKE N'%138%' OR ext.Contact_Person IS NOT NULL) ORDER BY main.BP_Code;注意,U9数据库如果是中文环境,条件值记得加上N前缀,避免中文乱码。这种大宽表的LEFT JOIN在数据量很大时会慢,最好先分页,或加几个临时表。
为了性能和简洁,我更建议将查询拆成两步:第一步从主表查出符合条件的BP_ID列表(只查主表),第二步再根据BP_ID去关联扩展表批量获取详细字段。这样每步都能走主键索引,避免了大表JOIN。
4.4 数据库直连必须遵守的规矩
数据库直连不是让你随便写改和删,只允许SELECT权限。我给团队定了几条铁律:一是严禁裸奔的SELECT *,一定要列出需要的字段,否则带宽和内存都会爆;二是查询必须带条件,严禁不带WHERE的全表扫描;三是大结果集必须分页,建议用OFFSET FETCH或ROW_NUMBER();四是查询高峰期(比如月末结账)避开执行大查询,防止锁表。
如果只是临时查一次,开一个查询窗口跑完就关。如果有长期固定的BP查询需求,建议建一个视图,把常用的关联逻辑写进去,这样应用层只需要SELECT * FROM V_BP_Query WHERE 编码 LIKE '%xx%'就行。视图在U9升级后可能需要重建,所以要放到脚本管理里做版本控制。
5. 常见问题与排查技巧
5.1 查询出来数据与U9界面不一致
这个问题很坑。明明在数据库查询SQL返回的数据,和U9界面上看到的同一BP记录对不上。最常见的原因是U9有"组织隔离"和数据权限。同一个BP在不同的Org(组织)下可能有不同的属性值,数据库里存的是所有组织的值,但U9界面默认按当前登陆人所属组织过滤。排查时先看SQL条件里有没有Org_ID或OrgCode字段,加上后再查。
还有一个原因是数据缓存。U9某些主数据查询走缓存,界面看到的是缓存中的旧值。这种场景下,界面刷新或重启应用服务器就能解决。我在项目里遇到过两次,一度以为是SQL写错了,最后发现是应用服务器缓存了将近一天的旧数据。
5.2 数据库里找不到对应表或字段"
如果你用的是精简版的U9数据库,可能部分模块的表被删减了,或者因为版本差异表名带上了前缀后缀。比如有的版本叫Base_BP_Main,有的叫Base_BP_Master,还有的可能叫T_Base_BP_Info。遇到这种情况别急,用数据库自带的"查看依赖关系"或者查一下INFORMATION_SCHEMA.TABLES表名中包含BP%的表,基本上能定位。
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '%BP%' ORDER BY TABLE_NAME;用这个SQL把全库包含"BP"的表捞出来,再和第一步抓到的SQL对比,很快能找到。实在不行,去U9的元数据仓库或者开发工具里找实体定义,那是最权威的。
5.3 接口调用超时或返回慢
如果通过SV调用BP查询总是超时,先检查是不是查询条件太宽泛。比如BP_Name LIke '%%'这种空条件,会把整个BP表的数据取回来,不超时才怪。解决办法是强制分页和加必选条件,比如要求至少传入编码前缀或分类。还可以调整U9服务端的超时时间,IIS里对SV站点设置脚本超时为120s或更长,但治标不治本,根子上还是要限速。
我在一个项目里,用接口一次性拉了6万条BP进行缓存同步,结果跑了四十分钟直接超时。后来改成每次取3000条,后台循环,不到十分钟就全部拿完了。所以,批量取数务必采用分页游标模式。
5.4 权限与安全合规提醒
BP数据涉及客户和供应商的商业信息,怎么查都不为过,但查询结果不能随意扩散。无论是用接口还是直连数据库,都建议用只读账号,并在代码里记录日志,保留查询人、查询时间、查询条件。我见过有公司开发人员为了方便,直接用了sa账号连数据库做定时导出,后来闹出了事故。该走审计流程的必须走,账号权限能最小化就最小化,这一点无论业务多着急都不能省。
6. 实际项目里的几条心得
最后聊几句我在多个项目里攒下的经验。第一个心得是,别一上来就写代码。先跟业务方确认他们要的到底是"查出来看"还是"查出来用"。如果是看,标准界面加高级筛选足以;如果是要用,再评估是走接口还是走数据库。很多项目原本只需要一个Excel模板,结果被开发成了独立系统,开销大不说,业务方还不满意,因为功能太重了。
第二个心得是,数据库直接写SQL是效率最高的查找方式,但也是风险最高的。我通常会先花半小时用SQL Profiler跟踪一次界面操作,把表结构关系摸清楚,然后才敢写SQL。这样预判到的坑比事后排查少很多。
第三个心得是,无论是写接口还是写SQL,一定要留一个可配置的查询条件类。BP查询需求日后一定会变,字段的增加和筛选逻辑的调整是必然的。把条件参数化设计好,后面维护会省很多事。
如果你也正被U9的BP查询折磨,不妨按我上面说的三步走:先打开高级筛选,再试接口,最后直连数据库。大部分需求都能在这三层里找到合适解法。真要是遇到连SQL都搞不定的场景,大概率就是权限或数据架构的问题了,那就需要更细致的排查了。