2015년 1월 22일 목요일

SQL_정리8

SQL 사용법에 대해 알아보자!

18. DML(INSERT, UPDATE, DELETE) 문의 이해

DML(Data Mainpulation Language)은 데이터베이스에 데이터를 입력, 수정, 삭제하는 명령어. DML은 SELECT 문장과는 달리 데이터베이스의 내용을 변경하는 작업을 수행함으로 이전보다는 훨씬 주의 깊게 계획되고 실행돼야 함 이렇게 DML작업은 안전한 작업과 결과가 요구됨으로 이를 위해 오라클을 트랜잭션이라는 단위로 일련의 작업 과정을 관리

>INSERT, UPDATE, DELETE
SQL>INSERT IN <테이블> [ (컬럼, 컬럼, ....)]
   2    VALUES (값, 값, ...);

* INSERT는 하나의 행만을 삽입
 - INSERT의 새로운 표준은 여러 개의 행을 삽입할 수 있도록 하고 있지만 오라클은 아직 지원하지 않음
* 컬럼 리스트와 VALUES 절의 값은 반드시 1:1로 대응
* 테이블의 모든 컬럼에 값을 입력하는 경우 컬럼 리스트를 생략 가능
 - VALUES 절에 기술하는 값의 순서는 DESC 명령으로 확인된 테이블의 컬럼 순서와 일치해야 함
* 입력되는 값이 숫자가 아닌 경우 반드시 단일 인용 부호를 사용
* INSERT에서 생략된 컬럼은 NULL을 입력
* 명시적으로 NULL을 입력 할 경우 'NULL'을 기술
* 값 대신에 DEFAULT를 기술하면 테이블에 정의된 DEFAULT 값이 입력

SQL>UPDATE 테이블
  2     SET 컬럼 = 값, 컬럼 = 값, ...
  3     [WHERE 조건];

* 컬럼의 값을 수정
* WHERE 절을 생략하면 모든 행의 지정된 컬럼 값이 수정
* 여러 컬럼의 값을 동시에 수정 할 수 있음
* 값 대신에 DEFAULT를 기술하면 테이블에 정의된 DEFAULT 값으로 변경

SQL>DELETE FROM 테이블
  2     [WHERE 조건];

* 행 단위로 데이터를 삭제
* 조건이 없는 경우 테이블의 모든 행을 삭제
* 테이블을 삭제해도 테이블의 물리적인 구조는 변하지 않음

SQL>COMMIT;

* 작업을 완료
* 모든 DML 문을 수행한 후 작업을 완료 할 때 반드시 필요

SQL>ROLLBACK;

* 작업을 취소
* 모든 DML 문을 수행한 후 작업을 취소 할 때 반드시 필요

데이터를 입력하고 수정하고 삭제하는 명령은 DB를 운영하는데 핵심적인 요소지만 간혹 DB의 운영 형태에 따라 DML중에 일부가 불필요하거나 제한되는 경우가 있음. 그러나 보통의 데이터베이스에서 정보 입력, 수정, 삭제는 기본적인 작업임. 보통의 프로그램에서는 기본적인 형태의 DML 문장을 이용함으로 여기에 제시된 실습만으로도 불편하지 않지만 DBA거나 DB를 관리하는 작업을 수행하는 경우라면 다양한 작업을 위해 특수한 DML을 수행하는 경우도 많음

-예제1. Dept 테이블의 모든 데이터를 삭제한 다음 ROLLBACK을 수행한 다음 결과를 확인
SQL>DELETE FROM dept;
SQL>SELECT * FROM dept;
SQL>ROLLBACK;
SQL>SELECT * FROM dept;


19. 서브 쿼리를 이용한 DML문과 다이렉트 로드

다량의 DML 작업을 수행하는 경우 서브 쿼리문을 이용 하는 경우가 많음.  INSERT 작업에 대량 데이터를 입력 할 때 서브 쿼리를 이용하는 것은 매우 유용하고 빠른 작업 방법임. 특히 메모리를 통하지 않고 직접 데이터를 디스크에 입력하는 다이렉트 로드를  같이 사용하면 작업 효과는 배가 됨. 

> 서브 쿼리를 이용한 INSERT
SQL>INSERT IN [/*+  APPEND */] 테이블 [NOLOGGING] [(컬럼, 컬럼, ...)]
  2     SELECT 문장

* 서브 쿼리 (SELECT 문)에 검색된 행을 입력 값으로 사용
* 한 번에 많은 행을 입력 가능
* 컬럼 리스트와 SELECT 문의 컬럼이 1:1로 대응 되어야 함
* SELECT 문에 사용한 서브 쿼리 문과는 달리 괄호를 쓰지 않음
* /*+ APPEND */
 - 다이렉트 로드를 이용
 - 대량의 입력 작업을 더 빠르게 작업
*[NOLOGGING]
 - 로그 정보를 남기지 않음으로 입력 작업이 빨라짐
 - 장애 발생 시 복구 불가

-예제2. 각 사원의 정보와 근무지를 emp3 테이블에 저장
SQL>DESC emp3


SQL>INSERT /*+ APPEND */ INTO emp3 NOLOGGING (eno, ename, dno, dname)
  2     SELECT eno, ename, d.dno, dname
  3     FROM emp e, dept d
  4     WHERE d.dno = e.dno;
  

SQL>SELECT * FROM emp3;


SQL>COMMIT;

SQL>SELECT * FROM emp3;


SELECT 작업이나 DML 작업은 모두 메모리에서 이루어짐. 오라클은 SGA라는 메모리 영역 내에 데이터베이스 버퍼 캐시(Database Buffer Cache)라는 영역을 작업 공간으로 이용함. 특별한 언급이 없는 이상 모든 데이터의 입력, 수정, 삭제는 모두 데이터베이스 버퍼 캐시에서 작업되고 이렇게 작업된 내용은 일정한 규칙에 따라 데이터 파일에 적용. 오라클은 이렇게 입력, 수정, 삭제된 데이터를 즉시 파일에 기록하지 않고 일정 수준 모아서 작업하는 방법을 통해 디스크 접근 횟수와 양을 줄여 성능을 향상시킴. 이와 같은 작업 방식은 다수의 사용자가 데이터를 동시에 입출력하는 일반적인 환경에서 매우 효율적이지만 일괄적인 대량의 DML 작업이 수행되는 경우는 오히려 무척 비효율적인 방식이 됨. 다량의 DML 작업이 일괄적으로 발생하면 기존 방식에서는 일단 메모리(데이터베이스 버퍼 캐시)에 정보를 저장한 다음 이를 디스크에 반영하고 다시 메모리를 정리하는 등의 작업을 수행해야 함 다이렉트 로드(Direct load)는 이를 단순화해서 대량 DML 작업이 메모리를 통하지 않고 디스크에 직접 작업ㅇ르 수행. DML 작업을 다이렉트 로드로 작업하기 위해서는 '/*+ APPEND */'를 문장에 삽입
그리고 오라클은 안전한 시스템 운영을 위해 장애 발생 시 복구 할 수 있도록 DB에 변경을 가하는 모든 작업의 내역을 리두 로그(Redo log) 영역에 로그 정보로 보관. 그러나 대량의 DML 작업은 작업의 효율을 위해 이를 수행하지 않을 수  있음. 작업 성능을 위해 로그 정보를 남기지 않는다면, 'NOLOGGING' 옵션을 이용. 그러나 이렇게 복구가 불가능하도록 작업한 이후에는 장애에 대비한 백업을 잊지 않아야 함. 대량의 작업에서 다이렉트 로드와 노로깅 방식을 이용하면 작업 성능은 비약적으로 빨라지지만 작업의 안전도는 떨어질 수 밖에 없음.
다이렉트 로드를 이용 대량의 데이터를 테이블에 입력한 경우 입력되는 물리적인 위치가 일반 입력과 달라 트랜젝션을 마무리 하지 않고 검색하게 되면 'ORA-12838 : 병렬로 수정한 후 객체를 읽거나 수정 할 수 없습니다.' 라는 에러를 만나게 됨. 이것은 입력 데이터의 손상을 방지하기 위한 기능. 반드시 COMMIT 한 이후 검색해야 함.

▲ 원래 '/* */'의 용도는 실행과 무관한 주석이용하기 위한 것이었음. 여기에 '+'를 추가하여 힌트(hint)절로 이용하는데 힌트 절은 오라클에게 이 문장을 수행하는 최적의 방법을 조언

> 서브 쿼리를 이용한 UPDATE
SQL>UPDATE 테이블
  2    SET 컬럼명 = (SELECT 문장), 
  3           컬럼명 = (SELECT 문장), ...
  4    [WHERE 조건] ;

SQL>UPDATE 테이블
  2    SET (컬럼, 컬럼, ....) = (SELECT 문장)
  3    [WHERE 조건] ;

* UPDATE 문의 SET절에는 단일 행 서브 쿼리나 다중 열 서브 쿼리를 이용할 수 있음
 - 다중 열인 경우도 반드시 결과 행은 하나여야 함
 - SET 절에는 단일 행 연산자만 기술 가능
* 서브 쿼리에 의해 검색되는 Data는 반드시 수정되는 컬럼명과 1:1 대응 되어야 함
* Set 절에는 단일행 서브 쿼리만 허용되지만 WHERE 절의 서브 쿼리는 연산자에 따라 달라짐

<문제>
1. st_score_avg 테이블에 각 학생의 평점을 학과별, 과목별로 입력
 st_score_avg
SQL>DESC st_score_avg;

테이블 생성

create table st_score_avg (
sno            VARCHAR2(6),
sname    VARCHAR2(10),
syear            number(1),
avg_result     number(3)

);


테이블에 값 입력

INSERT /*+ APPEND */ INTO st_score_avg NOLOGGING
(sno, sname, syear, avg_result)
select s.sno, s.sname, s.syear, sc.result
from student s, course c, score sc
where s.sno = sc.sno and c.cno = sc.cno;


2. 화학과 학생이 수강하는 과목을 강의하는 교수의 부임일자를 1년 늦도록 수정

update professor
set hiredate = add_months(hiredate, 12);


20. 트랜잭션(Transaction)과 잠금(Lock)의 이해
응용 프로그램 개발 과정에서 잘못 설계된 트랜잭션으로 인해 많은 문제가 발생함 문제의 대부분은 업무 분석의 미숙이나 트랜잭션이 일으키는 잠금(Lock)에 대한 이해가 부족해서 나타나는 것들임. DBA와 같이 DBMS를 운영하는 직군의 사람들에 비해서 개발자나 사용자의 경우 이에 대한 이해가 많이 부족한 경우가 많음

트랜잭션이란
트랜잭션은 반드시 함께 실행되는 작업의 단위를 의미함. 즉 사용자의 의도에 따라 여러개의 문장으로 구성된 트랜잭션은 반드시 동시에 실행(COMMIT) 되거나 취소(ROLLBACK)됨. RDBMS는 트랜잭션을 통해 작업의 단위를 결정함으로써 작업 결과의 신뢰성을 확보
예를 들어 금융 거래 중에 계좌 이체를 생각해보면. A라는 사람이 소유 계좌의 금액 중 100만원을 B라는 사람의 계좌로 이체한다면 최소 두 가지 작업이 수행돼어야 함. 첫 번째 작업은 A의 계좌에서 100만원이 출금되는 것이고, 두 번째는 B의 계좌에 100만원이 입금되는 것임. 그런데 만약 A의 계좌에서 100만원이 출금 된 이후 두 번째 작업이 수행되기 전에 시스템이 다운된다면 B의 계좌에 돈이 입금되지 않고 A의 계좌에서만 100만원만 없어지게 될 수 있다. 이런 문제를 방지하기 위해 두 개의 작업을 하나의 트랜잭션으로 묶어 놓으면 두 개의 작업이 모두 실행되지 않으면 모두 취소됨으로 거래의 신뢰도를 높일 수 있음. 즉 두 번째 작업인 입금이 실행되지 않으면 첫 번째 출금 작업은 자동으로 취소되도록 함으로써 거래의 신뢰성을 확보하는 것. 트랜잭션은 이와 같이 더 이상 쪼갤 수 없는 최소한의 작업 단위로써 다음의 특징을 갖음
* 원자성(Aotomicity)
 - 트랜잭션은 최소의 작업 단위로서 전체가 처리되거나 취소 될 수 있지만 일부만 처리 될 수는 없음
* 일관성(Consistency)
 - 트랜잭션이 실행된 이후 데이터베이스의 무결성은 반드시 유지 되어야 함
* 독립성(Isolation)
 - 트랜잭션을 여러 개 동시에 실행하더라도 각각의 트랜잭션은 서로 영향을 줄 수 없음.
  즉 실행이 종료되지 않은 트랜잭션의 결과는 다른 트랜잭션에서 참조하는 것이 불가능
* 영속성(Durability)
 - 종료된 트랜잭션의 결과는 반드시 데이터베이스에 반영되어야 함

트랜잭션의 시작과 종료
1. 시작
 * 이전 트랜잭션이 종료된 이후 DML(INSERT, UPDATE, DELETE)문장이나 DDL(CREATE, ALTER, DROP, TRUNCATE), DCL(GRANT, REVOKE) 문장에 실행됐을 때 시작
2. 종료
 * COMMIT이나 ROLLBACK 명령이 실행될 때 종료
 * DDL이나 DCL문장의 실행이 완료되면 자동으로 종료
 * 사용자의 정상 종료 시에 종료
 * 데드락(Deadlock)이 걸리면 트랜잭션의 일부만 종료
 트랜잭션은 하나의 세션에서 단지 하나만 시작할 수 있으며 DML인 경우 반드시 COMMIT이나 ROLLBACK으로 종료 하지만 DDL이나 DCL의 경우 문장이 실행되고 난 후 자동으로 종료. 오라클은 SAVE POINT 명령을 이용 트랜잭션의 과정을 제어 가능

트랜잭션과 언두 세그먼트(Undo segment)
DML 작업을 ROLLBACK하기 위해서는 작업 이전 데이터를 어딘가에 저장해 두어야 함. 오라클은 작업이 발생하면 즉시 테이블의 내용을 변경함으로서 DELETE나 UPDATE문을 실행하면 테이블의 정보가 즉시 변경됨. 그런데 사용자가 ROLLBACK을 실행하면 DML작업에 의해서 변경된 정보를 이전 정보로 환원해야하는데 만일 작업 이전 정보를 저장하고 있지 않다면 ROLLBACK을 실행할 수 없게 됨. 이렇게 작업 이전 정보를 저장하고 관리하는 것은 트랜잭션의 기본적인 기능이며 이를 위해 오라클은 언두 테이블스페이스와 언두 세그먼트라는 물리적인 구조를 이용. 이들은 모두 자동으로 관리되며 사용자는 이들 정보를 직접 볼 수 없고 단지 트랜잭션에서 사용.

트랜잭션 과정
 테이블에 INSERT 작업이 수행될 때, 입력 데이터가 INSERT 구문이 실행될 때 테이블에 저장될지 아니면 COMMIT이 실행될 때 저장될지의 문제는 RDBMS의 구조에 따라 다름. 오라클은 INSERT문이 실행 될 때 행이 입력되고 입력된 행에만 잠금이 걸리며 COMMIT될 때 잠금이 풀림. 물론 UPDATE나 DELETE도 수행 과정은 동일. UPDATE 문장을 통해 트랜잭션 과정을 살펴보면
1. UPDATE 문장이 테이블에 발생하면, 문장 수행을 위해 사용할 언두 세그먼트(segment)를 결정

Undo Segment
ROLLBACK을 위해서는 작업 이전의 데이터를 어디엔가 보관해두어야 하고 그 장소를 undo segment라 함. 

2. 테이블에 저장되어있던 원래 값이 언두 세그먼트에 저장
3. UPDATE 된 값이 테이블에 저장
4. 값이 변경된 행은 독점 잠금이 발생

 독점 잠금이란 현재 세션 이외에는 접근을 불허하는 잠금으로 다른 세션으로 접속시에는 Undo Segment의 값을 보게 됨. 오라클에서는 행 단위로 독점 잠금을 수행하지만 SQL 서버에서는 페이지 단위로 독점 잠금을 수행. 한 페이지에 여러 행이 묶여 있기 때문에 페이지 단위로 독점 잠금을 수행하면 다른 행에도 접근하지 못할 수 있음.


5. 테이블에는 공유 잠금이 발생
6. 트랜잭션이 COMMIT 되면 잠금은 해제되고 변경된 값은 영구히 저장
7. 트랜잭션이 ROLLBACK 되면 언두 세그먼트에 저장했던 원래 값을 테이블로 환원하고 잠금이 해제

주제에서 약간 벗어난 이야기지만 트랜잭션 과정에 관해서 RDBMS의 대표적인 제품인 오라클과 SQL 서버의 차이점을 살펴보면 먼저 오라클은 그림과 같이 DML 명령어가 수행 될 때 데이터가 테이블에 적용되지만 SQL 서버는 COMMIT을 수행할 때 데이터가 테이블에 적용됨. 그리고 오라클은 행마다 독점 잠금을 걸지만 SQL 서버는 I/O 단위 인 페이지 전체에 독점 잠금을 검. 이로 인해 SQL 서버의 경우 실제 트랜잭션이 발생하지 않은 행들까지 독점 잠금이 발생할 수 있음. 간혹 이를 오해해서 오라클의 물리적인 I/O 단위를 혼동하는 경우가 있는데 오라클의 물리적인 I/O 단위는 SQL 서버의 페이지와 유사한 블록(BLOCK) 단위이긴 하지만 보통의 트랜잭션에서 잠금 대상이 아님

독점 잠금(Exclusive lock)과 공유 잠금(Share lock)
독점 잠금은 현재 세션이외에는 접근을 불허하는 잠금 방식. 이는 트랜잭션의 특징인 독립성을 보장하기 위한 것. 트랜잭션으로 행에 잠금이 발생하면 다른 세션에서는 해당 행을 검색할 수 없고, 단지 언두 세그먼트의 정보만 보게 됨. 그리고 이 때 테이블에는 공유 잠금이 발생하는데 이것은 DML 작업으로 행이 잠겨 있는 테이블에 대해서 DDL(DROP, ALTER)작업을 방지함.

실습에서 보는 바와 같이 트랜잭션은 잠금을 발생시키는데 이로 인해 잠금을 발생시킨 세션이외에 다른 세션들은 잠금이 발생한 테이블을 검색 할 때 잠금이 걸린 행에 대해서는 물리적으로 다른 영역인 언두 세그먼트를 읽음. 이에 따라 테이블의 정보가 이미 변경되었다 하더라도 이전 정보를 읽음. 이런 현상은 트랜잭션이 종료 될 때 까지 계속됨. 그러나 이렇게 지연된 트랜잭션에 의해 언두 영역을 읽는 것은 검색 시에 I/O량을 늘려서 성능을 저하시킴. 특히 OLTP(online transaction processing)와 같이 다량의 짧은 트랜잭션이 발생하는 시스템에서 지연된 트랜잭션은 시스템에 많은 부담을 줌. 이런 경우 트랜잭션을 가능하면 짧게 유지해서 이렇게 성능이 저하되는 현상을 감소 가능.

독점 잠금은 데이터에 대한 독점적인 사용을 획득하는 것으로 리소스에 걸리는 여러 개의 작업들의 독립성을 보장하는 방법임. 트랜잭션들은 실행 순서대로 행에 작업가능하고 이로 인해 대기 현상이 발생함. 오라클은 독점 잠금에 대해서 시간 순서대로 트랜잭션을 보장하며 이들 작업들은 앞의 트랜잭션이 종료되면 데이터에 대한 독점적인 접근을 보장 받음.

데드 락은 두 개의 트랜잭션이 서로 상대가 독점 락을 통해 점유한 리소스에 동시에 접근해서 둘 다 대기 상태가 될 때 발생. 데드 락은 이렇게 둘 이상의 트랜잭션이 서로 상대가 독점 락을 통해 점유한 리소스에 동시에 접근해서 둘 다 대기 상태가 될 때 발생. 데드 락은 이렇게 둘 이상의 트랜잭션이 서로 상대의 독점 락에 의해서 대기하는 현상. RDB뿐 아니라 OS등의 응용 시스템에서도 빈번히 나타나는 문제. 오라클은 데드 락을 발생시킨 세션의 마지막 명령을 보존하고 데드 락을 유발한 상대 세션의 마지막 명령을 ROLLBACK 시키는 것으로 데드 락을 해결. 이 때 세션 전체가 ROLLBACK 되는 것은 아님으로 주의.

21. 테이블 생성과 데이터 타입의 이해

DB에서 데이터를 저장하는 가장 기본적인 구조는 테이블(TABLE)임. 오라클에는 테이블 이외에 뷰(VIEW), 인덱스(INDEX), 시퀀스(SEQUENCE) 등 여러 개체가 있고 이들 중에 물리적인 공간을 갖는 개체를 세그먼트(SEGMENT)라고 분류하는데, 이 중 테이블은 사용자가 직접 조회 가능한 데이터를 저장하는 유일한 세그먼트임. 테이블은 DB에서 가장 중요한 개체이며 최종 사용자에게 DB는 단지 테이블의 모임으로 보일 만큼 테이블은 가장 핵심적인 단위임. 

> 테이블 생성과 삭제

테이블은 행(Row)과 컬럼(Colum)으로 이루어진 매우 익숙한 형태의 자료구조임. 테이블과 같은 구조의 자료구조는 문서의 일반적인 형태로 DB 이전부터 사용되어 옴. 정보시스템과 무관하게 문서로 널리 사용되는 이것을 행과 열로 구성된 표 또는 도표라 부름.
정보 시스템에서 문서의 형태였던 도표의 구조를 도입해서 테이블로 사용한 것임. 초창기 파일 시스템을 기반으로 한 DB 시스템에서 레코드와 필드로 구성된 파일이 도표를 대신했찌만 RDB(Relatinal DataBase)가 본격적으로 사용되면서부터 행(Row)과 컬럼(Column)으로 구성된 테이블이 이를 대신.

SQL>CREATE TABLE 테이블 (
  2   컬럼 데이터_타입 [DEFAULT default 값] [컬럼 레벨 제약조건],
  3   컬럼 데이터_타입 [DEFAULT default 값] [컬럼 레벨 제약조건],  4   .............
  5   [테이블 레벨 제약조건],
  6   ........
  7   );

* 데이터_타입
 - 컬럼에 입력될 데이터의 종류와 크기를 결정
* DEFAULT
 - 입력이 누락됐을 때 기본 입력 값을 정의
 - Default 값을 지정하지 않으면 널 값이 저장
* 컬럼 레벨 제약 조건
 - PK, FK, UK, CHECK, NOT NULL 등을 지정
* 테이블 레벨 제약 조건
 - PK, FK, UK, CHECK 만 지정
 - NOT NULL은 정의 불가

SQL>DROP TABLE 테이블
  2   [CASCADE CONSTRAINT];
* 테이블을 삭제함
 - OS와 비슷하게 테이블을 물리적으로 삭제하지 않고 휴지통에 넣어두어 나중에 복구 가능하도록 함.
 - 공간이 부족하면 휴지통의 공간은 자동으로 재사용
 - 삭제된 테이블은 BIN$로 시작하는 이름이 할당
* CASCADE CONSTRAINT : 테이블이 다른 테이블로부터 참조되는 경우 해당 제약 조건을 먼저 삭제한 후 테이블을 삭제

SQL>PURGE RECYCLEBIN;
* 휴지통에 저장된 모든 내용을 깨끗이 삭제

SQL>SELECT table_name
  2   FROM user_tables;
* 'SELECT * FROM tab' 과 동일하게 소유한 테이블 목록을 검색
* user_tables와 같이 오라클의 메타 정보를 저장하고 검색하는 테이블을 데이터 딕셔너리 (Data Dictionary) 또는 딕셔너리(Dictionary)라 함
* user_tables에는 소유한 테이블 이름뿐 아니라 테이블의 물리적인 상태를 포함한 테이블에 대한 다양한 정보가 저장

SQL>SELECT table_name, column_name, data_type, data_length
  2   FROM user_tab_columns
  3  [WHERE table_name = 테이블;]
* 'DESC 테이블'과 동일한 내용을 검색
* User_tab_columns를 이용해서 테이블의 구조를 검색
* data_type : 컬럼의 데이터 타입
* data_length : 컬럼의 길이

테이블 생성에서 이름 규칙
* 문자로 시작
* 30자 이내
* 영문, 숫자, _, $, #만을 사용
 - 한글 사용은 가능하지만 되도록 사용하지 않는 것이 좋음.
* 테이블의 이름은 동일한 유저(스키마) 안에서 유일해야 함
* 예약어는 사용이 불가능
* 대소문자를 구별하지 않음
 - 생성 할 때 사용한 문자와는 관계없이 모든 이름은 대문자로 정의
 - 테이블 이름은 딕셔너리에 저장되는데, 모두 대문자로 저장

데이터 타입
* 오라클은 다양한 데이터 타입을 제공
* 문자 타입
 - VARCHAR2, CHAR, LONG, CLOB
* 숫자 타입
 - NUMBER
* 날짜 타입
 - DATA
* 이진 타입
 - RAW, LONG RAW, BLOB, BFILE
* ROWID 타입
 - ROWID

오라클에는 우리가 사용하는 어떤 언어보다도 많은 데이터 타입을 제공. 그러나 이것들은 대부분 PL/SQL 이라는 프로그래밍을 위해 제공되는 것들임. 일반적인 SQL문에서 이들을 사용하는 것은 별가치가 없음.
VARCHAR2, NUMBER, DATE 타입으로, 이 세 가지 타입이 전체 테이블의 99% 이상 사용.

숫자인가 문자인가
 실무에서 어떤 컬럼의 데이터 타입을 정할 때 숫자인지 문자인지 결정하는 것은 결코 간단하지 않음. 예를 들면 계좌번호, 주민번호, 사번, 학번, ... 등과 같이 경계가 모호한 데이터들이 주변에 많기 때문임. 주민번호의 예를 들어 보면, 주민 번호는 숫자라고 생각 할 수 있지만, 문자임. 어떤 사람의 주민번호 앞자리가 061311이고 만약 이것이 숫자였다면 이것을 읽을 때 '육만삼천삼백십일'이라고 읽었을 것임. 하지만 이 값을 읽을 때 대부분의 사람들은 '영육일삼일일'이라고 읽음.
무엇이 문자이고, 숫자인지에 대한 명확한 답은 없지만 일반적이고 상당한 이유가 있는 답은 존재함. 그것은 컬럼의 데이터가 연산의 대상이 되는 것은 숫자로 정의하고 그렇지 않은 것은 일단 문자로 구별하는 것. 연산의 대상이 될 수 있는 예를 보면 월급, 보너스, 좌석의 수, 사원의 수 .. 등이 있음. 이들은 당연히 숫자형임. 반대로 연산의 대상이 아닌 경우는 사번, 학번 등이 있음. 앞에 예로든 주민 번호의 예를 보면, 주민번호를 대상으로 +,-,/,* 등의 사칙연산을 수행한 결과 값이 의미를 갖는다면 이것은 숫자, 하지만 의미가 없다면 문자로 간주함
데이터 딕셔너리(Data Dictionary)
 데이터 딕셔너리는 데이터베이스의 상태나 내부 운영과 관련된 정보를 저장해주는 테이블.
이들 테이블을 물리적으로 특별한 공간에 저장되며 각 사용자의 관리 레벨에 따라 접근 가능한 목록이 달라짐. 보통은 DBA가 RDBMS를 관리하기 위해 사용하지만 일반 사용자도 자신의 여러 개체에 대한 정보를 조회하기 위해 사용 가능. 실제 딕셔너리 테이블들은 직접 검색하는 것이 아니라 뷰를 통해 검색하지만 어떤 경우든 SELECT 만 가능함으로 이를 구별 하는 것은 별 의미 없음.
딕셔너리는 원도우즈의 제어판과 비슷한 기능을 가진 것으로 이름이 항상 복수형이라는 특징 보유

딕셔너리 이름의 접두어
 딕셔너리는 이름의 접두어만으로 접근 레벨을 짐작 할 수 있음. 접두어는 'DBA_', 'ALL_', 'USER_' 이렇게 세 가지가 있으며 각각의 의미는 다음과 같음

* DBA_ : 관리자만 검색 가능한 딕셔너리에 붙는 접두어
 (DBA_TABLES, DBA_INDEXES, DBA_TABLESPACES, DBA_DATA_FILES, ...)
* ALL_ : 일반 사용자가 검색 가능한 딕셔너리, 접근 가능한 개체에 대한 정보를 담고 있음
 (ALL_TABLES, ALL_CONSTRAINTS, ALL_INDEXES, ALL_VIEWS, ...)
* USER_ : 일반 사용자가 검색 가능한 딕셔너리, 소유하고 있는 개체에 대한 정보를 담고 있음
 (USER_TABLES, USER_CONSTRAINTS, USER_INDEXES, USER_VIEWS, ...)

관리자는 DBA_ 접두어를 가진 딕셔너리를 이용하고 일반 사용자는 보통 USER_ 접두어를 가진 딕셔너리를 이용하는 데 11g 버전 기준으로 DBA_ 딕셔너리는 대략 700여개, USER_ 딕셔너리는 대략 400여개가 있음. 딕셔너리 목록은 DICT 테이블을 통해서 검색 가능함.
DICT 테이블의 전체 검색 결과는 현재 사용자가 접근 가능한 딕셔너리의 목록이므로 사용자와 무관하게 사용 가능.

22. 제약조건 이해와 설정 1 : PK, FK

 제약 조선이란 테이블 단위에서 데이터의 무결성을 보장해주는 규칙. 제약 조건은 테이블에 데이터가 입력, 수정, 삭제되거나 테이블이 삭제, 변경될 경우 잘못된 트랜잭션이 수행되지 않도록 결함을 유발할 가능성이 있는 작업을 방지하는 역할을 담당. 특히 PK(Primary Key)와 FK(Foreign Key)는 테이블의 필수 요소로써 모든 테이블은 이들 둘 중 하나 이상을 반드시 포함하고 있음.

제약 조건
 * 테이블 단위에서 정의되고 적용됨
 * 종속성이 존재하는 경우 테이블의 삭제를 막음
 * 자료가 삽입, 갱신, 삭제될 때마다 규칙이 적용.
 * 일시적으로 활성화하거나 비활성화 하는 것이 가능
 * USER_CONSTRAINTS, USER_CONS_COLUMNS 딕셔너리에서 검색
 * 제약 조건은 개체처럼 관리되므로 반드시 이름이 필요. 제약  조건에 이름을 정의하지 않으면 오라클 서버가 자동으로 SYS_Cn 형태의 이름을 붙임

오라클에서 제공되는 제약조건들
* PRIMARY KEY
* FOREIGN KEY
* UNIQUE KEY
* NOT NULL
* CHECK

> PK와 FK의 정의

PK(Primary Key, 주키, 주식별자)
 - PK는 테이블의 모든 데이터를 유일하게 식별해주는 컬럼. 예를 들어 데이터베이스에는 여러분을 나타내는 정보가 여러가지가 있음. 이름, 성별, 주소, 출신 학교 등등.. 이 중에 어떤 데이터가 연속적으로 여러분을 유일하게 구분 할 수 있는 데이터 인지 생각해보면 됨.
 - 이 중에 주민번호, 학번, 사번 등의 데이터는 중복 없이 여러분의 데이터를 유일하게 구분 할 수 있도록 해주는 데이터 들임. 이들 데이터가 바로 PK가 됨. 이런 이유로 PK는 중복되지 않으면서 NULL일 수 없는 컬럼이 선택. 만일 PK를 주민번호로 했다면 이 때 이름이나 성별등의 데이터는 PK인 주민번호에 의해서 식별 됨. 다르게 표현하면 PK인 주민번호에 대해서 이름, 성별, 주소 등은 함수적 종속(functional dependency) 관계에 있다고 표현. PK의 가장 중요한 정의는 함수적 종속이라는 수학적인 정의임. PK가 없다면 RDB에서 그건 테이블이 될 수 없음.

FK(Foreing Key, 외부키, 외부식별자)
 - FK는 테이블 간 관계(Relationship)를 의미. 사원 테이블의 부서번호 컬럼은 각 사원이 소속된 부서의 번호가 저장된 컬럼. 이 때 사원은 당연히 회사에 존재하는 부서에만 소속될 수 있음. 만일 회사에 총무부와 회계부서만 존한다면 모든 사원은 반드시 이 두 부서에만 속해야 함. 만일 어떤 사원이 존재하지 않는 부서에 소속된다면 어떨까? 이것은 부서 테이블을 확인함으로써 확인 가능함. 즉 사원 테이블의 부서번호 컬럼에는 부서 테이블의 부서번호 컬럼에 존재하는 데이터만 입력 가능함. 그런데 입력하는 사람이 입력 시점에 일일이 이를 확인하는 것은 어려운 일임. 이런 경우 사원 테이블의 부서 번호 컬럼에 FK를 설정해서 부서 테이블의 부서번호 컬럼을 참조하도록 한다면 사용자가 부서 테이블에 존재하지 않는 부서번호를 사원 테이블에 입력하는 것을 DBMS가 자동으로 막아줌. 이 때 우리는 사원 테이블과 부서 테이블 간에 관계라 하고 관계는 주로 FK로 표현.

> PK(Primary Key), FK(Foreign Key) 설정과 조회

- Primary Key 설정
SQL> CREATE TABLE 테이블 (
 2    **내용**
 3    CONSTRAINT 제약_조건 PRIMARY KEY (컬럼)
 4    );

SQL> CREATE TABLE 테이블 (
 2    컬럼 데이터_타입 CONSTRAINT 제약_조건 PRIMARY KEY, ****

* 테이블을 생성할 때 PK를 정의
* PK는 각 행을 고유하게 식별하는 역할을 담당
* 테이블 당 하나만 정의 가능
* 지정된 컬럼에는 중복된 값이나 NULL값이 입력 될 수 없음
* PK로 지정 가능한 컬럼이 여러 개 있을 때는 검색에 많이 사용되고 간단하고 짧은 컬럼을 지정
* 주 식별자, 주키, 주식별자 등으로 불림
* 고유 인덱스(Unique Index)가 자동으로 생성

- Foreign Key 설정
SQL> CREATE TABLE 테이블 (
  2    ****
  3    CONSTRAINT 제약_조건 FOREIGN KEY (컬럼)
  4    REFERENCE 참조할_테이블 (참조할_컬럼) [ON DELETE CASCADE]);
SQL> CREATE TABLE 테이블 (
  2    컬럼명 데이터_타입 CONSTRAINT 제약_조건 FOREIGN KEY
  3            REFERENCES 참조할_테이블 (참조할_컬럼)
  4            [ON DELETE CASCADE],
  5    *************

* 테이블을 생성할 때 FK를 정의
* FK가 정의된 테이블이 하위(자식) 테이블
* 참조되는 테이블이 상위(부모) 테이블
* 상위 테이블은 미리 생성되어 있어야함
* 상위 테이블의 참조되는 컬럼에 존재하는 값만을 입력 할 수 있음
* 상위 테이블에 참조되는 행의 데이터는 FK를 위배하는 삭제나 변경이 불가능
* 상위 테이블은 FK로 인해 삭제가 불가능
* ON DELETE CASCADE : 참조되는 상위 테이블의 행에 대한 DELETE를 허용
 - 하위 테이블의 행도 같이 지워짐
* 자료형이 반드시 일치해야 함
* 참조되는 컬럼은 PK이거나 UK(Unique Key)만 가능
* 외부키, 참조키, 외부 식별자 등으로 불림


 - 제약조건을 정의하는 방법은 NOT NULL을 제외하고 두 가지 방법이 제공.
  하나는 컬럼을 정의하면서 같이 정의하는 방법으로 컬럼 레벨 정의라고 하고 하나는 테이블 생성 명령어 마지막에 기술하는 방법으로 테이블 레벨 정의라 함. 둘 중 어떤 것을 사용해도 상관없지만 가능한 테이블 레벨 정의를 사용하는 것이 가독성이 높음

제약 조건 조회
SQL> SELECT c.table_name, c.constraint_name, c.constraint_type, s.colum_name
  2    FROM user_constraints c, user_cons_columns s
  3    WHERE c.constraint_name = s.constraint_name
  4    AND c.table_name in (검색_대상_테이블_목록)
  5    ORDER BY c.table_name;

* 테이블에 정의된 제약 조건을 검색
* 'AND c.table_name in (검색_대상_테이블_목록)' 부분을 생략하면 소유한 모든 테이블의 제약조건을 검색
* constraint_name : CONSTRAINT 이름
* constraint_type : CONSTRAINT 타입
 - P(PK), R(FK), U(UK), C(NOT NULL, CHECK)

SQL> SELECT p.table_name 상위테이블, p.constraint_name 상위제약조건,
  2       c.table_name 하위테이블, c.constraint_name 참조제약조건
  3    FROM user_constraints p, user_constraints c
  4    WHERE c.r_constraint_name=p.constraint_name
  5    AND p.table_name in (검색_대상_테이블_목록)
  6    ORDER BY p.table_name;

* 지정한 테이블을 참조하는 테이블의 목록을 확인
* 하위 테이블이 존재하는 상위 테이블은 테이블 삭제가 불가능

테이블 상세 도표
 - 테이블 상세 도표는 테이블의 구조를 한 눈에 파악 할 수 있는 간단한 도표임. 테이블의 구조와 관계는 보통 ERD(Entity-Relationship Diagram)를 이용하지만 본서가 모델링 과정을 다루지 않는 관계로 각 테이블의 정보를 도표로 정리 한 것. 보통 개체 상세도라고도 불리는 상세도에서 우리에게 필요한 부분만을 발췌함. 이를 통해 테이블의 구조, 참조 관계등을 명확히 파악할 수 있음. 테이블을 생성하기 전에 반드시 테이블에 대한 작업 계획이 완성돼야 하는데 그 중 가장 중요한 것이 이들 상세도표임.


▲ 내부 무결성과 외부 무결성
 - 무결성은 데이터에 결함이 없음을 보장하는 성격을 의미. 이미 앞에서 개체 무결성과 관계 무결성에 대해서 살펴봄. 이번에는 무결성을 바라보는 시야를 좀 바꿔 외부 무결성과 내부 무결성에 대해 알아봄.

* 내부 무결성
 - 테이블 내에 데이터나 각 테이블간의 데이터에 결함이 없음을 의미. 즉 이전에 살펴본 개체 무결성과 관계 무결성을 통칭한 의미. 이것은 보통 RDBMS의 기술적인 기능에 의해 보장. 이를 위해 RDBMS는 제약조건, 트리거, 내장 프로그램 등의 기능 제공

* 외부 무결성
 - 테이블 내에 정보와 정보들이 사상하고 있는 대상에 결함이 없음을 보장. 예를 들면 회사에 근무 중인 직원이 10명인데 사원 테이블 상에 11명의 직원이 등록 되어 있고, 이들이 급여를 정상적으로 수령하는 중이라면 비록 테이블 내에 또는 테이블 간에 무결성에 문제가 없다하더라도 외부 무결성이 훼손된 것으로 간주됨. 보통 훼손된 외부 무결성 중에 최종 사용자나 관리자의 실수에 의한 경우에는 제약 조건과 여러 통제를 통해서 어느 정도 보장하는 것이 가능하지만 고의 또는 모의에 의해 훼손된 경우는 보장이 불가능. 이럴 경우는 내부 통제가 반드시 마련되어 있어야 함.


<문제>
1. 다음 테이블에 대한 표를 보고 테이블 생성을 위한 스크립트를 생성
* student















SQL_정리7

SQL 사용법에 대해 알아보자!

16. 그룹 함수와 GROUP BY
그룹 함수는 검색된 여러 행을 이용 통계 정보를 계산하는 함수. 개발자는 복잡한 응용 개발이 필요한 통계 정보 검색을 그룹 함수를 이용하여 간단히 할 수 있음. 그룹 함수의 대상이 되는 행의 집합은 테이블 전체이거나 필요하다면, 조건 검색된 행이거나 일정한 단위로 묶인 행일 수 있음.

> 그룹함수

그룹함수는 여러 개의 행으로부터 하나의 계산되거나 추출한 값을 제공. 그룹 함수에 의한 결과는 대부분 그룹 함수 없이 응용으로 처리할 경우 많은 노력을 필요로 함



그룹 함수를 사용하는 경우 고려 사항
* NULL 값은 무시
* 반드시 단 하나의 값만을 반환
* GROUP BY 설정 없이 일반 컬럼과 기술 될 수 없음

-예제1. 사원의 급여 평균을 검색
SQL>SELECT AVG(sal), ROUND(AVG(sal)) 
   2     FROM emp;

그룹 함수는 보통 통계정보를 계산하기 위해 사용되는 통계 함수인 경우가 많음. 사용 방식은 단일 행 함수 또는 그룹 함수와 같이 중첩해서 사용하는 것이 가능, 출력 값의 형식은 세션의 설정이나 중첩된 단일 함수를 이용해서 지정 가능

> 그룹 함수와 GROUP BY절

SQL>SELECT [DISTINCT / ALL]  컬럼 or 그룹함수, ...
  2     FROM 테이블
  3     WHERE 조건
  4     GROUP BY Group 대상
  5     ORDER BY 정렬대상 [ASC/DESC]

* SELECT 절에 그룹 함수와 컬럼이 같이 기술된 경우 해당 컬럼은 반드시 GROUP BY 절에 그룹화 돼야 함
* SELECT 절에 그룹 함수와 같이 쓰인 일반 컬럼이 GROUP BY 절에 기술되지 않으면 카디널리티(cardinality)가 일치하지 않아 'ORA-00937: not a single-group group function' 에러가 발생
* 예전에는 오름차순 정렬이 기본으로 제공되었지만 현재는 상황에 따라 다름. (ORDER BY 절을 이용 DESC로 변경 가능)
* 결과 값이 정렬되길 원한다면 반드시 ORDER BY절을 추가

-예제4. 업무별 평균 급여, 평균 연봉과 부서별 평균 연봉을 검색
SQL>SELECT job, ROUND(AVG(sal)), ROUND(AVG(sal*12+NVL(comm,0))) 
   2    FROM emp 
   3    GROUP BY job;



SQL>SELECT d.dno, dname, ROUND(AVG(sal*12+NVL(comm,0))) 
  2    FROM dept d, emp e 
  3    WHERE d.dno = e.dno 
  4    GROUP BY d.dno, dname 
  5    ORDER BY d.dno;



GROUP BY절의 의미는 그룹 함수를 GROUP BY절에 지정된 컬럼의 값이 같은 행에 대해서 통계 정보를 계산하라는 의미

카디널리티(Cardinality)
RDB에서 카디널리티란 검색되는 각 컬럼의 행의 수를 의미하거나 컬럼에 저장된 값의 종류를 의미하기도 함. 예를 들면 보통 학번이나 주민번호와 같이 중복이 없는 컬럼의 카디널리티는 매우 높고 성별이나 학년등 중복 값이 많은 컬럼은 카디널리티가 낮음
이번 그룹 함수에서 이야가하는 카디널리티는 동시에 검색되는 각 컬럼과 항목들의 행의 개수를 의미. 그룹 함수를 사용하는 하나의 SELECT 문장에서 각 검색 항목들은 반드시 카디널리티가 일치해야 하며 이를 위해 GROUP BY절이 이용.

SELECT dno, AVG(sal) FROM emp;

AVG(sal)에서 검색된 값은 당연히 하나.(평균값이므로) 그러나 dno 컬럼의 값은 반드시 하나일 필요는 없음. RDBMS는 이렇게 검색되는 항목의 개수가 서로 다를 가능성이 있는 문장이 수행되면 'ORA-00937 : 단일 그룹의 그룹 함수가 아닙니다.' 에러를 발생. 이 때 일반 컬럼인 dno를 'GROUP BY'절에 기술하면 dno 값이 같은 행을 기준으로 집합을 만들고 여기에 대해서 각각 AVG(sal)을 계산함으로 dno의 검색 항목 개수와 AVG(sal) 결과 값의 개수가 동일해 짐. 이제 카디널리티가 일치해졌으므로 검색문은 에러가 발생하지 않고 수행됨
'그룹 함수와 같이 검색되는 모든 컬럼은 반드시 GROUP BY절에 기술함'. 이것은 원칙!

GROUP BY와 정렬

보통 GROUP BY 절은 정렬을 수행한다고 알려져 있다. 그러나 오라클은 기본적으로 GROUP BY를 수행 할 때 정렬을 수행하지 않고 해시(Hash) 함수를 이용함. 그러므로 결과 값도 GROUP BY절에 기술된 컬럼으로 정렬 되서 검색되지 않음. 거의 대부분의 경우에 정렬보다는 해시의 수행 성능이 더 빠르기 때문. 만일 정렬된 검색 결과를 얻고자 한다면 반드시 ORDER BY 절을 기술

SQL>SELECT d.dno, dname ROUND(AVG(sal*12+NVL(comm,0))) 
   2     FROM dept d, emp e 
   3     WHERE d.dno = e.dno 
   4     GROUP BY d.dno, dname;

문장은 다음 문장과 동일

SQL>SELECT dno, dname, ROUND(AVG(sal*12+NVL(comm,0)))
  2    FROM dept
  3    WHERE d.dno = e.dno
  4    GROUP BY d.dno, dname;

후자가 최신의 표현법이므로 이를 사용하는 것이 좋을 것 같다고 생각하겠지만 막상 현장에서는 옛 표현법 씀, 그러나 가능한 새로운 표현법을 사용하길 권장

<문제>
1. 30번 부서의 업무별 연봉의 평균을 검색
 (단. 출력 양식은 소수이하 두 자리까지 통일된 형식으로 출력)
SQL>select job, to_char(avg(sal*12+nvl(comm,0)),'9999999.99')
  2    from emp
  3    where dno = 30
  4    group by job;





2. 물리학과 학생 중에 학년별로 성적이 가장 우수한 학생의 평점을 검색
SQL>select syear, max(avr) 
  2    from student
  3    where major = '물리'
  4    group by syear
  5    order by syear;



3. 학년별로 환산 평점의 평균값을 검색
SQL>select syear, TO_CHAR(avg(avr), '9.99') 
  2    from student 
  3    group by syear 
  4    order by syear;




4. 교수별로 현재 수강중인 학생의 수를 검색
SQL>select p.pname, count(*)
  2    from professor p, course c, score sc
  3    where c.pno = p.pno AND c.cno = sc.cno

  4    GROUP BY p.pname, p.pno;



5. 화학과 1학년 학생 중 평점이 평균 이하인 학생을 검색
SQL>select sname, avr
  2    from student
  3    where major = '화학'
  4    and syear = 1
  5    and avr <= (select avg(avr) from student where major = '화학' and syear = 1) ;


17. 그룹 함수와 HAVING

그룹 함수를 포함한 조건은 일반 조건과 계산하는 과정이 상당히 다름. 일반 조건의 경우 컬럼의 값을 단지 조건과 비교하면 되지만 그룹 함수의 결과를 조건으로 하는 경우 GROUP BY 절의 사용 유무에 따라 결과 값이 달라지므로 조건에 그룹함수가 포함된 경우 이것은 일반 조건과 동일한 시점에 처리 할 수 없음
SQL은 그래서 HAVING 절을 제공

> 그룹 함수와 HAVING절
HAVING 절은 해석상 WHERE 절과 동일. 단 조건 내용에 그룹 함수를 포함하는 것 만을 포함. 일반 조건은 WHERE절에 기술하지만 그룹 함수를 포함한 조건은 HAVING절에 기술
SELECT 구문의 마지막 추가 구문

SQL>SELECT [DISTINCT/ALL] 컬럼 or 그룹함수, ...
  2    FROM 테이블
  3    WHERE 조건
  4    GROUP BY Group 대상
  5    HAVING < 그룹 함수 포함 조건 >
  6    ORDER BY 정렬대상 [ASC/DESC]

* HAVING : 조건 중에 그룹 함수를 포함하는 조건을 기술
* HAVING 절은 그룹 된 결과에 대한 조건이므로 가능한 GROUP BY 절 뒤에 기술하는 것을 권함

GROUP BY 절에 따른 그룹 함수 결과 값의 변화

-예제3. 부서 중 가장 급여를 많이 받는 부서를 검색
SQL>SELECT dno, AVG(sal)
  2    FROM emp
  3    group by dno
  4    having avg(sal) = (select max(avg(sal)) from emp group by dno);


중첩된 그룹 함수를 사용한 경우 GROUP BY 절에 기술한 컬럼일지라도 카디널리티 문제로 SELECT 절에 검색할 수 없음을 알게됨. 이런 경우 위 예제와 같이 서브 쿼리를 이용하면 해당 컬럼을 검색 할 수 있음. 이 구문은 매우 전형적으로 쓰이는 구문이므로 구조를 기억해 두면 많은 부문에서 활용 가능

<문제>
1. 근무 중인 직원이 4명 이상인 부서를 검색(부서번호, 인원) (4명이상이 없어 1명이상으로 수정)
SQL>select dno, count(*)
  2    from emp
  3    group by dno
  4    having count(*) > 1;



2. 업무별 평균 연봉이 2만불 이상인 업무를 검색
SQL>select job, TRUNC(avg(sal*12+nvl(comm,0)))
  2    from emp
  3    group by job
  4    having avg(sal*12+nvl(comm,0)) >= 20000;




3. 각 학과의 학년별 인원중 인원이 6명 이상인 학년을 검색
SQL>select major, syear, count(*)
  2    from student
  3    group by major, syear
  4    having count(*) > 1;



4. 인원 수가 가장 많은 학과를 검색
SQL>select major,count(*)
  2    from student
  3    group by major
  4    having count(*) = (select max(count(*)) from student group by major);


5. 학생 중 기말고사 성적이 가장 낮은 학생의 정보를 검색
SQL>select sno, TRUNC((avg(result)))
  2    from score
  3    group by sno
  4    having avg(result) = (select min(avg(result)) from score group by sno);