규칙 강화 전에 과거 픽업 선택 채우기

MySQLBeginner
지금 연습하기

소개

매장의 픽업 열은 이미 확장되었으며 기존 클라이언트의 기본값은 counter입니다. 과거 주문에는 여전히 승인된 선택을 채워야 합니다. 이후 주문도 이미 들어왔습니다. 하나는 새 열을 생략했고 하나는 garden을 명시적으로 선택했습니다. 광범위하게 과거 값을 채우면 적법한 이 새 선택을 덮어쓸 수 있습니다.

backfill.sql과 tighten.sql을 완성하고 정확한 승인 과거 매핑을 적용하며 두 클라이언트 계약을 보존합니다. 앞 실습 개념과 기본 SELECT/WHERE, UPDATE가 선수 지식이며 JOIN, 집계, 임시 검증 장치, 트랜잭션 패턴은 여기서 배웁니다. 이 독립 Terminal 단원에는 자체 확장 테이블, 데이터, 전용 MySQL이 있습니다. 이전 VM의 파일, 연결, 진행 상태는 이어지지 않습니다.

승인된 과거 ID의 orders.pickup_code만 갱신하고 해당 열의 자료형/기본값/NULL 허용 여부/참조 규칙을 강화하세요. 원래 헤더, 상품 관계, 이후 픽업 선택, 승인/픽업 장소 테이블, 시연 테이블, 준비된 도구를 보존하세요. 아래 연습 주문 301과 302만 추가할 수 있습니다. 요청은 경쟁 승인 쓰기가 없는 통제된 유지보수 시간에 순차 실행합니다. 온라인 마이그레이션, 분산 조정, DDL 자동 롤백을 구현하지 않습니다. 저자의 예상 시간에는 읽기와 일반적인 실수가 포함되며 초보자 시간 측정 데이터는 없습니다.

완전하게 승인된 과거 선택만 적용하기

이 단계에서는 업무 매핑을 검증하고 트랜잭션으로 과거 값을 채우는 파일을 저장하며, 잘못된 매핑이 모든 주문 값을 변경 없이 남기는지 확인합니다.

Terminal에서 시작해 독립 작업 공간으로 이동하세요:

cd /home/labex/project/pickup-desk
./shop status
./shop map
./shop points

과거 집합은 legacy_orders이며 ID는 101, 102, 103입니다. 승인 매핑은 101이 north, 102가 counter, 103이 garden이라고 정합니다. 원래 총액은 2100, 3400, 0으로 남습니다. 두 Sam 주문을 고객 이름으로 조인하면 안 됩니다. 이후 ID 201과 202는 과거 집합 밖입니다. 201은 counter, 202는 garden으로 유지하고 상품 상세도 남겨야 합니다.

과거 식별 정보별 승인 행 수를 세세요. COUNT(*)는 행을 셉니다. GROUP BY order_id는 ID별 그룹을 만들고 HAVING은 개수를 센 뒤 그룹을 필터링합니다. <>는 같지 않음을 뜻합니다. 이 쿼리는 중복되거나 그 밖에 정확히 한 행이 아닌 그룹을 반환합니다:

./db -e "SELECT order_id,COUNT(*) FROM approved_pickups GROUP BY order_id HAVING COUNT(*)<>1;"

빈 결과가 예상됩니다. 없는 행은 그룹을 만들지 않으므로 완전히 누락된 ID는 이 쿼리로 드러나지 않습니다. LEFT JOIN은 왼쪽 모든 행을 유지하고 일치가 없으면 오른쪽 필드를 NULL로 둡니다. l과 m은 짧은 테이블 별칭이며 ON은 정확한 주문 ID를 연결합니다. IS NULL은 오른쪽 일치가 없는 경우를 선택합니다:

./db -e "SELECT l.order_id FROM legacy_orders l LEFT JOIN approved_pickups m ON m.order_id=l.order_id WHERE m.order_id IS NULL;"

각 매핑 코드가 pickup_points에 존재하고 모든 매핑 ID가 legacy_orders에 속해야 한다는 조건도 요구하세요. NULL 코드는 이 관계 검사에서 불일치로 셉니다:

./db -e "SELECT m.order_id,m.pickup_code FROM approved_pickups m LEFT JOIN pickup_points p ON p.code=m.pickup_code WHERE p.code IS NULL;"
./db -e "SELECT m.order_id FROM approved_pickups m LEFT JOIN legacy_orders l ON l.order_id=m.order_id WHERE l.order_id IS NULL;"

네 쿼리가 모두 비어 있어야 합니다. 준비된 실제 매핑은 유효하며 읽기 전용입니다. 빈 감사 출력은 유용한 근거지만 재사용 SQL 파일도 유효한 부분만 무작정 갱신하지 말고 잘못된 매핑을 거부해야 합니다.

임시 테이블은 데이터베이스 연결 하나에 속하며 연결이 닫히면 사라집니다. 작은 검증 장치는 정수 0만 저장합니다. CHECK(errors=0)은 0이 아닌 개수를, NOT NULL은 누락된 개수를 거부합니다. INSERT ... SELECT COUNT(*)는 위반 수를 삽입합니다. 검증용 삽입 하나라도 실패하면 MySQL 기본 배치는 업무 UPDATE 전에 멈추고 연결을 닫습니다. 보류 중인 InnoDB 데이터 변경은 롤백됩니다. ENGINE=InnoDB는 임시 테이블에 트랜잭션을 지원하는 MySQL 스토리지 엔진을 선택합니다. 임시 테이블 자체는 롤백 가능한 영구 스키마 변경이 아닙니다. 각 apply나 trial은 새 연결을 시작하므로 이전 임시 테이블을 재사용할 필요가 없습니다.

앞 실습에서 소개한 따옴표 지정 here-document로 전체 파일을 저장하세요. 괄호 안의 파생 쿼리 duplicates는 정확히 한 행이 아닌 그룹을 바깥 COUNT가 셀 수 있는 행으로 만듭니다. UPDATE JOIN은 승인된 정확한 ID의 주문만 바꾸며 모든 주문에 기본값을 부여하지 않습니다. COMMIT은 검증이 성공한 뒤에만 데이터 변경을 영구 저장합니다:

cat > backfill.sql <<'SQL'
CREATE TEMPORARY TABLE map_guard (
    errors INT NOT NULL CHECK(errors=0)
) ENGINE=InnoDB;
START TRANSACTION;
INSERT INTO map_guard
SELECT COUNT(*) FROM legacy_orders l
LEFT JOIN approved_pickups m ON m.order_id=l.order_id
WHERE m.order_id IS NULL;
INSERT INTO map_guard
SELECT COUNT(*) FROM (
    SELECT order_id FROM approved_pickups
    GROUP BY order_id HAVING COUNT(*)<>1
) duplicates;
INSERT INTO map_guard
SELECT COUNT(*) FROM approved_pickups m
LEFT JOIN pickup_points p ON p.code=m.pickup_code
WHERE p.code IS NULL;
INSERT INTO map_guard
SELECT COUNT(*) FROM approved_pickups m
LEFT JOIN legacy_orders l ON l.order_id=m.order_id
WHERE l.order_id IS NULL;
UPDATE orders o
JOIN approved_pickups m ON m.order_id=o.order_id
SET o.pickup_code=m.pickup_code;
COMMIT;
SQL

먼저 사용 후 버릴 MySQL 데이터베이스에서 저장 파일을 실행하세요. trial은 다른 과거 ID와 값을 가진 격리 마이그레이션 계정으로 파일을 변경 없이 실행합니다. 실제 SQL 종료 상태와 실제 보존 비교를 보고하며, 실제 매핑을 편집하거나 SQL을 고치지 않습니다. 잘못된 사례에서 native_sql_status가 0이 아니고 accepted가 true인 것은 실패가 예상되었고 모든 업무 값이 그대로였을 때뿐입니다. 이는 거부를 시험하는 데 성공했다는 뜻이지 SQL 쓰기가 성공했다는 뜻이 아닙니다.

./shop trial backfill.sql good
./shop trial backfill.sql missing
./shop trial backfill.sql duplicate
./shop trial backfill.sql unknown
./shop trial backfill.sql null
./shop trial backfill.sql extra

good 시험은 이후의 명시적 픽업 선택을 보존하면서 모든 승인 선택을 적용합니다. 각 잘못된 시험은 MySQL 자체 CHECK 오류 3819와 변경되지 않은 업무 사실을 보고합니다. 각각 승인을 제거하거나, 식별 정보를 중복시키거나, 알 수 없는 장소를 쓰거나, NULL을 쓰거나, 이후 주문을 덮어쓰려는 사례입니다. 항상 실패하는 파일은 good 시험을 통과할 수 없습니다. 시험이 허용되지 않으면 실제 stderr와 사유를 읽고 cat backfill.sql을 확인한 뒤 전체 파일을 수정하세요. 시험을 초록색으로 만들려고 준비된 도구나 매핑을 바꾸지 마세요.

같은 원본 파일을 유효한 실제 매핑에 적용하고 영향받는 주문과 보호할 주문을 각각 읽으세요:

./shop apply backfill.sql
./shop show 101
./shop show 102
./shop show 103
./shop show 201
./shop show 202

과거 코드는 north, counter, garden입니다. 이후 201은 counter, 202는 garden으로 남으며 메모와 상세도 그대로입니다. 기본값으로 채워진 스키마가 성공했다는 사실만으로 이 과거 선택이 입증되지는 않습니다. 같은 변경 없는 승인 매핑으로 새 연결에서 순수 데이터 채우기를 다시 실행하면 같은 선택이 나옵니다. 임의의 일회성 ALTER 파일을 반복해도 된다는 허가는 아닙니다.

./shopp는 ls로 확인해 도구 이름을 수정하세요. 구문 오류는 저장 SQL을 확인하고 덮어쓴 뒤 다시 적용하세요. 준비된 배치는 첫 MySQL 자체 오류에서 멈추고 연결을 닫습니다. --force나 실패한 검증을 넘어 계속하는 대화형 SOURCE 흐름을 사용하지 마세요. 이 통제된 유지보수 시간은 동시 승인 편집을 제외합니다. 별도 감사 쿼리는 일반적인 동시 마이그레이션 잠금 프로토콜이 아닙니다.

두 주문 클라이언트를 유지하며 규칙 강화하기

이 단계에서는 열을 생략하는 기존 쓰기와 새 명시적 선택을 계속 사용할 수 있게 하면서 유효하고 NULL이 아닌 픽업 참조를 강제합니다.

계속 Terminal의 /home/labex/project/pickup-desk에서 작업하세요. 승인된 과거 값 채우기가 끝났습니다. 실제 정의를 확인하세요:

./db -e "SHOW CREATE TABLE orders;"

열은 아직 NULL을 허용하고 명시적 기본값은 counter입니다. NOT NULL은 명시적 NULL을 거부하며 그 자체로 기존 클라이언트에 기본값을 제공하지는 않습니다. 외래 키는 pickup_code를 실제 고유한 pickup_points.code에 연결해 알 수 없는 코드를 거부합니다. RESTRICT는 사용 중인 장소의 삭제나 식별 정보 변경을 막습니다. 이는 애플리케이션 드롭다운 검사가 아니라 실제 데이터베이스 규칙입니다.

MODIFY COLUMN은 의도한 전체 열 정의를 다시 명시합니다. NOT NULL을 추가하면서 VARCHAR(16)과 DEFAULT 'counter'를 모두 유지하세요. MODIFY에서 생략한 속성은 유지되지 않을 수 있습니다. 쉼표는 두 변경을 하나의 ALTER 문에 둡니다. 제약 조건 이름은 규칙을 식별하지만 동등한 이름도 허용됩니다. 앞 단원에서 배운 암묵적 커밋 경계가 있으므로 이 DDL은 데이터 채우기 트랜잭션 밖에 두세요.

cat > tighten.sql <<'SQL'
ALTER TABLE orders
MODIFY COLUMN pickup_code VARCHAR(16) NOT NULL DEFAULT 'counter',
ADD CONSTRAINT order_pickup_fk FOREIGN KEY(pickup_code)
REFERENCES pickup_points(code)
ON DELETE RESTRICT ON UPDATE RESTRICT;
SQL
./shop apply tighten.sql
./db -e "SHOW COLUMNS FROM orders;"
./db -e "SHOW CREATE TABLE orders;"

pickup_code가 기본값 counter인 NOT NULL이고, 부모 변경이 제한된 pickup_points(code) 참조가 있는지 읽으세요. 모든 기존 값은 남습니다. 이제 허용된 연습 주문 두 개를 만드세요:

./shop old 301 Mina 1600 MUG 1
./shop new 302 Pia 1900 BAG 2 north
./shop show 301
./shop show 302

실제 기존 방식 행은 counter를, 새 방식 행은 north를 저장합니다. 상품 수량은 1과 2이며 두 트랜잭션 모두 커밋되었습니다. 기존 클라이언트는 여전히 pickup_code를 생략합니다. 값을 제공하도록 몰래 다시 작성한 것이 아닙니다.

다음 두 호출은 의도된 실패이며 0이 아닌 상태로 종료해야 합니다. new-null은 필드를 생략하지 않고 실제 SQL NULL을 제공합니다. MySQL 자체 오류 1048이 거부해야 합니다. unknown을 제공하는 일반 새 클라이언트는 MySQL 자체 외래 키 오류 1452를 받아야 합니다. 어느 실패도 헤더나 상품을 남기면 안 되므로 같은 미사용 ID를 사용합니다:

./shop new-null 399 Invalid 100 MUG 1
./shop new 399 Invalid 100 MUG 1 unknown
./shop show 399
./shop status

show 399에는 빈 헤더/상품이 있습니다. 과거 선택 세 개, 이후 201/202, 새로 만든 301/302는 올바르게 남습니다. MySQL 자체 오류는 주문 처리에 성공한 빈 결과나 연결 문제가 아닙니다. 준비된 클라이언트가 실패한 트랜잭션을 닫으므로 VM 초기화가 필요하지 않습니다. 기존 허용 주문을 읽는 것으로 이어가세요.

최종 결과는 재사용 가능한 데이터 채우기/규칙 강화 SQL과 실제로 올바른 주문 시스템입니다. 평가는 이 실제 결과를 읽고, 적법한 쓰기와 금지된 부모 변경을 포함해 사용 후 버릴 MySQL 자원에서 저장한 원본 SQL과 현재 정의를 시험합니다. 실제 매장에 주문을 만들거나 마이그레이션을 적용하거나 빠진 값을 고치지 않습니다.

성공한 tighten 파일을 실수로 다시 실행하면 이름 있는 외래 키가 이미 있을 수 있습니다. SHOW CREATE TABLE을 확인하고 유효한 정의를 보존하세요. 스크립트를 반복 가능하게 만들려고 orders를 삭제/재생성하지 마세요. 오래된 파일도 명시된 이전 단계에서의 올바른 일회성 마이그레이션을 담아야 합니다. 유지보수 시간의 결과는 잠금 없는 DDL, 임의의 배포 롤백, 지속적인 운영 가용성, 관리자 위조에 대한 저항성을 입증하지 않습니다.

요약

정확한 과거 식별 정보를 검증하고, 업무 값을 바꾸기 전에 불완전하거나 모호한 매핑을 거부하며, 이후 클라이언트 선택을 덮어쓰지 않고 트랜잭션으로 과거 데이터를 채웠습니다. 별도 규칙 강화 DDL은 기존 클라이언트의 기본값을 보존하면서 NULL이 아닌 유효한 픽업 참조를 강제했습니다. 실제 주문 클라이언트 두 버전은 모두 계속 동작하며 잘못된 쓰기는 MySQL 자체 규칙으로 실패합니다.