feat: --limit 全表扫描支持按真实 ID 排序/升降序/范围过滤
此前 --limit 仅按总排号排序取前 N 个。现扩展为:
- 排序按数据库真实 ID 列(config.id_field),新增 --order {asc,desc}(默认 asc)
- 新增 --range START,END 按真实 ID 闭区间过滤(与 --order/--limit 可组合)
- 上述三参数仅全表扫描生效,与 --id/--sn/--ids-file 互斥
db.py: fetch_all_ids 新增 order/id_min/id_max 参数,用派生表
(内层 DISTINCT 取 (总排号,ID) 配对,外层按 ID 排序)规避
SQL Server "SELECT DISTINCT 时 ORDER BY 列须在选择列表" 的限制;
id_field 未配置时降级按总排号排序并告警。
write_attachments.py: 新增 --order/--range CLI 参数并接线,含 --range
格式校验(须为 START,END 两个整数);模块 docstring 补示例。
README.md: 写入用法块与项目结构注释补充 --order/--range 说明。
自测(小数据量,只读+部分 dry-run):--limit 3 升/降序、--range 800,805
升序、--range 800,805 --order desc --limit 3 均验证返回总排号按真实 ID
正确排序且在范围内;dry-run 不落库;--range 格式错误正确报错退出。
Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
This commit is contained in:
17
README.md
17
README.md
@@ -235,12 +235,21 @@ JSON 的括号、引号、转义更容易被模型写错。约定 LLM 只输出"
|
|||||||
`Common.Attachment` 表,供下游系统按总排号查询该订单的附件类目。
|
`Common.Attachment` 表,供下游系统按总排号查询该订单的附件类目。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 全量写入(按总排号遍历源表全部记录)
|
# 全量写入(按真实 ID 升序遍历源表全部记录)
|
||||||
python write_attachments.py
|
python write_attachments.py
|
||||||
|
|
||||||
# 仅前 N 个总排号(测试用,避免一次性消耗大量 LLM 额度)
|
# 仅前 N 个(按真实 ID 升序,测试用,避免一次性消耗大量 LLM 额度)
|
||||||
python write_attachments.py --limit 10
|
python write_attachments.py --limit 10
|
||||||
|
|
||||||
|
# 降序取前 N 个
|
||||||
|
python write_attachments.py --limit 10 --order desc
|
||||||
|
|
||||||
|
# 按真实 ID 闭区间过滤:仅处理 ID 在 [800,805] 的记录
|
||||||
|
python write_attachments.py --range 800,805
|
||||||
|
|
||||||
|
# 组合:区间内降序、再取前 3 个
|
||||||
|
python write_attachments.py --range 800,805 --order desc --limit 3
|
||||||
|
|
||||||
# 指定总排号文件(每行一个,键类型 sn)
|
# 指定总排号文件(每行一个,键类型 sn)
|
||||||
python write_attachments.py --ids-file ids.txt
|
python write_attachments.py --ids-file ids.txt
|
||||||
|
|
||||||
@@ -252,7 +261,7 @@ python write_attachments.py --sn 26B742,26B743,26B744
|
|||||||
python write_attachments.py --id 802
|
python write_attachments.py --id 802
|
||||||
python write_attachments.py --id 802,803
|
python write_attachments.py --id 802,803
|
||||||
|
|
||||||
# --id / --sn / --ids-file 可混用;提供任一后不再全表扫描(--limit 此时无效)
|
# --id / --sn / --ids-file 可混用;提供任一后不再全表扫描(--limit/--order/--range 此时无效)
|
||||||
|
|
||||||
# 粗分类写入(小类统一填占位值);不指定 --mode 则用配置文件 business.default_mode
|
# 粗分类写入(小类统一填占位值);不指定 --mode 则用配置文件 business.default_mode
|
||||||
python write_attachments.py --mode coarse
|
python write_attachments.py --mode coarse
|
||||||
@@ -288,7 +297,7 @@ python write_attachments.py --mode fine --enable-other
|
|||||||
```
|
```
|
||||||
├── config.yaml # 配置文件
|
├── config.yaml # 配置文件
|
||||||
├── main.py # 命令行入口:分类并输出 JSON Lines(支持 --summary 在 stderr 打印批汇总;直接 import 同目录各模块)
|
├── main.py # 命令行入口:分类并输出 JSON Lines(支持 --summary 在 stderr 打印批汇总;直接 import 同目录各模块)
|
||||||
├── write_attachments.py # 写入入口:分类结果落库到 Common.Attachment(全量/--limit/--id/--sn/--ids-file/--mode/--dry-run/--enable-other/--config;运行后打印 token/缓存汇总)
|
├── write_attachments.py # 写入入口:分类结果落库到 Common.Attachment(全表扫描支持 --limit/--order/--range 按真实 ID 排序与范围过滤;--id/--sn/--ids-file/--mode/--dry-run/--enable-other/--config;运行后打印 token/缓存汇总)
|
||||||
├── requirements.txt
|
├── requirements.txt
|
||||||
├── config_loader.py # YAML 配置读取与校验
|
├── config_loader.py # YAML 配置读取与校验
|
||||||
├── db.py # SQL Server 查询与落库(fetch_params_by_ids 支持按 id/sn 双键查询、fetch_all_ids / upsert_attachments,pyodbc)
|
├── db.py # SQL Server 查询与落库(fetch_params_by_ids 支持按 id/sn 双键查询、fetch_all_ids / upsert_attachments,pyodbc)
|
||||||
|
|||||||
52
db.py
52
db.py
@@ -143,21 +143,57 @@ SENTINEL_MINOR = "无"
|
|||||||
NO_MINOR = "无"
|
NO_MINOR = "无"
|
||||||
|
|
||||||
|
|
||||||
def fetch_all_ids(db_cfg: dict[str, Any], limit: int | None = None) -> list[str]:
|
def fetch_all_ids(
|
||||||
"""拉取源表全部总排号(去重、排除 NULL),用于全量分类写入。
|
db_cfg: dict[str, Any],
|
||||||
|
limit: int | None = None,
|
||||||
|
order: str = "asc",
|
||||||
|
id_min: int | None = None,
|
||||||
|
id_max: int | None = None,
|
||||||
|
) -> list[str]:
|
||||||
|
"""拉取源表总排号列表,供全量/范围分类写入。
|
||||||
|
|
||||||
limit 用于测试时只取前 N 个(按总排号排序)。返回的总排号列表可直接
|
排序按数据库真实 ID 列(config.id_field);未配置 id_field 时降级为按总排号
|
||||||
喂给 classify_batch。
|
列排序并告警。order 取 "asc" / "desc"。id_min / id_max 按真实 ID 列做闭区间
|
||||||
|
过滤(含端点);任一为 None 表示不限制该侧。limit 取排序+过滤后的前 N 条。
|
||||||
|
|
||||||
|
返回的仍是总排号列表,可直接喂给 classify_batch(键类型统一为 sn)。
|
||||||
|
用派生表(内层 DISTINCT 取 (总排号, ID) 配对,外层按 ID 排序)规避 SQL Server
|
||||||
|
"SELECT DISTINCT 时 ORDER BY 列须出现在选择列表"的限制。
|
||||||
"""
|
"""
|
||||||
schema = db_cfg["schema"]
|
schema = db_cfg["schema"]
|
||||||
table = db_cfg["table"]
|
table = db_cfg["table"]
|
||||||
id_col = db_cfg["id_column"]
|
sn_col = db_cfg["id_column"] # 总排号列(返回列)
|
||||||
|
id_field = db_cfg.get("id_field") # 真实 ID 列(排序/过滤用)
|
||||||
qualified = f"[{schema}].[{table}]"
|
qualified = f"[{schema}].[{table}]"
|
||||||
sql = f"SELECT DISTINCT [{id_col}] FROM {qualified} WHERE [{id_col}] IS NOT NULL"
|
|
||||||
params: list[Any] = []
|
order_dir = "DESC" if order == "desc" else "ASC"
|
||||||
|
inner_where = [f"[{sn_col}] IS NOT NULL"]
|
||||||
|
inner_params: list[Any] = []
|
||||||
|
|
||||||
|
if id_field:
|
||||||
|
inner_where.append(f"[{id_field}] IS NOT NULL")
|
||||||
|
if id_min is not None:
|
||||||
|
inner_where.append(f"[{id_field}] >= ?")
|
||||||
|
inner_params.append(int(id_min))
|
||||||
|
if id_max is not None:
|
||||||
|
inner_where.append(f"[{id_field}] <= ?")
|
||||||
|
inner_params.append(int(id_max))
|
||||||
|
order_expr = "[id]" # 派生表别名
|
||||||
|
inner_select = f"SELECT DISTINCT [{sn_col}] AS sn, [{id_field}] AS id"
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
"未配置 database.id_field,--limit/--range 将按总排号列排序(无法按真实 ID 排序/范围过滤)"
|
||||||
|
)
|
||||||
|
order_expr = "[sn]"
|
||||||
|
inner_select = f"SELECT DISTINCT [{sn_col}] AS sn"
|
||||||
|
|
||||||
|
inner_sql = f"{inner_select} FROM {qualified} WHERE {' AND '.join(inner_where)}"
|
||||||
|
sql = f"SELECT [sn] FROM ({inner_sql}) AS t ORDER BY {order_expr} {order_dir}"
|
||||||
|
params = list(inner_params)
|
||||||
if limit is not None:
|
if limit is not None:
|
||||||
sql += f" ORDER BY [{id_col}] OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"
|
sql += " OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"
|
||||||
params.append(int(limit))
|
params.append(int(limit))
|
||||||
|
|
||||||
conn_str = _build_conn_str(db_cfg)
|
conn_str = _build_conn_str(db_cfg)
|
||||||
try:
|
try:
|
||||||
with pyodbc.connect(conn_str, timeout=db_cfg["connect_timeout"]) as conn:
|
with pyodbc.connect(conn_str, timeout=db_cfg["connect_timeout"]) as conn:
|
||||||
|
|||||||
@@ -10,8 +10,11 @@
|
|||||||
- has_attachment=null (not_found / llm_error / db_error): 不写(属"无法确定/失败")。
|
- has_attachment=null (not_found / llm_error / db_error): 不写(属"无法确定/失败")。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
python write_attachments.py # 全量写入
|
python write_attachments.py # 全量写入(按真实 ID 升序)
|
||||||
python write_attachments.py --limit 10 # 仅前 10 个总排号(测试)
|
python write_attachments.py --limit 10 # 仅前 10 个(按真实 ID 升序,测试)
|
||||||
|
python write_attachments.py --limit 10 --order desc # 按真实 ID 降序取前 10 个
|
||||||
|
python write_attachments.py --range 800,805 # 真实 ID 在 [800,805] 闭区间内
|
||||||
|
python write_attachments.py --range 800,805 --order desc --limit 3 # 组合:区间内降序取前3
|
||||||
python write_attachments.py --ids-file ids.txt # 指定总排号(文件,每行一个)
|
python write_attachments.py --ids-file ids.txt # 指定总排号(文件,每行一个)
|
||||||
python write_attachments.py --sn 26B742 # 单个总排号写入(键类型 sn)
|
python write_attachments.py --sn 26B742 # 单个总排号写入(键类型 sn)
|
||||||
python write_attachments.py --sn 26B742,26B743 # 多个总排号(逗号分隔)写入
|
python write_attachments.py --sn 26B742,26B743 # 多个总排号(逗号分隔)写入
|
||||||
@@ -126,7 +129,16 @@ def main() -> None:
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--limit", type=int, default=None,
|
"--limit", type=int, default=None,
|
||||||
help="仅处理前 N 个总排号(按总排号排序),测试用;与 --id/--sn/--ids-file 互斥",
|
help="取排序+范围过滤后的前 N 条总排号,测试用;与 --id/--sn/--ids-file 互斥",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--order", type=str, default="asc", choices=["asc", "desc"],
|
||||||
|
help="全表扫描时的排序方向(按真实 ID 列排序),默认 asc;与 --id/--sn/--ids-file 互斥",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--range", type=str, default=None,
|
||||||
|
help="按真实 ID 列的值域过滤,闭区间,格式 START,END(如 800,805);"
|
||||||
|
"与 --order/--limit 可组合;与 --id/--sn/--ids-file 互斥",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ids-file", type=str, default=None,
|
"--ids-file", type=str, default=None,
|
||||||
@@ -174,12 +186,29 @@ def main() -> None:
|
|||||||
n_sn = sum(1 for _, k in ids if k == "sn")
|
n_sn = sum(1 for _, k in ids if k == "sn")
|
||||||
logger.info("从 --id/--sn/--ids-file 读取 %d 个标识符(id=%d, sn=%d)", len(ids), n_id, n_sn)
|
logger.info("从 --id/--sn/--ids-file 读取 %d 个标识符(id=%d, sn=%d)", len(ids), n_id, n_sn)
|
||||||
else:
|
else:
|
||||||
sn_list = fetch_all_ids(cfg["database"], limit=args.limit)
|
# 全表扫描路径:--range/--order/--limit 仅在此生效
|
||||||
|
if args.range:
|
||||||
|
parts = [p.strip() for p in args.range.split(",")]
|
||||||
|
if len(parts) != 2 or not parts[0] or not parts[1]:
|
||||||
|
print("[错误] --range 需为 START,END 两个数字,如 800,805", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
id_min, id_max = int(parts[0]), int(parts[1])
|
||||||
|
except ValueError:
|
||||||
|
print("[错误] --range 的 START/END 必须为整数", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
id_min = id_max = None
|
||||||
|
sn_list = fetch_all_ids(
|
||||||
|
cfg["database"], limit=args.limit, order=args.order,
|
||||||
|
id_min=id_min, id_max=id_max,
|
||||||
|
)
|
||||||
# 全表扫描得到的是总排号,键类型统一为 sn
|
# 全表扫描得到的是总排号,键类型统一为 sn
|
||||||
ids = [(s, "sn") for s in sn_list]
|
ids = [(s, "sn") for s in sn_list]
|
||||||
logger.info(
|
logger.info(
|
||||||
"源表读取 %d 个总排号%s", len(ids),
|
"源表读取 %d 个总排号(order=%s%s%s)", len(ids), args.order,
|
||||||
f"(limit={args.limit})" if args.limit else "",
|
f", range=[{id_min},{id_max}]" if args.range else "",
|
||||||
|
f", limit={args.limit}" if args.limit else "",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not ids:
|
if not ids:
|
||||||
|
|||||||
Reference in New Issue
Block a user