본문 바로가기

웹_프론트_백엔드/JAVA프레임윅기반_풀스택

2020.04.09

1. Sub Query(서브쿼리)
 : 쿼리 안의 쿼리


2. 주의사항
1) SubQuery는 연산자 오른쪽에 위치해야 하며 반드시 괄호로 묶어야 한다
2) 특별한 경우를 제외하고는 SubQuery절에는 Order By가 올 수 없다
3) 단일행SubQuery , 다중행SubQuery에 따라 연산자를 잘 선택해야 한다


3. Sub Query 종류
1) 단일행
    사용 가능한 연산자 : = != <> > >= < <=

2) 다중행
    사용 가능한 연산자 : IN >ANY ALL =ANY <=ANY >=ALL <=ALL

3) 다중칼럼

4) 상호연관

 


4. Sub Query 위치별로 이름이 다르다
1) SELECT(Sub Query) : Scala Sub Query(스칼라 서브쿼리)


2) FROM(Sub Query) : Inline View(인라인 뷰)


3) WHERE(Sub Query) : Sub Query(서브쿼리)

 

 

5. View
 : 가상테이블


[뷰를 사용하는 목적?] 보안, 편의성

 

 

6. 뷰 생성 구문
   CREATE [OR REPLACE] VIEW 뷰명 [컬럼명]
   AS
   (Sub Query)

 

 

7. View 종류

1) 단순 View (Simple View)

 : 서브쿼리에 join 조건 없이 만듦.

2) 복합 View (Complex View)

 : 서브쿼리에 여러 개의 테이블이 Join 되어 만듦.

3) 인라인 View (INLINE View)

 : FROM 절의 서브쿼리 형태로 만듦, 1회용으로만 사용

 

 

8. View를 생성하려면 계정에 권한 필요

[권한 확인] SELECT PRIVILEGE FROM DBA_SYS_PRIVS WHERE GRANTEE ='SCOTT0316';
[권한이 없는 경우] GRANT create view TO scott0316;

[주의!] 반드시 SYSTEM 계정으로 들어가기 

 

 

9. Constraint(제약조건)
** DBMS는 데이터의 무결성을 보장하기 위해,

    잘못된 데이터가 저장되는 것을 방지하기 위해 제약조건을 사용

 

** 제약 조건은 테이블의 '컬럼'에 설정하여 사용되며, 
    해당 '컬럼'에 설정된 제약조건에 위배된 데이터가 들어오는 경우 에러 발생

 

[데이터 무결성(Integrity)이란?] 저장된 데이터에는 '잘못된 데이터'가 없어야 한다는 의미

 

 

10. 제약조건 종류
1) NOT NULL

 : 이 조건이 설정된 컬럼에는 NULL 입력 불가


2) UNIQUE

 : 이 조건이 설정된 컬럼에는 중복값 입력 불가


3) PRIMARY KEY

 : 테이블당 단 1개만 설정 가능, 

   데이터 유일성 보장,

   NOT NULL + UNIQUE의 의미


4) FOREIGN KEY 

 : 다른 테이블의 컬럼을 참조해서 무결성 검사,

   외래키 값은 NULL 혹은 참조할 수 있는 값이어야 한다.


5) CHECK

 : 설정된 값만 입력 허용

 

 

11. 무결성 종류
1) 개체 무결성 

 : 릴레이션에서 기본키를 구성하는 속성은 널(Null)값이나 중복값을 가질 수 없다

   PRIMARY KEY

 

2) 도메인 무결성

 : 특정 속성의 값은 그 속성이 정의된 도메인에 속한 값이어야 한다.

   CHECK, DEFAULT, NOT NULL


3) 참조 무결성 

 : 외래키 값은 NULL이거나, 참조할 수 없는 외래키 값을 가질 수 없다

   FOREIGN KEY


4) 고유 무결성

 : 특정 속성에 속한 값은 서로 달라야 한다

  UNIQUE, NOT NULL, PRIMARY KEY 


5) 널 무결성

 : 릴레이션의 속성값이 NULL이 될 수 없다 

   NOT NULL


6) 관계 무결성

 : 릴레이션 

   ON DELETE, NO UPDATE

 

 

12. 제약조건명의 관리를 위해 이름을 붙여주는 것이 좋음
[개발자들끼리 약속!!] 릴레이션명_컬렴명_제약조건명 으로 주로 실무에서 이런 식으로 많이 작성함

 

 

13. 제약조건 조회
 : 테이블에 제약조건을 설정하면 그 내용은 ‘딕셔너리’ 에 저장


[사용자 딕셔너리] USER_CONSTRAINTS 와 USER_CONS_COLUMNS  사용


[DB 전체 딕셔너리] DBA_CONSTRAINTS 와 DBA_CONSTRAINT_COLUMNS 사용


[딕셔너리( Dictionary )란?] 스키마 · 사용자 · 객체 · 권한 · 롤 · 데이터베이스의 정보 등 
                                   오라클 데이터베이스를 운영하는데 필요한 정보를 관리하는 별도의 객체들

[CONSTRAINT_TYPE] P : Primary Key,   U : Unique,  C : Check, Not Null   R : Foreign Key

 

 

14. 외래키 설정시 주의
 : 참조되는 ‘부모테이블의 컬럼’은 반드시 Primary Key이거나 Unique

 

 

15. FOREIGN KEY + ON DELETE / ON UPDATE 옵션
   데이터를 지울려고 한다..!!
   지울려고 하는 데이터가 FK를 설정이 되어 있다...!!
   거기다가 설상가상으로 삭제해야할 데이터가 누군가가 참조하고 있어...!!
   이럴때는 설정한 옵션에 따라 지울수 있는 여부가 달라진다..!!

 

** ON DELETE / ON UPDATE 옵션 종류
    NO ACTION : 참조테이블(부모테이블)에 변화가 있어도 자식테이블에 아무런 조치를 취하지 않음
    CASCADE : 참조테이블의 데이터가 삭제 혹은 변경되면 자식테이블의 관련 데이터도 모두 삭제 혹은 변경
    SET NULL : 참조테이블에 변화가 있으면 자식테이블의 관련데이터는 NULL로 변경
    SET DEFAULT : 참조테이블의 변화가 있으면 자식테이블의 관련데이터는 기본데이터로 변경

 

 

16. 인덱스 생성 구문

    CREATE UNIQUE INDEX 인덱스명
    ON 테이블이름(컬럼명1  ASC | DESC,  컬럼명2 ... )

 

 

17. 지금까지 배운 CREATE로 생성한 것들(총 4개)
 : CREATE USER, CREATE TABLE, CREATE SEQUENCE, CREATE VIEW 

 

[실습코드]

 

1. [폴더] 07_SubQuery
1) SubQuery1.sql

-- 단일행 Sub Query : 결과가 한개 1행만 나오는 것

-- t_emp 테이블에서 scott 보다 급여를 많이 받는 사람의 이름과 급여 출력
SELECT * FROM t_emp;

SELECT sal FROM t_emp WHERE ename = 'SCOTT';

SELECT ename, sal
FROM t_emp
WHERE sal > (SELECT sal FROM t_emp WHERE ename = 'SCOTT'); 

-- t_student 테이블에서 가장 키 큰 학생의 '이름'과 '키'를 출력
SELECT * FROM t_student;

SELECT MAX(height) FROM t_student;

SELECT name, height
FROM t_student
WHERE height = (SELECT MAX(height) FROM t_student);

-- 1. 단일행 서브 쿼리
-- t_student, t_department 테이블 사용하여 
-- 이윤나 학생과 1전공이 동일한 학생들의 이름과 1전공 이름을 출력하세요
SELECT * FROM t_student;
SELECT * FROM t_department;

SELECT deptno1 FROM t_student WHERE name = '이윤나';

SELECT s.name "학생이름", d.dname "1전공"
FROM t_student s, t_department d
WHERE s.deptno1 = d.deptno 
	AND s.deptno1 = (SELECT deptno1 FROM t_student WHERE name = '이윤나');

-- t_professor, t_department 테이블 : 
-- 입사일이 송도권 교수보다 나중에 입사한 사람의 
-- 이름과 입사일, 학과명을 출력하세요
SELECT * FROM t_professor;
SELECT * FROM t_department;

SELECT hiredate FROM t_professor WHERE name = '송도권';

SELECT name, hiredate 
FROM t_professor
WHERE hiredate > (SELECT hiredate FROM t_professor WHERE name = '송도권');

SELECT p.name "이름", p.hiredate "입사일", d.dname"학과명" 
FROM t_professor p, t_department d
WHERE p.deptno = d.deptno
AND hiredate > (SELECT hiredate FROM t_professor WHERE name = '송도권');

-- 쌤이 작성하신 코드
SELECT p.name "교수명", TO_CHAR(p.hiredate, 'YYYY-MM-DD') "입사일", d.dname "학과명"
FROM t_professor p, t_department d
WHERE p.deptno = d.deptno
	AND 
	p.hiredate > (SELECT hiredate FROM t_professor WHERE name = '송도권');

-- t_student 테이블 :  
-- 1전공이 101번인 학과의 평균 몸무게보다 몸무게가 많은 학생들의 
-- 이름과 몸무게를 출력하세요
SELECT * FROM t_student;

SELECT AVG(weight)
FROM t_student
WHERE deptno1 = 101;

SELECT name "이름", weight "몸무게"
FROM t_student
WHERE weight > (SELECT AVG(weight) FROM t_student WHERE deptno1=101);

-- t_professor 테이블에서 심슨 교수와 같은 입사일에 입사한 교수 중, 
-- 조인형 교수보다 월급을 적게 받는 교수의  이름과 급여, 입사일을 출력하세요
SELECT * FROM t_professor;

SELECT pay
FROM t_professor
WHERE name = '조인형';

SELECT hiredate
FROM t_professor
WHERE name = '심슨';

SELECT name, pay
FROM t_professor
WHERE hiredate = (SELECT hiredate FROM t_professor WHERE name = '심슨') 
	AND pay < (SELECT pay FROM t_professor WHERE name = '조인형');

-- 쌤이 작성하신 코드
SELECT name "이름", pay "급여", hiredate "입사일"
FROM t_professor
WHERE hiredate = (SELECT hiredate FROM t_professor WHERE name = '심슨')
		AND 
	pay < (SELECT pay FROM t_professor WHERE name = '조인형');


-- 2. 다중행 쿼리
-- Sub Query 결과가 2건 이상 출력되는 것을 말합니다.
-- 다중행 Sub Query 와 함께 사용하는 연산자
-- 	IN 같은 값을 찾음
--	>ANY 최소값을 반환함 (서브쿼리 결과중 가장작은것보다 큰)
--	<ANY 최대값을 반환함 (서브쿼리 결과중 가장큰것보다 작은)
--	<ALL 최소값을 반환함 (서브쿼리 결과중 가장작은것보다 작은)
--	>ALL 최대값을 반환함 (서브쿼리 결과중 가장큰것보다 큰)
--	EXIST Sub Query 값이 있을 경우 반환

-- t_emp2, t_dept2 테이블 : 근무지역 (t_dept2.area)이 
-- 서울 지사인 모든 사원들의 사번(empno)과 이름(name), 부서번호(deptno)를 출력하세요
SELECT * FROM t_emp2;
SELECT * FROM t_dept2;

SELECT dcode FROM t_dept2 WHERE area = '서울지사';

SELECT empno, name, deptno
FROM t_emp2
WHERE deptno IN (SELECT dcode FROM t_dept2 WHERE area = '서울지사');

-- t_emp2 테이블 : 전체직원중 과장 직급의 최소연봉자보다 
-- 연봉이 높은 사람의 이름(name)과 직급(post), 연봉(pay)을 출력하세요.  
-- 단, 연봉 출력 형식은 천 단위 구분 기호와 원 표시를 하세요
SELECT pay FROM t_emp2 WHERE post = '과장';

SELECT name "이름", post "직급", TO_CHAR(pay, '999,999,999') || '원' "연봉"
FROM t_emp2
WHERE pay >ANY (SELECT pay FROM t_emp2 WHERE post = '과장');

-- t_student 테이블 : 전체학생중에서 체중이 4학년 학생들의 체중에서 
-- 가장 적게 나가는 학생보다 몸무게가 적은 학생의 이름과 학년과 몸무게를 출력하세요
SELECT * FROM t_student;

SELECT weight FROM t_student WHERE grade = 4;

SELECT name "이름", grade "학년", weight "몸무게"
FROM t_student
WHERE weight <ALL (SELECT weight FROM t_student WHERE grade = 4);


2) SubQuery2.sql

-- 다중칼럼 Sub Query : Sub Query 결과가 2건 이상 출력되는 것
-- Sub Query 결과가 여러 칼럼인 경우.  
-- 주로 Primary Key 를 여러 칼럼을 합쳐서 만들었을 경우 한꺼번에 
-- 비교하기 위해서 자주 사용.

-- t_student 테이블을 조회하여 
-- 각 학년별로 최대 키를 가진 학생들의 학년과 이름과 키를 출력하세요,  
-- 학년 오름차순으로 출력
SELECT  grade, MAX(height)
FROM t_student
GROUP BY grade;

SELECT grade "학년", name "이름", height "키"
FROM t_student
WHERE (grade, height)
	IN (SELECT  grade, MAX(height) FROM t_student GROUP BY grade)
--ORDER BY grade ASC;
--ORDER BY 1 ASC;
ORDER BY "학년" ASC;	-- 유일하게 ORDER BY 컬럼에다가 작성한 별명을 사용 할 수 있다..!!

-- t_professor , t_department 테이블 : 
-- 각 학과별로 입사일이 가장 오래된 교수의 교수번호와 이름, 학과명을 출력하세요.  
-- 단 학과이름 순으로 오름차순 정렬하세요
SELECT * FROM t_professor;
SELECT * FROM t_department;

SELECT p.profno "교수번호", p.name "교수명",
	    TO_CHAR(p.hiredate, 'YYYY-MM-DD') "입사일", d.dname "학과명"
FROM t_professor p, t_department d
WHERE p.deptno = d.deptno
AND (p.deptno, p.hiredate) 
	IN (SELECT deptno, MIN(hiredate) FROM t_professor GROUP BY deptno)
ORDER BY d.dname ASC;

-- t_emp2 테이블 : 
-- 직급별로 해당직급에서 최대 연봉을 받는 직원의 이름과 직급, 연봉을 출력하세요,
-- 단, 연봉순으로 오름차순 정렬하세요
SELECT * FROM t_emp2;

SELECT name "사원명", post "직급", pay "연봉"
FROM t_emp2
WHERE (post, pay) IN (SELECT post, MAX(pay) FROM t_emp2 GROUP BY post)
ORDER BY pay;

-- t_emp2, t_dept2 테이블 : 
-- 각 부서별 평균 연봉을 구하고 그 중에서 평균 연봉이 가장 적은 부서의 평균연봉보다
-- 적게 받는 직원들의 부서명, 직원명, 연봉을 출력 하세요
SELECT * FROM t_emp2; 
SELECT * FROM t_dept2;

SELECT AVG(pay) FROM t_emp2 GROUP BY deptno;

SELECT d.dname "부서명", e.name "직원명", e.pay "연봉"
FROM t_emp2 e, t_dept2 d
WHERE e.deptno = d.dcode
	AND e.pay <ALL (SELECT AVG(pay) FROM t_emp2 GROUP BY deptno)
ORDER BY e.pay;


-- 상호연관 Sub Query

-- t_emp2 테이블 :  
-- 직원들 중에서 자신의 직급의 평균연봉과 같거나 
-- 많이 받는 사람들의 이름과 직급, 현재 연봉을 출력하세요.
SELECT * FROM t_emp2;

SELECT a.name "사원이름", NVL(a.post, ' ') "직급", a.pay "급여"
FROM t_emp2 a
--WHERE a.pay >= (a.post 직급의 평균 연봉)
WHERE a.pay >= (SELECT AVG(b.pay) FROM t_emp2 b WHERE NVL(a.post, ' ') = NVL(b.post, ' '));

SELECT AVG(b.pay) FROM t_emp2 b WHERE '과장' = b.post;


-- Scalar Sub Query (스칼라 서브 쿼리)
-- JOIN 과 같은 결과 나옴.  
-- 그러나 데이터 양이 적은 경우는 스칼라서브쿼리 방식이 Join 보다 나은 성능을 보여줌

-- t_emp2, t_dept2 테이블: 조회하여 사원들의 이름과 부서 이름을 출력
SELECT * FROM t_emp2;
SELECT * FROM t_dept2;

SELECT dname FROM t_dept2;

SELECT e.name "사원이름", 
	(SELECT d.dname FROM t_dept2 d WHERE e.deptno = d.dcode) "부서이름"
FROM t_emp2 e;


3) SubQuery3.sql

DELETE FROM test_emp_a;
DELETE FROM test_emp_b;

CREATE TABLE test_emp_a (
	emp_id NUMBER,
	emp_name VARCHAR2(100)
);
CREATE TABLE test_emp_b (
	emp_id NUMBER,
	emp_name VARCHAR2(100)
);


-- 한 row씩 INSERT
INSERT INTO test_emp_a VALUES (101, '장윤성');
INSERT INTO test_emp_b VALUES (201, '고유성');

SELECT * FROM test_emp_a;
SELECT * FROM test_emp_b;


-- 동시에 여러 개 테이블에 INSERT 하기
-- 다중 테이블 INSERT 구문에 sub query 필요
INSERT ALL 
	INTO test_emp_a VALUES(102, '조현주')
	INTO test_emp_b VALUES(202, '조현주')
SELECT * FROM dual;

SELECT * FROM test_emp_a;
SELECT * FROM test_emp_b;

-- sub query로 INSERT 가능
INSERT INTO test_emp_a (SELECT 400, '안성원' FROM dual);
INSERT INTO test_emp_b (SELECT 500, '김성원' FROM dual);

SELECT * FROM test_emp_a;
SELECT * FROM test_emp_b;

-- 테이블 묻고 더블로!
INSERT INTO test_emp_a (SELECT * FROM test_emp_a);
SELECT * FROM test_emp_a;

INSERT INTO test_emp_b(emp_name) (SELECT emp_name FROM test_emp_a);
SELECT * FROM test_emp_b;

 

4) rownum.sql

-- SELECT 결과물중 맨 위의 5개만 출력해보고 싶으면 어케 해야 하나?
--   ex) 게시판.. 페이징

-- DBMS 마다 구현 방법 다름
--	MYSQL : LIMIT
-- 	MS SQL server : TOP
-- 	ORACLE : ROWNUM 

SELECT empno, ename, sal FROM t_emp;

-- 자동적으로 오라클에서 붙여주는 행번호 객체(ROWNUM)
SELECT ROWNUM, empno, ename, sal FROM t_emp;

-- 직원번호 역순으로 출력했으나 ROWNUM은 여전히 1부터 출력됨
SELECT ROWNUM, empno, ename, sal 
FROM t_emp
ORDER BY empno DESC;

-- 상위 5개만..!!
SELECT ROWNUM, empno, ename, sal 
FROM t_emp
WHERE ROWNUM <= 5
ORDER BY empno DESC;

-- SELECT에 ROWNUM 없어도 동작
SELECT empno, ename, sal FROM T_EMP
WHERE ROWNUM <= 5 ORDER BY empno DESC;

-- ROWNUM > 5은 동작 안함, ROWNUM 범위가 1이 포함 안되면 동작 안함.
SELECT ROWNUM, empno, ename, sal FROM T_EMP
WHERE ROWNUM > 5 ORDER BY empno DESC;

-- 상위 5개 출력
-- row1 ~ row5 까지 출력(1 page)
SELECT ROWNUM, empno, ename, sal FROM T_EMP
WHERE ROWNUM >= 1 AND ROWNUM < 1 + 5 
ORDER BY empno DESC;

-- (2page)
SELECT ROWNUM, empno, ename, sal FROM T_EMP
WHERE ROWNUM >= 6 AND ROWNUM < 6 + 5 
ORDER BY empno DESC;	-- 안나온다..!! 호에엥..!!


-- phonebook 뻥튀기...!!
SELECT * FROM phonebook;
--INSERT INTO phonebook (SELECT * FROM phonebook);	-- 에러, Primary Key 중복!
INSERT INTO PHONEBOOK 
	(SELECT phonebook_seq.nextval, pb_name, pb_phonenum, pb_memo, SYSDATE FROM phonebook);

-- ROWNUM rev.
SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC;

SELECT t.*
FROM(SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC) t;

SELECT ROWNUM AS RNUM, t.*
FROM(SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC) t;

-- 한 페이지 당 5개 데이터
-- 두 번째 페이지
SELECT *
FROM (
	SELECT ROWNUM AS RNUM, t.*
	FROM(SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC) t
)
WHERE RNUM >= 6 AND RNUM < 6 + 5;

-- 네번째 페이지
SELECT * 
FROM (
	SELECT ROWNUM AS RNUM, t.*
	FROM (SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC) t
)
WHERE RNUM >= 16 AND RNUM < 16 + 5;

-- 한 페이지 당 10개 테이터
-- 3번째 페이지
SELECT *
FROM (
	SELECT ROWNUM AS RNUM, t.*
	FROM(SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC) t
)
WHERE RNUM >= 21 AND RNUM < 21 + 10;

--자바 응용 가능
SELECT *
FROM (
	SELECT ROWNUM AS RNUM, t.*
	FROM(SELECT pb_uid, pb_name, pb_phonenum FROM phonebook ORDER BY pb_uid DESC) t
)
WHERE RNUM >= ? AND RNUM < ? + ?;	-- 이런 식으로 자바에서 응용가능

 

 

2. [폴더] 08_View
1) View.sql

CREATE VIEW v_prof
AS 
SELECT profno, name, email, hpage
FROM t_professor;

SELECT * FROM v_prof;

SELECT tname FROM tab;	-- VIEW 확인 가능..!!

-- OR REPLACE 는 혹시 이전에 같은 이름의 view 가 있으면 삭제하고 새로 만들라는 의미
CREATE OR REPLACE VIEW v_prof
AS 
SELECT profno, name, email, hpage
FROM t_professor;

SELECT * FROM v_prof;

-- VIEW 생성시 별도의 컬럼 이름을 지정해 줄 수 있다
CREATE OR REPLACE VIEW v_prof(pfno, nm, em, hp)
AS 
SELECT profno, name, email, hpage
FROM t_professor;

SELECT * FROM v_prof;

-- t_professor, t_department 테이블을 join 하여
-- 교수번호와 교수이름과 소속학과 이름을 조회하는 view 를 생성하세요.  
-- (이름: v_prof_dept)
SELECT * FROM t_professor;
SELECT * FROM t_department;

CREATE OR REPLACE VIEW v_prof_dept
AS 
SELECT p.profno "교수번호", p.name "교수명", d.DNAME "소속학과명"
FROM t_professor p, t_department d
WHERE p.deptno = d.deptno;
-- 확인
SELECT * FROM v_prof_dept;

-- t_student, t_department 테이블 : 
-- 학과별로 학생들의 최대키와 최대몸무게, 학과 이름을 출력하세요
SELECT * FROM t_student;
SELECT * FROM t_department;

SELECT 
	d.dname "학과명", 
	s.max_height "최대키", 
	s.max_weight "최대몸무게"
FROM 
	(SELECT deptno1, MAX(height) max_height, MAX(weight) max_weight
	FROM t_student
	GROUP BY deptno1 ) s , 
	t_department d
WHERE 
	s.deptno1 = d.deptno;

-- t_student, t_department 테이블 : 
-- 학과별(deptno1)로 가장 키가 큰 학생들의 이름과 키, 학과이름을 
-- 인라인뷰 를 사용하여 다음과 같이 출력하세요
SELECT * FROM t_student;
SELECT * FROM t_department;

SELECT 
	d.dname "학과명", a.max_height "최대키", s.name "학생이름", s.height "키"
FROM 
	(SELECT deptno1, MAX(height) max_height FROM t_student GROUP BY deptno1) a,
	t_student s, t_department d
WHERE 
	s.deptno1 = a.deptno1 AND s.height = a.max_height
	AND s.deptno1 = d.deptno;

-- t_student 테이블 : 
-- 학생의 키가 동일 학년의 평균 키보다 큰 학생들의 학년과 이름과 키, .
-- 해당 학년의 평균키를 출력하되, inline view 를 사용해서 아래와 같이 출력하세요.
-- 단 학년 칼럼은 오름 차순으로 정렬.
SELECT * FROM t_student;

SELECT 
	s.grade "학년", s.name "이름", s.height "키", a.avg_height "평균키"
FROM 
	(SELECT grade, AVG(height) avg_height FROM t_student GROUP BY grade) a,
	t_student s
WHERE 
	a.grade = s.grade AND s.height > a.avg_height
ORDER BY 1;

 

 

3. [폴더] 09_Constraint
1) Constraint01.sql

-- 제약조건 (Constraint)
-- 테이블 생성시 동시에 설정하기

-- t_dept2.dcode 참조 예정
SELECT * FROM t_dept2;	-- dcode : 0001, 1000 ... 1011

-- 제약조건명을 명시하지 않는 방법
DROP TABLE t_emp3 CASCADE CONSTRAINT;
CREATE TABLE t_emp3 (
	no NUMBER(4) PRIMARY KEY,
	name VARCHAR2(10) NOT NULL,
	jumin VARCHAR2(13) NOT NULL UNIQUE,
	area NUMBER(1) CHECK(area < 5),
	deptno VARCHAR2(6) REFERENCES t_dept2(dcode)
);

-- 별도의 항목으로 제약조건 정의 가능
-- not null은 별도로 지정 불가
DROP TABLE t_emp4 CASCADE CONSTRAINT;
CREATE TABLE t_emp4 (
	no NUMBER(4),
	name VARCHAR2(10) NOT NULL,
	jumin VARCHAR2(13) NOT NULL,
	area NUMBER(1),
	deptno VARCHAR2(6),
	PRIMARY KEY(no),
	UNIQUE(jumin),
	CHECK(area < 5),
	FOREIGN KEY(deptno) REFERENCES t_dept2(dcode)
);


-- 제약조건명을 명시하여 정의
DROP TABLE t_emp3 CASCADE CONSTRAINT;
CREATE TABLE t_emp3 (
	no NUMBER(4) CONSTRAINT emp3_no_pk PRIMARY KEY,
	name VARCHAR2(10) CONSTRAINT cmp3_name_nn NOT NULL,
	jumin VARCHAR2(13) 
		CONSTRAINT emp3_jumin_nn NOT NULL 
		CONSTRAINT emp3_jumin_uk UNIQUE,
	area NUMBER(1) CONSTRAINT emp3_area_dk CHECK(area < 5),
	deptno VARCHAR2(6) CONSTRAINT emp3_deptno_fk REFERENCES t_dept2(dcode)
);

DROP TABLE t_emp3 CASCADE CONSTRAINT;
CREATE TABLE t_emp3 (
	no NUMBER(4),
	name VARCHAR2(10) CONSTRAINT emp3_name_nn NOT NULL,
	jumin VARCHAR2(13) CONSTRAINT emp3_jumin_nn NOT NULL,
	area NUMBER(1),
	deptno VARCHAR2(6),
	CONSTRAINT emp3_no_pk PRIMARY KEY(no),
	CONSTRAINT emp3_jumin_uk UNIQUE(jumin),
	CONSTRAINT emp3_area_ck CHECK(area < 5),
	CONSTRAINT emp3_deptno_fk FOREIGN KEY(deptno) REFERENCES t_dept2(dcode)
);

-- 제약조건 조회하기
SELECT owner, constraint_name, constraint_type, status
FROM user_constraints
WHERE table_name = 'T_EMP4';	-- 테이블명 대문자로!

SELECT owner, constraint_name, constraint_type, status
FROM user_constraints
WHERE table_name = 'T_EMP3';	


-- #9005 : 제약조건에 맞는 / 위배되는 DML 시도해보기
INSERT INTO t_emp3 VALUES(1, '오라클', '1234561234567', 4, 1000);
-- 두번 실행하면 오류!
-- ORA-00001: unique constraint (SCOTT0316.EMP3_NO_PK) violated

INSERT INTO t_emp3 VALUES(2, '오라클', '1234561234567', 4, 1000);
-- jumin UNIQUE 오류!  
-- ORA-00001: unique constraint (SCOTT0316.EMP3_JUMIN_UK) violated

INSERT INTO t_emp3 VALUES(2, '오라클', '2222222222222222', 4, 1000);
-- VARCHAR2(13) 초과  오류! 
-- ORA-12899: value too large for column "SCOTT0316"."T_EMP3"."JUMIN" (actual: 16, maximum: 13)

INSERT INTO t_emp3 VALUES(2, '오라클', '2222222222222', 10, 1000);
-- CHECK 오류!
-- ORA-01438: value larger than specified precision allowed for this column

INSERT INTO t_emp3 VALUES(2, '오라클', '2222222222222', 3, 2000);
-- FK오류!  
-- ORA-02291: integrity constraint (SCOTT0316.EMP3_DEPTNO_FK) violated - parent key not found

INSERT INTO t_emp3 (NO, jumin, area, deptno) VALUES (2, '3333333333333', 4, 1001);
-- NN 오류! name 항목이 없음, 제약조건이 not null임...!!
-- ORA-01400: cannot insert NULL into ("SCOTT0316"."T_EMP3"."NAME")

-- INSERT뿐 아니라 UPDATE/DELETE 에서도 오류 발생 가능
UPDATE t_emp3 SET area = 10 WHERE NO = 1;  -- CHECK 값 오류

SELECT * FROM t_emp3;
-- 위의 SELECT문을 통해 deptno, 즉 dcode 1000이 참조되어 있는 것을 확인..!!
DELETE FROM t_dept2 WHERE dcode = 1000;  
-- 참조하고 있는 부모는 삭제 불가
-- ORA-02292: integrity constraint (SCOTT0316.EMP3_DEPTNO_FK) violated - child record found


-- 테이블 생성 후에 ALTER 명령 사용하여 제약조건 추가 가능!
-- t_emp4 의 name 컬럼 UNIQUE 제약 조건 추가
-- ALTER ~ ADD
-- ALTER ~ MODIFY
-- ALTER ~ DROP 

ALTER TABLE t_emp4 ADD CONSTRAINT emp4_name_uk UNIQUE(name);

-- t_emp4 테이블의 area 컬럼에 NOT NULL 제약조건 추가해보세요
ALTER TABLE t_emp4 ADD CONSTRAINT emp4_area_nn NOT NULL(area);	-- 에러
-- 이미 컬럼의 기본값인 NULL로 되어 있기 때문에 ADD가 아닌 MODIFY로 해야 한다.
-- 위의 112번째를 아래의 115번째줄처럼 바꿔서 처리해야 가능하다..!!
ALTER TABLE t_emp4 ADD CONSTRAINT emp4_name_uk UNIQUE(name);

-- 외래키 추가
-- t_emp4 테이블의 no 컬럼이 t_emp2 테이블의 
-- empno 컬럼의 값을 참조하도록 참조키 제약조건을 설정하세요
ALTER TABLE t_emp4 
ADD CONSTRAINT emp4_name_fk FOREIGN KEY(name) REFERENCES t_emp2(name);
-- 그냥 실행하면 에러다   ORA-02270: no matching unique or primary key for this column-list
-- 참조되는 부모테이블의 컬럼은 Primary Key 이거나 Unique 해야 한다!!!

-- 일단 부모테이블의 name을 unique로 바꾼뒤 위의 쿼리를 다시 실행하기..!!
-- 그럼 가능하다...
ALTER TABLE t_emp2
ADD CONSTRAINT emp2_name_uk UNIQUE(name);

-- t_emp3 테이블을 DROP 하고, 이전과 같은 조건으로 다시 만들되
-- t_dept2(dcode)를 참조하는 detpno 컬럼은 
-- 부모테이블의 해당 데이터가 삭제되면 함께 삭제되도록 설정
DROP TABLE t_emp3 CASCADE CONSTRAINT;
CREATE TABLE t_emp3 (
	no NUMBER(4) CONSTRAINT emp3_no_pk PRIMARY KEY,
	name VARCHAR2(10) CONSTRAINT cmp3_name_nn NOT NULL,
	jumin VARCHAR2(13) 
		CONSTRAINT emp3_jumin_nn NOT NULL 
		CONSTRAINT emp3_jumin_uk UNIQUE,
	area NUMBER(1) CONSTRAINT emp3_area_dk CHECK(area < 5),
	deptno VARCHAR2(6) 
		CONSTRAINT emp3_deptno_fk REFERENCES t_dept2(dcode)
		ON DELETE CASCADE	-- 부모 삭제되면 자식도 삭제
		--ON DELETE SET NULL	-- 부모 삭제되면 NULL 값으로
);

-- t_emp4 테이블의 name 필드의 제약조건 예
-- 부모테이블이 삭제되면 NULL이 되도록 설정하기 (ALTER 사용)
ALTER TABLE t_emp4 DROP CONSTRAINT emp4_name_fk; -- 일단 기존 제약조건 삭제

ALTER TABLE t_emp4 
ADD CONSTRAINT emp4_name_fk FOREIGN KEY(name)
	REFERENCES t_emp2(name)
	ON DELETE SET NULL; -- 부모 삭제되면 자식은 null로 변환
	
	
------------------------------------------------------------------------------
-- DISABLE NOVALIDATE
-- t_validate, t_novalidate 테이블 사용하기
SELECT * FROM t_novalidate;
SELECT * FROM t_validate;

SELECT owner, constraint_name, CONSTRAINT_type, status 
FROM user_constraints
WHERE table_name = 'T_VALIDATE';

INSERT INTO t_validate VALUES(1, 'DDD');  -- 처음에는 에러! PK이니깐..!!
-- ORA-00001: unique constraint (SCOTT0316.TV_NO_PK) violated

SELECT owner, constraint_name, constraint_type, status 
FROM user_constraints
WHERE table_name = 'T-NOVALIDATE';

INSERT INTO t_novalidate VALUES(1, 'DDD');  -- 처음에는 에러! PK이니깐..!!
-- ORA-00001: unique constraint (SCOTT0316.SYS_C007028) violated

 

 

4. [폴더] 10_Index
1) Index.sql

-- 인덱스 (Index)
-- 데이터(레코드)를 빠르게 접근하기 위해 <키, 주소> 쌍으로 구성된 데이터 구조

--인덱스가 없으면 테이블에서 특정값을 찾기 위해 모든 데이터를 다 뒤지는 TABLE SCAN 발생
--기본키를 위한 인덱스를 기본 인덱스,   
-- 일반적으론 테이블에 기본키가 있으면 기본키에 대한 기본 인덱스가 자동으로 생성된다.

-- 인덱스 종류
--  m-원 검색트리,  B-트리, B*-트리,  B+-트리 등이 있다.

-- 인덱스를 잘 사용하면 데이터베이스 성능이 좋아지게 할수도 있지만,
-- 잘못 설정하면 오히려 역효과가 날수도 있다.

-- 오라클
-- 기본적으로 INSERT 되는 순서대로 이입력됨.  정렬없이 저장됨.

-- 사원의 인덱스
SELECT empno, ROWID FROM t_emp;	-- ROWID, ROW INSERT 될때마다 부여됨, SELECT 시 기본적으로 참조

SELECT empno, ROWID FROM t_emp ORDER BY empno DESC;	-- ROWID도 내림차순이 됨

SELECT ename, ROWID
FROM t_emp ORDER BY ename DESC;

-- 인덱스 <key, rowid> 에 입력
-----------------------------------------------------------
-- (1) B-tree 인덱스
--	① UNIQUE INDEX
--  ② Non Unique INDEX
--  ③ Function Based Index
--  ④ Descending Index
--  ⑤ 결합 인덱스 (Composite Index)

-----------------------------------------------------------
-- UNIQUE INDEX
--   key 값이 중복되는 데이터가 없다는 뜻
--   이경우 인덱스 성능이 매우 좋아진다.
--   당장은 중복된 값이 안들어오지만, 향후에 중복될 값이 입력될 가능성이 있는 칼럼에는 
--   절.대.로 인덱스 생성하면 안됨!!!!!!!!

-- 인덱스 생성 구문
--   CREATE UNIQUE INDEX 인덱스명
--  ON 테이블이름(컬럼명1  ASC | DESC,  컬럼명2 ... )

-- 연습
-- t_dept2 테이블의  dname 컬럼에 UNIQUE INDEX 생성
--  인덱스의 이름은 idx_dept2_dname 으로 생성

SELECT * FROM t_dept2;
CREATE UNIQUE INDEX idx_dept2_dname ON t_dept2(dname);

INSERT INTO t_dept2 VALUES(9100, '임시매장', 1006, '서울지사');

INSERT INTO t_dept2 VALUES(9101, '임시매장', 1006, '부산지사');	-- 에러
-- ORA-00001: unique constraint (SCOTT0316.IDX_DEPT2_DNAME) violated


-- Non Unique INDEX
--  UNIQUE 인덱스가 성능은 좋으나 모든 컬럼에 다 만들수 있는 것은 아니다.
--  중복된 데이터가 들어가야 하는 경우는 Non Unique INDEX 사용

-- 인덱스 생성 구문
--   CREATE INDEX 인덱스명
--   ON 테이블이름(컬럼명1  ASC | DESC,  컬럼명2 ... )

-- 예제
-- t_professor 테이블의 position 칼럼에 Non unique 인덱스를 내림차순 생성
CREATE INDEX idx_prof_position
ON t_professor(POSITION DESC);

-- ① DBeaver에서 확인해보기

/*
 * 인덱스는 어느 칼럼에 만들어야 하나? 
 * 답은 WHERE절에 오는 조건 칼럼이나 조인 조건 칼럼에 만들어 두어야 함
 * 가령 WHERE sal = 100  <-- sal 컬럼에 인덱스 만들어야 함.
 * sal 칼럼에 인덱스가 있다면 오라클은 sal 칼럼의 인덱스를 활용할수 있다.
 * 
 * 그런데!
 * sal 컬럼에 인덱스 생성하고 나서..
 * 정작 sql 문장에서는 WHERE sal + 100 = 200 조건으로 조회하면..
 * 인덱스는 활용 못함.
 * 이런 현상을 INDEX Suppressing Error 라고 함
 * 인덱스는 잘 만들어 놓고.. SQL 를 잘 못 작성하여 인덱스 사용 못하는 경우.
 * 
 * 명심!  :  인덱스를 사용하려 했다면, WHERE 조건절에 절대로 다른 형태로 가공해서는 안됨.
 * 
 * 그런데, 꼭 그렇게 사용해야 한다면?
 * WHERE sal + 100 = 200.. ??
 * 
 * 그러면 인덱스를 생성할때 저 형태로 , 즉 sal + 100 인덱스를 생성하면 됨.
 * 이를 '함수 기반 인덱스' 라 함.
 * 
 */

CREATE INDEX idx_prof_pay_fbi 
ON t_professor(pay + 100);

-- ② DBeaver에서 확인해보기

-- 위와 같이 하면 오라클은 인덱스를 만들때 저 연산을 수행해서 인덱스 저장 가능.

-- 함수기반인덱스(FBI) 는 근본 해결책은 아니다.
-- 조건이 변경되면 다시 만들어야 하고..
-- FBI 는 기존 인덱스를 활용할수 없는 단점도 있다.  
--  ※ dbeaber 에서 생성 형태 확인해보자

--------------------------------------------
-- DESCENDING INDEX
--   인덱스들을 생성할때 내림차순 (큰 값이 먼저 오게) 인덱스 생성
--    큰값을 (큰값부터) 많이 조회하는 SQL에서 유리.
--    ex) 계좌조회.  최근날짜 (큰 날짜) 부터..
--    ex) 매출상위 매장순 ..  매출 큰 매장부터..

-- 예제
CREATE INDEX idx_prof_pay 
ON t_professor(pay DESC);

-- ③ DBeaver에서 확인해보기

 

[추가] DBeaver에서 확인 하기 - ①

[추가] DBeaver에서 확인 하기 - ②

[추가] DBeaver에서 확인 하기 - ③

'웹_프론트_백엔드 > JAVA프레임윅기반_풀스택' 카테고리의 다른 글

2020.04.13  (0) 2020.04.13
2020.04.10  (0) 2020.04.10
2020.04.08  (0) 2020.04.08
2020.04.07  (0) 2020.04.07
2020.04.06  (0) 2020.04.06