PAYATHON 2026

如何将 PROGRAMS 字段中的代码和金额拆分为多条记录?

数据阿明

可以。将 PROGRAMS 按分号拆分,每两个元素组成一条记录:第一个是 PROGRAM_CD,第二个是 PROGRAM_AMT,并复制原行的 ACCOUNTID。

如果数据通过 CSV 导入,可以先用脚本生成明细文件,再导入目标表。

拆分时必须保留原始顺序

例如:

01;25000;02;40411

按位置配对后为:

01 → 25000
02 → 40411

不能先对代码或金额排序,也不能分别拆分后随意关联,否则可能导致代码与金额错配。

目标表中的每一行表示某个账户的一项 PROGRAM。账户与明细仍是一对多关系,因此 ACCOUNTID 会重复出现。

用 Python 生成可导入的明细文件

下面的脚本读取 accounts.csv,生成 account_programs.csv。示例文件使用 | 分隔列,因此脚本使用 delimiter="|";如果实际输入文件使用逗号,调整输入文件对应的分隔符即可。

import csv
from decimal import Decimal, InvalidOperation

records = []

with open("accounts.csv", "r", encoding="utf-8-sig", newline="") as source:
    reader = csv.DictReader(source, delimiter="|")
    required_columns = {"ACCOUNTID", "PROGRAMS"}

    if not required_columns.issubset(reader.fieldnames or []):
        raise ValueError("Missing ACCOUNTID or PROGRAMS column")

    for row_number, row in enumerate(reader, start=2):
        account_id = (row["ACCOUNTID"] or "").strip()
        programs = row["PROGRAMS"]

        if None in row or not account_id or programs is None:
            raise ValueError(f"Invalid source row: {row_number}")

        tokens = [part.strip() for part in programs.split(";")]

        if len(tokens) % 2 != 0 or any(token == "" for token in tokens):
            raise ValueError(
                f"Invalid code/amount pairs at row {row_number}: {account_id}"
            )

        for index in range(0, len(tokens), 2):
            program_code = tokens[index]
            amount_text = tokens[index + 1]

            try:
                amount = Decimal(amount_text)
            except InvalidOperation as exc:
                raise ValueError(
                    f"Invalid amount at row {row_number}: {amount_text}"
                ) from exc

            if not amount.is_finite():
                raise ValueError(
                    f"Non-finite amount at row {row_number}: {amount_text}"
                )

            records.append((account_id, program_code, format(amount, "f")))

with open("account_programs.csv", "w", encoding="utf-8", newline="") as target:
    writer = csv.writer(target, delimiter="|")
    writer.writerow(["ACCOUNTID", "PROGRAM_CD", "PROGRAM_AMT"])
    writer.writerows(records)

脚本会先解析全部记录,再创建输出文件。如果发现缺少金额、空代码或无效金额,就会报错并停止。脚本不会删除空元素,因为这样可能改变后续代码和金额的配对位置。

题目中的输入按上述规则展开后应得到:

ACCOUNTID|PROGRAM_CD|PROGRAM_AMT
AC30400290|01|25000
AC30400290|02|40411
AC30400290|04|30730
AC30400290|17|30730
AC01410304|02|65100
AC01410304|22|48401

将明细导入 SQL Server

如果目标数据库是 SQL Server,可以先创建明细表,再通过现有的文件导入流程加载生成的文件:

CREATE TABLE dbo.AccountPrograms
(
    ACCOUNTID   varchar(50)   NOT NULL,
    PROGRAM_CD  varchar(20)   NOT NULL,
    PROGRAM_AMT decimal(19,2) NOT NULL
);

字符串长度和 decimal(19,2) 都是示例选择,需要根据实际数据调整。导入时,将列分隔符设置为 |,并将第一行识别为列名。

PROGRAM_CD 应使用字符串类型,以保留 01、02 这样的前导零。金额应根据业务需要选择精确数值类型,并在导入前确认数值范围和小数位数符合目标列要求。

需要确认的数据规则

  • 空 PROGRAMS 如何处理:示例脚本将其视为错误。如果业务允许账户没有 PROGRAM,可以改为跳过该账户的空值。
  • 末尾分号是否合法:01;25000; 会产生一个空元素,脚本会报错。只有确定末尾分号是允许的格式时,才应专门处理。
  • 同一账户能否重复出现同一代码:题目没有说明,因此示例表没有添加 (ACCOUNTID, PROGRAM_CD) 唯一约束。确认业务规则后再决定是否添加。
  • 重复导入如何处理:再次追加同一个文件会产生重复明细,需要在导入流程中明确是替换、更新还是追加。

示例脚本将结果暂存在内存中,适合能够装入内存的文件。数据量较大时,可以逐行写入临时文件,校验完成后再导入数据库。

备注:内容仅供参考。