Everything has an expiration date
[Oracle] 20231026 [프로그램소스] - 231026_01_SCOTT, 231026_02_HR, 231026_03_SCOTT 본문
[Oracle]/SQL (Program source)
[Oracle] 20231026 [프로그램소스] - 231026_01_SCOTT, 231026_02_HR, 231026_03_SCOTT
Jelly-fish 2023. 10. 26. 17:48
20231026_01_SCOTT.SQL
-- [서브상단쿼리 내부 케이스문 실험]
--************************************************************************
SELECT ENAME, (SELECT CASE WHEN HIREDATE > E.HIREDATE
THEN '라라'
WHEN HIREDATE = E.HIREDATE
THEN '루루'
ELSE '하하'
END
FROM EMP
WHERE ENAME = E.ENAME) -- 단일 값이 될 수 있도록 WHERE 절을 이용해야 한다.
FROM EMP E;
SELECT TO_CHAR(HIREDATE, 'YYYY-MM')
FROM EMP;
SELECT CASE WHEN TO_CHAR(HIREDATE, 'YYYY-MM') = '1980-12'
THEN '왓핫핫'
WHEN TO_CHAR(HIREDATE, 'YYYY-MM') = '1981-02'
THEN '유후'
ELSE '루라라'
END "TEST"
FROM EMP;
SELECT ENAME
FROM EMP;
SELECT CASE WHEN TO_CHAR(HIREDATE, 'YYYY-MM') = '1980-12'
THEN '라라'
WHEN TO_CHAR(HIREDATE, 'YYYY-MM') = '1981-02'
THEN '루루'
ELSE '하하'
END
FROM EMP;
--************************************************************************
SELECT USER
FROM DUAL;
--==>> SCOTT
-- 지난시간 복습
--○ EMP 테이블과 DEPT 테이블을 대상으로
-- 직종이 MANAGER 와 CLERK 인 사원들만
-- 부서번호, 부서명, 사원명, 직종명, 급여 항목을 조회한다.
SELECT *
FROM EMP;
SELECT *
FROM DEPT;
SELECT E.DEPTNO "부서번호", D.DNAME "부서명", E.ENAME "사원명"
, E.JOB "직종명", E.SAL "급여"
FROM EMP E
FULL OUTER JOIN
DEPT D
ON
E.DEPTNO = D.DEPTNO
WHERE E.JOB IN ('MANAGER', 'CLERK');
SELECT E.DEPTNO, D.DNAME, E.ENAME, E.JOB, E.SAL
FROM TBL_EMP E
LEFT JOIN
TBL_DEPT D
ON E.DEPTNO = D.DEPTNO
WHERE E.JOB = 'MANAGER'
OR E.JOB = 'CLERK';
-- 부서번호, 부서명, 사원명, 직종명, 급여
-- DEPTNO DNAME ENAME JOB SAL
-- E, D D E E E
SELECT 부서번호, 부서명, 사원명, 직종명, 급여
FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;
SELECT DEPTNO, DNAME, ENAME, JOB, SAL
FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;
--==>> 에러 발생
-- (ORA-00904: "DENAME": invalid identifier)
--> 두 테이블 간 중복되는 컬럼에 대한
-- 소속 테이블을 정해줘야(명시해줘야) 한다.
-- DEPTNO 가 두 테이블 모두 존재하는 COLUMN 이므로
-- 어떤 테이블에서 가져와야 하는지 알 수 없다.
-- 양쪽에 모두 존재하는 컬럼을 꺼내쓸 때는, 부모 테이블의 컬럼을 꺼내 써야 한다.
-- 부모 테이블? - 어떤 테이블이 부모 테이블이고, 어떤 테이블이 자식 테이블일까?
-- 연결고리 컬럼 DEPTNO 컬럼을 봤을 때...
-- DEPT 컬럼에는 10, 20, 30, 40 이 하나씩이지만
-- EMP 에는 10이 여러 개, 20이 여러 개...
-- 즉, 하나의 부모에 여러 자식이 있으므로 DEPT 테이블이 부모가 되는 것이다.
--==>> 에러 발생
-- (ORA-00918: column ambiguously defined)
--> 두 테이블 간 중복되는 컬럼에 대한
-- 소속 테이블을 정해줘야(명시해줘야) 한다.
-- DEPTNO 가 두 테이블 모두 존재하는 COLUMN 이므로
-- 어떤 테이블에서 가져와야 하는지 알 수 없다.
--****************************************************************
-- → DNAME 이라는 컬럼은 DEPT 테이블에만 존재하므로
-- 테이블 별칭 ALIAS 를 따로 명시해 주지 않아도 괜찮지만
-- DEPTNO의 경우, EMP, DEPT 테이블에 모두 있는 컬럼이다 보니까
-- 두 테이블 중 어떤 테이블에서 가져와야 하는지를 오라클이 알 수 없으므로
-- 오류가 발생한 것이다.
-- ※ [총정리] ------------------------------------------------------------------
-- 따라서 양쪽에 겹치는 컬럼이 존재할 경우
-- 소속 테이블을 명시해 주어야 오류가 발생하지 않는다.
--------------------------------------------------------------------------------
-- 이때 소속 테이블을 명시할 경우, 부모 테이블로 사용해야 한다.
-- 두 개의 관계에서, DEPTNO 가 하나인 경우 = 부모
-- DEPTNO 가 여러 개 인 경우 = 자식
SELECT D.DEPTNO, DNAME, ENAME, JOB, SAL
FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;
-- ※ 두 테이블 간 중복되는 컬럼에 대해 소속 테이블을 명시하는 경우
-- 부모 테이블의 컬럼을 참조할 수 있도록 처리해야 한다.
SELECT *
FROM EMP; -- 자식테이블 (DEPTNO 의 값이 여러 개임.) → 자식
SELECT *
FROM DEPT; -- 부모 테이블 (DEPTNO 의 값이 단일 값임.) → 부모
SELECT D.DEPTNO, DNAME, ENAME, JOB, SAL
FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;
--★★ 두 테이블에 모두 포함되어 있는 중복된 컬럼이 아니더라도
-- 컬럼의 소속 테이블을 명시해 줄 수 있기를 권장한다.
-- 중복된 컬럼이 아니더라도 모두 테이블의 ALIAS를 앞에 붙여주었다.
SELECT D.DEPTNO, D.DNAME, E.ENAME, E.JOB, E.SAL
FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO;
--[왜? 컬럼의 소속 테이블을 명시해 주어야 하나?]
-- 처음에 컬럼을 발견하자마자 바로 불러왔다면 오류가 발생하지않았을 것이다
-- 별칭을 정해주지 않으면 FROM 에 속한 모든 테이블을 조회한 후에 컬럼을 가져오기 때문이다.
-- 즉, 별칭을 정하지 않으면 EMP, DEPT 테이블을 모두 조회해 보고 컬럼이 존재하는 테이블에서 가져오기 때문에
-- 별칭을 명시해주게 되면 테이블의 별칭과 일치하는 테이블만 조회하므로 양쪽 다 둘러보지 않는다.
-- 업무 지시가 명확해지는 것이다.
-- 즉, 모든 테이블을 조회하지 않고 명시해 준 ALIAS에 속하는 테이블만 조회하므로
-- 리소스가 감소한다.
-- 데이터 양이 많아지게 되면, ALIAS나 테이블명을 붙여주지 않은 쿼리문을 수행할 때
-- 딜레이가 더욱 발생할 수 있다.
-- [왜? 양쪽 테이블에서 중복 컬럼이 있을 경우 부모 테이블에서 가져와야 하는가]
-- 자식 것에서 따온 후에 OUTER JOIN을 하게 되면...
-- 부모 테이블에 있는 값이더라도 DEPTNO = 40
-- 자식 것에 40이 포함되어 있지 않으면 DEPTNO 가 40인 항목이 NULL 로 나온다.
SELECT E.DEPTNO, D.DNAME, E.ENAME, E.JOB, E.SAL
FROM EMP E, DEPT D
WHERE E.DEPTNO(+) = D.DEPTNO;
--==>>
/*
10 ACCOUNTING CLARK MANAGER 2450
10 ACCOUNTING KING PRESIDENT 5000
10 ACCOUNTING MILLER CLERK 1300
20 RESEARCH JONES MANAGER 2975
20 RESEARCH FORD ANALYST 3000
20 RESEARCH ADAMS CLERK 1100
20 RESEARCH SMITH CLERK 800
20 RESEARCH SCOTT ANALYST 3000
30 SALES WARD SALESMAN 1250
30 SALES TURNER SALESMAN 1500
30 SALES ALLEN SALESMAN 1600
30 SALES JAMES CLERK 950
30 SALES BLAKE MANAGER 2850
30 SALES MARTIN SALESMAN 1250
(null) OPERATIONS ← CHECK~!!! (E.DEPTNO)
자식 테이블 EMP 로 DEPTNO를 가져오자
E.DEPTNO 부서번호가 NULL인것을 확인!!
*/
SELECT D.DEPTNO, D.DNAME, E.ENAME, E.JOB, E.SAL
FROM EMP E, DEPT D
WHERE E.DEPTNO(+) = D.DEPTNO;
--==>>
/*
10 ACCOUNTING CLARK MANAGER 2450
10 ACCOUNTING KING PRESIDENT 5000
10 ACCOUNTING MILLER CLERK 1300
20 RESEARCH JONES MANAGER 2975
20 RESEARCH FORD ANALYST 3000
20 RESEARCH ADAMS CLERK 1100
20 RESEARCH SMITH CLERK 800
20 RESEARCH SCOTT ANALYST 3000
30 SALES WARD SALESMAN 1250
30 SALES TURNER SALESMAN 1500
30 SALES ALLEN SALESMAN 1600
30 SALES JAMES CLERK 950
30 SALES BLAKE MANAGER 2850
30 SALES MARTIN SALESMAN 1250
40 OPERATIONS ← CHECK~!!! (D.DEPTNO)
부모 테이블 DEPT 로 DEPTNO를 가져오자
D.DEPTNO 부서번호가 40으로 적용된 것을 확인!!
*/
SELECT *
FROM EMP;
--○ SELF JOIN (자기 조인)
-- EMP 테이블의 데이터를 다음과 같이 조회할 수 있도록 쿼리문을 구성한다.
/*
-------------------------------------------------------------------------
사원번호 사원명 직종명 관리자번호 관리자명 관리자직종명
-------------------------------------------------------------------------
7369 SMITH CLERK 7902 FORD ANALYST
*/
SELECT E3.EMPNO "사원번호"
, E3.ENAME "사원명"
, E3.JOB "직종명"
, MT.EMPNO "관리자번호"
, MT.ENAME "관리자명"
, MT.JOB "관리자직종명"
FROM
EMP E3
FULL OUTER JOIN
(
SELECT E2.EMPNO "EMPNO"
, E2.ENAME "ENAME"
, E2.JOB "JOB"
FROM EMP E2
WHERE E2. EMPNO
IN
(
SELECT E.MGR
FROM
(
SELECT EMPNO "사원번호"
, ENAME "사원명"
, JOB "직종명"
, MGR "관리자번호"
FROM EMP
) T
INNER JOIN
EMP E
ON E.EMPNO = T.사원번호
WHERE E.MGR = T.관리자번호
)
) MT
ON E3.MGR = MT.EMPNO
ORDER BY 사원번호 ASC;
SELECT E3.EMPNO "사원번호"
, E3.ENAME "사원명"
, E3.JOB "직종명"
, MT.EMPNO "관리자번호"
, MT.ENAME "관리자명"
, MT.JOB "관리자직종명"
FROM
EMP E3 -- 일반 EMP 테이블 (모든 사원 추출 용도)
FULL OUTER JOIN
(
SELECT E2.EMPNO "EMPNO"
, E2.ENAME "ENAME"
, E2.JOB "JOB"
FROM EMP E2
WHERE E2. EMPNO
IN
(
SELECT E.MGR
FROM EMP E
)
) MT -- 매니저 테이블 (매니저에 해당하는 사원 추출 용도)
ON E3.MGR = MT.EMPNO
ORDER BY 사원번호 ASC;
SELECT ENAME
FROM EMP
WHERE ENAME LIKE TO_CHAR('S' + 'MITH');
SELECT CHAR('S' + 'MITH')
FROM DUAL;
-- EMP 테이블의 데이터를 다음과 같이 조회할 수 있도록 쿼리문을 구성한다.
/*
총 2개의 테이블이 필요하다.
-------------------------------------------------------------------------
사원번호 사원명 직종명 관리자번호 관리자명 관리자직종명
EMPNO ENAME JOB MGR
EMPNO ENAME JOB
-------------------------------------------------------------------------
7369 SMITH CLERK 7902 FORD ANALYST
---------------------------------------- E1
-------------------------------- E2
*/
SELECT 사원번호 사원명 직종명 관리자번호 관리자명 관리자직종명
FROM EMP;
SELECT EMPNO "사원번호", ENAME "사원명", JOB "직종명", MGR "관리자번호"
, EMPNO "관리자번호" , ENAME "관리자명", JOB "관리자직종명"
FROM EMP;
SELECT E1.EMPNO "사원번호", E1.ENAME "사원명", E1.JOB "직종명", E1.MGR "관리자번호"
, E2.EMPNO "관리자번호", E2.ENAME "관리자명", E2.JOB "관리자직종명" -- 관리자
FROM EMP E1 JOIN EMP E2
ON E1.MGR = E2.EMPNO;
-- KING 이 빠져 있으므로 OUTER 조인을 사용해야 한다.
SELECT E1.EMPNO "사원번호", E1.ENAME "사원명", E1.JOB "직종명", E1.MGR "관리자번호"
, E2.EMPNO "관리자번호", E2.ENAME "관리자명", E2.JOB "관리자직종명" -- 관리자
FROM EMP E1 LEFT JOIN EMP E2
ON E1.MGR = E2.EMPNO;
-- KING이 포함되어 있다!
-- 92 코드로 변경
-- LEFT 조인 삭제 -> CROSS 조인으로 변경 (92)
-- 조건식의 E2.EMPNO(+) 구성.
SELECT E1.EMPNO "사원번호", E1.ENAME "사원명", E1.JOB "직종명", E1.MGR "관리자번호"
, E2.EMPNO "관리자번호", E2.ENAME "관리자명", E2.JOB "관리자직종명" -- 관리자
FROM EMP E1, EMP E2
WHERE E1.MGR = E2.EMPNO(+);
20231026_02_HR.SQL
SELECT USER
FROM DUAL;
--==>> HR
-- ID : HR, PW : LION
-- ID : SCOTT, PW : TIGER
--○ 세 개 이상의 테이블 조인(JOIN, 결합)
-- 형식 1. (SQL 1992 CODE)
SELECT 테이블명1.컬럼명, 테이블명2.컬럼명, 테이블명3.컬럼명
FROM 테이블명1, 테이블명2, 테이블명3
WHERE 테이블명1.컬럼명1 = 테이블명2.컬럼명1
AND 테이블명2.컬럼명2 = 테이블명3.컬럼명2;
-- 형식 2. (SQL 1999 CODE)
SELECT 테이블명1.컬럼명, 테이블명2.컬럼명, 테이블명3.컬럼명
FROM 테이블명1 JOIN 테이블명2
ON 테이블명1.컬럼명1 = 테이블명2.컬럼명1 -- ① 테이블1, 테이블2를 먼저 결합
JOIN 테이블명3 -- ② 결합된 결과에 또다른 테이블3 결합
ON 테이블명2.컬럼명2 = 테이블명3.컬럼명2;
--○ HR 계정 소유의 테이블 또는 뷰 목록 조회
SELECT *
FROM TAB;
--==>>
/*
COUNTRIES TABLE
DEPARTMENTS TABLE
EMPLOYEES TABLE
EMP_DETAILS_VIEW VIEW
JOBS TABLE
JOB_HISTORY TABLE
LOCATIONS TABLE
REGIONS TABLE
*/
--○ HR.JOBS, HR.EMPLOYEES, HR.DEPARTMENTS 테이블을 대상으로
-- 직원들의 데이터를
-- FIRST_NAME, LAST_NAME, JOB_TITLE, DEPARTMENT_NAME 항목으로 조회한다.
-- JOBS : JOB_ID JOB_TITLE MIN_SALARY MAX_SALARY
-- EMPLOYEES : EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL
-- PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
-- DEPARTMENTS : DEPARTMENT_ID DEPARTMENT_NAME MANAGER_ID LOCATION_ID
-- FULL OUTER JOIN
SELECT E.FIRST_NAME, E.LAST_NAME, J.JOB_TITLE, D.DEPARTMENT_NAME
FROM JOBS J
INNER JOIN
EMPLOYEES E
ON J.JOB_ID = E.JOB_ID
FULL OUTER JOIN -- 전체를 OUTER JOIN 한 후에
DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID
WHERE E.FIRST_NAME IS NOT NULL; -- 이름이 NULL 이 아닌 사원만 조회.
-- LEFT OUTER
SELECT E.FIRST_NAME, E.LAST_NAME, J.JOB_TITLE, D.DEPARTMENT_NAME
FROM JOBS J
INNER JOIN
EMPLOYEES E
ON J.JOB_ID = E.JOB_ID
LEFT OUTER JOIN -- EMPLOYEES 테이블의 전체 요소가 출력될 수 있도록
-- LEFT OUTER JOIN 으로 구성.
DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID;
-- [선생님 풀이]
--○ HR.JOBS, HR.EMPLOYEES, HR.DEPARTMENTS 테이블을 대상으로
-- 직원들의 데이터를
-- FIRST_NAME, LAST_NAME, JOB_TITLE, DEPARTMENT_NAME 항목으로 조회한다.
-- E E J D
-- 부서에 대한 정보
SELECT *
FROM DEPARTMENTS;
--↑
--◎ 관계 컬럼 : DEPARTMENT_ID → 관계 컬럼
--↓
-- 사원에 대한 정보
-- 직종의 ID가 들어 감.
SELECT *
FROM EMPLOYEES;
--↑
--◎ 관계 컬럼 : JOB_ID → 관계 컬럼
--↓
-- 직종 ID별
-- 직종의 이름이 들어 감.
SELECT *
FROM JOBS;
SELECT E.FIRST_NAME, E.LAST_NAME, D.DEPARTMENT_NAME
FROM EMPLOYEES E JOIN DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID;
-- 레코드의 개수 확인.
SELECT COUNT(*)
FROM EMPLOYEES;
--==>> 107
SELECT COUNT(*)
FROM EMPLOYEES E JOIN DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID;
--==>> 106
--> 연결되지 못한 하나가 제외되었음을 확인할 수 있다!!
SELECT *
FROM EMPLOYEES;
--==>>
/*
DEPARTMENT_ID가 (null)이어서 제외됨.
178 Kimberely Grant KGRANT 011.44.1644.429263 2007-05-24 SA_REP 7000 0.15 149 (null)
*/
-- 모든 사원을 반영할 수 있도록
-- LEFT OUTER JOIN 을 사용.
SELECT COUNT(*)
FROM EMPLOYEES E LEFT OUTER JOIN DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID;
--==>> 107
--> LEFT OUTER JOIN 을 하자 Kimberely 가 제대로 반영되었음을 확인할 수 있다.
SELECT E.FIRST_NAME, E.LAST_NAME, J.JOB_TITLE, D.DEPARTMENT_NAME
FROM EMPLOYEES E LEFT OUTER JOIN DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID
JOIN JOBS J
ON E.JOB_ID = J.JOB_ID;
-- 연결고리 컬럼 안의 값이 NULL 일 때 데이터가 제외되므로 이를 주의하여 관찰하고
-- 제외되는 대상이 있는지 COUNT(*) 함수를 사용하여 확인한 후에
-- INNER JOIN 을 써야할지, OUTER JOIN 을 써야할지 결정해야 한다.
--○ EMPLOYEES, DEPARTMENTS, JOBS, LOCATIONS, COUNTRIES, REGIONS 테이블을 대상으로
-- 직원들의 데이터를 다음과 같이 조회한다.
-- FIRST_NAME, LAST_NAME, JOB_TITLE, DEPARTMENT_NAME, CITY, COUNTRY_NAME, REGION_NAME
-- [테이블별 컬럼]===============================================================
-- JOBS : JOB_ID JOB_TITLE MIN_SALARY MAX_SALARY
-- EMPLOYEES : EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL
-- PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT
-- MANAGER_ID DEPARTMENT_ID
-- DEPARTMENTS : DEPARTMENT_ID DEPARTMENT_NAME MANAGER_ID LOCATION_ID
-- LOCATIONS : LOCATION_ID STREET_ADDRESS POSTAL_CODE CITY
-- STATE_PROVINCE COUNTRY_ID
-- COUNTRIES : COUNTRY_ID COUNTRY_NAME REGION_ID
-- REGIONS : REGION_ID REGION_NAME
--==============================================================================
-- FIRST_NAME, LAST_NAME, JOB_TITLE, DEPARTMENT_NAME, CITY, COUNTRY_NAME, REGION_NAME
-- E E J D L C R
-- 99 CODE!! - FULL OUTER JOIN 사용 후, WHERE 조건 걸기.
SELECT E.FIRST_NAME, E.LAST_NAME, J.JOB_TITLE, D.DEPARTMENT_NAME
, L.CITY, C.COUNTRY_NAME, R.REGION_NAME
FROM EMPLOYEES E
FULL OUTER JOIN
JOBS J
ON E.JOB_ID = J.JOB_ID
FULL OUTER JOIN
DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID
FULL OUTER JOIN
LOCATIONS L
ON D.LOCATION_ID = L.LOCATION_ID
FULL OUTER JOIN
COUNTRIES C
ON L.COUNTRY_ID = C.COUNTRY_ID
FULL OUTER JOIN
REGIONS R
ON C.REGION_ID = R.REGION_ID
WHERE E.FIRST_NAME IS NOT NULL;
-- 99 CODE!! - 모두 LEFT OUTER JOIN 사용.
SELECT E.FIRST_NAME, E.LAST_NAME, J.JOB_TITLE, D.DEPARTMENT_NAME
, L.CITY, C.COUNTRY_NAME, R.REGION_NAME
FROM EMPLOYEES E
LEFT OUTER JOIN
JOBS J
ON E.JOB_ID = J.JOB_ID
LEFT OUTER JOIN
DEPARTMENTS D
ON E.DEPARTMENT_ID = D.DEPARTMENT_ID
LEFT OUTER JOIN
LOCATIONS L
ON D.LOCATION_ID = L.LOCATION_ID
LEFT OUTER JOIN
COUNTRIES C
ON L.COUNTRY_ID = C.COUNTRY_ID
LEFT OUTER JOIN
REGIONS R
ON C.REGION_ID = R.REGION_ID
WHERE E.FIRST_NAME IS NOT NULL;
-- 92 CODE!!
SELECT E.FIRST_NAME, E.LAST_NAME, J.JOB_TITLE, D.DEPARTMENT_NAME
, L.CITY, C.COUNTRY_NAME, R.REGION_NAME
FROM EMPLOYEES E, JOBS J, DEPARTMENTS D, LOCATIONS L, COUNTRIES C, REGIONS R
WHERE E.JOB_ID = J.JOB_ID(+)
AND
E.DEPARTMENT_ID = D.DEPARTMENT_ID(+)
AND
D.LOCATION_ID = L.LOCATION_ID(+)
AND
L.COUNTRY_ID = C.COUNTRY_ID(+)
AND
C.REGION_ID = R.REGION_ID(+);
-- FIRST_NAME, LAST_NAME, JOB_TITLE, DEPARTMENT_NAME, CITY, COUNTRY_NAME, REGION_NAME
20231026_03_SCOTT.SQL
SELECT USER
FROM DUAL;
--==>> SCOTT
---------------------------------------------------
-- JOIN 을 통해 테이블을 결합시키게 되면
-- A 테이블 B 테이블 (조건에 맞게끔 연결시킨다.)
-- 좌우로 합치는 것이다.
-- 합집합 UNION
-- UNION 은 길게 세로로 합치는 것
---------------------------------------------------
--■■■ UNION / UNION ALL ■■■--
--○ 실습 테이블 생성(테이블명 : TBL_JUMUN)
CREATE TABLE TBL_JUMUN -- 주문 테이블 생성
( JUNO NUMBER -- 주문 번호
, JECODE VARCHAR2(30) -- 제품 코드
, JUSU NUMBER -- 주문 수량
, JUDAY DATE DEFAULT SYSDATE -- 주문 일자
);
--==>> Table TBL_JUMUN이(가) 생성되었습니다.
-- 고객의 주문이 발생(접수)되었을 경우
-- 주문 내용에 대한 데이터가 입력될 수 있도록 처리하는 테이블
-- 실습 진행 간 제품 코드(JECODE) 에는
-- 편의상 제품의 코드를 참조한다고 가정하고 제품의 이름을 입력한다.
--○ 데이터 입력 → 고객의 주문 발생(접수)
INSERT INTO TBL_JUMUN
VALUES(1, '포스틱', 20, TO_DATE('2001-11-01 09:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(2, '빅파이', 10, TO_DATE('2001-11-01 10:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(3, '사또밥', 30, TO_DATE('2001-11-01 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(4, '포카칩', 20, TO_DATE('2001-11-02 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(5, '포카칩', 30, TO_DATE('2001-11-03 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(6, '빅파이', 20, TO_DATE('2001-11-04 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(7, '홈런볼', 10, TO_DATE('2001-11-05 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(8, '홈런볼', 10, TO_DATE('2001-11-06 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(9, '오예스', 20, TO_DATE('2001-11-07 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(10, '사또밥', 10, TO_DATE('2001-11-08 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(11, '빼빼로', 20, TO_DATE('2001-11-09 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(12, '감자깡', 10, TO_DATE('2001-11-10 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(13, '빼빼로', 20, TO_DATE('2001-11-11 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(14, '홈런볼', 20, TO_DATE('2001-11-12 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(15, '포카칩', 10, TO_DATE('2001-11-13 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(16, '포카칩', 20, TO_DATE('2001-11-16 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(17, '마이쮸', 20, TO_DATE('2001-11-17 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(18, '맛동산', 30, TO_DATE('2001-11-18 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(19, '맛동산', 20, TO_DATE('2001-11-19 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
INSERT INTO TBL_JUMUN
VALUES(20, '꼬북칩', 30, TO_DATE('2001-11-20 11:10:10', 'YYYY-MM-DD HH24:MI:SS'));
--==>> 1 행 이(가) 삽입되었습니다. * 20
--○ 확인
SELECT *
FROM TBL_JUMUN;
--==>>
/*
JUNO JECODE JUSU JUDAY
1 포스틱 20 2001-11-01
2 빅파이 10 2001-11-01
3 사또밥 30 2001-11-01
4 포카칩 20 2001-11-02
5 포카칩 30 2001-11-03
6 빅파이 20 2001-11-04
7 홈런볼 10 2001-11-05
8 홈런볼 10 2001-11-06
9 오예스 20 2001-11-07
10 사또밥 10 2001-11-08
11 빼빼로 20 2001-11-09
12 감자깡 10 2001-11-10
13 빼빼로 20 2001-11-11
14 홈런볼 20 2001-11-12
15 포카칩 10 2001-11-13
16 포카칩 20 2001-11-16
17 마이쮸 20 2001-11-17
18 맛동산 30 2001-11-18
19 맛동산 20 2001-11-19
20 꼬북칩 30 2001-11-20
*/
--○ 커밋
COMMIT;
--==>> 커밋 완료.
-- ALTER SESSION SET을 테이블 COMMIT 이전에 하게 되면
-- AUTO COMMIT(자동커밋)이 되어버리므로 항상 유의하자.
--○ 날짜에 대한 세션 설정 변경
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
--○ 확인
SELECT *
FROM TBL_JUMUN;
--==>>
/*
JUNO JECODE JUSU JUDAY
1 포스틱 20 2001-11-01 09:10:10
2 빅파이 10 2001-11-01 10:10:10
3 사또밥 30 2001-11-01 11:10:10
4 포카칩 20 2001-11-02 11:10:10
5 포카칩 30 2001-11-03 11:10:10
6 빅파이 20 2001-11-04 11:10:10
7 홈런볼 10 2001-11-05 11:10:10
8 홈런볼 10 2001-11-06 11:10:10
9 오예스 20 2001-11-07 11:10:10
10 사또밥 10 2001-11-08 11:10:10
11 빼빼로 20 2001-11-09 11:10:10
12 감자깡 10 2001-11-10 11:10:10
13 빼빼로 20 2001-11-11 11:10:10
14 홈런볼 20 2001-11-12 11:10:10
15 포카칩 10 2001-11-13 11:10:10
16 포카칩 20 2001-11-16 11:10:10
17 마이쮸 20 2001-11-17 11:10:10
18 맛동산 30 2001-11-18 11:10:10
19 맛동산 20 2001-11-19 11:10:10
20 꼬북칩 30 2001-11-20 11:10:10
*/
--○ 추가 데이터 입력 → 2001년 부터 시작된 주문이 현재(2023년)까지 계속 발생~!!! 진행중~!!!
-- 과자 사업 하는 현욱이가 장사가 잘 되구 있다
INSERT INTO TBL_JUMUN VALUES(98764, '꼬북칩', 10, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98765, '빼빼로', 30, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98766, '빼빼로', 20, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98767, '에이스', 40, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98768, '홈런볼', 10, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98769, '감자깡', 20, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98770, '맛동산', 10, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98771, '웨하스', 20, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98772, '맛동산', 30, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98773, '오레오', 20, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98774, '빼빼로', 50, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
INSERT INTO TBL_JUMUN VALUES(98775, '오감자', 10, SYSDATE);
--==>> 1 행 이(가) 삽입되었습니다.
--○ 확인
SELECT *
FROM TBL_JUMUN;
--==>>
/*
1 포스틱 20 2001-11-01 09:10:10
2 빅파이 10 2001-11-01 10:10:10
3 사또밥 30 2001-11-01 11:10:10
4 포카칩 20 2001-11-02 11:10:10
5 포카칩 30 2001-11-03 11:10:10
6 빅파이 20 2001-11-04 11:10:10
7 홈런볼 10 2001-11-05 11:10:10
8 홈런볼 10 2001-11-06 11:10:10
9 오예스 20 2001-11-07 11:10:10
10 사또밥 10 2001-11-08 11:10:10
11 빼빼로 20 2001-11-09 11:10:10
12 감자깡 10 2001-11-10 11:10:10
13 빼빼로 20 2001-11-11 11:10:10
14 홈런볼 20 2001-11-12 11:10:10
15 포카칩 10 2001-11-13 11:10:10
16 포카칩 20 2001-11-16 11:10:10
17 마이쮸 20 2001-11-17 11:10:10
18 맛동산 30 2001-11-18 11:10:10
19 맛동산 20 2001-11-19 11:10:10
20 꼬북칩 30 2001-11-20 11:10:10
98764 꼬북칩 10 2023-10-26 12:46:10
98765 빼빼로 30 2023-10-26 12:46:58
98766 빼빼로 20 2023-10-26 12:47:25
98767 에이스 40 2023-10-26 12:47:53
98768 홈런볼 10 2023-10-26 12:48:16
98769 감자깡 20 2023-10-26 12:48:38
98770 맛동산 10 2023-10-26 12:49:03
98771 웨하스 20 2023-10-26 12:49:25
98772 맛동산 30 2023-10-26 12:49:45
98773 오레오 20 2023-10-26 12:50:08
98774 빼빼로 50 2023-10-26 12:50:29
98775 오감자 10 2023-10-26 12:50:54
*/
--※ 현욱이가 과자 쇼핑몰 운영 중...
-- TBL_JUMIN 테이블이 무거워진 상황
--◎ 부가 설명
--◎ [테이블이 무거워졌다?]*********************************************************
-- 부서 테이블의 경우 : 꾸준히 계속 늘어나진 않는다.
-- 마트에서 판매하는 물품들의 가짓수 : 상당히 많다. 5만 가지 정도...
-- 새로운 물건이 들어왔을 때, 5만 2천 건...
-- 마트에서 더 이상 취급하지 말자, 4만 7천 건... 상당히 변동적이다.
-- 대략 5만 건의 물품을 다루는데, 데이터의 수량이 변화한다.
-- 그럼에도 유지된다.
-- 한 해 지날 때마다, 회사 사원들의 수가 1000건 이상 증가하지 않는다.
-- 하지만 쇼핑몰의 주문 테이블의 경우
-- 사용자가 주문이 발생할 때마다 계속해서 데이터의 INSERT가 일어나는 테이블이다.
-- → 즉, 회사 사원 테이블, 부서 테이블, 물품 테이블처럼
-- 일정 수준의 데이터량에서 예상 가능한 변동을 일으키는 테이블이 아니다.
-- 하나의 테이블 내부에 데이터가 꽉 찼을 경우, 다른 테이블에 넣으라는 식으로 프로그램 구성 불가능하다.
-- 이렇게 구성하려면 프로그램을 아예 다 뜯어 고쳐야 한다.
-- 즉, 새로운 테이블을 생성해서 데이터가 저장되는 공간을 새로운 테이블로 저장되도록 진행할 수 없다.
-- A 테이블이 차면 B 테이블에 넣어 → (Ⅹ) 불가능!
-- 쌓인 데이터들을 전부 지워버린다? -(Ⅹ) 하 면 안 돼
-- 예를 들어... 반품이 들어왔다면?
-- 이전에 발송했던 주문내역들을 모두 지워놓은 상황이라면 이것을 확인할 수가 없어진다.
-- 이것을 삭제했는데, 국세청에서 확인을 나왔다면? -> 안 된다!
-- 꽉 찬 테이블의 일부를 다른 곳에 백업시키고 그 만큼을 비워준다.
-- 그렇게 되면 빈 공간에 새로운 데이터들을 담을 수가 있다!! (○)
-- TBL_JUMUN 의 데이터들을 다른 곳에 이관시키도록 처리하자!
-- 2002년의 주문 내역들을 잘라내서 다른 테이블에 옮겨 담으면 되겠다!
--******************************************************************************
--※ 현욱이가 과자 쇼핑몰 운영 중...
-- TBL_JUMIN 테이블이 무거워진 상황
-- 어플리케이션과의 연동으로 인해 주문 내역을
-- 앞으로는 다른 테이블에 저장될 수 있도록 만드는 것은 거의 불가능한 상황.
-- 기존의 모든 데이터를 덮어놓고 지우는 것도 불가능한 상황.
-- → 결과적으로...
-- 현재까지 누적된 주문 데이터 중
-- 금일 발생한 주문 내역을 제외하고
-- 나머지 데이터를 다른 테이블(TBL_JUMUNBACKUP)로
-- 데이터를 이관하여 백업을 수행할 계획
-- TBL_JUMUN 안에 오늘 주문한 내역이 아닌 것들을 저장해 보자.
-- 값 확인
SELECT *
FROM TBL_JUMUN;
-- 테이블 생성 (TBL_JUMUNBACKUP)
-- 오늘 날짜가 아닌 데이터들만 조회하도록 변경.
SELECT TO_CHAR(JUDAY, 'YYYY-MM-DD')
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
-- 이것을 백업 테이블에 저장..
CREATE TABLE TBL_JUMUNBACKUP
AS
SELECT *
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
SELECT *
FROM TBL_JUMUNBACKUP;
-------------------------------------------------------------------------------
-- 테이블 복사 (JUMUN 테이블)
CREATE TABLE TBL_JUMUN_TEST_TABLE
AS
SELECT *
FROM TBL_JUMUN;
-- 이전 날짜 테이블에서 삭제하기.
DELETE
FROM TBL_JUMUN_TEST_TABLE
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
-- 확인 (오늘 날짜만 남아있음)
SELECT *
FROM TBL_JUMUN_TEST_TABLE;
/*
98764 꼬북칩 10 2023-10-26 12:46:10
98765 빼빼로 30 2023-10-26 12:46:58
98766 빼빼로 20 2023-10-26 12:47:25
98767 에이스 40 2023-10-26 12:47:53
98768 홈런볼 10 2023-10-26 12:48:16
98769 감자깡 20 2023-10-26 12:48:38
98770 맛동산 10 2023-10-26 12:49:03
98771 웨하스 20 2023-10-26 12:49:25
98772 맛동산 30 2023-10-26 12:49:45
98773 오레오 20 2023-10-26 12:50:08
98774 빼빼로 50 2023-10-26 12:50:29
98775 오감자 10 2023-10-26 12:50:54
*/
CREATE TABLE TBL_JUMUNBACKUP2
AS
SELECT *
FROM 금일발생한주문이아닌것;
SELECT *
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
---------------------------------
-- '2023-10-26'
CREATE TABLE TBL_JUMUNBACKUP2
AS
SELECT *
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
--==>> Table TBL_JUMUNBACKUP2이(가) 생성되었습니다.
-- 확인
SELECT *
FROM TBL_JUMUNBACKUP2;
--==>>
/*
1 포스틱 20 2001-11-01 09:10:10
2 빅파이 10 2001-11-01 10:10:10
3 사또밥 30 2001-11-01 11:10:10
4 포카칩 20 2001-11-02 11:10:10
5 포카칩 30 2001-11-03 11:10:10
6 빅파이 20 2001-11-04 11:10:10
7 홈런볼 10 2001-11-05 11:10:10
8 홈런볼 10 2001-11-06 11:10:10
9 오예스 20 2001-11-07 11:10:10
10 사또밥 10 2001-11-08 11:10:10
11 빼빼로 20 2001-11-09 11:10:10
12 감자깡 10 2001-11-10 11:10:10
13 빼빼로 20 2001-11-11 11:10:10
14 홈런볼 20 2001-11-12 11:10:10
15 포카칩 10 2001-11-13 11:10:10
16 포카칩 20 2001-11-16 11:10:10
17 마이쮸 20 2001-11-17 11:10:10
18 맛동산 30 2001-11-18 11:10:10
19 맛동산 20 2001-11-19 11:10:10
20 꼬북칩 30 2001-11-20 11:10:10
*/
--> TBL_JUMUN 테이블의 데이터들 중
-- 금일 주뭄 내역 이외의 데이터는 모두 TBL_JUMUNBACKUP2 테이블에
-- 백업을 마친 상태~!!!
-- TBL_JUMUN 테이블의 데이터들 중
-- 백업을 마친 데이터들... 즉, 금일 발생한 주문 내역이 아닌 데이터들
-- 제거
-- 데이터 확인
SELECT *
FROM TBL_JUMUN;
-- 제거해야 할 데이터 확인
SELECT *
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
-- 제거해야 될 데이터에 문제가 없다면...
-- 이전 날짜 테이블에서 삭제하기.
DELETE
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
--==>> 20개 행 이(가) 삭제되었습니다.
-- 확인 (오늘 날짜만 남아있음)
SELECT *
FROM TBL_JUMUN;
-- 선생님 설명
SELECT *
FROM TBL_JUMUN
WHERE 금일발생한주문이 아닌것;
SELECT *
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
DELETE
FROM TBL_JUMUN
WHERE TO_CHAR(JUDAY, 'YYYY-MM-DD') != TO_CHAR(SYSDATE, 'YYYY-MM-DD');
--==>> 20개 행 이(가) 삭제되었습니다.
-- (→ 93763개 행이 삭제된 상황)
-- 확인
SELECT *
FROM TBL_JUMUN;
--==>>
/*
JUNO JECODE JUSU JUDAY
98764 꼬북칩 10 2023-10-26 12:46:10
98765 빼빼로 30 2023-10-26 12:46:58
98766 빼빼로 20 2023-10-26 12:47:25
98767 에이스 40 2023-10-26 12:47:53
98768 홈런볼 10 2023-10-26 12:48:16
98769 감자깡 20 2023-10-26 12:48:38
98770 맛동산 10 2023-10-26 12:49:03
98771 웨하스 20 2023-10-26 12:49:25
98772 맛동산 30 2023-10-26 12:49:45
98773 오레오 20 2023-10-26 12:50:08
98774 빼빼로 50 2023-10-26 12:50:29
98775 오감자 10 2023-10-26 12:50:54
*/
-- 커밋
COMMIT;
--==>> 커밋 완료.
--※ 아직 제품 발송이 이루어지지 않은 금일 주문 데이터를 제외하고
-- 이전의 모든 주문 데이터들이 삭제된 상황이므로
-- 테이블은 행(레코드)의 개수가 줄어들어 매우 가벼워진 상황이다.
-- 그런데, 지금까지 주문받은 내역에 대한 정보를
-- 제품별 총 주문량으로나타내어야 할 상황이 발생하게 되었다.
-- 그렇다면, TBL_JUMUNBACKUP 테이블이ㅡ 레코드(행)와
-- TBL_JUMUN 테이블의 레코드(행)를 모두 합쳐
-- 하나의 테이블을 조회하는 것과 같은 결과를 확인할 수 있도록
-- 조회가 이루어져야 한다.
-- 이때, 사용할 수 있는 것이 UNION!!
--※ 컬럼과 컬럼의 관계를 고려하여 테이블을 결합하고자 하는 경우
-- JOIN 을 사용하지만
-- 레코드와 레코드를 결합하고자 하는 경우
-- UNION / UNION ALL 을 사용할 수 있다.
SELECT *
FROM TBL_JUMUNBACKUP;
SELECT *
FROM TBL_JUMUN;
-- UNION 을 넣자, 두 개의 테이블의 레코드가 합쳐진 것을 확인할 수 있다.
SELECT *
FROM TBL_JUMUNBACKUP
UNION
SELECT *
FROM TBL_JUMUN;
-- ↑
-- UNION 과 UNION ALL 의 차이점을 찾을 수 없다.
-- ↓
SELECT *
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT *
FROM TBL_JUMUN;
----------------------------------------
SELECT *
FROM TBL_JUMUN
UNION
SELECT *
FROM TBL_JUMUNBACKUP;
-- ↑
-- UNION 과 UNION ALL 의 차이점을 찾을 수 없다.
-- ↓
SELECT *
FROM TBL_JUMUN
UNION ALL
SELECT *
FROM TBL_JUMUNBACKUP;
-- UNION
-- UNION ALL 첫 번째 컬럼을 기준으로 해서 오름차순 정렬이 들어간다.
-- TBL_JUMUNBACKUP 을 위에 작성했을 때는 둘의 결과에 차이가 없었지만
-- TBL_JUMUNBACKUP 을 아래에 작성하고 TBL_JUMUN 을 위에 작성하자
-- UNION : 첫 번째 컬럼을 기준으로 해서 오름차순 정렬이 들어간 것을 확인
-- UNION ALL : 테이블이 결합된 순서대로 조회한 것을 확인.
-- UNION ALL : 중복된 데이터가 있더라도 이것을 제거하지 않고 결합한다. (비교Ⅹ)
-- 결합된 테이블 순서대로 레코드를 결합한다. (정렬Ⅹ)
-- UNION : 중복된 데이터가 있을 경우 제거한다. (비교)
-- 첫 번째 컬럼을 기준으로 오름차순 정렬한다. (정렬)
-- UNION ALL 에는 정렬 기능읻 들어가지 않기 때문에 리소스 소모가 훨씬 적으므로
-- 실제로 더 많이 사용한다.
--※ UNION 은 항상 결과물의 첫 번째 컬럼을 기준으로
-- 오름차순 정렬을 수행한다.
-- 반면, UNION ALL 은 결합된 순서(쿼리문에서 테이블을 명시한 순서) 대로
-- 조회한 결과를 있는 그대로 반환한다. (→ 정렬 없음)
-- 이로 인해 UNION 이 부하가 더 크다. (→ 리소스 소모가 더 크다)
-- 또한, UNION 은 결과물에 대한 중복된 레코드(행)가 존재할 경우
-- 중복을 제거하고 1개 행만 조회된 결과를 반환하게 된다.
--○ 지금까지 주문받은 데이터를 통해
-- 제품별 총 주문량을 조회할 수 있는 쿼리문을 구성한다.
-- TBL_JUMUN, TBL_JUMUNBACKUP 에서
-- 총 꼬북칩, 빼빼로가 몇 번 주문되었는지 확인.
SELECT T.JECODE "제품명"
, SUM(T.JUSU) "주문횟수"
FROM
(
SELECT TJ.JECODE, TJ.JUSU
FROM TBL_JUMUN TJ
UNION ALL
SELECT TB.JECODE, TB.JUSU
FROM TBL_JUMUNBACKUP TB
) T
GROUP BY T.JECODE
ORDER BY 주문횟수 ASC;
SELECT T.JECODE "제품코드", SUM(T.JUSU) "주문수량"
FROM
(
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU
FROM TBL_JUMUN
) T
GROUP BY T.JECODE;
--==>>
/*
제품코드 주문수량
꼬북칩 40
오예스 20
맛동산 90
웨하스 20
에이스 40
포카칩 80
오감자 10
빅파이 30
사또밥 40
홈런볼 50
빼빼로 140
감자깡 30
마이쮸 20
오레오 20
포스틱 20
*/
--○ INTERSECT / MINUS (교집합 / 차집합)
-- TBL_JUMUNBACKUP 테이블과 TBL_JUMUN 테이블에서
-- 제품코드와 주문수량의 값이 똑같은 행만 추출해서 조회하고자 한다.
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT JECODE, JUSU
FROM TBL_JUMUN;
--==>>
/*
[양쪽의 테이블에 모두 동일하게 들어있는 데이터들이다.]
맛동산 30
빼빼로 20
홈런볼 10
*/
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
MINUS
SELECT JECODE, JUSU
FROM TBL_JUMUN;
--> TBL_JUMUNBACKUP 에서 교집합이었던 데이터들을 제거하고 남은 데이터들만 조회된다.
--==>>
/*
감자깡 10
꼬북칩 30
마이쮸 20
맛동산 20
빅파이 10
빅파이 20
사또밥 10
사또밥 30
오예스 20
포스틱 20
포카칩 10
포카칩 20
포카칩 30
홈런볼 20
*/
--○ TBL_JUMUNBACKUP 테이블과 TBL_JUMUN 테이블을 대상으로
-- 제품코드와 주문량의 값이 똑같은 행의 정보를
-- 주문번호, 제품코드, 주문량, 주문일자 항목으로 조회한다.
--SELECT *
--FROM
--(
-- SELECT JECODE, JUSU, JUNO, JUDAY
-- FROM TBL_JUMUNBACKUP
-- UNION ALL
-- SELECT JECODE, JUSU, JUNO, JUDAY
-- FROM TBL_JUMUN
--);
--
--SELECT *
--FROM TBL_JUMUN;
--○ TBL_JUMUNBACKUP 테이블과 TBL_JUMUN 테이블을 대상으로
-- 제품코드와 주문량의 값이 똑같은 행의 정보를
-- 주문번호, 제품코드, 주문량, 주문일자 항목으로 조회한다.
-- INNER JOIN
SELECT U.JUNO, U.JECODE, U.JUSU, U.JUDAY
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) U
INNER JOIN
(
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT JECODE, JUSU
FROM TBL_JUMUN
) I
ON U.JECODE = I.JECODE
AND
U.JUSU = I.JUSU
ORDER BY JUDAY;
-- 중첩 서브 쿼리
SELECT U.JUNO, U.JECODE, U.JUSU, U.JUDAY
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) U
WHERE (U.JECODE, U.JUSU)
IN
(
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT JECODE, JUSU
FROM TBL_JUMUN
);
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN;
--==>>
/*
포스틱 20 1 2001-11-01 09:10:10
빅파이 10 2 2001-11-01 10:10:10
사또밥 30 3 2001-11-01 11:10:10
포카칩 20 4 2001-11-02 11:10:10
포카칩 30 5 2001-11-03 11:10:10
빅파이 20 6 2001-11-04 11:10:10
홈런볼 10 7 2001-11-05 11:10:10
홈런볼 10 8 2001-11-06 11:10:10
오예스 20 9 2001-11-07 11:10:10
사또밥 10 10 2001-11-08 11:10:10
빼빼로 20 11 2001-11-09 11:10:10
감자깡 10 12 2001-11-10 11:10:10
빼빼로 20 13 2001-11-11 11:10:10
홈런볼 20 14 2001-11-12 11:10:10
포카칩 10 15 2001-11-13 11:10:10
포카칩 20 16 2001-11-16 11:10:10
마이쮸 20 17 2001-11-17 11:10:10
맛동산 30 18 2001-11-18 11:10:10
맛동산 20 19 2001-11-19 11:10:10
꼬북칩 30 20 2001-11-20 11:10:10
꼬북칩 10 98764 2023-10-26 12:46:10
빼빼로 30 98765 2023-10-26 12:46:58
빼빼로 20 98766 2023-10-26 12:47:25
에이스 40 98767 2023-10-26 12:47:53
홈런볼 10 98768 2023-10-26 12:48:16
감자깡 20 98769 2023-10-26 12:48:38
맛동산 10 98770 2023-10-26 12:49:03
웨하스 20 98771 2023-10-26 12:49:25
맛동산 30 98772 2023-10-26 12:49:45
오레오 20 98773 2023-10-26 12:50:08
빼빼로 50 98774 2023-10-26 12:50:29
오감자 10 98775 2023-10-26 12:50:54
*/
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT JECODE, JUSU
FROM TBL_JUMUN;
--==>>
/*
맛동산 30
빼빼로 20
홈런볼 10
*/
/*
7 홈런볼 10 2001-11-05 11:10:10
8 홈런볼 10 2001-11-06 11:10:10
11 빼빼로 20 2001-11-09 11:10:10
13 빼빼로 20 2001-11-11 11:10:10
18 맛동산 30 2001-11-18 11:10:10
98766 빼빼로 20 2023-10-26 12:47:25
98768 홈런볼 10 2023-10-26 12:48:16
98772 맛동산 30 2023-10-26 12:49:45
*/
-- 4개의 레코드가 모두 동일한 것을 찾으므로 조회 번호가 없다.
-- 주문번호가 주문 데이터별로 모두 다 다르므로 불가능하다.
SELECT JUNO, JECODE, JUSU, JUDAY
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT JUNO, JECODE, JUSU, JUDAY
FROM TBL_JUMUN;
--==>> 조회 결과 없음
-- 방법1.
SELECT T2.JUNO "주문번호", T1.JECODE "제품코드"
, T1.JUSU "주문수량", T2.JUDAY "주문일자"
FROM
(
SELECT JECODE, JUSU
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT JECODE, JUSU
FROM TBL_JUMUN
)T1
JOIN
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
)T2
ON T1.JECODE = T2.JECODE
AND
T1.JUSU = T2.JUSU;
--==>>
/*
7 홈런볼 10 2001-11-05 11:10:10
8 홈런볼 10 2001-11-06 11:10:10
11 빼빼로 20 2001-11-09 11:10:10
13 빼빼로 20 2001-11-11 11:10:10
18 맛동산 30 2001-11-18 11:10:10
98766 빼빼로 20 2023-10-26 12:47:25
98768 홈런볼 10 2023-10-26 12:48:16
98772 맛동산 30 2023-10-26 12:49:45
*/
-- 방법 2.
-- 잘못된 WHERE 문!
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE T.JECODE IN ('맛동산', '빼빼로', '홈런볼')
AND T.JUSU IN (30, 20, 10)
-- 이렇게 사용하면 안된다!
-- 맛동산은 반드시 30개여야 하고
-- 빼빼로는 반드시 20개
-- 홈런볼은 반드시 10개 여야 하는데
-- 위와 같이 작성하면,
-- 맛동산 20개, 맛동산 10개도 모두 조건에 맞고
-- 빼빼로 30개, 10개도 모두 조건에 맞으며
-- 홈런볼 30개, 20개도 조건에 맞기 때문에 논리적으로 문제가 있다!
-- ★[즉, 제품코드와 주문 수량을 동시에 조건으로 넣어 주어야만 한다!!!!!]
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE T.JECODE와 J.JUSU 의 묶음 결과가 '맛동산30', '뺴빼로20', '홈런볼10';
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE (T.JECODE || T.JUSU) IN ('맛동산30', '빼빼로20', '홈런볼10');
--> CONCAT(T.JECODE, T.JUSU)
/*
홈런볼 10 7 2001-11-05 11:10:10
홈런볼 10 8 2001-11-06 11:10:10
빼빼로 20 11 2001-11-09 11:10:10
빼빼로 20 13 2001-11-11 11:10:10
맛동산 30 18 2001-11-18 11:10:10
빼빼로 20 98766 2023-10-26 12:47:25
홈런볼 10 98768 2023-10-26 12:48:16
맛동산 30 98772 2023-10-26 12:49:45
*/
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE (T.JECODE || T.JUSU) IN ('맛동산30', '빼빼로20', '홈런볼10');
-- ⓐ 제품 코드와 주문 수량을 결합한 결과값을 반환하는 쿼리문
SELECT (JECODE || JUSU)
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT (JECODE || JUSU)
FROM TBL_JUMUN;
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE (T.JECODE || T.JUSU)
IN
(SELECT (JECODE || JUSU) -- ⓐ 쿼리문을 서브쿼리로 사용하여
FROM TBL_JUMUNBACKUP -- 제품코드와 주문 수량을 결합한 문자열을 반환하여
INTERSECT -- 그 문자를 WHERE 조건식으로 사용한다.
SELECT (JECODE || JUSU)
FROM TBL_JUMUN);
-- 다시 선생님 설명 이어서!!
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE (T.JECODE || T.JUSU) IN ('맛동산30', '빼빼로20', '홈런볼10');
-- 제품코드와 주문량을 문자열로 합친 결과를 반환하는 쿼리
SELECT CONCAT(JECODE, JUSU)
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT CONCAT(JECODE, JUSU)
FROM TBL_JUMUN;
SELECT T.*
FROM
(
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUNBACKUP
UNION ALL
SELECT JECODE, JUSU, JUNO, JUDAY
FROM TBL_JUMUN
) T
WHERE CONCAT(T.JECODE, T.JUSU) IN (SELECT CONCAT(JECODE, JUSU)
FROM TBL_JUMUNBACKUP
INTERSECT
SELECT CONCAT(JECODE, JUSU)
FROM TBL_JUMUN);
--==>>
/*
홈런볼 10 7 2001-11-05 11:10:10
홈런볼 10 8 2001-11-06 11:10:10
빼빼로 20 11 2001-11-09 11:10:10
빼빼로 20 13 2001-11-11 11:10:10
맛동산 30 18 2001-11-18 11:10:10
빼빼로 20 98766 2023-10-26 12:47:25
홈런볼 10 98768 2023-10-26 12:48:16
맛동산 30 98772 2023-10-26 12:49:45
*/
----------------------------------------------------------
SELECT D.DEPTNO,D.DNAME, E.ENAME, E.SAL
FROM EMP E JOIN DEPT D
ON E.DEPTNO = D.DEPTNO;
--==>>
/*
10 ACCOUNTING CLARK 2450
10 ACCOUNTING KING 5000
10 ACCOUNTING MILLER 1300
20 RESEARCH JONES 2975
20 RESEARCH FORD 3000
20 RESEARCH ADAMS 1100
20 RESEARCH SMITH 800
20 RESEARCH SCOTT 3000
30 SALES WARD 1250
30 SALES TURNER 1500
30 SALES ALLEN 1600
30 SALES JAMES 950
30 SALES BLAKE 2850
30 SALES MARTIN 1250
*/
-- ON 조건이 없고, 중첩되는 컬럼을 어떤 테이블에서 가져올지
-- 테이블 명(또는 별칭 ALIAS)를 DEPTNO 에 명시해 주지 않아서 오류 발생.
SELECT DEPTNO, DNAME, ENAME, SAL
FROM EMP E JOIN DEPT D;
--==>> 에러 발생
-- (ORA-00905: missing keyword)
-- 나 이것 좀 자연스럽게 JOIN 해 줘...ㅎㅎ
-- 실행하면, 에러 안 나고 결과가 조회된다.... 우와...
SELECT DEPTNO, DNAME, ENAME, SAL
FROM EMP E NATURAL JOIN DEPT D;
--==>>
/*
10 ACCOUNTING CLARK 2450
10 ACCOUNTING KING 5000
10 ACCOUNTING MILLER 1300
20 RESEARCH JONES 2975
20 RESEARCH FORD 3000
20 RESEARCH ADAMS 1100
20 RESEARCH SMITH 800
20 RESEARCH SCOTT 3000
30 SALES WARD 1250
30 SALES TURNER 1500
30 SALES ALLEN 1600
30 SALES JAMES 950
30 SALES BLAKE 2850
30 SALES MARTIN 1250
*/
-- 제대로 결합되는지 확인하는 용도로 NATURAL JOIN 을 사용하는 것은 괜찮지만
-- 위의 JOIN은 모든 것을 오라클에게 맡겨버리는 것이므로 올바르지 않다.
-- NATURAL JOIN 을 하기 위해서는 오라클이 쿼리문의 컬럼과 테이블을 모두 분석해서
-- 처리해야 하기 때문에 굉장히 리소스 소모가 크다.
-- 이렇게 처리하지 말고, 우리가 직접 JOIN의 결합 규칙이나
-- 컬럼의 테이블명을 명시해 주도록 하는 것이 올바르다.
SELECT DEPTNO, DNAME, ENAME, SAL
FROM EMP E JOIN DEPT D
USING(DEPTNO); --> JOIN할 때, DEPTNO 컬럼을 이용해서 JOIN 해 줘!
-- 중간에 한번 변환과정을 거치는 쿼리...
--------------------------------------------------------------------------------
--○ TBL_EMP 테이블에서 급여가 가장 많은 사원의
-- 사원번호, 사원명, 직종명, 급여 항목을 조회한다.
-- 급여가 가장 많은 사원의 사원번호, 사원명, 직종명, 급여 항목 조회
-- 중첩 서브 쿼리 (WHERE 절에 사용)
SELECT EMPNO "사원번호", ENAME "사원명", JOB "직종명", SAL "급여"
FROM TBL_EMP
WHERE SAL
IN
(SELECT MAX(SAL)
FROM TBL_EMP);
-- INNER JOIN
SELECT E.*
FROM
(
SELECT EMPNO "사원번호", ENAME "사원명", JOB "직종명", SAL "급여"
FROM TBL_EMP
) E
INNER JOIN
(
SELECT MAX(SAL) "SAL"
FROM TBL_EMP
) MS
ON E.급여 = MS.SAL;
-- 급여를 가장 많이 받는 사원의 급여
SELECT MAX(SAL)
FROM TBL_EMP;
--==>> 5000
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE (급여가 가장 많은 사원);
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE 급여 = (급여를 가장 많이 받는 사원의 급여);
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL = (SELECT MAX(SAL)
FROM TBL_EMP);
--==>> 7839 KING PRESIDENT 5000
-- 『=ANY』
-- 『=ALL』
SELECT SAL
FROM TBL_EMP;
--==>
/*
800
1600
1250
2975
1250
2850
2450
3000
5000
1500
1100
950
3000
1300
1500
2000
1700
2500
1000
*/
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL =ANY (800, 1600, 1250, 2975, 1250, 2850, 2450, 3000, 5000, 1500, 1100, 950, 3000, 1300
,1500, 2000, 1700);
-- 사원들의 급여 중, 괄호 안의 값과 하나라도 겹친다면 그 레코드를 조회한다.
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL = ALL (800, 1600, 1250, 2975, 1250, 2850, 2450, 3000, 5000, 1500, 1100, 950, 3000, 1300
,1500, 2000, 1700);
-- 급여가 800 이기도 하면서 1600 이기도 하면서...
-- 괄호 안에 있는 것들과 모두 같은 값들을 찾겠다는 의미 -> 논리적으로 잘못 되었다.
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL >= ALL (800, 1600, 1250, 2975, 1250, 2850, 2450, 3000, 5000, 1500, 1100, 950, 3000, 1300
,1500, 2000, 1700);
-- 모든 것들과 비교했을 때 크거나 같은 급여다!! 그것이 바로 5000! 최댓값!!!
-- 하나하나 각각의 사원들과 모두 비교해 봤을 때 다 크거나 같으려면
-- KING 의 SAL 값인 5000밖에 가능한 값이 없다!
-- 괄호 안의 값은 어떤 쿼리?
-- 모든 사원의 SAL 을 조회하는 쿼리!
SELECT SAL
FROM TBL_EMP;
-- 따라서 위의 쿼리를 ALL 옆의 괄호에 서브 쿼리로 사용해 주게 되면 같은 결과를 반환한다.
-- WHERE에 집계 함수를 쓰지 않고 MAX값을 파악하여 연산할 수 있다.
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL >= ALL (SELECT SAL
FROM TBL_EMP);
--○ TBL_EMP 테이블에서 20번 부서에 근무하는 사원들 중 급여가 가장 많은 사람의
-- 사원번호, 사원명, 직종명, 급여 항목을 조회하는 쿼리문을 구성한다.
-- ALL 을 이용하여 WHERE 절에 집계함수를 쓰지 않고 풀이한 방식
-- 쌤이 알려주신 신기한 방식
SELECT EMPNO "사원번호", ENAME "사원명", JOB "직종명", SAL "급여"
FROM TBL_EMP
WHERE DEPTNO = 20
AND
SAL >= ALL(SELECT SAL
FROM TBL_EMP
WHERE DEPTNO = 20);
-- 이건... 내가 원래 풀던 방식.
-- 중첩 서브 쿼리를 이용해서 MAX(SAL) 값을 이용하는 방식
SELECT EMPNO "사원번호", ENAME "사원명", JOB "직종명", SAL "급여"
FROM TBL_EMP
WHERE DEPTNO = 20
AND
SAL
IN
(SELECT MAX(SAL)
FROM TBL_EMP
WHERE DEPTNO = 20);
SELECT 사원번호, 사원명, 직종명, 급여
FROM TBL_EMP
WHERE SAL = (가장많은급여)
DEPTNO = 20;
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL = (가장많은급여)
AND
DEPTNO = 20;
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL = (SELECT MAX(SAL)
FROM TBL_EMP) -- 급여가 가장 많은 사원 KING = 5000이 들어간다.
-- SUB QUARY 내에서도 조건을 명시해 주어야 한다.
AND
DEPTNO = 20;
--==>> 조회 결과 없음
-- 외부 쿼리 밑에 비록 DEPTNO = 20이 있지만
-- 서브 쿼리 내부에도 DEPTNO = 20이라는 조건을 넣어야 한다!!
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL = (SELECT MAX(SAL)
FROM TBL_EMP
WHERE DEPTNO = 20) -- 급여가 가장 많은 사원 KING = 5000이 들어간다.
-- SUB QUARY 내에서도 조건을 명시해 주어야 한다.
AND
DEPTNO = 20;
-- 안의 서브쿼리에서 얻어내는 MAX(SAL) 의 값을 구해내는 과정에서 조건을 구해야 하므로
-- 내부에도 WHERE DEPTNO = 20 을 작성해 준 것!!
--> 따라서, 서브 쿼리 내부에서 WHERE 조건문은 절대 생략되어선 안되는 절이다.
--==>>
/*
7788 SCOTT ANALYST 3000
7902 FORD ANALYST 3000
*/
SELECT EMPNO, ENAME, JOB, SAL
FROM TBL_EMP
WHERE SAL >= ALL(SELECT SAL
FROM TBL_EMP
WHERE DEPTNO = 20) -- 이때에도 이 조건문은 생략되어선 안 된다.
-- 내부 쿼리 결과값
/*
800
2975
3000
1100
3000
*/
AND
DEPTNO = 20;
'[Oracle] > SQL (Program source)' 카테고리의 다른 글
[Oracle] 20231030 [프로그램소스] - 20231030_01_HR, TBL_INSA(중간 수행평가) (0) | 2023.10.30 |
---|---|
[Oracle] 20231027 [프로그램소스] - 20231027_01_SCOTT, TBL_INSA(중간 수행평가) (0) | 2023.10.27 |
[Oracle] 20231025 [프로그램소스] - 20231025_01_scott (0) | 2023.10.25 |
[Oracle] 20231024 [프로그램소스 - 그룹화] - 20231024_01_scott (0) | 2023.10.24 |
★ [Oracle] 20231020 [프로그램소스 - 수료일까지 남은 연, 월, 일 해설 (전체시간 초로 변경)] (0) | 2023.10.23 |