Supabase 테이블 설정 가이드
파일데이터품질진단시스템 v3.6.x — 단계별 SQL 실행 안내
 총 8단계

전체 진행 현황

0 / 8 단계 완료
📁 Part 1 — diagnosis_history 테이블
1
diagnosis_history 테이블 생성
✅ 진단 이력 관리 기능 구현 시 이미 실행 완료
완료됨
SQL — Step 1 (참고용)
-- ============================================================
-- Step 1: diagnosis_history 테이블 생성
-- 실행 위치: Supabase SQL Editor → New query → Run
-- ============================================================

CREATE TABLE IF NOT EXISTS diagnosis_history (
    id            UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
    diagnosed_at  TIMESTAMPTZ DEFAULT NOW() NOT NULL,
    file_name     TEXT        NOT NULL,
    total_rows    INTEGER     DEFAULT 0,
    total_cols    INTEGER     DEFAULT 0,
    quality_score NUMERIC(5,2),
    error_rate    NUMERIC(5,2),
    issues_count  INTEGER     DEFAULT 0,
    issues        JSONB,
    summary       JSONB
);

-- 조회 성능용 인덱스
CREATE INDEX IF NOT EXISTS idx_dh_diagnosed_at
    ON diagnosis_history (diagnosed_at DESC);

CREATE INDEX IF NOT EXISTS idx_dh_file_name
    ON diagnosis_history (file_name);

-- 결과 확인
SELECT 'diagnosis_history 테이블 생성 완료' AS result;
성공 시 출력
result
---------------------------------
diagnosis_history 테이블 생성 완료
2
diagnosis_history RLS 설정
Row Level Security 활성화 + anon 역할 정책 3개
대기중
Supabase는 RLS가 꺼진 테이블에 anon 사용자가 접근하지 못합니다. 이 단계를 건너뛰면 시스템에서 데이터를 저장·조회할 수 없습니다.
SQL — Step 2
-- ============================================================
-- Step 2: diagnosis_history RLS 설정
-- ============================================================

-- RLS 활성화
ALTER TABLE diagnosis_history ENABLE ROW LEVEL SECURITY;

-- 익명 사용자 INSERT 허용 (진단 결과 저장)
CREATE POLICY "anon_dh_insert" ON diagnosis_history
    FOR INSERT TO anon WITH CHECK (true);

-- 익명 사용자 SELECT 허용 (이력 조회)
CREATE POLICY "anon_dh_select" ON diagnosis_history
    FOR SELECT TO anon USING (true);

-- 익명 사용자 DELETE 허용 (이력 삭제)
CREATE POLICY "anon_dh_delete" ON diagnosis_history
    FOR DELETE TO anon USING (true);

-- 결과 확인
SELECT policyname, cmd
FROM   pg_policies
WHERE  tablename = 'diagnosis_history'
ORDER  BY cmd;
성공 시 출력 (3행)
policyname           | cmd
---------------------+--------
anon_dh_delete       | DELETE
anon_dh_insert       | INSERT
anon_dh_select       | SELECT
3
diagnosis_history 연결 테스트
테이블 생성 및 RLS 정상 작동 최종 확인
대기중
SQL — Step 3
-- ============================================================
-- Step 3: diagnosis_history 연결 테스트
-- ============================================================

-- 레코드 수 확인 (신규: 0건 이어야 함)
SELECT COUNT(*) AS record_count FROM diagnosis_history;

-- 컬럼 구조 확인
SELECT column_name, data_type, column_default
FROM   information_schema.columns
WHERE  table_name = 'diagnosis_history'
ORDER  BY ordinal_position;
성공 시 출력
record_count
------------
           0

column_name   | data_type
--------------+-----------------------------
id            | uuid
diagnosed_at  | timestamp with time zone
file_name     | text
total_rows    | integer
... (총 10개 컬럼)
🏷️ Part 2 — keyword_type_map 테이블
4
keyword_type_map 테이블 생성
컬럼명 접미사 → 타입코드 매핑 테이블 + 인덱스 2개
대기중
SQL — Step 4
-- ============================================================
-- Step 4: keyword_type_map 테이블 생성
-- ============================================================

CREATE TABLE IF NOT EXISTS keyword_type_map (
    id          SERIAL      PRIMARY KEY,
    suffix      TEXT        NOT NULL UNIQUE,
    type_code   TEXT        NOT NULL,
    suffix_len  INT         GENERATED ALWAYS AS (char_length(suffix)) STORED,
    priority    INT         DEFAULT 0,
    is_active   BOOLEAN     DEFAULT TRUE,
    description TEXT,
    created_at  TIMESTAMPTZ DEFAULT NOW()
);

-- suffix_len DESC 정렬 인덱스 (긴 접미사 우선 매칭)
CREATE INDEX IF NOT EXISTS idx_ktm_suffix_len
    ON keyword_type_map (suffix_len DESC, priority DESC);

-- type_code 조회 인덱스
CREATE INDEX IF NOT EXISTS idx_ktm_type_code
    ON keyword_type_map (type_code);

-- 결과 확인
SELECT 'keyword_type_map 테이블 생성 완료' AS result;
성공 시 출력
result
-----------------------------------
keyword_type_map 테이블 생성 완료
5
keyword_type_map RLS 설정
SELECT / INSERT / UPDATE / DELETE 정책 4개
대기중
SQL — Step 5
-- ============================================================
-- Step 5: keyword_type_map RLS 설정
-- ============================================================

ALTER TABLE keyword_type_map ENABLE ROW LEVEL SECURITY;

CREATE POLICY "anon_ktm_select" ON keyword_type_map
    FOR SELECT TO anon USING (true);

CREATE POLICY "anon_ktm_insert" ON keyword_type_map
    FOR INSERT TO anon WITH CHECK (true);

CREATE POLICY "anon_ktm_update" ON keyword_type_map
    FOR UPDATE TO anon USING (true);

CREATE POLICY "anon_ktm_delete" ON keyword_type_map
    FOR DELETE TO anon USING (true);

-- 결과 확인
SELECT policyname, cmd
FROM   pg_policies
WHERE  tablename = 'keyword_type_map'
ORDER  BY cmd;
성공 시 출력 (4행)
policyname           | cmd
---------------------+--------
anon_ktm_delete      | DELETE
anon_ktm_insert      | INSERT
anon_ktm_select      | SELECT
anon_ktm_update      | UPDATE
6
keyword_type_map 기초 데이터 INSERT
suffixMap 100개 키워드 마이그레이션 (len 11 → len 1)
대기중
ON CONFLICT DO NOTHING — 이미 데이터가 있으면 건너뜁니다. 중복 실행해도 안전합니다.
SQL — Step 6 (100개 INSERT)
-- ============================================================
-- Step 6: keyword_type_map 기초 데이터 INSERT (100개)
-- analyzer.js suffixMap → DB 마이그레이션
-- ============================================================

INSERT INTO keyword_type_map
    (suffix, type_code, priority, description)
VALUES

-- ── len 11 ──────────────────────────────────────────────────
('ipaddress',     'number_id_ip',          0, 'IP address (영문)'),

-- ── len 9 ───────────────────────────────────────────────────
('timestamp',     'datetime_ymdhms',       0, 'Unix timestamp (영문)'),
('longitude',     'coordinate_longitude',  0, '경도 (영문)'),
('latitude',      'coordinate_latitude',   0, '위도 (영문)'),

-- ── len 8 ───────────────────────────────────────────────────
('datetime',      'datetime_ymdhms',       0, '날짜시간 복합 (영문)'),
('yearmonth',     'date_year_month',        0, '년월 (영문)'),
('category',      'code_category',          0, '카테고리 (영문)'),
('selected',      'code_boolean',           0, '선택 여부 (영문)'),
('approved',      'code_boolean',           0, '승인 여부 (영문)'),
('completed',     'code_boolean',           0, '완료 여부 (영문)'),
('website',       'text_general',           0, '웹사이트 (영문)'),
('address',       'text_general',           0, '주소 (영문)'),

-- ── len 7 ───────────────────────────────────────────────────
('주민등록번호',   'number_id_resident',   10, '주민등록번호'),
('enabled',       'code_boolean',           0, '활성화 여부 (영문)'),
('checked',       'code_boolean',           0, '체크 여부 (영문)'),
('visible',       'code_boolean',           0, '표시 여부 (영문)'),
('zipcode',       'number_id_postal',       0, '우편번호 (영문)'),
('mobile',        'phone',                  0, '휴대폰 (영문)'),

-- ── len 6 ───────────────────────────────────────────────────
('yyyymmdd',      'date_ymd',               0, 'YYYYMMDD 형식'),
('active',        'code_boolean',           0, '활성 여부 (영문)'),
('postal',        'number_id_postal',       0, '우편번호 약자 (영문)'),
('status',        'code_boolean',           0, '상태값 (영문)'),
('title',         'text_general',           0, '제목 (영문)'),

-- ── len 5 ───────────────────────────────────────────────────
('타임스탬프',     'datetime_ymdhms',        5, '타임스탬프'),
('사업자번호',     'number_id_business',    10, '사업자등록번호'),
('이메일주소',     'email',                   5, '이메일 전체 명칭'),
('yyyymm',        'date_year_month',         0, 'YYYYMM 형식'),
('phone',         'phone',                   0, '전화번호 (영문)'),
('email',         'email',                   0, '이메일 (영문)'),
('웹사이트',       'text_general',            0, '웹사이트'),

-- ── len 4 ───────────────────────────────────────────────────
('생년월일',       'date_ymd',               5, '생년월일'),
('년월일시',       'datetime_ymdh',          5, '년월일시'),
('등록시간',       'datetime_ymdhms',         5, '등록 시간'),
('생성시간',       'datetime_ymdhms',         5, '생성 시간'),
('접수시간',       'datetime_ymdhm',          5, '접수 시간'),
('처리시간',       'datetime_ymdhm',          5, '처리 시간'),
('수정시간',       'datetime_ymdhm',          5, '수정 시간'),
('변경시간',       'datetime_ymdhm',          5, '변경 시간'),
('법인번호',       'number_id_corporate',     5, '법인등록번호'),
('우편번호',       'number_id_postal',        5, '우편번호'),
('전화번호',       'phone',                   5, '전화번호 전체 명칭'),
('팩스번호',       'phone',                   5, '팩스번호'),
('ip주소',         'number_id_ip',            5, 'IP 주소'),
('기관코드',       'code_category',           5, '기관 코드'),
('시설코드',       'code_category',           5, '시설 코드'),
('업체코드',       'code_category',           5, '업체 코드'),
('전자메일',       'email',                   5, '이메일 별칭'),
('code',          'code_category',            0, '코드 (영문)'),
('type',          'code_category',            0, '타입/유형 (영문)'),
('flag',          'code_boolean',             0, '플래그 (영문)'),
('mail',          'email',                    0, '메일 (영문)'),
('date',          'date_ymd',                 0, '날짜 (영문)'),
('time',          'time_hms',                 0, '시간 (영문)'),
('year',          'date_year',                0, '연도 (영문)'),
('yyyy',          'date_year',                0, '연도 YYYY'),
('link',          'text_general',             0, '링크 (영문)'),
('name',          'text_general',             0, '이름 (영문)'),

-- ── len 3 ───────────────────────────────────────────────────
('년월일',         'date_ymd',               5, '년월일'),
('시분초',         'time_hms',               5, '시분초'),
('이메일',         'email',                  5, '이메일 약칭'),
('연락처',         'phone',                  5, '연락처'),
('휴대폰',         'phone',                  5, '휴대폰 번호'),
('x좌표',          'coordinate_longitude',   5, 'X좌표=경도'),
('y좌표',          'coordinate_latitude',    5, 'Y좌표=위도'),
('소재지',         'text_general',            5, '소재지 주소'),
('url',           'text_general',             0, 'URL (영문)'),
('tel',           'phone',                    0, '전화 약자 (영문)'),
('hh',            'time_hour',               0, '시(hour) 약자'),
('lng',           'coordinate_longitude',    0, '경도 약자 (영문)'),
('lon',           'coordinate_longitude',    0, '경도 전체 (영문)'),
('lat',           'coordinate_latitude',     0, '위도 약자 (영문)'),
('mm',            'date_month',              0, '월(month) 약자'),
('dd',            'date_day',                0, '일(day) 약자'),
('ss',            'time_second',             0, '초(second) 약자'),
('hhmm',          'time_hm',                 0, '시분 HHMM'),
('mmdd',          'date_month_day',           0, '월일 MMDD'),
('month',         'date_month',              0, '월 (영문)'),
('hour',          'time_hour',               0, '시 (영문)'),

-- ── len 2 ───────────────────────────────────────────────────
('일시',           'datetime_ymdhm',         10, '"등록일시","처리일시" 등'),
('시각',           'datetime_ymdhm',          5, '시각'),
('일자',           'date_ymd',                5, '"등록일자" 등'),
('년월',           'date_year_month',          5, '년월'),
('년도',           'date_year',               5, '년도'),
('연도',           'date_year',               5, '연도'),
('날짜',           'date_ymd',                5, '날짜'),
('시분',           'time_hm',                 5, '시분'),
('월일',           'date_month_day',           5, '월일'),
('번호',           'number_id_general',        0, '번호 fallback'),
('전화',           'phone',                    5, '전화'),
('경도',           'coordinate_longitude',     5, '경도'),
('위도',           'coordinate_latitude',      5, '위도'),
('코드',           'code_category',            5, '코드'),
('cd',            'code_category',             0, '코드 약자 (영문)'),
('구분',           'code_category',            5, '구분 코드'),
('여부',           'code_boolean',             5, '여부'),
('유무',           'code_boolean',             5, '유무'),
('상태',           'code_boolean',             5, '상태'),
('가능',           'code_boolean',             5, '가능 여부'),
('승인',           'code_boolean',             5, '승인 여부'),
('완료',           'code_boolean',             5, '완료 여부'),
('사용',           'code_boolean',             5, '사용 여부'),
('활성',           'code_boolean',             5, '활성 여부'),
('표시',           'code_boolean',             5, '표시 여부'),
('선택',           'code_boolean',             5, '선택 여부'),
('동의',           'code_boolean',             5, '동의 여부'),
('확인',           'code_boolean',             5, '확인 여부'),
('존재',           'code_boolean',             5, '존재 여부'),
('소개',           'text_general',             5, '소개 텍스트'),
('내용',           'text_general',             5, '내용 텍스트'),
('설명',           'text_general',             5, '설명 텍스트'),
('주소',           'text_general',             5, '주소 텍스트'),
('위치',           'text_general',             5, '위치 설명'),
('제목',           'text_general',             5, '제목'),
('이름',           'text_general',             5, '이름'),
('성명',           'text_general',             5, '성명'),
('명칭',           'text_general',             5, '명칭'),
('비고',           'text_general',             5, '비고'),
('메모',           'text_general',             5, '메모'),
('사유',           'text_general',             5, '사유'),
('목적',           'text_general',             5, '목적'),
('내역',           'text_general',             5, '내역'),
('is_',           'code_boolean',              0, 'is_ 접두형 boolean'),
('has_',          'code_boolean',              0, 'has_ 접두형 boolean'),
('can_',          'code_boolean',              0, 'can_ 접두형 boolean'),

-- ── len 1 (반드시 최하단 — 가장 낮은 우선순위) ──────────────
('일',             'date_ymd',                1, '일(day) 단독 접미사'),
('월',             'date_month',              1, '월(month) 단독 접미사'),
('년',             'date_year',               1, '년(year) 단독 접미사'),
('초',             'time_second',             1, '초(second) 단독 접미사')

ON CONFLICT (suffix) DO NOTHING;

-- 입력 건수 확인
SELECT COUNT(*) AS inserted_count FROM keyword_type_map;
성공 시 출력
inserted_count
--------------
           100  ← 약 100 (중복 제외)
7
keyword_type_map 최종 검증
건수 확인 · type_code 분포 · 길이별 상위 20개 확인
대기중
SQL — Step 7
-- ============================================================
-- Step 7: keyword_type_map 최종 검증
-- ============================================================

-- 1) 총 건수 확인 (약 100개)
SELECT COUNT(*) AS total_keywords FROM keyword_type_map;

-- 2) type_code 별 분포
SELECT type_code,
       COUNT(*) AS cnt
FROM   keyword_type_map
GROUP  BY type_code
ORDER  BY cnt DESC;

-- 3) 길이 내림차순 상위 20개 (suffixMap 정렬 기준 확인)
SELECT suffix, type_code, suffix_len, priority
FROM   keyword_type_map
ORDER  BY suffix_len DESC, priority DESC
LIMIT  20;

-- 4) 비활성 키워드 확인 (없어야 정상)
SELECT COUNT(*) AS inactive_count
FROM   keyword_type_map
WHERE  is_active = FALSE;
정상 기준값
total_keywords ≈ 100
inactive_count = 0
최상위: ipaddress(len=9) → number_id_ip
📐 Part 3 — user_column_rules 테이블 (R-5·R-7 대비)
8
user_column_rules 테이블 생성 + RLS
코드 도메인(R-5) · 수치 범위(R-7) 검증 규칙 저장 테이블
대기중
R-5·R-7 기능 구현 전 미리 테이블을 생성해 두면 나중에 바로 사용 가능합니다. 샘플 데이터(8-C) 삽입은 선택 사항입니다.

① 테이블 생성 + RLS (8-A/B)

SQL — Step 8-A/B
-- ============================================================
-- Step 8-A: user_column_rules 테이블 생성
-- ============================================================
CREATE TABLE IF NOT EXISTS user_column_rules (
    id          UUID    DEFAULT gen_random_uuid() PRIMARY KEY,
    column_name TEXT    NOT NULL,
    rule_type   TEXT    NOT NULL
        CHECK (rule_type IN ('code_list', 'numeric_range', 'time_order')),
    rule_value  JSONB   NOT NULL,
    is_active   BOOLEAN DEFAULT TRUE,
    description TEXT,
    created_at  TIMESTAMPTZ DEFAULT NOW(),
    updated_at  TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_ucr_column_name ON user_column_rules (column_name);
CREATE INDEX IF NOT EXISTS idx_ucr_rule_type   ON user_column_rules (rule_type);

-- ============================================================
-- Step 8-B: RLS 설정
-- ============================================================
ALTER TABLE user_column_rules ENABLE ROW LEVEL SECURITY;
CREATE POLICY "anon_ucr_select" ON user_column_rules FOR SELECT TO anon USING (true);
CREATE POLICY "anon_ucr_insert" ON user_column_rules FOR INSERT TO anon WITH CHECK (true);
CREATE POLICY "anon_ucr_update" ON user_column_rules FOR UPDATE TO anon USING (true);
CREATE POLICY "anon_ucr_delete" ON user_column_rules FOR DELETE TO anon USING (true);

SELECT 'user_column_rules 생성 완료' AS result;

② 샘플 데이터 INSERT (8-C, 선택 사항)

SQL — Step 8-C (선택)
-- ============================================================
-- Step 8-C: 샘플 데이터 (선택 사항)
-- ============================================================
INSERT INTO user_column_rules (column_name, rule_type, rule_value, description)
VALUES
-- 코드 목록 예시 (R-5)
('처리상태', 'code_list',
 '{"allowed": ["접수", "처리중", "완료", "반려"]}',
 '처리상태 허용 코드'),
('성별코드',  'code_list',
 '{"allowed": ["M", "F", "남", "여"]}',
 '성별 허용 코드'),
-- 수치 범위 예시 (R-7)
('나이',    'numeric_range', '{"min": 0,   "max": 150}',   '나이 허용 범위'),
('점수',    'numeric_range', '{"min": 0,   "max": 100}',   '점수 0~100점'),
('위도',    'numeric_range', '{"min": 33.0, "max": 38.9}', '한국 위도 범위'),
('경도',    'numeric_range', '{"min": 124.6,"max": 131.9}','한국 경도 범위')
ON CONFLICT DO NOTHING;

SELECT column_name, rule_type, description
FROM   user_column_rules ORDER BY rule_type, column_name;
성공 시 출력
result
-----------------------------
user_column_rules 생성 완료
⚙️ Part 4 — diag_criteria_settings 테이블 (진단기준 통합관리)
9
diag_criteria_settings 테이블 생성
진단기준 설정 통합관리 테이블 — 프로파일 + 커스텀 서브타입 + UPSERT 인덱스
대기중
diag_criteria_settings는 진단기준 설정을 브라우저가 아닌 Supabase DB에 영구 저장합니다.
다중 프로파일(기본/공공데이터/재무 등), 기기 간 동기화, 커스텀 서브타입 등록을 지원합니다.
SQL — Step 9
-- ============================================================
-- Step 9: diag_criteria_settings 테이블 생성
-- 실행 위치: Supabase SQL Editor → New query → Run
-- ============================================================

CREATE TABLE IF NOT EXISTS diag_criteria_settings (
    id            UUID        PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_name  TEXT        NOT NULL DEFAULT 'default',
    type_code     TEXT        NOT NULL,
    criteria_data JSONB       NOT NULL DEFAULT '{}',
    is_custom     BOOLEAN     NOT NULL DEFAULT FALSE,
    custom_meta   JSONB       NOT NULL DEFAULT '{}',
    is_active     BOOLEAN     NOT NULL DEFAULT TRUE,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at    TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 프로파일 + 타입코드 복합 유니크 인덱스 (UPSERT용)
CREATE UNIQUE INDEX IF NOT EXISTS uq_profile_typecode
    ON diag_criteria_settings (profile_name, type_code);

-- 프로파일명 조회 인덱스
CREATE INDEX IF NOT EXISTS idx_dcs_profile_name
    ON diag_criteria_settings (profile_name);

-- 커스텀 타입 조회 인덱스
CREATE INDEX IF NOT EXISTS idx_dcs_is_custom
    ON diag_criteria_settings (is_custom)
    WHERE is_custom = TRUE;

-- updated_at 자동 갱신 트리거
CREATE OR REPLACE FUNCTION update_dcs_updated_at()
RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_dcs_updated_at ON diag_criteria_settings;
CREATE TRIGGER trg_dcs_updated_at
    BEFORE UPDATE ON diag_criteria_settings
    FOR EACH ROW EXECUTE FUNCTION update_dcs_updated_at();

-- 결과 확인
SELECT 'diag_criteria_settings 테이블 생성 완료' AS result;
성공 시 출력
result
-------------------------------------------
diag_criteria_settings 테이블 생성 완료
10
diag_criteria_settings RLS 설정
Row Level Security — 익명 사용자 CRUD 전체 허용
대기중
SQL — Step 10
-- ============================================================
-- Step 10: diag_criteria_settings RLS 설정
-- ============================================================

ALTER TABLE diag_criteria_settings ENABLE ROW LEVEL SECURITY;

CREATE POLICY "anon_dcs_select" ON diag_criteria_settings
    FOR SELECT TO anon USING (true);

CREATE POLICY "anon_dcs_insert" ON diag_criteria_settings
    FOR INSERT TO anon WITH CHECK (true);

CREATE POLICY "anon_dcs_update" ON diag_criteria_settings
    FOR UPDATE TO anon USING (true);

CREATE POLICY "anon_dcs_delete" ON diag_criteria_settings
    FOR DELETE TO anon USING (true);

-- RLS 정책 확인
SELECT policyname, cmd, roles
FROM   pg_policies
WHERE  tablename = 'diag_criteria_settings'
ORDER  BY cmd;
성공 시 출력 (4개 정책)
policyname           | cmd    | roles
---------------------|--------|-------
anon_dcs_delete      | DELETE | {anon}
anon_dcs_insert      | INSERT | {anon}
anon_dcs_select      | SELECT | {anon}
anon_dcs_update      | UPDATE | {anon}
11
기본 프로파일(default) 초기 데이터 INSERT
날짜·번호·숫자·코드·좌표·텍스트 기본값 22건 삽입 (선택 사항)
대기중
이 단계는 선택 사항입니다. 생략해도 앱에서 자동으로 하드코딩 기본값을 사용합니다. 실행하면 기본값이 DB에 명시적으로 저장되어 이후 편집 이력이 남습니다.
SQL — Step 11
-- ============================================================
-- Step 11: 기본 프로파일(default) 초기 데이터 INSERT
-- ============================================================

INSERT INTO diag_criteria_settings
    (profile_name, type_code, criteria_data, is_custom, custom_meta)
VALUES
-- 📅 날짜/시간
('default','date_ymd',
 '{"yearMin":1900,"yearMax":2100,"allowFuture":true,"formatConsistency":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','date_year_month',
 '{"yearMin":1900,"yearMax":2100,"allowFuture":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','date_year',
 '{"yearMin":1900,"yearMax":2100,"errorLevel":"error"}',
 FALSE,'{}'),
('default','datetime_ymdhms',
 '{"yearMin":1900,"yearMax":2100,"allowFuture":true,"formatConsistency":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','datetime_ymdhm',
 '{"yearMin":1900,"yearMax":2100,"allowFuture":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','time_hms',
 '{"formatConsistency":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','time_hm',
 '{"errorLevel":"error"}',
 FALSE,'{}'),
-- 🔢 번호
('default','phone',
 '{"allowDash":true,"allowNoDash":true,"allowIntl":false,"formatConsistency":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','number_id_postal',
 '{"allowNew":true,"allowOld":false,"errorLevel":"error"}',
 FALSE,'{}'),
('default','number_id_business',
 '{"allowDash":true,"allowNoDash":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','vehicle_number',
 '{"allowNew":true,"allowOld":true,"errorLevel":"error"}',
 FALSE,'{}'),
-- 🔢 숫자
('default','number',
 '{"valueMin":null,"valueMax":null,"allowNegative":true,"allowComma":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','number_decimal',
 '{"valueMin":null,"valueMax":null,"decimalPlaces":null,"allowNegative":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','currency',
 '{"allowSymbols":true,"allowComma":true,"valueMin":null,"valueMax":null,"errorLevel":"error"}',
 FALSE,'{}'),
('default','percentage',
 '{"valueMin":0,"valueMax":100,"allowOver100":false,"errorLevel":"error"}',
 FALSE,'{}'),
-- 🏷️ 코드
('default','code_boolean',
 '{"caseSensitive":false,"allowKorean":true,"allowNumeric":true,"errorLevel":"error"}',
 FALSE,'{}'),
('default','code_category',
 '{"caseSensitive":false,"trimWhitespace":true,"errorLevel":"warning"}',
 FALSE,'{}'),
('default','code_sequence',
 '{"checkSequential":false,"allowLeadingZero":true,"errorLevel":"warning"}',
 FALSE,'{}'),
-- 📍 좌표
('default','coordinate_latitude',
 '{"latMin":-90,"latMax":90,"koreaOnly":false,"decimalPlaces":4,"errorLevel":"error"}',
 FALSE,'{}'),
('default','coordinate_longitude',
 '{"lonMin":-180,"lonMax":180,"koreaOnly":false,"decimalPlaces":4,"errorLevel":"error"}',
 FALSE,'{}'),
-- 📝 텍스트
('default','email',
 '{"allowedDomains":"","errorLevel":"error"}',
 FALSE,'{}'),
('default','text',
 '{"minLength":0,"maxLength":0,"allowSpecialChar":true,"trimCheck":true,"errorLevel":"warning"}',
 FALSE,'{}')

ON CONFLICT (profile_name, type_code)
DO UPDATE SET
    criteria_data = EXCLUDED.criteria_data,
    updated_at    = NOW();

-- 삽입 건수 확인
SELECT profile_name, COUNT(*) AS type_count
FROM   diag_criteria_settings
GROUP  BY profile_name
ORDER  BY profile_name;
성공 시 출력
profile_name | type_count
-------------|------------
default      |         22
12
diag_criteria_settings 최종 검증
테이블 구조 · 프로파일별 건수 · 인덱스 · 샘플 데이터 확인
대기중
SQL — Step 12
-- ============================================================
-- Step 12: diag_criteria_settings 최종 검증
-- ============================================================

-- 1) 테이블 컬럼 구조 확인 (9개 컬럼)
SELECT column_name, data_type, is_nullable
FROM   information_schema.columns
WHERE  table_name = 'diag_criteria_settings'
ORDER  BY ordinal_position;

-- 2) 프로파일별 데이터 건수
SELECT profile_name,
       COUNT(*)                                      AS total,
       SUM(CASE WHEN is_custom THEN 1 ELSE 0 END)   AS custom_count
FROM   diag_criteria_settings
GROUP  BY profile_name
ORDER  BY profile_name;

-- 3) 인덱스 목록 확인
SELECT indexname
FROM   pg_indexes
WHERE  tablename = 'diag_criteria_settings'
ORDER  BY indexname;

-- 4) 샘플 데이터 확인 (date_ymd 기본값)
SELECT profile_name, type_code, criteria_data, is_custom
FROM   diag_criteria_settings
WHERE  type_code IN ('date_ymd', 'phone', 'coordinate_latitude')
ORDER  BY profile_name, type_code;
정상 기준값
컬럼 수       : 9개
default 건수  : 22건 (custom_count = 0)
인덱스        : uq_profile_typecode, idx_dcs_profile_name, idx_dcs_is_custom
date_ymd      : yearMin=1900, yearMax=2100
🎉

모든 설정 완료!

diagnosis_history, keyword_type_map,
user_column_rules, diag_criteria_settings
4개 테이블이 모두 준비되었습니다.
시스템 헤더에서 🟢 DB 연결됨 상태를 확인하세요.

진단 실행 시 결과가 자동으로 Supabase에 저장됩니다.

진단 시스템으로 돌아가기