Skip to content
Back to skills

Data Validation Patterns

ASecurity

마이그레이션 데이터 검증 패턴: 행 수 비교, 체크섬, 샘플링 검증, FK 무결성, 비즈니스 규칙 검증 쿼리 설계 가이드. '데이터 검증', '마이그레이션 검증', '체크섬', '행 수 비교', '무결성 검증', '회귀 테스트', 'Go/No-Go 체크리스트' 등 마이그레이션 데이터 정합성 검증 시 이 스킬을 사용한다. validation-engineer의 검증 설계 역량을 강화한다. 단, 스키마 매핑이나 롤백 계획은 이 스킬의 범위가 아니다.

  • 1,256 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 29, 2026
databasespythongosql

Security analysis

A100/100

Scanned May 29, 2026

npx -y skills add revfactory/harness-100 --skill data-validation-patterns --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Data Validation Patterns?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Data Validation Patterns
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/revfactory-data-validation-patterns/badge)](https://www.skillsdirectory.com/skills/revfactory-data-validation-patterns)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: data-validation-patterns
description: "마이그레이션 데이터 검증 패턴: 행 수 비교, 체크섬, 샘플링 검증, FK 무결성, 비즈니스 규칙 검증 쿼리 설계 가이드. '데이터 검증', '마이그레이션 검증', '체크섬', '행 수 비교', '무결성 검증', '회귀 테스트', 'Go/No-Go 체크리스트' 등 마이그레이션 데이터 정합성 검증 시 이 스킬을 사용한다. validation-engineer의 검증 설계 역량을 강화한다. 단, 스키마 매핑이나 롤백 계획은 이 스킬의 범위가 아니다."
---

# Data Validation Patterns — 마이그레이션 검증 패턴 가이드

마이그레이션 전후 데이터 정합성을 검증하는 체계적 패턴과 쿼리 모음.

## 검증 계층 모델

```
Level 5: 비즈니스 규칙 검증  ← 도메인 특화 규칙
Level 4: 교차 참조 검증      ← 테이블 간 관계
Level 3: 데이터 값 검증      ← 변환 정확성
Level 2: 스키마 검증         ← 구조 일치
Level 1: 건수 검증           ← 행/열 수 일치
```

## Level 1: 건수 검증

```sql
-- 소스
SELECT 'orders' AS table_name, COUNT(*) AS row_count FROM source.orders
UNION ALL
SELECT 'customers', COUNT(*) FROM source.customers
UNION ALL
SELECT 'products', COUNT(*) FROM source.products;

-- 타깃 (동일 쿼리)
SELECT 'orders' AS table_name, COUNT(*) AS row_count FROM target.orders
UNION ALL ...;

-- 차이 비교
SELECT s.table_name,
       s.row_count AS source_count,
       t.row_count AS target_count,
       s.row_count - t.row_count AS diff,
       CASE WHEN s.row_count = t.row_count THEN 'PASS' ELSE 'FAIL' END AS status
FROM source_counts s JOIN target_counts t ON s.table_name = t.table_name;
```

## Level 2: 스키마 검증

```sql
-- 컬럼 수 비교
SELECT s.table_name,
       s.col_count AS source_cols,
       t.col_count AS target_cols,
       CASE WHEN s.col_count = t.col_count THEN 'PASS' ELSE 'CHECK' END
FROM (SELECT table_name, COUNT(*) col_count
      FROM source_information_schema.columns GROUP BY table_name) s
JOIN (SELECT table_name, COUNT(*) col_count
      FROM target_information_schema.columns GROUP BY table_name) t
ON s.table_name = t.table_name;

-- NULL 제약 비교
-- PK/FK 제약 비교
-- 인덱스 비교
```

## Level 3: 데이터 값 검증

### 체크섬 비교

```sql
-- 행 단위 체크섬 (PostgreSQL)
SELECT id, md5(ROW(order_id, customer_id, total_amount, created_at)::text) AS row_hash
FROM orders;

-- 테이블 전체 체크섬
SELECT md5(string_agg(row_hash, '' ORDER BY id)) AS table_hash
FROM (
    SELECT id, md5(ROW(*)::text) AS row_hash FROM orders
) t;

-- MySQL 체크섬
CHECKSUM TABLE orders;
```

### 샘플링 검증

```python
def sample_validation(source_conn, target_conn, table, pk_col, sample_size=1000):
    """무작위 샘플 N건을 행 단위로 비교"""
    # 1. PK 무작위 추출
    pks = source_conn.execute(
        f"SELECT {pk_col} FROM {table} ORDER BY RANDOM() LIMIT {sample_size}"
    ).fetchall()

    mismatches = []
    for pk in pks:
        source_row = source_conn.execute(
            f"SELECT * FROM {table} WHERE {pk_col} = %s", (pk,)
        ).fetchone()
        target_row = target_conn.execute(
            f"SELECT * FROM {table} WHERE {pk_col} = %s", (pk,)
        ).fetchone()

        if not rows_equal(source_row, target_row):
            mismatches.append({
                'pk': pk, 'source': source_row, 'target': target_row,
                'diff_columns': find_diff_columns(source_row, target_row)
            })

    return {
        'table': table,
        'sample_size': sample_size,
        'mismatches': len(mismatches),
        'match_rate': (sample_size - len(mismatches)) / sample_size,
        'details': mismatches[:10]  # 상위 10건만
    }
```

### 집계 비교

```sql
-- 수치 컬럼 집계 비교
SELECT
    COUNT(*) AS cnt,
    SUM(total_amount) AS sum_amount,
    AVG(total_amount) AS avg_amount,
    MIN(total_amount) AS min_amount,
    MAX(total_amount) AS max_amount,
    COUNT(DISTINCT customer_id) AS unique_customers
FROM orders
WHERE created_at BETWEEN '2024-01-01' AND '2024-12-31';
```

## Level 4: 교차 참조 검증

```sql
-- FK 무결성: orders.customer_id가 customers.id에 존재하는가?
SELECT o.order_id, o.customer_id
FROM target.orders o
LEFT JOIN target.customers c ON o.customer_id = c.id
WHERE c.id IS NULL;
-- 결과가 0행이어야 통과

-- 역방향: 주문이 있는 고객이 모두 존재하는가?
SELECT DISTINCT o.customer_id
FROM source.orders o
WHERE o.customer_id NOT IN (SELECT id FROM target.customers);
```

## Level 5: 비즈니스 규칙 검증

```sql
-- 규칙 1: 주문 총액 = 주문항목 합계
SELECT o.order_id, o.total_amount, SUM(oi.price * oi.quantity) AS calc_total,
       ABS(o.total_amount - SUM(oi.price * oi.quantity)) AS diff
FROM target.orders o
JOIN target.order_items oi ON o.id = oi.order_id
GROUP BY o.order_id, o.total_amount
HAVING ABS(o.total_amount - SUM(oi.price * oi.quantity)) > 0.01;

-- 규칙 2: 상태 전이 유효성
SELECT * FROM target.orders
WHERE status = 'SHIPPED' AND paid_at IS NULL;
-- 결제 없이 배송은 불가 → 0행이어야 함

-- 규칙 3: 날짜 순서
SELECT * FROM target.orders
WHERE created_at > paid_at OR paid_at > shipped_at;
-- 생성 > 결제 > 배송 순서 위반 → 0행이어야 함
```

## Go/No-Go 체크리스트

```markdown
## 마이그레이션 Go/No-Go 판정

### 필수 통과 (전부 PASS여야 Go)
- [ ] L1: 전체 테이블 행 수 100% 일치
- [ ] L2: 스키마 구조 일치 (컬럼 수, 타입, 제약)
- [ ] L3: 체크섬 100% 일치 (또는 샘플 99.99%)
- [ ] L4: FK 무결성 위반 0건
- [ ] L5: 핵심 비즈니스 규칙 위반 0건

### 경고 허용 (문서화 후 Go 가능)
- [ ] 날짜/시간 밀리초 차이 (타임존 변환 시)
- [ ] 문자열 트레일링 공백 차이
- [ ] 소수점 끝자리 반올림 차이

### 자동 판정
전체 PASS → ✅ Go
필수 1건 이상 FAIL → ❌ No-Go
경고만 있음 → ⚠️ 조건부 Go (승인 필요)
```

## 검증 자동화 프레임워크

```python
class MigrationValidator:
    def __init__(self, source, target, tables):
        self.source = source
        self.target = target
        self.tables = tables
        self.results = []

    def run_all(self):
        for table in self.tables:
            self.results.append({
                'table': table,
                'row_count': self.check_row_count(table),
                'checksum': self.check_checksum(table),
                'fk_integrity': self.check_fk(table),
                'business_rules': self.check_rules(table),
            })
        return self.generate_report()

    def verdict(self):
        failed = [r for r in self.results if not r['all_pass']]
        return "GO" if not failed else "NO-GO"
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.