AI가 작성한 SQL을 손으로 계산한 SQLite 결과와 비교하기
AI가 만든 SQL을 완성된 답이 아니라 초안으로 취급하고, 정답을 알고 있는 작은 SQLite 데이터베이스에서 시험합니다. 실제 데이터에 적용하기 전에 AI 결과를 손으로 계산한 예상 표와 비교합니다.
AI가 만든 정규표현식을 완성된 규칙이 아니라 초안으로 취급합니다. 작은 합성 테스트 표를 만들고 예상 결과와 실제 매칭 결과를 비교한 뒤, 수정된 패턴과 검토 보고서를 확인합니다.
이런 분께 맞아요AI를 이용해 정규표현식 초안을 만들고, 실제 사용 전에 알려진 일치·불일치 사례로 패턴을 검증하려는 사람을 위한 안내입니다.
사내 업무 ID의 형식이 WK-YYYY-NNNN이라고 가정합니다. 접두사는 대문자 WK여야 하고, 연도는 2000부터 2099까지여야 하며, 구분자는 하이픈이어야 합니다. 마지막 부분은 정확히 4개의 ASCII 숫자로 구성되어야 합니다.
AI가 WK-\d{4}-\d{4}라는 패턴을 제안했다고 가정해 보겠습니다. 겉보기에는 적절하지만 연도에 숫자 4개만 요구할 뿐 2000-2099 범위를 강제하지 않습니다. 바로 실제 데이터에 적용하는 대신 정답을 알고 있는 예제로 시험하는 것이 안전합니다.
아래 데이터는 이 글을 위해 작성한 합성 데이터입니다. regex_cases.csv로 저장하세요. 일치해야 하는 문자열 3개와 일치하면 안 되는 문자열 7개가 들어 있습니다.
case_id,value,should_match
C001,WK-2026-0001,yes
C002,WK-2000-0042,yes
C003,WK-2099-9999,yes
C004,WK-1999-0001,no
C005,wk-2026-0001,no
C006,WK-2026-001,no
C007,WK-2026-00001,no
C008,WK-2026_0001,no
C009," WK-2026-0001",no
C010,WK-20A6-0001,no
허용 연도의 양쪽 경계, 범위를 벗어난 연도, 소문자 접두사, 짧고 긴 번호 필드, 잘못된 구분자, 앞쪽 공백, 연도 안의 비숫자 문자를 포함했습니다. 정규식을 실행하기 전에 사람이 직접 분류할 수 있을 정도로 작은 데이터입니다.
AI 패턴 WK-\d{4}-\d{4}는 C001, C002, C003을 받아들여야 합니다. C005부터 C010은 구조가 맞지 않으므로 거부해야 합니다. 하지만 C004의 1999도 숫자 4개이므로 이 패턴은 C004까지 잘못 받아들입니다.
| 사례 | 예상 | AI 패턴 | 결과 |
|---|---|---|---|
| C001 | MATCH | MATCH | PASS |
| C002 | MATCH | MATCH | PASS |
| C003 | MATCH | MATCH | PASS |
| C004 | NO MATCH | MATCH | FAIL |
| C005 | NO MATCH | NO MATCH | PASS |
| C006 | NO MATCH | NO MATCH | PASS |
| C007 | NO MATCH | NO MATCH | PASS |
| C008 | NO MATCH | NO MATCH | PASS |
| C009 | NO MATCH | NO MATCH | PASS |
| C010 | NO MATCH | NO MATCH | PASS |
수정 패턴은 WK-20[0-9]{2}-[0-9]{4}입니다. 앞의 20으로 연도를 2000부터 2099까지로 제한하고, [0-9]로 의도한 숫자 집합을 명시합니다. 스크립트에서는 전체 문자열이 패턴에 맞아야 하므로 fullmatch를 사용합니다.
다음 스크립트를 ai_regex_check.py로 저장하세요. AI가 제안한 패턴과 수정된 패턴을 모두 시험하고, 각 사례의 결과를 CSV 보고서로 저장합니다. 기존 출력 폴더는 재사용하지 않습니다.
import csv
import re
from pathlib import Path
SOURCE = Path("regex_cases.csv")
OUTPUT_DIR = Path("outputs") / "regex_check_result"
REPORT = OUTPUT_DIR / "regex_check.csv"
AI_PATTERN = re.compile(r"WK-\d{4}-\d{4}")
REVISED_PATTERN = re.compile(r"WK-20[0-9]{2}-[0-9]{4}")
def parse_expected(value: str) -> bool:
normalized = value.strip().lower()
if normalized == "yes":
return True
if normalized == "no":
return False
raise ValueError(f"Expected yes or no, got: {value!r}")
def label(value: bool) -> str:
return "MATCH" if value else "NO MATCH"
def main() -> None:
if not SOURCE.is_file():
raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
results = []
ai_failures = 0
revised_failures = 0
with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
required = {"case_id", "value", "should_match"}
if reader.fieldnames is None or not required.issubset(reader.fieldnames):
raise ValueError("CSV is missing a required column.")
for row_number, row in enumerate(reader, start=2):
expected = parse_expected(row["should_match"])
text = row["value"]
ai_actual = AI_PATTERN.fullmatch(text) is not None
revised_actual = REVISED_PATTERN.fullmatch(text) is not None
ai_pass = ai_actual == expected
revised_pass = revised_actual == expected
if not ai_pass:
ai_failures += 1
if not revised_pass:
revised_failures += 1
results.append({
"case_id": row["case_id"],
"value": text,
"expected": label(expected),
"ai_actual": label(ai_actual),
"ai_test": "PASS" if ai_pass else "FAIL",
"revised_actual": label(revised_actual),
"revised_test": "PASS" if revised_pass else "FAIL",
})
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
fields = [
"case_id", "value", "expected", "ai_actual", "ai_test",
"revised_actual", "revised_test"
]
with REPORT.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=fields)
writer.writeheader()
writer.writerows(results)
print(f"Cases: {len(results)}.")
print(f"AI pattern failures: {ai_failures}.")
print(f"Revised pattern failures: {revised_failures}.")
print(f"Report: {REPORT.as_posix()}")
if revised_failures:
raise RuntimeError("Revised pattern still fails one or more tests.")
if __name__ == "__main__":
main()
python ai_regex_check.py테스트 사례는 10개입니다. AI 패턴은 C004 하나에서 실패해야 하고, 수정된 패턴은 10개 사례를 모두 통과해야 합니다.
| 항목 | 예상값 |
|---|---|
| 전체 사례 | 10 |
| AI 패턴 실패 | 1 |
| 수정 패턴 실패 | 0 |
| AI 실패 사례 | C004 |
| 수정 패턴 통과 사례 | 10 |
아래 예상 콘솔 출력은 테스트 표와 스크립트를 바탕으로 손으로 도출한 결과입니다. 실제 실행 로그가 아닙니다.
Cases: 10.
AI pattern failures: 1.
Revised pattern failures: 0.
Report: outputs/regex_check_result/regex_check.csv| 실수 | 문제가 되는 이유 |
|---|---|
| 일치해야 하는 문자열만 시험 | 너무 느슨한 정규식도 음성 사례를 넣기 전에는 맞는 것처럼 보일 수 있습니다. |
| AI 패턴을 사용하면서 요구사항을 다시 쓰지 않음 | 패턴이 실제로 원한 문제와 조금 다른 문제를 해결할 수 있습니다. |
| 정상 사례 하나만 확인 | 경계, 길이, 대소문자, 구분자 오류가 시험되지 않습니다. |
| 필드 전체 규칙인데 search 사용 | 잘못된 전체 문자열 안에서 유효해 보이는 일부 문자열만 발견될 수 있습니다. |
| 정규식 결과를 본 뒤 기대값 변경 | 테스트가 요구사항에 대한 독립적인 기준 역할을 하지 못하게 됩니다. |
10개 테스트를 통과했다는 것은 수정된 패턴이 이 10개의 합성 사례에서 예상대로 동작한다는 뜻일 뿐입니다. 가능한 모든 입력에 대해 정확하다는 수학적 증명은 아닙니다. 새로운 경계나 실패 사례를 발견할 때마다 테스트를 추가해야 합니다.
정규식은 형식을 검사할 뿐 업무상 사실까지 검증하지 않습니다. WK-2026-1234가 완벽하게 매칭되더라도 실제 데이터베이스에 존재하지 않을 수 있습니다. 존재 여부, 고유성, 권한, 다른 필드와의 관계는 별도 검사가 필요합니다.
이 작은 예제는 매우 길거나 공격적으로 구성된 입력에서의 성능을 평가하지 않습니다. 더 복잡한 AI 생성 패턴을 신뢰되지 않은 텍스트를 처리하는 서비스에 적용할 때는 정확성뿐 아니라 실행 시간 특성도 별도로 검토해야 합니다.
2026-09-20 · 예제 수동 검토 · 대상: Python 3.12 · 표준 라이브러리: csv, pathlib, re · 실행하지 않음
설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.