ㅁ SQL DEVELOPER에서 관리자 계정으로 접속해서 새 계정 생성.
CREATE USER DDL IDENTIFIED BY DDL;
GRANT CONNECT, RESOURCE TO DDL;
- 초록색 플러스 아이콘(접속창)에서, DB 서버가 있다면 [세부정보]-[호스트이름]에 그 IP 주소를 칠 텐데 본인 PC를 쓸 거라 그냥 localhost 쓰면 됨.
ㅁ 새로 만든 계정이라 테이블이 없다.
- 데이터를 보관하기 위해서는 테이블이 필요하다.
ㅁ < DDL >
- Data Definition Language 데이터 정의어
- 데이터베이스 객체들(생성, 수정, 삭제)을 관리하는 언어
- CREATE, ALTER, DROP, RENAME, TRUNCATE
ㅁ 데이터베이스 객체
- 저것들이 다 객체다.
- 자바의 객체랑은 다르다.
- 테이블, 뷰, 시퀀스, 인덱스, 패키지, 트리거, 프로시저, 함수, 동의어, 사용자(USER) 등 데이터베이스 내에 정의하는 모든 것을 일컫는다
ㅁ 테이블
- 행과 열로 구성되는 가장 기본적인 데이터베이스 객체.
- 모든 데이터들은 테이블을 통해서 저장된다.
ㅁ 테이블 생성
- CREATE TABLE 테이블명( 컬럼명 자료형(SIZE) 제약조건,
컬럼명 자료형(SIZE),
컬럼명 자료형,
... );
- SIZE를 지정하지 않아도 되는 자료형도 있다. // NUMBER, DATE, CLOB 등
- 그리고 각 컬럼마다 제약조건을 둘 수 있다.
ㅁ 자료형(데이터 타입)
- 크게 3가지로 나뉜다. 숫자, 문자, 날짜 타입.
(1) 숫자
- NUMBER 오라클은 자바처럼 정수형, 실수형 따로 취급하지 않고 다 취급한다.
- NUMBER 타입은 사이즈를 지정 할 수도 있고 지정하지 않을 수도 있다.
- NUMBER(p, s)
p는 정밀도 라고 한다. 전체 유효 숫자를 뜻한다.
s는 소수부의 유효 숫자를 뜻한다.
- s를 생략하면 정수 타입.
- p,s를 생략하면 정수, 실수를 구분하지 않고 보관
- 근데 둘 다 안쓸 거다.
(2) 문자
i) CHAR(SIZE)
- 고정문자 크기. 최대 2000바이트까지 저장 가능.
- 휴대폰 번호, 주민번호 등은 글자 수 변동이 적은 고정문자다.
- 고정크기라 자동으로 빈 공간을 공백으로 채운다.
ii) VARCHAR2(SIZE)
- VAR는 가변을 의미함. 2는 2배를 의미함.
- 가변문자 데이터 보관시 사용. 최대 4000바이트까지 저장 가능.
- 이름, 주소 등은 글자 수 변동이 큰 가변문자다.
iii) CLOB
- 한글은 한 문자가 3바이트다. 그래서 4000바이트로도 처리할 수 없는 큰 문자 데이터를 저장할 때.
- 영어, 숫자, 특수문자(보통)는 한 문자가 1바이트.
(3) 날짜
- 회사마다 DATE 타입, TIMESTAMP 위주로 하는 회사가 있다.
i) DATE
- 날짜/시간
- 년, 월, 일, 시, 분, 초
ii) TIMESTAMP
- 정밀한 날짜.시간
- 년, 월, 일, 시, 분, 초 + 마이크로초(백만분의 1초)
ㅁ 고정문자와 가변문자
- CHAR(6) 고정문자에 'ABC' 3글자만 보관시켜도 불필요한 공백 3개를 넣어서 저장함. 'ABC '
- VARCHAR2(6) 가변문자에 'ABC' 3글자를 보관시키면 'ABC'만 저장됨.
ㅁ 예제 (MEMBER라는 이름의 회원데이터를 담기 위한 테이블 생성)
CREATE TABLE MEMBER(
MEM_NO NUMBER,
MEM_ID VARCHAR2(20),
MEM_PWD VARCHAR2(20),
MEM_NAME VARCHAR2(20),
GENDER CHAR(3),
PHONE VARCHAR2(13),
EMAIL VARCHAR2(40),
MEM_DATE DATE
);
- 오라클은 다 대문자로 인식해서 낙타표기법 의미 없다. 여러 단어를 조합시킬 땐 언더바를 주로 쓴다.
ㅁ 데이터 딕셔너리
- 데이터베이스 객체들의 정보를 저장하고있는 시스템 테이블
- [참고] USER_TABLES: 이 계정이 가지고 있는 테이블의 정보를 표현하고 있는 테이블
SELECT * FROM USER_TABLES;
- [참고] USER_TAB_COLUMNS: 이 계정이 가지고 있는 테이블 상의 모든 컬럼 정보를 표현
SELECT * FROM USER_TAB_COLUMNS;
ㅁ 컬럼에 주석 달기
- COMMENT ON COLUMN 테이블명.컬럼명 IS '주석내용';
- 잘못 실행했다면 수정 후에 다시 재실행. (삭제 없이 그냥 마지막 내용으로 갱신됨)
COMMENT ON COLUMN MEMBER.MEM_NO IS '회원번호';
ㅁ 테이블에 데이터를 추가시키는 구문 (DML:INSERT)
- INSERT INTO 테이블명 VALUES(값1, 값2, 값3)
- 컬럼의 개수만큼 값을 나열해야 함.
- 한 행 단위로 값을 넣는다.
INSERT INTO MEMBER VALUES(1, 'user01', 'pass01', '홍길동',
'남', '010-1111-2222', 'aaa@naver.com', SYSDATE);
INSERT INTO MEMBER VALUES(2, 'user02', 'pass02', '홍길녀',
'여', null, NULL, '23/12/25');
- 지금이야 값을 직접 넣지만 웹에서 받아올 때도 있다. 그러면 필수 입력이 아닌 곳은 비어있을 것.
- null을 제시할 때는 그냥 null 쓰면 됨. 대문자 NULL도 된다.
ㅁ
INSERT INTO MEMBER VALUES( NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
- 이런건 존재 가치 없는 데이터라 오류가 나야 하는데 정상적으로 삽입됨.
- 정상적으로 삽입되는 이유는 각 컬럼들마다 제약조건을 걸지 않았기 때문이다.
ㅁ 테이블 삭제 구문
- DROP TABLE MEMBER;
ㅁ 제약조건 CONSTRAINT
- 원하는 데이터값(유효한 형식의 값)만 유지하기 위해서 특정 컬럼에 설정하는 제약
- 각 컬럼마다 유효한 데이터만 들어올 수 있도록 제한을 둘 수 있다.
- NOT NULL, UNIQUE, CHECK(조건), PRIMARY KEY, FOREIGN KEY
i) 표현법1. 컬럼 레벨 방식
CREATE TABLE 테이블명( 컬럼명 데이터타입 [CONSTRAINT 제약조건명] 제약조건,
컬럼명 데이터타입 [CONSTRAINT 제약조건명] 제약조건,
컬럼명 데이터타입 [CONSTRAINT 제약조건명] 제약조건,
... );
ii) 표현법2. 테이블 레벨 방식
- 테이블 레벨 방식은 일단 이전에 컬럼명을 먼저 만든 것만 쓸 수 있다.
CREATE TABLE 테이블명( 컬럼명 데이터타입
컬럼명 데이터타입,
컬럼명 데이터타입,
...
[CONSTRAINT 제약조건명] 제약조건(컬럼명),
[CONSTRAINT 제약조건명] 제약조건(컬럼명) );
(1) NOT NULL
- 해당 컬럼에 반드시 값이 존재해야만 할 경우(NULL이 들어와선 안 됨)
- 컬럼 레벨 방식으로만 부여 가능
DROP TABLE MEMBER;
CREATE TABLE MEMBER(
MEM_NO NUMBER NOT NULL,
MEM_ID VARCHAR2(20) NOT NULL,
MEM_PWD VARCHAR2(20) NOT NULL,
MEM_NAME VARCHAR2(20) NOT NULL,
GENDER CHAR(3),
PHONE VARCHAR2(13),
EMAIL VARCHAR2(40),
MEM_DATE DATE NOT NULL
);
INSERT INTO MEMBER VALUES(1, 'user01', 'pass01', '홍길동', '남', '010-1111-2222', 'aaa@naver.com', SYSDATE);
INSERT INTO MEMBER VALUES(2, 'user02', 'pass02', '홍길녀', '여', null, NULL, '23/12/25');
INSERT INTO MEMBER VALUES( NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
- 첫번째, 두번째 INSERT문은 정상적으로 실행되지만, 세번째 INSERT문은 의도대로 오류 발생.
INSERT INTO MEMBER VALUES(3, 'user01', 'pass03', '김말동', NULL, NULL, NULL, '23/12/30');
- 그런데 아이디가 중복되었음에도 불구하고 오류나지 않고 추가되어 버림.
(2) UNIQUE
- 컬럼값에 중복값을 제한하는 제약조건.
- 한 컬럼에 여러 제약조건을 부여할 때는 그냥 컴마 없이 띄어쓰기.
- 컬럼 레벨 방식, 테이블 레벨 방식 다 가능.
DROP TABLE MEMBER;
CREATE TABLE MEMBER(
MEM_NO NUMBER NOT NULL,
MEM_ID VARCHAR2(20) NOT NULL UNIQUE, // 컬럼 레벨 방식
MEM_PWD VARCHAR2(20) NOT NULL,
MEM_NAME VARCHAR2(20) NOT NULL,
GENDER CHAR(3),
PHONE VARCHAR2(13),
EMAIL VARCHAR2(40),
MEM_DATE DATE NOT NULL,
UNIQUE(MEM_NO) // 테이블 레벨 방식
);
INSERT INTO MEMBER VALUES(1, 'user01', 'pass01', '홍길동', '남', '010-1111-2222', 'aaa@naver.com', SYSDATE);
INSERT INTO MEMBER VALUES(2, 'user02', 'pass02', '홍길녀', '여', null, NULL, '23/12/25');
INSERT INTO MEMBER VALUES(3, 'user01', 'pass03', '김말동', NULL, NULL, NULL, '23/12/30');
- 첫번째, 두번째 INSERT문은 정상적으로 실행되지만, 세번째 INSERT문은 의도대로 오류 발생.
※ 오류 구문
unique constraint (DDL.SYS_C007067) violated // 계정명.임의의제약조건명
- 테이블-열 탭에서 NOT NULL은 NULLABLE로 파악 가능함.
- 테이블-제약조건 탭에서 제약조건들 볼 수 있음.
- SEARCH_CONDITON 내용 부분을 더블클릭 하면 어떤 컬럼에서 위배했는 지도 알려줌.
- 제약조건명을 내가 설정하지 않으면 CONSTRAINT_NAME에 보이는 것처럼 시스템이 임의로 설정함.
- 제약조건 앞에 CONSTRAINT라는 키워드와 함께 제약조건명을 지정해줄 수 있다. 안하면 시스템이 임의로 설정함.
- 제약조건명은 특정 테이블뿐 아니라, 그 계정 전체가 공유해서 계정 내에서 중복되면 안 됨.
- NOT NULL은 NN, UNIQUE는 UQ, NAME은 NM으로 많이들 줄인다.
- 3번째 행을 실행하면 오류가 발생하면서 내가 설정한 제약조건명으로 알려준다.
INSERT INTO MEMBER VALUES(3, 'user03', 'pass03', '김말동', 'N', NULL, NULL, '23/12/30');
- 성별에 유효한 값('남', '여')가 아닌 'N'이 들어왔음에도 오류가 나지 않는다.
(참고로 더블클릭해서 보면 'N '가 들어왔다)
- 내가 원하는 형식의 데이터만 들어올 수 있게 할 수 있다.
(3) CHECK(조건식) 제약조건
- 해당 컬럼에 들어올 수 있는 값에 대한 조건을 줄 수 있다.
- 4번째 문장을 실행하면 저런 오류 발생.
- 제약조건 명은 설정하지 않아서 저런게 나옴.
- CHECK 제약조건 설정해도 NULL은 들어갈 수 있음. NULL도 막으려면 NOT NULL 제약조건 줘야 함.
(4) PRIMARY KEY(기본키) 제약조건
- 테이블에서 각 행들을 식별하기 위해 사용될 컬럼에 부여하는 제약조건
- PRIMARY KEY 제약조건이 설정된 컬럼은 식별자 역할을 수행한다.
- 기본키는 NULL이어도 안 되고, 중복값이어도 안 된다.
- 테이블당 오로지 한 개만 설정 가능하다.
- 회원번호, 사원번호, 부서id, 직급코드, 주문번호, 예약조건, 운송장번호, 학번, 교수번호 등
- PK 제약조건을 주면 NOT NULL과 UNIQUE가 자동으로 부여되어서 또 쓸 필요가 없다.
- PK가 부여된 회원번호 컬럼이 데이터를 식별하는 역할을 한다.
ㅇ 복합키
- 테이블당 PK는 하나만 가능하지만, 여러 컬럼을 묶어서 PK로 지정하는 것은 가능하다.
- 복합키는 무조건 테이블 레벨 방식으로만 가능하다.
- 복합키 사용 예시. 어떤 회원이 어떤 상품을 찜했는지에 대한 데이터를 보관하는 테이블
CREATE TABLE TB_LIKE(
MEM_NO NUMBER,
PRODUCT_NAME VARCHAR2(50),
LIKE_DATE DATE,
PRIMARY KEY(MEM_NO, PRODUCT_NAME)
);
/* 1번 회원이 A상품 오늘날짜 찜
1번 회원이 B상품 오늘날짜 찜
2번 회원이 A상품 오늘날짜 찜
1번 회원이 A상품 오늘날짜 찜 */
- 4번째 문장은 들어오면 안 됨. 근데 MEM_NO로만 PK를 설정하면 2번 문장도 안됨. 복합키를 사용한다.
※ 내가 만드려는 테이블이 예약어와 겹치면 테이블명을 TB_LIKE 혹은 LIKE_TB 이렇게 만들고는 한다.
- 1, 2, 3번째 문장은 정상 실행. 4번째 문장 실행시 오류 발생.
(4) FOREIGN KEY(외래키) 제약조건
- 다른 테이블에 존재하는 값만 들어와야하는 특정 컬럼에 부여하는 제약조건.
- 다른 테이블을 참조한다고 표현한다.
- FOREIGN KEY 제약조건을 부여하는 순간 두 테이블 간의 부모-자식 관계가 형성된다.
-- 회원 등급에 대한 데이터를 따로 보관하는 테이블 생성
CREATE TABLE GRADE(
GRADE_CODE NUMBER PRIMARY KEY,
GRADE_NAME VARCHAR2(30) NOT NULL UNIQUE );
INSERT INTO GRADE VALUES(10, '일반회원'); // 실행
INSERT INTO GRADE VALUES(20, '우수회원'); // 실행
INSERT INTO GRADE VALUES(30, '특별회원'); // 실행
DROP TABLE MEMBER; // 실행
CREATE TABLE MEMBER(
MEM_NO NUMBER CONSTRAINT MEMNO_PK PRIMARY KEY,
MEM_ID VARCHAR2(20) NOT NULL UNIQUE,
MEM_PWD VARCHAR2(20) NOT NULL,
MEM_NAME VARCHAR2(20)NOT NULL,
GENDER CHAR(3) CHECK(GENDER IN ('남', '여')) ,
PHONE VARCHAR2(13),
EMAIL VARCHAR2(40),
GRADE_ID NUMBER, // 회원 등급 번호(GRADE 테이블의 GRADE_CODE 컬럼)를 보관할 컬럼
MEM_DATE DATE NOT NULL
);
INSERT INTO MEMBER VALUES (1, 'user01', 'pass01', '홍길순', '여', null, null, null, sysdate);
INSERT INTO MEMBER VALUES (2, 'user02', 'pass02', '김말동', null, null, null, 10, sysdate);
INSERT INTO MEMBER VALUES (3, 'user03', 'pass03', '강개순', '여', null, null, 40, sysdate);
- 세번째 문장은 오류가 나야 하는데 오류가 나지 않고 정상 실행된다.
- 다른 테이블에 존재하는 값만 들어오게끔 제한을 둘 수 있다.
(즉, 다른 테이블을 참조하게끔 설정할 수 있다)
i) 표현법1. 컬럼 레벨 방식
컬럼명 데이터타입 [CONSTRAINT 제약조건명] REFERENCES 참조할테이블명[(참조할컬럼명)]
ii) 표현법2. 테이블 레벨 방식
[CONSTRAINT 제약조건명] FOREIGN KEY(컬럼명) REFERENCES 참조할테이블명[(참조할컬럼명)]
- 참조할 컬럼명을 생략하면, 참조할 테이블의 PK 컬럼 자동 참조.
- 외래키 제약조건 설정해도 NULL은 허용됨. NULL도 막으려면 NOT NULL 제약조건 줘야 함.
- 1, 2번재 문장은 정상 실행. 3번재 문장은 오류 발생.
integrity constraint/ 데이터 무결성 위배
계정명.제약조건명으로 알려줌.
그리고 추가적으로 parent key not found라고 알려줌.
- 외래키 제약조건을 설정하면, GRADE 테이블이 부모 MEMBER가 자식 테이블이 된다.
- 부모는 한 등급에 여러 회원이 존재. 자식은 한 회원에 한 등급만 가진다.
- 항상 부모가 1, 자식이 다인 1:N 관계(1대 다 관계)가 된다.
ㅇ 외래키 제약조건을 설정한 경우, 부모 데이터 삭제시 문제 발생. (자식 데이터 삭제는 문제 없음)
- [데이터 삭제] DELETE FROM 테이블명 WHERE 조건;
- GRANT 테이블(부모테이블)의 10번 등급 삭제
- DELETE FROM GRADE WHERE GRADE_CODE=30;
이건 삭제 잘 됨. 30이라는 값을 자식 테이블에서 쓰고 있지 않아서.
- ROLLBACK;
마지막으로 한 조작 롤백시킴. 다시 GRADE에 30이 생김.
- 이 값을 쓰고있는 자식 데이터가 있어도 무조건 삭제하고 싶다면
외래키 제약조건 설정시 삭제옵션을 지정한다.
(삭제옵션: 부모데이터 삭제시 그 데이터를 사용하고 있는 자식데이터를 어떻게 처리할건지)
(1) ON DELETE RESTRICTED (기본값)
- 자식데이터로 쓰이는 부모데이터는 삭제가 불가능하다.
(2) ON DELETE SET NULL
- 부모데이터 삭제시 해당 데이터를 쓰고 있는 자식데이터 값을 NULL로 변경한다.
(3) ON DELETE CASCADE
- 부모데이터 삭제시 해당 데이터를 쓰고 있는 자식데이터도 같이 삭제한다.
- 아까와 달리 GRADE_CODE가 10인 데이터를 GRADE 테이블에서 삭제하는 것이 가능하다.
- 자식 테이블에 GRADE_ID가 GRADE_CODE를 참조하고 있던 데이터는 GRADE_ID가 (null)이 되었다.
- 외래키 제약조건 설정시 삭제옵션을 ON DELETE CASCADE로 지정한 경우,
GRADE_CODE가 10인 데이터를 GRADE 테이블(부모 테이블)에서 삭제하면 MEMBER 테이블(자식 테이블)에서 GRADE_ID가 10인 데이터들이 전부 삭제되고 2행만 조회됨.
ㅁ DEFAULT(기본값) 설정하기
- 특정 컬럼에 기본값(DEFAULT)을 설정할 수 있다.
- INSERT시 NULL이 아닌 기본값이 INSERT되도록 지정.
- 데이터의 특정 컬럼만 데이터를 삽입할 때, 기본값이 설정되어 있으면 기본값이, 아니라면 NULL이 기록된다.
CREATE TABLE 테이블명 ( 컬럼명 데이터타입 [CONSTRAINT 제약조건명] 제약조건,
컬럼명 데이터타입,
컬럼명 데이터타입 DEFAULT 기본값,
......
[CONSTRAINT 제약조건명] 제약조건(컬럼명) );
- "DEFAULT SYSDATE"가 "NOT NULL" 뒤로 가면 에러 남.
- MEM_AGE 컬럼처럼 컬럼에 DEFAULT 값을 설정 안했는데 DEFAULT를 주면 (null)로 기록된다.
ㅁ INSERT INTO 테이블명(컬럼명, 컬럼명) VALUES(값, 값);
- INSERT문을 모든 컬럼을 명시하지 않고도 쓸 수 있다.
- 선택하지 않은 컬럼의 값으로는 DEFAULT 값이 기록된다.
DEFAULT값이 설정되어 있지 않으면 NULL이 기록됨.
- 한 행 단위로 INSERT이기 때문에 명시하지 않은 다른 컬럼에도 무언가는 기록된다.
- SYSDATE는 NOT NULL이지만 DEFAULT가 있어서 컬럼에 값을 삽입하지 않아도 오류가 발생하지 않음.
=====================================================================
ㅁ < SUBQUERRY를 활용한 테이블 생성 >
- 서브쿼리로 조회되는 결과를 테이블로 생성시킬 수 있다.
- 주로 테이블 복사를 진행할 때 사용된다. (기존 데이터는 그대로 두고 복사본 만들 때)
CREATE TABLE 테이블명 AS 서브쿼리;
- 서브쿼리 결과값이 고스란히 테이블로 만들어진다.
ㅁ EMPLOYEE 테이블을 복제한 새로운 테이블 생성
CREATE TABLE EMPLOYEE_COPY AS SELECT *
FROM EMPLOYEE;
SELECT *
FROM EMPLOYEE_COPY;
- 현재 조회되는 컬럼명이랑 데이터타입, 데이터 값, NOT NULL 제약조건은 복사가 진행되었다.
- 복사가 안되는 것 : 디폴트 설정값, 주석, NOT NULL을 제외한 모든 제약조건.
- 원본키는 PK가 있는데 복사본은 PK 제약조건이 복사 진행 안됐다.
- 오로지 NOT NULL 제약조건만 복사된다. 다른 제약조건은 복사가 안된다.
- 데이터 값 말고 구조(컬럼명, 자료형)만 복사하고 싶은 경우.
CREATE TABLE EMPLOYEE_COPY2( EMP_ID 데이터타입 제약조건,
EMP_NAME 데이터타입 제약조건,
... );
- 이렇게 하나하나 컬럼을 쓸 수도 있지만,
CREATE TABLE EMPLOYEE_COPY2 AS SELECT EMP_ID, EMP_NAME, SALRY, BONUS
FROM EMPLOYEE;
- 이러면 데이터도 다 복사가 되어버림.
- 대놓고 거짓인 조건을 주면 된다. 그럼 매행 FALSE여서 조회 안 됨.
CREATE TABLE EMPLOYEE_COPY2 AS SELECT EMP_ID, EMP_NAME, SALRY, BONUS
FROM EMPLOYEE
WHERE 1=0; // 데이터는 말고 구조만 복사
- 이러면 컬럼명, 데이터타입은 원본과 동일하게 맞춰짐.
NOT NULL 제약조건 정도는 복사 되고.
데이터는 없음.
ㅁ 서브쿼리 SELECT절에 산술 연산식, 함수식이 기술되어있는 경우에는 별칭을 반드시 지정해야 한다.
- 4번째 컬럼이 연봉이라는 이름으로 생성됨. 그리고 자동으로 넘버타입이 됨. 산술연산식이라.
- 그 컬럼명을 조건으로 활용할 수도 있다.
ㅁ < 테이블 생성 후 제약조건 별도로 추가하기 >
- 서브쿼리로 NOT NULL 제약조건 말고 다른 제약조건을 복사하고 싶다면 따로 추가적으로 제약조건을 추가한다.
ALTER TABLE 테이블명 ADD PRIMARY KEY(컬럼명);
ALTER TABLE 테이블명 ADD FOREIGN KEY(컬럼명) REFERENCES 참조할테이블명 [(컬럼명)];
ALTER TABLE 테이블명 ADD UNIQUE(컬럼명);
ALTER TABLE 테이블명 ADD CHECK(컬럼에대한조건식);
ALTER TABLE 테이블명 MODIFY 컬럼명 NOT NULL;
- 제약조건명을 주고 싶다면 "CONSTRAINT 제약조건명" 추가하면 된다.
- NOT NULL 제약조건은 ADD가 아닌 MODIFY로 추가할 수 있다.
'클라우드 활용 자바개발자 양성과정 > 02. 데이터베이스 활용' 카테고리의 다른 글
08_DDL(ALTER, DROP) + DCL(GRANT, REVOKE) (0) | 2024.07.23 |
---|---|
07_DML(INSERT, UPDATE, DELETE) (0) | 2024.07.23 |
데이터베이스 활용 실습문제 모음 (0) | 2024.07.19 |
05. SUBQUERY (0) | 2024.07.19 |
04. JOIN (조인문) (0) | 2024.07.18 |