요약

자주 쓰는 질의에 이름을 붙여 두고 표처럼 쓰는 것이다. 창문과 같아서, 창문 자체에는 아무것도 들어 있지 않고 창밖 풍경이 바뀌면 창으로 보이는 것도 바로 바뀐다. 긴 질의를 매번 다시 쓰지 않아도 되고, 보여 주고 싶은 행과 열만 내보일 수 있다. 읽는 것은 진짜 표와 똑같이 되지만, 창문에 대고 풍경을 고치는 일은 많은 경우에 안 된다.

창문은 풍경을 있는 그대로 보여 준다. 뷰는 여러 표를 잇거나, 세고 더한 값을 보여 줄 수도 있다.

예시로 보기

“ProductX 프로젝트에서 일하는 직원의 이름”을 알고 싶다. 직원 이름은 EMPLOYEE에, 프로젝트 이름은 PROJECT에, 누가 어디서 일하는지는 WORKS_ON에 있다. 세 표를 이어야 한다.

SELECT Fname, Lname
FROM   EMPLOYEE, PROJECT, WORKS_ON
WHERE  Ssn = Essn AND Pno = Pnumber
  AND  Pname = 'ProductX';

직원과 프로젝트를 함께 묻는 질의는 자주 나온다. 그때마다 세 표와 조인 조건 둘을 다시 써야 한다. 세 표를 이어 놓은 결과에 WORKS_ON1이라는 이름을 붙여 두면 어떨까?

CREATE VIEW WORKS_ON1
AS SELECT Fname, Lname, Pname, Hours
   FROM   EMPLOYEE, PROJECT, WORKS_ON
   WHERE  Ssn = Essn AND Pno = Pnumber;

이제 같은 질문을 표 하나에 묻듯이 쓴다.

SELECT Fname, Lname
FROM   WORKS_ON1
WHERE  Pname = 'ProductX';

두 질의의 답은 같다. John Smith와 Joyce English다. WORKS_ON1을 통째로 펼치면 WORKS_ON의 행마다 한 줄씩 16행이 나온다. 그 가운데 다섯 줄은 이렇다.

Fname Lname Pname Hours
John Smith ProductX 32.5
John Smith ProductY 7.5
Ramesh Narayan ProductZ 40.0
Joyce English ProductX 20.0
Joyce English ProductY 20.0

이 16행은 어디에도 저장돼 있지 않다. 저장된 것은 CREATE VIEW 뒤에 적은 질의 글뿐이다.

검증: WORKS_ON1 16행과 두 질의의 답, DEPT_INFO 세 행, 카탈로그에 정의만 있는 것, 직원을 넣은 뒤 바뀌는 답, DROP VIEW, DEPT5EMP 4행, BASIC_EMP_DATA 8행 3열, 카드 C2·C3 — 75_sql-view_verify.py. PostgreSQL 16에서 실행: 뷰에만 조회 권한을 준 사용자가 기본 테이블을 못 읽는 것 — 75_sql-view_verify.sql

정의

뷰는 다른 표들에서 끌어낸 표 하나다. 특정 행과 열만 골라내도 되고, 묶어서 요약한 값을 담아도 된다. 실제로 저장된 꼴로 있지 않아도 되므로 가상 테이블이라고도 부른다. 추가 저장 자리가 들지 않는다.

  • 기본 테이블(base table). 튜플이 늘 데이터베이스에 실제로 저장돼 있는 표다. CREATE TABLE로 만든 EMPLOYEE, PROJECT 같은 표다.
  • 정의 테이블(defining table). 어떤 뷰를 만드는 데 쓴 표다. WORKS_ON1의 정의 테이블은 EMPLOYEE, PROJECT, WORKS_ON 셋이다.

뷰는 이렇게 만든다.

CREATE VIEW <뷰 이름> AS <select 문>

<select 문>이 뷰에 무엇이 담기는지를 정한다.

열 이름. 이름을 따로 안 적으면 정의 테이블의 열 이름을 그대로 물려받는다. WORKS_ON1의 열은 Fname, Lname, Pname, Hours다. 뷰 이름 뒤 괄호에 이름을 직접 적어 줄 수도 있다.

CREATE VIEW DEPT_INFO (Dept_name, No_of_emps, Total_sal)
AS SELECT   Dname, COUNT (*), SUM (Salary)
   FROM     DEPARTMENT, EMPLOYEE
   WHERE    Dnumber = Dno
   GROUP BY Dname;
Dept_name No_of_emps Total_sal
Research 4 133000
Administration 3 93000
Headquarters 1 55000

COUNT (*)와 SUM (Salary)는 계산해서 나온 열이라 물려받을 이름이 없다. 이름을 안 적으면 제품이 알아서 붙인다. SQLite는 COUNT (*)라는 글자를 그대로 열 이름으로 쓰고, PostgreSQL 16은 count, sum이라고 붙인다. 뒤에서 이 열을 가리켜 쓰려면 직접 이름을 주는 편이 낫다1.

질의. 뷰에 던지는 질의는 기본 테이블에 던지는 질의와 똑같이 쓴다. 제한이 없다.

늘 최신이다. 정의 테이블의 튜플을 고치면 뷰는 그 변화를 저절로 담아야 한다. 뷰를 최신으로 맞추는 일은 사용자가 아니라 DBMS의 몫이다. 5번 부서에 급여 27000인 직원 한 명을 넣고 DEPT_INFO를 다시 물으면, 뷰를 새로 만들지 않았는데도 Research 줄이 (Research, 5, 160000)으로 나온다1. DBMS가 이 일을 어떻게 해내는지는 질의 수정과 뷰 실체화에서 다룬다.

지우기. 더 쓰지 않는 뷰는 DROP VIEW로 지운다. 뷰만 없어지고 정의 테이블은 그대로다.

DROP VIEW WORKS_ON1;

보이는 범위 줄이기

뷰는 어떤 사용자에게 보이면 안 되는 행이나 열을 가리는 데에도 쓴다.

행 가리기. 5번 부서 직원의 정보만 봐도 되는 사용자가 있다. 이 뷰를 만들고, 그 사용자에게 EMPLOYEE가 아니라 뷰를 조회할 권한만 준다. 그 사용자에게는 Dno = 5인 네 명만 보인다.

CREATE VIEW DEPT5EMP AS
SELECT *
FROM   EMPLOYEE
WHERE  Dno = 5;

열 가리기. 민감한 열은 빼고 이름과 주소만 보이게 한다. 급여(Salary)와 Ssn은 이 뷰에 없다.

CREATE VIEW BASIC_EMP_DATA AS
SELECT Fname, Lname, Address
FROM   EMPLOYEE;

PostgreSQL 16에서 사용자를 하나 만들어 두 뷰에만 조회 권한을 주고 그 사용자로 질의해 보았다. DEPT5EMP에서는 4행, BASIC_EMP_DATA에서는 8행이 나왔다. SELECT … FROM EMPLOYEE는 “permission denied for table employee”로 거부되었고, BASIC_EMP_DATA에서 Salary를 고르는 질의는 그런 열이 없다는 오류가 났다2.

활용

  • 쓰는 때 둘. 조인이나 집계가 들어간 복잡한 질의를 자주 던질 때 질의를 짧게 만든다. 권한이 없는 사용자에게 일부만 보여 준다.
  • 뷰 위에 뷰. 뷰도 표처럼 쓰이므로 뷰를 정의 테이블로 삼아 또 뷰를 만들 수 있다. DEPT5EMP에서 급여 30000 이상인 사람의 이름만 고른 뷰를 만들어 확인했다1.
  • 흔한 실수 1. 뷰에 질의하면 조인을 안 하니까 빠르다고 생각하는 것. 질의 글이 짧아질 뿐이다. 뷰를 저장해 두지 않는 한 DBMS는 물을 때마다 세 표를 잇는다.
  • 흔한 실수 2. 뷰가 기대고 있는 기본 테이블을 지우는 것. PostgreSQL 16은 뷰가 기대는 표를 지우려 하면 오류를 내고, CASCADE를 붙이면 뷰까지 함께 지운다. SQLite는 표를 그냥 지우고, 남은 뷰는 질의할 때 “no such table” 오류를 낸다2.

연결

자주 하는 오해

“뷰는 만들 때의 데이터를 복사해 둔 표다”

틀렸다. 뷰가 표처럼 보이고 SELECT * FROM 뷰로 행이 나오니 어딘가에 그 행들이 저장돼 있다고 생각하기 쉽다. 실제로 저장되는 것은 CREATE VIEW 뒤에 적은 질의 글이다. SQLite의 카탈로그를 보면 기본 테이블 WORKS_ON에는 저장 자리 번호가 있고, 뷰 WORKS_ON1에는 정의 글만 있고 저장 자리는 0이다. 확인하는 법: 뷰와 나란히 CREATE TABLE … AS SELECT로 진짜 복사본을 떠 둔 뒤 5번 부서에 직원을 한 명 넣는다. 뷰는 (Research, 5, 160000)을 내고, 복사본은 (Research, 4, 133000)에 그대로 있다1.

확인 문제

C1 뷰와 기본 테이블은 무엇이 다른가? 직원 이름과 프로젝트 이름, 일한 시간을 한 표처럼 보여 주는 뷰 WORKS_ON1을 만드는 문장을 쓰라.

답: 기본 테이블은 튜플이 실제로 저장돼 있다. 뷰는 다른 표에서 끌어낸 표이고 정의(질의)만 저장된다.

CREATE VIEW WORKS_ON1
AS SELECT Fname, Lname, Pname, Hours
   FROM   EMPLOYEE, PROJECT, WORKS_ON
   WHERE  Ssn = Essn AND Pno = Pnumber;

이유: 조인 조건 둘이 빠지면 세 표의 모든 조합이 나온다. 열 이름을 따로 안 적었으니 뷰의 열은 Fname, Lname, Pname, Hours다.

C2 DEPT_INFO를 만든 다음 날, EMPLOYEE에 5번 부서 직원 한 명(급여 27000)이 새로 들어왔다. SELECT * FROM DEPT_INFO의 Research 줄은 어떻게 나오는가? 뷰를 다시 만들어야 하는가? 까닭을 대라.

답: (Research, 5, 160000)이다. 다시 만들 필요가 없다.
이유: 뷰에는 행이 아니라 질의가 저장돼 있고, DBMS가 뷰를 늘 최신으로 맞춘다. 흔한 오답은 “만들 때의 (Research, 4, 133000)이 나온다”이다. 그것은 CREATE TABLE … AS SELECT로 뜬 복사본의 동작이다.

C3 DEPT5EMP 위에 “급여가 30000 이상인 사람의 이름”만 보이는 뷰 DEPT5_NAMES를 만들라. 처음 데이터에서 누가 보이는가? Smith의 급여를 29000으로 내리면?

답:

CREATE VIEW DEPT5_NAMES AS
SELECT Fname, Lname
FROM   DEPT5EMP
WHERE  Salary >= 30000;

Narayan(38000), Smith(30000), Wong(40000)이 보인다. Smith의 급여를 29000으로 내리면 Narayan과 Wong만 보인다.
이유: 뷰를 정의 테이블로 삼아 뷰를 만들 수 있다. DEPT5EMP가 5번 부서 네 명을 내고, 그 가운데 English(25000)가 조건에서 빠진다. 기본 테이블이 바뀌면 그 위에 얹힌 뷰들이 모두 따라 바뀐다1.

  1. SQLite 3.50.4와 PostgreSQL 16에서 실행해 확인했다. ↩ ↩2 ↩3 ↩4 ↩5

  2. PostgreSQL 16에서 사용자를 만들어 GRANT SELECT ON DEPT5EMP, BASIC_EMP_DATA만 준 뒤 그 사용자로 실행해 확인했다. 뷰가 기대는 표를 지울 때의 동작도 두 제품에서 실행한 결과다. ↩ ↩2