MariaDB 강좌 / VIEW로 자주 쓰는 조회를 정의하기
VIEW는 자주 사용하는 SELECT 정의를 데이터베이스 객체로 저장해 이름으로 조회하는 기능입니다. 일반 뷰는 조회 결과 행 자체를 별도로 보관하는 테이블이 아니며, 권한 기준과 수정 가능 여부는 뷰 정의에 따라 확인해야 합니다.
한 문장용 CTE와 저장된 조회 정의 구분하기
여러 보고서에서 결제 주문별 상품 금액을 반복 계산한다면 같은 SELECT 정의를 뷰로 관리할 수 있습니다. CTE는 한 SQL 문 안의 이름이고 VIEW는 생성 후 다른 문장에서도 사용할 수 있는 객체입니다. 어느 열을 공개할지와 기존 프로그램이 기대하는 열 이름을 함께 설계합니다. 한 문장 안에서만 필요한 이름은 WITH로 공통 테이블 식 만들고 쿼리 나누기과 비교하고, 저장된 뷰를 누가 실행할 수 있는지는 사용자 계정 만들고 필요한 권한만 부여하기의 계정·권한 범위로 검토합니다.
| 대상 | 보관하는 것 | 수명 |
|---|---|---|
| 일반 VIEW | SELECT 정의 | DROP VIEW까지 |
| CTE | 한 문장의 중간 조회 이름 | 그 SQL 문 안 |
| 실제 테이블 | 저장된 행 | 삭제·정책에 따라 유지 |
원본 테이블과 별도의 뷰 이름 준비하기
학습용 MariaDB 서버의 shopcourse에 course_user로 접속합니다. 명령줄 클라이언트와 UTF-8 SQL 파일 실행 방법대로 코드를 UTF-8 파일에 저장하고 SOURCE C:/mariadb-lab/lesson.sql;처럼 실제 파일 경로로 실행할 수 있습니다. SQL 문은 운영체제 터미널이 아닌 MariaDB 연결 안에서 실행합니다.
먼저 SELECT VERSION(), DATABASE();와 SHOW TABLES;로 대상 서버·DB·이름 중복을 확인합니다. 아래 c34_customers, c34_products, c34_orders, c34_items는 이 글의 독립된 연습용 이름입니다. 이미 존재하면 정의와 행을 확인하고, 보관할 자료가 없는 이 글의 실습 테이블임이 확실할 때만 마지막 절의 정리 순서로 지운 뒤 다시 만듭니다. 기존 강좌의 동명 없는 테이블이나 운영 데이터는 삭제하지 않습니다.
USE shopcourse; CREATE TABLE c34_customers ( customer_id INT NOT NULL PRIMARY KEY, customer_name VARCHAR(50) NOT NULL, city VARCHAR(30) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE c34_products ( product_id INT NOT NULL PRIMARY KEY, product_name VARCHAR(100) NOT NULL, category VARCHAR(30) NOT NULL, price DECIMAL(10,2) NOT NULL CHECK (price >= 0), stock INT NOT NULL CHECK (stock >= 0) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE c34_orders ( order_id INT NOT NULL PRIMARY KEY, customer_id INT NOT NULL, ordered_at DATETIME NOT NULL, status VARCHAR(20) NOT NULL, shipping_fee DECIMAL(10,2) NOT NULL DEFAULT 1000, CONSTRAINT fk_c34_order_customer FOREIGN KEY (customer_id) REFERENCES c34_customers(customer_id) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE c34_items ( order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL CHECK (quantity > 0), unit_price DECIMAL(10,2) NOT NULL CHECK (unit_price >= 0), PRIMARY KEY (order_id, product_id), CONSTRAINT fk_c34_item_order FOREIGN KEY (order_id) REFERENCES c34_orders(order_id) ON DELETE RESTRICT, CONSTRAINT fk_c34_item_product FOREIGN KEY (product_id) REFERENCES c34_products(product_id) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO c34_customers VALUES (1,'김민수','서울'),(2,'이서연','부산'), (3,'박지우','서울'),(4,'최하늘','대전'); INSERT INTO c34_products VALUES (1,'노트','문구',3500,20),(2,'볼펜','문구',1500,50), (3,'머그컵','생활',8000,12),(4,'스탠드','생활',25000,5); INSERT INTO c34_orders (order_id,customer_id,ordered_at,status,shipping_fee) VALUES (101,1,'2026-01-10 10:00:00','paid',1000), (102,1,'2026-01-20 11:00:00','paid',1000), (103,2,'2026-02-05 12:00:00','paid',1000), (104,3,'2026-02-10 13:00:00','canceled',1000); INSERT INTO c34_items VALUES (101,1,2,3500),(101,2,3,1500),(102,3,1,8000), (103,1,1,3500),(103,3,2,8000),(104,2,1,1500);
c34_paid_order_totals라는 이름의 기존 뷰도 없는지 SHOW FULL TABLES로 확인합니다. 이 실습은 처음 생성하는 객체이므로 CREATE OR REPLACE로 기존 정의를 자동 덮어쓰지 않습니다. 실제 변경 전에는 SHOW CREATE VIEW로 현재 정의를 백업해야 합니다.
INVOKER 기준으로 조회 정의 저장하고 사용하기
CREATE SQL SECURITY INVOKER VIEW c34_paid_order_totals AS SELECT o.order_id,o.customer_id,o.ordered_at, SUM(i.quantity*i.unit_price) AS goods_total FROM c34_orders o JOIN c34_items i ON i.order_id=o.order_id WHERE o.status='paid' GROUP BY o.order_id,o.customer_id,o.ordered_at;
SELECT order_id,goods_total FROM c34_paid_order_totals ORDER BY order_id; SHOW CREATE VIEW c34_paid_order_totals;
조회 결과는 101번 11500.00, 102번 8000.00, 103번 19500.00입니다. 뷰의 정렬에 의존하지 않고 사용 시 ORDER BY를 명시합니다. INVOKER는 뷰를 호출하는 계정의 권한으로 원본 접근을 검사하도록 명시한 설정이며 호출 계정에 필요한 원본 권한도 있어야 합니다. CREATE VIEW와 SQL SECURITY를 확인하세요.
원본 변경이 조회 결과에 반영되는지 확인하기
START TRANSACTION; UPDATE c34_items SET quantity=3 WHERE order_id=101 AND product_id=1; SELECT goods_total FROM c34_paid_order_totals WHERE order_id=101; ROLLBACK; SELECT goods_total FROM c34_paid_order_totals WHERE order_id=101;
같은 연결의 트랜잭션 안에서 101번 주문 노트 수량을 2에서 3으로 바꾸면 뷰의 금액은 15000.00이 됩니다. 취소 뒤에는 다시 11500.00입니다. 뷰에 복사된 과거 행을 갱신한 것이 아니라 원본에 대한 조회 결과가 바뀐 것입니다. 다른 연결에서 보이는 시점은 격리 수준과 확정 여부에 영향을 받습니다.
권한·수정 가능성·성능의 경계 확인하기
이 뷰는 GROUP BY와 SUM을 사용하므로 뷰를 통해 임의로 원본 행을 수정할 수 있는 형태로 취급하지 않습니다. 단순 뷰 중 수정 가능한 경우가 있어도 모든 뷰가 UPDATE 가능한 것은 아닙니다. 원본을 수정하는 경로와 집계 보고 경로를 나누는 편이 안전합니다.
DEFINER 방식은 정의자 계정의 권한과 계정 수명에 영향을 받습니다. 정의자가 사라진 복원 환경에서 오류가 날 수 있으므로 덤프·복원 시 해당 속성을 확인해야 합니다. 뷰를 만들었다고 실행 결과가 자동 캐시되거나 항상 빨라지지는 않으며 실행 계획으로 평가합니다.
정의 변경과 정리 순서 확인하기
열 이름을 바꾸거나 제거하면 사용하는 쿼리에 영향을 줄 수 있습니다. SELECT *보다 공개 열을 명시하고 의존 관계를 확인한 뒤 정의를 변경합니다. 뷰가 없는 테이블을 참조하도록 남지 않게 정리 시 뷰부터 지웁니다. DROP VIEW는 이 예제의 원본 테이블 행을 삭제하지 않습니다.
DROP VIEW shopcourse.c34_paid_order_totals;
반복하기 전 현재 DB와 실제 정의·행을 확인하고 필요한 자료를 백업합니다. 보관할 데이터가 없는 이 글의 전용 실습 객체만 아래 순서로 정리합니다. 외래키가 있으면 자식부터 지웁니다. DROP TABLE은 구조와 데이터를 모두 없애며 일반적인 ROLLBACK으로 복원되지 않으므로 취소 가능한 DML 실습과 분리합니다.
DROP TABLE shopcourse.c34_items; DROP TABLE shopcourse.c34_orders; DROP TABLE shopcourse.c34_products; DROP TABLE shopcourse.c34_customers;









