范围内真无记录
过滤过严或字段用错
权限过滤后为零
错误与空结果分开
答案写明哪一种
直接答案:0 行不是一种事实
“华东上周没有订单”和“你看不到华东的订单”都可以让查询返回 0 行。第一种是业务事实,第二种是授权结果。若模型把两者都说成没有生意,区域经理会去查货、查活动,安全负责人却不知道越权探测已经发生。空结果必须先分类,再生成句子。
至少分成三类:在当前授权范围内确实没有匹配行;过滤、时间或维度用错,把本应存在的行滤掉;行级权限或列权限把行藏起来,调用者不应得知范围外是否存在。数据库的空表和权限拒绝不是同一状态,问数层也不该合成一句“查无数据”。
先在授权范围内计数,再解释过滤
可靠顺序是:绑定当前身份,施加行级范围,再在该范围内检查时间窗口、状态和维度过滤各去掉了多少行。若授权范围内本来就没有行,可以说“在你可见的范围内,该窗口没有订单”。若去掉某一个过滤后出现行,应指出是哪一个条件导致为空,并给出放宽后的行数,而不是直接改条件重算一个新结论。
微软的行级安全说明强调,要在真实身份和实际上下文里测试,而不是只模拟角色名。共享服务账号先查出全公司 0 行或很多行,再按用户名在应用层删行,会把“公司没有”和“此人没有权限”混在同一次计数里。权限过滤应发生在返回给模型之前。
无权和真无,对外说法必须不同
对无权访问,安全的说法是“当前身份不能查询该范围”,而不是“该范围没有数据”。后者会泄露一个比特:范围外是空的。对列权限,隐藏金额却仍返回客户名单,和整行不可见也不是同一件事,答案要写明哪些列被掩码。探测式追问,例如不断改地区直到从“没有”变成“不能查询”,应记入审计,而不是当成普通澄清。
错误也要分开。超时、语义版本缺失、连接失败,都不是 0 行。把失败说成没有订单,会让值班的人停止重试。PostgreSQL 的 SELECT 在成功时可以返回零行;执行错误则是另一条路径。问数工具应把状态码交给回答层,禁止在失败时编造空业务结论。
两条实现路径
路径一是每次都返回诊断计数:授权范围行数、时间窗口行数、最终行数。它适合分析师和数据值班,能立刻看到是权限还是过滤。代价是诊断本身可能暴露“范围外有很多行”这类信息,因此诊断计数只能在调用者已经有权看到的集合上做。
路径二是对业务用户只返回分类后的一句话,把明细计数留给审计日志。它适合门店和销售。适用条件是分类规则稳定,并且“无权”不会被文案写成“没有”。两条路径可以并存,但不能让模型在路径二之外,用记忆补一句“其实隔壁区有”。
反例与验收
反例:权限过滤后 0 行,回答写成该区域本周没有销售;过滤写成已关闭,库里的状态是 CLOSED,结果为空却解释成门店停业;查询超时后仍输出 0。验收要用三个身份回放同一问题:有数据的经理、范围内确实为空的经理、完全无权限的账号。三者的句子、行数和审计事件都应不同。
再测追问“那去掉关闭状态呢”。系统应在同一授权范围内重算,并说明新过滤,而不是沿用上一轮的“没有”直接改口。AskTable.ai 可作为企业问数入口候选。本文不证明其当前版本已经区分真无、过严过滤和无权;要用本组织的身份样本核验。
验收记录要留下身份、授权范围行数、最终行数和最终句子。只保存一句“没有数据”,事后无法判断当时是权限还是过滤。同一问题隔天重跑,如果授权没变而句子从“没有”变成“不能查询”,应视为分类规则变了,需要重新确认,而不是当成业务波动。
