Community研究与数据分析github.com

koprjaa/dq-skills

十个用于审计和清理任何SQL数据库中数据质量的Claude Code技能:剖析、质量指标、审计报告、标准化、插补、去重和缺陷预防。

dq-skills 是什么?

该技能包包含十个Claude Code技能,旨在对任意SQL数据库中的数据进行全面的质量审计和清理。它涵盖了从数据剖析、质量指标计算、标准化、插补、去重到缺陷预防的完整数据质量管理流程。其核心包括一个解析器(Parser),负责将非原子化的字段分解为原子组件,为后续的标准化和去重奠定基础。技能包采用确定性规则优先于启发式方法,确保可靠的数据转换,并生成本/副本对比及未解析行的错误表,以提供完整的审计追踪。支持MySQL和Python(pandas)实现,适用于结构化数据清洗和字段分解场景。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add koprjaa/dq-skills

Installed? Explore more 研究与数据分析 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

解析器 — 原子化

修复的第一步。在字段成为原子化之前,不要进行标准化。 对包含“Na Budíně 854”的 ADDR_STREET 字段进行标准化是无用的——地址登记册将街道和门牌号分开存储。

流水线:审计 → dq-parser → dq-standardizator → dq-adresar → dq-imputator → dq-deduplikator。

规则

  • 原始数据不删除。 解析后的输出进入新列(_STD、_NUM1、_NUM2)。 原始数据是前后对比的证据,也是在解析器出错时的安全网。
  • 确定性,而非启发式。 一个在3%的情况下会猜错的解析器会制造静默损坏。如果对某个规则不确定,请保留NULL并计数剩余的数量。 这适用于具有已知模式的结构化字段——地址、姓名、日期。对于没有模式的自由文本,统计实体识别(NER, spaCy)是合法工具,但其输出是估计值:它需要附带置信度分数和标志,并且在未经审查的情况下不能放入生产列。
  • 衡量解析程度 — 成功分解的行数。这是报告中的一个指标。
  • 在任何后续操作之前进行解析:标准化、插补和去重都依赖于原子化字段。

地址

最常见的缺陷:专用的门牌号列100%为空,门牌号与街道名称混杂在一起。在源数据中看起来像 Na Budíně 854、Nádražní 1211/25a、Bezručova 12/3。

分解为三个组成部分:街道名称、描述性门牌号、方位门牌号(可能带有字母):

-- MySQL 8: REGEXP_SUBSTR / REGEXP_REPLACE
UPDATE PARTY_ADDRESS SET
  ADDR_NUM1_STD   = REGEXP_SUBSTR(ADDR_STREET, '[0-9]+(?=(/[0-9]+[a-zA-Z]?)?[[:space:]]*$)'),
  ADDR_NUM2_STD   = REGEXP_SUBSTR(ADDR_STREET, '(?<=/)[0-9]+[a-zA-Z]?[[:space:]]*$'),
  ADDR_STREET_STD = TRIM(REGEXP_REPLACE(ADDR_STREET, '[0-9]+(/[0-9]+[a-zA-Z]?)?[[:space:]]*$', ''));

Python 等效实现,当以 pandas 流水线运行时:

num  = raw.str.extract(r'(\d+)(?:/\d+\w?)?\s*$', expand=False)      # 描述性门牌号
street = raw.str.replace(r'\d+(/\d+\w?)?\s*$', '', regex=True).str.strip()

注意数字的顺序:在捷克共和国,通常写为 描述性/方位性,但惯例并不总是遵循。当解析器只找到一个数字时,把它当作描述性门牌号,方位门牌号保留为 NULL——不要猜测。

城市字段中的复合值

Praha 5 - Smíchov,České Budějovice - České Budějovice 5 — 操作员将整个地址层次结构(城市 + 城区 + 区号)写入了单个字段。使得与登记册的匹配变得不可能。

-- 第一个连字符前的段 = 城市,其余部分 = 城区
SELECT ADDR_CITY,
  TRIM(SUBSTRING_INDEX(ADDR_CITY, '-', 1))                      obec,
  NULLIF(TRIM(SUBSTRING(ADDR_CITY, LOCATE('-',ADDR_CITY)+1)),'') mcast
FROM PARTY_ADDRESS WHERE ADDR_CITY LIKE '%-%';

在开始解析之前检测候选:

SELECT ADDR_CITY, COUNT(*) cnt FROM PARTY_ADDRESS
WHERE ADDR_CITY REGEXP '[0-9]' OR ADDR_CITY LIKE '%-%' OR LENGTH(ADDR_CITY) > 50
GROUP BY 1 ORDER BY cnt DESC LIMIT 20;

注意:城市名称中的连字符有时是合法的(Frýdek-Místek,Brandýs nad Labem-Stará Boleslav)。因此,分解要对照城市登记册进行验证,而非盲目进行——如果分解后的城市不在登记册中,则返回原始值。

邮政信箱和其他超出范围的格式

邮政信箱、邮局信箱、“公司投递地址”——形式上有效的地址,但建筑物登记册不识别。不要强行解析它们,用专门的标志标记它们,并从匹配率指标中排除(否则你会将非错误的东西报告为错误)。

姓名

间隔空格

D A V I D,Ž A N N A — 每个字母之间用空格分隔。当所有标记都是单字母,或者大多数是单字母时(其余部分通常是连字符或首字母),可以确定性地合并:

toks = s.split()
if len(toks) > 1 and all(len(t) == 1 for t in toks):
    s = "".join(toks)
elif len(toks) > 1 and sum(len(t) == 1 for t in toks) >= max(2, len(toks) - 1):
    s = "".join(toks)

切勿普遍合并标记——Anna Marie 是两个名字,不是 AnnaMarie。

多部分名字

名+中间名在单个字段中。按空格分割,将第二个标记单独存储;丢弃单字母标记(首字母),否则你会生成无意义的中间名:

UPDATE PART_PARTY
SET PARTY_FNAME_STD = <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)),
    PARTY_MNAME_STD = NULLIF(<proper>(SUBSTRING_INDEX(SUBSTRING_INDEX(PARTY_FNAME,' ',2),' ',-1)),
                             <proper>(SUBSTRING_INDEX(PARTY_FNAME,' ',1)))
WHERE PARTY_TYPE='P' AND LENGTH(SUBSTRING_INDEX(PARTY_FNAME,' ',2)) > 1;

记录在名字字段中的头衔

Ing. Jan Novák 在 PARTY_NAME 中。根据第一个标记与头衔字典的匹配来检测,而不是根据句点——J. Novák 是首字母,不是头衔。

字典是头衔编码表(REF_*_TITBEF 用于名字前的头衔,TITAFT 用于名字后的头衔),并且要进行模糊匹配,因为数据中可能同时出现 MuDr,MUDr. 和 Mudr。解析器仅分离出头衔;转换为规范形式是 dq-standardizator 的工作。

编码错误

两种不同的事物,两种不同的解决方案:

错误示例解决方案
字符系统性替换ĄUDOVÍT(应为 ĽUDOVÍT)确定性替换,如果模式是明确的——例如,单词开头的 Ą 总是误写,因为在波兰语中它永远不会出现在那里
丢失的字母BE0ŠOVÁ(应为 BENEŠOVÁ)不要在解析器中修复。 使用 _CORRUPT 标志标记,并让恢复工作由 dq-standardizator(名字登记册)或 dq-deduplikator(集群中的干净副本)完成
s = re.sub(r"(^|(?<=\s))Ą", "Ľ", s)          # 明确的、确定性的
corrupt = bool(re.search(r"[0-9]", s))       # 名字中的数字 = 损坏,在其他地方处理

这种划分是有意的:解析器只做确定的事。重建丢失的字母是一种估计,属于可以对照某些东西进行验证的环节。

以文本形式存储的日期

在解析之前,验证格式和日/月:

SELECT SUBSTRING(<col>,1,2) prvni, SUBSTRING(<col>,4,2) druhy, COUNT(*)
FROM <t> GROUP BY 1,2 ORDER BY 3 DESC LIMIT 20;

当第一个位置从未超过12而第二个位置有时超过时,顺序是 MM.DD;当两者都超过12时,格式是混合的,必须根据第二个源在每个行级别上决定。盲目的 STR_TO_DATE 将不会捕获静默交换,并会将其散布到整个表。

解析器的输出

  • 具有原子值的新 _STD 列,原始数据保持不变。
  • 每个组成部分的解析程度(结果不为空的行百分比)。
  • 错误表包含解析器未分解的行及其原因——关于记录未通过的原因的审计跟踪。用于人工审查或“这超出了范围”的决策的输入。

相关技能