
SQL 프로그래밍
# 2024-02-14
-- 지난 시간 돌아보기~
-- <내장함수 중 중요한 것들>
-- 문자 : LEFT() RIGHT() MID() SUBSTRING() SUBSTRING_INDEX() LCASE() UCASE() TRIM들 CAST() CONVERT() REPLACE()
-- 날짜 : FORMAT() TO_CHAR() ADD_DATE() SUB_DAT() CURDATE() SYSDATE() DATE_FORMAT DATEDIFF() TIMEDIFF()
-- 수치 : CEILING() FLOOR() TRUNCATE() 모듈러함수라 하셨는데 그게 뭐지...
-- --------------------------------------------------------------------------------------------------------------------------------
-- 6장 : 내장 함수 (어제 이어서)
-- [제어 흐름 함수]
-- CASE 연산자
/*
사용법 ① : 특정 값을 갖고 비교값과 비교하여 맞으면 결과 출력 하는 방식으로 JAVA의 SWITCH문과 비슷함
CASE value
WHEN 비교값 THEN 결과
WHEN 비교값 THEN 결과
WHEN 비교값 THEN 결과
ELSE 다 안맞으면 나오는 결과
END
사용법 ② : 특정 조건일 떄 특정 결과가 나오게하는 방식 JAVA의 IF문과 비슷함.
CASE
WHEN 조건절 THEN 결과
WHEN 조건절 THEN 결과
WHEN 조건절 THEN 결과
ELSE 다 아니면 나오는 결과
END
*/
# SELECT문 안에서 쓰기보다는 프로시저 안에서 쓰는게 좋음 (DB성능 좋으면 상관없겠지만!)
# 하지만 현실에서는 개발할 시간도 부족해서 쿼리는 그냥 좀 느려도 그냥 SELECT문 안에 적는 경우가 많음
SELECT * FROM tbl_member;
-- CASE 사용법 ① 예시
SELECT memberId, NAME, addr1,
CASE jobCode
WHEN '01' THEN '학생'
WHEN '02' THEN '회사원'
WHEN '03' THEN '공무원'
WHEN '04' THEN '교사'
ELSE '기타'
END AS job
FROM tbl_member;
-- CASE 사용법 ② 예시
SELECT
orderNo, orderDate, memberId, orderAmount,
CASE WHEN cancelYN = 'N' THEN orderAmount ELSE 0 END AS normal,
CASE WHEN cancelYN = 'Y' THen orderAmount ELSE 0 END AS cancel
FROM tbl_orderinfo
ORDER BY orderNo
;
-- 조인 한번 해보면서 CASE문 적용해보기! (조인 아직 안배움)
# 아래 예시는 좋은 예시는 아님... 이런건 프로시저에서만 쓰거나 뷰 에서만 써야하는 경우 임.
SELECT
M.memberID, M.name,
OI.orderNo, OI.orderDate, OI.orderAmount,
GI.goodsCode, GI.goodsName, Gi.unitCode,
CASE GI.goodsCode
WHEN 'GDS001' THEN '노트'
WHEN 'GDS002' THEN '연필'
WHEN 'GDS003' THEN '복사지'
WHEN 'GDS004' THEN '볼펜'
WHEN 'GDS005' THEN '네임펜'
WHEN 'GDS006' THEN '크레파스'
END AS GC,
OD.orderedCnt, OD.amount
FROM tbl_orderinfo AS OI
INNER JOIN tbl_orderdetail AS OD ON OI.orderNo = OD.orderNo
INNER JOIN tbl_goodsinfo AS GI ON GI.goodsCode = OD.goodsCode
INNER JOIN tbl_member AS M ON M.memberId = OI.memberId;
-- JOIN 한번 더 해보기
# 가능한 JOIN으로 다 될 수 있도록 애초에 DB를 구성해야함
# SELECT문 안에 CASE로 사용하는 것보다 JOIN으로 사용하는게 훨씬 빠르다 하심
# CASE로 쓸 수 있는건 거의 대부분 JOIN으로 다 가능할 것임. JOIN으로 사용하도록!!!!!!
SELECT
EM.first_name, EM.last_name,
DE.emp_no,
DP.dept_no, DP.dept_name
FROM dept_emp AS DE
INNER JOIN departments AS DP ON DE.dept_no = DP.dept_no
INNER JOIN employees AS EM ON EM.emp_no = DE.emp_no
;
-- DECODE_ORACLE() 함수
# ORACLE에서 쓰던 함수인데 쓰기 편해서 MariaDB에서도 사용할 수 있게 됨
/*
사용법
DECODE_ORACLE(값
, 비교값, 결과
, 비교값, 결과
, 비교값, 결과
, 나머지 결과) AS 컬럼 이름정하기
*/
-- DECODE_ORACLE 사용해보기
# CASE문과 달리 key, value를 콤마로만 구분되서 작성 시 주의가 필요합니다.
# 쭉 줄줄이 한줄로 이어서 써도 되지만 위의 이유로 인해 줄넘김을 작성해주는게 좋습니다. (내가 일하는 필드에 룰에 따르긴해야함!)
SELECT
memberId, NAME, jumin, addr1,
decode_oracle(jobcode,
'01', '학생',
'02', '회사원',
'03', '공무원',
'04', '교사',
'기타' ) AS jobname
FROM tbl_member
ORDER BY memberId
;
-- IF() 함수
/*
사용법
IF(조건문, 참일 때 결과, 거짓일 떄 결과);
*/
-- IF 사용해보기 (간단한버전)
SELECT if(1<2, 'true', 'false');
-- IF 제대로 사용해보기!
SELECT
orderNO, orderDate, memberId, orderAmount, cancelYN,
if(cancelYN = 'Y', '주문취소', '정상주문') AS orderStatus
FROM tbl_orderinfo
ORDER BY orderNo;
-- 막 바꿔보기 (IF / CASE / DECODE_ORACLE)
# Java에서 if문을 switch문으로 바꿀 수 있던 것 처럼 SQL에서도 CASE를 IF로 CASE를 DECODE_ORACLE로 바꿀 수 있고 바꿀줄 알아야함.
SELECT
orderNO, orderDate, memberId, orderAmount, cancelYN,
if(cancelYN = 'Y', '주문취소', '정상주문') AS orderStatus1, # IF()버전
CASE WHEN cancelYN = 'Y' THEN '주문취소' ELSE '정상주문' END AS orderStatus2, # CASE 버전
DECODE_ORACLE(cancelYN, 'Y', '주문취소', 'N', '정상주문') AS orderStatus3 # DECODE_ORACLE() 버전
FROM tbl_orderinfo
ORDER BY orderNo;
-- IFNULL(), NVL(), NULLIF(), NVL2()
# NULL인지 비교를 간단히 하기 위해 만들어진 함수
/*
사용법
IFNULL(대상, 대상이 NULL일 경우 결과)
NVL(대상, 대상이 NNULL일 경우 결과)
NULLIF(대상1, 대상2) => 대상1이 대상2가 같으면 NULL반환 / 아니면 대상1을 반환
NVL2(대상, NULL이 아닐때 결과, NULL일 때 결과)
*/
-- 사용해보기!
UPDATE tbl_member SET addr2 = NULL WHERE memberId = 'gee1'; #null인게 없어서 특정 항목 null로 만들어줌
SELECT
memberId, NAME, jumin,
addr1,
IFNULL(addr2, '빈주소') AS addr2,
IFNULL(addr2, '') AS addr3,
NVL(addr2, '') AS addr4,
NULLIF(addr1, addr2) AS addr12,
NULLIF(addr2, NULL) AS addr22, # ISNULL과 같은 용도로 쓰는 법
NVL2(addr2, addr2, '빈주소') AS addr23 # Java 삼항연산자와 유사 (조건) ? true일 경우 실행 : false일 경우 실행;
FROM tbl_member
ORDER BY memberId;
-- [정보함수]
-- ROWNUM() : 순서대로 행번호를 부여합니다. (AUTO_INCREMENT와 비슷함, 자동으로 매겨지는 행번호!)
# 페이징할 떄 ROWCOUNT()랑 함께 사용하기도 함.
SELECT
rownum() AS rowNo,
memberID,
NAME,
jumin
FROM tbl_member
ORDER BY memberId ASC;
SELECT
rownum() AS rowNo,
memberID,
NAME,
jumin
FROM tbl_member
ORDER BY memberId DESC; # ROWNUM() 으로 행번호를 부여한 다음 DESC 정렬이되서 rowNo도 역순으로 나옴!
-- ★ ROW_COUNT() : 쿼리문이 실행되면서 영향을 미친 행 수를 반환합니다. (INSERT, UPDATE, DELETE를 통해)
# 쿼리문이 실행되어도 값이 사실상 기존 값과 같다면 0이 나옴 (진짜로 값이 변경되거나 삭제되거나 추가되야지 값이 제대로 나옴) - 문제점
# └> 이유 : MariaDB는 UPDATE문 실행 시 실제로 값이 변경되는게 아니면 실행한 척만 하고 실행 안함(덮어쓰기 안함)
# 사용목적 : INSERT, UPDATE, DELETE 하고 실제로 그게 잘 적용되었는지 알아보기 위해 사용하기도함...
SELECT
ROW_COUNT()
FROM tbl_member; # 뭐 INSERT, UPDATE, DELETE가 실행된적이 없으니 값이 다 0으로 나옴
UPDATE tbl_member SET addr2 = 'addr2' WHERE memberID = 'gee1';
SELECT ROW_COUNT();
# heidiSQL 설정에서 실행 옵션을 '일괄보내기'로 변경 후 위 두 쿼리를 한번에 실행해야 정상적인 결과를 받을 수 있습니다.
# 실행 옵션 => 상단 메뉴에서 '▶' 버튼 우측 하단 화살표 클릭 '일괄보내기'로 변경
-- DEFAULT(열) : 해당 열에 설정된 기본값(Default)을 반환해줍니다.
# 잘 안쓰지만, 테이블 명세를 쿼리로 작성해서..외부파일로 변환할 떄 정보 조회용으로 사용함..(=> 결론 : 잘 안씀!)
SELECT
DEFAULT(mileage),
DEFAULT(memberState),
DEFAULT(leaveDate)
FROM tbl_member
WHERE memberID = 'gee1';
-- ★ LAST_INSERT_ID() : 마지막 INSERT된 데이터의 AUTO_INCREMENT 열의 값을 반환
# 즉, AUTO_INCEREMENT를 쓰는 열이 없으면 못쓴다!
# 게시글에서 새 글 작성 후 성공적으로 저장되면 바로 방금 INSERT된 인덱스 넘버 받아서 바로 그걸로 해당 게시글 상세 보여줄 떄 사용될 수 있음.
INSERT INTO tbl_member (memberId, NAME, pwd, jumin, addr1, addr2, birthday, jobCode, mileage, memberState, regDate)
VALUES ('test', '테스트', '1234', '222222-2222222', '이것은 주소입니다.', '주소-주소', '2002-06-24', '05', 2000, 'Y', NOW());
SELECT LAST_INSERT_ID(); # 열심히 썼찌만.. 해당 테이블에는 AUTO_INCREMENT 속성을 갖는 열이 없어서 대차게 실패!!!
INSERT INTO tbl_test (title_no, title)
VALUES ('0011', '테스트');
SELECT LAST_INSERT_ID(), ROW_COUNT(); # 재도전 성공!
/*
보너스 : 게시글에서 어떻게 부모글과 댓글과 대댓글을 구분할까?
글번호 | 제목 | 글쓴이 | 글 인덱스
1 | 제목1 | 원작성자 | 1
1-1 | 난 댓글 | 작성자1 | 2
1-2 | 난 대댓글 | 작성자2 | 3
---------
글 인덱스 | 부모글 인덱스 | 글레벨 | 정렬순서
1 | 1(본인) | 0(게시글) | 1
2 | 1 | 1(댓글) | 2
3 | 2 | 2(대댓글) | 3
요런식으로 구성해서 게시글 리스트 구성함.
이 때 인덱스를 LAST_INSERT_ID()를 통해 인덱스를 가져올 수 있음
*/
-- USER(), CURRENT_USER() : 로그인하여 현재 DB를 사용중인 사용자 정보 표시함
# 쉘프로그래밍에서 현재 DB접속 로그인정보 갖고와서 작업하기도함.
SELECT USER(), CURRENT_USER();
-- DATABASE() : 현재 접속하고 있는 데이터베이스
SELECT DATABASE();
-- VERSION() : 데이터베이스 시스템의 버전 정보
SELECT VERSION();
-- ★ JSON_OBJECT() : JSON객체로 변환할 떄 사용 함
# 이런식으로 JSON형식으로 변환해서 REST API로 활용해야함
# 반대로 JSON 객체로 온 데이터를 DB에 넣을 줄 알아야함.
SELECT
JSON_OBJECT(
'memberId', memberId
, 'name', NAME
, 'jumin', jumin
, 'addr1', addr1
, 'addr2', addr2
, 'birthdaty', birthday
, 'jobCode', jobCode
, 'mileage', mileage
, 'memberState', memberState
, 'regDate', regDate
, 'leaveDate', leaveDate
) AS json_member
FROM tbl_member
ORDER BY memberId;
-- 묵시적 형변환 : 상황에 따라 자동으로 데이터 유형이 변환이 이루어지는 것
# 논리적 오류에 빠질 수 있으니 가능한 명시적으로 형변환해서 사용하도록 하자
-- 예시
# CASE 1 : 문자열 내 숫자가 담겨있는 경우 수치형으로 자동 형변환되서 계산 됨
SELECT '100' + '200';
SELECT '100' = 100;
SELECT CONCAT('100', '200'); # 자동 형변환 발생 안하려면 CONCAT으로 써야함
# CASE 2 : 숫자 + 문자(문자로 시작하는)일때는 숫자가 아닌건 전부 0 취급 함(MariaDB, MySQL 특징)
SELECT 100 + 'A100', 200 + 'B300A';
SELECT 'A100' + 100 , 'B300A' + 200;
SELECT 100 * 'A100', 200 / 'B300A';
SELECT 100 = 'A100', 200 = 'B300A';
# CASE 3 : 숫자 + 문자(숫자로 시작하는)일 때는 문자에서 숫자 부분만 갖고와서 계산 함
SELECT 100 + '100A', 200 + '300BA';
SELECT '100A' + 100 , '300BA' + 200;
SELECT 100 * '100A', 200 / '300BA';
SELECT 100 = '100A', 200 = '300BA'; # '100A'에서 앞 100만갖고와서 비교하므로 100 = '100A'에 대해 1(true)를 반환 함.
-- 7장 : 고급 SQL
# 월 : MariaDB 필기 시험 예정
# 목~금 : MariaDB 구술 면접형 시험 예정
-- JOIN절 : 두개 이상의 테이블을 연결해서 사용하는 것 (자기 자신 포함)
# 종류 : CROSS JOIN, INNER JOIN, OUTER JOIN, SELF JOIN
# CROSS JOIN >> OUTER JOIN > INNER JOIN > SELF JOIN 순으로 부하량 큽니다.
/*
[JOIN문 기본 형식]
SELECT 열목록
FROM 테이블명1 (AS alias명)
{INNER | OUTER | CROSS} JOIN 테이블명2 (AS alias명)
(ON 조인조건)
[작성예시]
SELECT 테이블명.컬럼명,,,
FROM 테이블명1
INNER JOIN 테이블명2
ON 테이블명1.컬럼1 = 테이블명2.컬럼2
[ALIAS 주는 예시]
SELECT T1.컬럼명,,,
FROM 테이블명1 AS T1
INNER JOIN 테이블명2 AS T2
ON T1.컬럼1 = T2.컬럼2
[TIP] ★★★★★
◆ JOIN 앞에 명시 안해주면, INNER JOIN으로 간주함. (다만, 가능한 명시적으로 작성해줌이 좋다.)
=> 선생님 TIP!! 생략하지 말자!!!
◆ FROM 테이블은 현재 업무의 목적성이 되는 테이블이 되야하고, JOIN 테이블은 FROM 테이블 기준 어떤 정보들을 갖고오고싶은지에 따라 추가
◆ AS로 alias를 안줘도 되지만 각 테이블에서 중복된 컬럼명이 있을 경우
또는 테이블명이 너무너무 길어서 매번 작성할 수 가 없을 경우가 있을 수 있어서, 최대한 alias를 주는게 옳다.
◆ SELECT 문 열목록에서도 AS로 정리해주는게 좋음
◆ ON 조인조건은 JOIN하는 테이블에 대해 특정 조건(프라이머리 키와 외래키로 연결)으로 연결해서 가져오는 조건을 거는 것 입니다.
WHERE절 에서도 비슷하게 할 수 있는데 그렇게 하면 JOIN이 이미 된 상태에서 추가 조건으로 제어하는 것이라
데이터 명확성이 떨어지므로 JOIN 관련 조건이면 ON에서 하는게 맞다.
=> 선생님 TIP!! 절대 JOIN조건을 WHERE절로 처리하지 말자!!!! (매우 강조하심)
◆ ON 조인 조건을 여러개로 해야할 경우 AND 키워드로 이용할 수 있습니다.
◆ JOIN 부분 생략해서 테이블명만 나열하는 식으로 할 수 있으나, 생략하지 말고 명시적으로 작성 하도록 해야한다.
=> 읽기도 힘들고, 유지보수할 때도 해독해야함! 자칫 잘못하면 코드 다 까봐야함.
=> 선생님 TIP!! 절대 이런식으로 생략해서 쓰지말자!!!! (매우 강조하심)
생략예시 : SELECT 테이블명. 컬럼명,,,
FROM 테이블명1, 테이블명2, 테이블명3
ON ~~~~
◆ 쿼리 작성 하고 항상 해당 데이터가 정말 맞는 데이터인지 검증하는 습관을 길러야함.
데이터 나온다고 끝이 아니라 그 데이터가 정말 내가 원한 데이터인지 검증하는 과정이 필요함.
*/
# 검색을 많이 하는 컬럼은 INDEX KEY로 잡아주는게 좋음 / 다만, INSERT UPDATE에 대해서는 성능 저하가 일어날 수 있으니 이 점 참고해야함. LIKE 장바구니~
-- INNER JOIN(내부조건) 사용해보기
USE employees;
SELECT * FROM departments; # 부서정보
SELECT * FROM employees; # 직원정보
SELECT * FROM dept_emp; # 직원별 부서정보
SELECT
EM.first_name, EM.last_name, EM.hire_date
, DP.dept_no, DP.dept_name
FROM employees AS EM
INNER JOIN dept_emp AS DE ON DE.emp_no = EM.emp_no
INNER JOIN departments AS DP ON DP.dept_no = DE.dept_no
WHERE DP.dept_no IN ('d002', 'd005', 'd008')
ORDER BY DP.dept_name, EM.hire_date DESC
;
-- CROSS JOIN(상호조인) 사용해보기
# CROSS JOIN은 그냥 두 테이블을 나란히 들고와서 나열하는 개념임. 뭐 연결 그런거 없음 그냥 나열!
# 그래서 다른 JOIN보다 속도가 더 느림! 무식하게 그냥 냅다 다들고오니깐 (중복되건 말건~ 그냥 다 갖고와~~)
SELECT *
FROM employees AS EM
CROSS JOIN salaries AS SL
LIMIT 100; # LIMIT 안걸고 그대로 실행하면 데이터 2억건 넘어서 큰일남...ㄷㄷ 심지어 CROSS JOIN 이라 더 오래걸림
SELECT *
FROM employees AS EM
CROSS JOIN salaries AS SL
ON EM.emp_no = SL.emp_no # 이 조건 걸면 매칭안되던 데이터는 다 없어짐
WHERE EM.emp_no = 499966
;
# 어떻게든 데이터 범위를 줄일 수 있는 조건이 뭐가 있나 찾아보던 흔적 >_< => 실패함
SELECT emp_no, COUNT(emp_no) AS cnt
FROM salaries
WHERE emp_no > 490000
GROUP BY emp_no
having cnt > 17
ORDER BY cnt DESC, emp_no DESC
LIMIT 10
;
-- OUTER JOIN(외부조인) 사용해보기
# OUTER JOIN은 LEFT/RIGHT JOIN으로 작성하며, JOIN 키워드를 기준으로 기준 테이블을 정하고 해당 기준 테이블의 데이터 기준으로 가져옵니다.
# 한쪽 테이블에서만 있는 데이터는 NULL로 나오는데 실제 NULL인 데이터인지는 검증이 필요합니다.
/*
[OUTER JOIN 기본 형식]
SELECT 컬럼,,,
FROM 테이블1
{LEFT | RIGHT} OUTER JOIN 테이블2
ON 조인 조건
*/
USE maria;
# RIGHT JOIN : RIGHT TABLE인 tbl_member 기준이라 tbl_orderInfo 테이블에 없는 데이터도 다 갖고 옴
SELECT
OI.memberId
, MB.name
FROM tbl_orderinfo AS OI # FROM 쪽 테이블이 LEFT 테이블
RIGHT OUTER JOIN tbl_member AS MB ON OI.memberId = MB.memberId # JOIN 우측 테이블이 RIGHT 테이블
ORDER BY OI.orderNo
;
# LEFT JOIN : LEFT TABLE인 tbl_orderInfo 기준이라 tbl_member 테이블에 없는 데이터도 다 갖고 옴 (근데 아래 예시 데이터에는 없는 데이터가 없음)
SELECT
OI.memberId
, MB.name
FROM tbl_orderinfo AS OI
LEFT OUTER JOIN tbl_member AS MB ON OI.memberId = MB.memberId
ORDER BY OI.orderNo
;
-- 실습!
/*
주문총액이 3,000보다 크고, 주문자의 직업코드가 '1' 또는 '2'인 회원이 주문한 주문내용으로
'주문번호', '주문자ID', '주문자 이름', '주문총액', '직업코드'를 조회한다.
결과 데이터는 주문번호의 오름차순으로 정렬해서 표시한다.
=> 이런 요구사항을 만나면, 일단 요구사항을 쪼개자!
1. 주문총액이 3,000 보다 크고
2. 주문자의 직업코드가 1 또는 2인 회원
3. 주문번호, 주문자 ID, 주문자 이름, 주문총액, 직업코드
4. 주문번호의 오름차순 정렬
[지현 분석]
- 일단 해당 요구사항은 주문정보가 주요 목적인 요구사항!
- SELECT는 주문번호, 주문자 ID, 주문자 이름, 주문총액, 직업코드
- FROM은 주문정보 테이블
- JOIN은 멤버 테이블
JOIN 방법은 INNER JOIN (주문테이블에도 있고 회원테이블에도 있는 데이터를 가져와야하기 떄문)
- WHERE에 들어가야하는 건 주문총액 > 3000 AND 직업코드 IN (1, 2)
- ORDER BY에 들어가야하는 건 주문번호 ASC
*/
SELECT * FROM tbl_member;
SELECT * FROM tbl_orderinfo;
SELECT
OI.orderNo, OI.memberId, MB.name, OI.orderAmount, MB.jobCode
FROM tbl_orderinfo AS OI
INNER JOIN tbl_member AS MB ON MB.memberId = OI.memberId
WHERE OI.orderAmount > 3000 AND MB.jobCode IN ('01', '02')
ORDER BY OI.orderNo ASC
;
/*
어떤 JOIN을 써야할까?
INNER JOIN : JOIN할 테이블들 모두 기준 데이터를 갖고있을 때 (기준 데이터가 외래키로 잡혀있던가, 각 테이블에서 인덱스 키로 잡혀있어야 함.)
OUTER JOIN : JOIN할 테이블에서 기준 데이터를 각 각 부분적으로 갖고있을 때
*/