2026년 1월 28일 - 5주차
강사님이 문제를 풀면서 문법 설명을 하셔서 수업 흐름대로 글을 적게되면 문법설명과 문제가 너무 뒤섞여버릴 것 같기 때문에 가독성을 위해 앞으로는 문법 설명 부분과 문제풀이 부분을 따로 나눠서 기술 하겠다.
LIKE: 조건을 작성할때 특정 문자열이 포함되어있는지 검색할때 사용한다.
- 기본구조: WHERE 테이블명 LIKE '문자열'; - '문자열' 부분에는 특수기호가 들어가야 한다.
- 여기서 LIKE 대신 =을 쓸때 특수문자를 쓰면 그건 그냥 그 특수문자가 들어있는 문자열을 검색한다는 뜻인다. ex) bookname = '축구%'; 는 책 이름이 축구%와 완전히 같은걸 검색한다.
- [특수문자]
- 특수문자는 LIKE에서만 사용가능하다.
- %: 0개 이상의 문자와 일치할때 사용한다. %의 위치에 따라 의미가 다른데, '김%' 같은 경우는 김으로 시작하는 모든 문자열을 의미하고, '%수'는 수로 끝나는 모든 문자열을 의미한다.%가 양쪽에 들어갈 수 있는데, 그런 경우에는 찾으려는 문자열이 어느 위치에 있던 상관없이 포함이 되어있는 경우를 의미한다.
- : %는 앞뒤로 몇개의 문자가 있어도 상관이 없었는데 는 앞뒤로 올 문자의 갯수를 지정한다. ex) name = '김__' 이라면 성이 김이고, 이름이 2글자인 사람이 검색된다. 만약 이름이 4글자라서 성을 제외한 이름이 3글자가 된다면 검색되지 않는다.
- ORDER BY: 정렬하는 것. 정렬하지 않았을때 나오는 행들의 순서는 의미가 없다. 어떤 순서로 나오는지 알 수 없고, 보장 할 수 없다는 뜻. 근데 그대로 입력 순서대로 나오기는 한다...
- 기본구조: SELECT * FROM 테이블명 ORDER BY 필드명;
- 기본은 오름차순이고 ASC가 생략된것, 하지만 써주는게 좋음 내림차순은 DESC이다. 보통은 내림차순이 더 많이 쓰인다. 최근에 올라온 글이 맨 위에 올라와야 하니까. 게시날짜 기준으로 내림차순으로 표시하는것.
- 필드명을 기준으로 정렬된다.
데이터베이스도 API가 존재하는데, SQL에서는 함수라고 부른다.
집계 함수의 대부분은 괄호안에 벡터를 넣어서 스칼라값을 추출하는 것이다. 여러줄의 데이터를 압축해서 한줄로 만드는 것이다. 그래서 괄호 안에 ,를 써서 여러가지 벡터를 넣을 수 없고, 단 하나의 벡터만 넣을 수 있다.
- SUM(): 합을 구하는 함수
- AVG(): 평균값 구하는 함수
- MIN(): 최소값 구하는 함수 - 제일 작은 값
- MAX(): 최대값 구하는 함수 - 제일 큰 값
- COUNT(): 보통 함수 안에는 벡터를 넣을 수 없지만, 이것은 횟수를 세는것이기 때문에 벡터를 넣을 수 있다. (벡터라고 해봤자 * 밖에 못넣는다)
- SELECT 다음에는 여러개의 필드명을 넣어서 여러개의 필드의 값을 뽑아 올 수 있는데, 이때 집계를 해서 벡터값이 아닌 스칼라값이 나왔다면 다른 것들도 무조건 집계를 해서 스칼라 값으로 축력해야 한다. 출력할때 스칼라값과 벡터값이 섞여있으면 오류가 된다.
GROUP BY: 같은 값을 가지는 것끼리 합친다.
- 기본구조: GROUP BY 필드명
필드명을 기준으로 같은 값을 가지는 것을 행에서 분리해서 모아준다. 기준을 세우는것이고 GROUP BY 한 벡터를 SELECT 해야 한다. 다른 벡터를 SELECT할 수 없으며, 다른 값을 출력 해야 한다면 벡터값을 그대로 사용하는 것이 아니라 벡터를 집계 해서 스칼라 값으로 만들어서 써야 한다. 안그러면 문법 오류수준.
- 사용 예시:
SELECT custid, SUM(saleprice) FROM Orders GROUP BY custid;- HAVING: WHERE와 같은 조건문인데 WHERE는 GROUP BY보다 먼저 실행되기 때문에 그룹으로 묶기 전에 하나의 행만 가지고 필터링할때 쓰이는데, HAVING은 GROUP BY보다 실행순서가 늦어서 그룹으로 묶은 다음에 또 조건을 붙일때 쓰인다. WHERE에는 COUNT(*)와 같은 집계 함수는 들어 갈 수 없고 비교 연산자와 같은것만 들어갈 수 있는데에 반해, HAVING은 COUNT(*)와 같은 집계 함수만 들어가고 비교 연산자는 보통 들어가지 않는다.
다른 테이블에 있는 내용을 가져와서 합치는 것. 조인을 많이 하면 성능이 저하될 수 있다. 그리고 SELECT할때 * 을 붙여서 출력하는건 의미가 없다. 그리고 테이블명 뒤에 별칭을 붙여줄 수 있는데, 앞으로 테이블명이 길어질거고 그걸 다 쓰긴 힘들기 때문이다. 조인의 종류는 여러가지가 있으며, join은 기본키가 외래키로 사용되고 있을때 할 수 있다. 그리고 sql의 종류가 다양하기 때문에 같은 join이라도 조금씩 다를 수 있는데, 대표적으로 Oracle join이랑 ANSI 표준 join이 있다.
- cross join: WHERE를 해주지 않으면 하나의 결과를 출력할때 모든 경우의 수를 가지는 값이 달라붙는데, 이게 cross join이고 의미가 없는 행동이다. 이렇게 쓰는 경우는 거의 없다.
- cross join 사용 예시SELECT * FROM Orders o, Customer c;
- inner join: 쓰레기값을 거르는 것. 한쪽의 기본키와 한쪽의 외래키를 같게 만들어주면 된다. inner join은 조인을 하는 기준(순서)이 중요하지 않으며, 어떤걸 기준으로 하더라도 결과는 바뀌지 않는다. 작동방식으로는 기준이 되는 테이블의 오른쪽에 다른 테이블이 달라붙고, 같은 필드명을 가지는 필드들을 하나로 만들어서 쓰레기 값이 제거된 채로 출력된다(외래키를 기본키로 만들어서 기본키를 기준으로 만들어서 사용한다). inner join의 특징인데, 다른 테이블에 필드가 있더라도, 기준이 되는 테이블에는 없어서 필드값이 매치되지 않으면 나오지 않는다.
- Oracle inner join 사용 예시SELECT * FROM Orders o, Customer c WHERE c.custid = o.custid;- ANSI 표준 inner join 사용 예시SELECT * FROM Orders o INNER JOIN Customer c ON c.custid = o.custid ;
- outer join: inner join이랑 거의 똑같은데 하나만 다르다. 기준이 되는 필드에 붙지 않는 값이 있다면 무조건 마지막에 붙게되는데, 이때 값은 다 NULL이다. 그렇기 때문에 outer join은 inner join과 다르게 테이블을 조인하는 기준(순서) 중요하다. 조인하는 순서가 바뀐다면 결과가 달라진다. (한번도 주문하지 않은 고객의 정보도 출력하고싶을때 쓴다)
SELECT * FROM Customer c LEFT OUTER JOIN Orders o ON c.custid = o.custid ;
- =의 뒤에는 반드시 스칼라 값이 나와야 하는데. 스칼라 값은 고정이 되어있는 값이기때문에 유동적으로 바뀌어야 할 때는 사용 할 수 없다. 이때 결과값으로 스칼라를 반환하는 쿼리를 하나 더 만들어서 =의 뒤에 괄호로 넣어서 사용하는것이 서브 쿼리이다. 가끔가다 서브쿼리를 사용하지 않아도 join으로 풀 수 있는 경우에는 join으로 푸는것이 권장된다.
- 어려워보이지만 쿼리를 따로따로 뜯어서 보면 쉽다.
[용어]
스칼라: 단 한개의 값을 말하며, 스칼라가 모여서 벡터를 이룬다.
백터: 결과값으로 나오는 1중 배열이며, 이때 가로가 아닌 세로의 값을 말하는 것이고, 벡터가 모여서 매트릭스가 된다.
매트릭스: 2중 배열. 결과값으로 나온 전체를 말함.
primary key: 기본키
foreign key: 외래키
오늘 작성한 코드 전체.
-- '축구의 역사'를 출간한 출판사를 검색하세요:
SELECT publisher FROM Book WHERE bookname = '축구의 역사';
-- 출판사를 검색하는거니까 SELECT publisher이고, 조건이 책 이름이 같은지.
-- 도서 이름에 '축구'가 포함된 출판사를 검색하세요:
SELECT * FROM Book WHERE bookname LIKE '축구%';
-- =은 완벽히 같아야 하므로 사용x
-- 도서 이름의 두번째 글자가 '구'인 책을 검색하세요:
SELECT * FROM Book WHERE bookname LIKE '_구';
-- 김씨 성을 가진 고객을 검색하세요:
SELECT * FROM Customer WHERE name LIKE '김__';
--이렇게 해도 되긴 한데, 이름이 4자 이상일수도 있으니까 아래처럼 하는게 안전함
SELECT * FROM Customer WHERE name LIKE '김%';
-- 축구에 관한 도서 중 가격이 20000원 이상인 도서를 구하세요
SELECT * FROM Book WHERE bookname LIKE '%축구%' AND price >= 20000;
-- 도서를 이름 순으로 검색하세요:
SELECT * FROM Book ORDER BY bookname ASC;
-- 도서를 가격 순으로 검색하고, 가격이 같으면 이름 순으로 출력하세요:
SELECT * FROM Book ORDER BY price ASC, Bookname DESC;
-- , 로 정렬 조건을 추가 할 수 있다.
-- 집계 GROUP BY
-- 고객이 주문한 도서의 총 판매액을 출력하세요:
SELECT SUM(saleprice) FROM Orders;
-- 이러면 필드명이 SUM(saleprice)로 나온다. SUM(saleprice) "총 판매액" 을 쓰면
-- 필드명을 원하는대로 설정 가능. 근데, 그 이름을 자바에서 써야해서 이렇게는 잘 안쓴다.
-- 자바에서는 필드명이 변수명인데, 변수명은 괄호를 쓸수 없기 때문에 AS로 바꿔야 한다.
SELECT SUM(saleprice) AS "총 판매액" FROM Orders;
SELECT SUM(saleprice) AS totalSaleprice FROM Orders;
-- 띄어쓰기 없을때는 ""을 쓰지 않아도 되지만 띄어쓰기를 넣고싶으면 ""을 넣어줘야 한다.
-- 사실 변수명으로 쓸거라서 어차피 띄어쓰기를 쓸 수 없기 때문에 필요없다.
-- 2번 김연아 고객이 주문한 도서의 총 판매액을 구하세요:
SELECT SUM(saleprice) FROM Orders WHERE custid = 2;
-- 고객이 주문한 도서의 총 판매액, 평균값, 최저값, 최고값을 구하세요:
SELECT
SUM(saleprice),
AVG(saleprice),
MIN(saleprice),
MAX(saleprice)
FROM Orders; # 길어지면 보통 내려서 씀
-- 마당 서점의 도서 판매 건수를 구하세요:
SELECT COUNT(*) FROM Orders;
-- 일반적으론 한번의 주문당 여러개를 구매 할 떄도 있어서 따로 필드를 만들어서 SUM을 쓰는데,
-- 여기는 그게 없으니까 한 주문당 한권으로 가정하고 갯수를 세는 방식으로 함
SELECT orderid, saleprice, 1234, 'ffff' FROM Orders;
-- 출력될 결과로 상수나 문자 사용가능, 사칙연산 수행 가능. 필드명끼리도 더하고 뺄수도 있다.
-- 고객이 주문한 도서의 최고값과 최저값의 차이를 구하세요:
SELECT
MAX(saleprice) - MIN(saleprice)
FROM Orders;
-- GROUP BY
-- 고객별 주문한 도서의 총 수량과 총 판매액을 구하세요:
SELECT custid, SUM(saleprice) FROM Orders
GROUP BY custid
;
-- 책별 판매량을 구하세요:
SELECT bookid, COUNT(*) AS cnt FROM Orders
GROUP BY bookid
ORDER BY cnt DESC;
-- 가격이 8000원 이상인 도서를 구매한 고객에 대해서, 고객별 주문도서의 총 수량을 구하세요:
-- 단, 2개 이상 구매한 고객만 구해라.
SELECT custid, COUNT(*) FROM Orders
WHERE saleprice >= 8000
GROUP BY custid
HAVING COUNT(*) >= 2
;
-- GROUP BY는 WHERE보다 실행순서가 늦다.
-- 그래서 가격이 8000원 이상인 도서를 필터링 먼저 하고 GROUP BY 함.
-- [연습문제 풀이]
-- 1번: 마당서점의 고객이 요구하는 다음 질문에 대해 sql문을 작성하시오.
-- 1. 도서번호가 1인 도서의 이름:
SELECT bookid, bookname FROM Book WHERE bookid = 1;
-- 2. 가격이 20,000원 이상인 도서의 이름
SELECT bookname, price FROM Book WHERE price >= 20000;
-- 3. 박지성의 총 구매액 (박지성의 고객번호는 1번)
SELECT custid, SUM(saleprice) FROM Orders WHERE custid = 1;
-- 4. 박지성이 구매한 도서의 수 (박지성의 고객번호는 1번)
SELECT custid, COUNT(*) FROM Orders WHERE custid = 1;
-- 2번: 마당서점의 운영자와 경영자가 요구하는 다음 질문에 대해 sql문을 작성하시오.
-- 1. 마당서점 도서의 총 개수
SELECT COUNT(*) FROM Book;
-- 2. 마당서점에 도서를 출고하는 출판사의 총 개수
SELECT COUNT(DISTINCT publisher) FROM Book;
-- 3. 모든 고객의 이름, 주소
SELECT name, address FROM Customer;
-- 4. 2025년 7월 4일~ 7월 7일 사이에 주문 받은 도서의 주문번호
SELECT orderid, orderdate FROM Orders WHERE orderdate BETWEEN '2025-07-04' AND '2025-07-07';
-- 5. 2025년 7월 4일~ 7월 7일 사이에 주문 받은 도서를 제외한 도서의 주문번호
SELECT orderid, orderdate FROM Orders WHERE orderdate NOT BETWEEN '2025-07-04' AND '2025-07-07';
-- 6. 성이 '김' 씨인 고객의 이름과 주소
SELECT name, address FROM Customer WHERE name LIKE '김%';
-- (7) 성이 ‘김’ 씨이고 이름이 ‘아’로 끝나는 고객의 이름과 주소
SELECT name, address FROM Customer WHERE name LIKE '김%아';
-- Oracle 조인
-- 고객과 고객의 주문에 관한 데이터를 모두 구하세요:
SELECT * FROM Orders o, Customer c
WHERE c.custid = o.custid;
# 같은것만 깔끔하게 출력하겠다.
-- ANSI 조인
SELECT *
FROM Orders o
INNER JOIN Customer c ON c.custid = o.custid
;
-- 고객과 고객의 주문에 관한 데이터를 고객번호 순으로 정렬하세요:
SELECT * FROM Orders o
INNER JOIN Customer c ON c.custid = o.custid
ORDER BY c.custid ASC # 외래키보다 기본키를 쓰는게 좋다. 관례임
;
-- 고객의 이름과 고객이 주문한 도서의 판매 가격을 구하세요:
SELECT c.name, o.saleprice FROM Orders o
INNER JOIN Customer c ON c.custid = o.custid
;
# join해서 필요한 정보만 추출(컨트롤)함
-- 고객별 주문한 모든 도서의 판매가격을 구하고, 고객별로 정렬하세요:
-- (이름 출력되야 하니까 name을 기준으로 그룹화 함 - 근데 같은 이름을 가진 신규 회원이 생길 수 있기 때문에 완벽한 정답은 아니다)
SELECT c.name, SUM(o.saleprice)
FROM Orders o INNER JOIN Customer c ON c.custid = o.custid
GROUP BY c.name
ORDER BY c.name ASC
;
-- 고객별로 주문한 도서의 총 판매액을 구하고, 고객별로 정렬하세요:
SELECT c.name, SUM(o.saleprice)
FROM Orders o INNER JOIN Customer c ON c.custid = o.custid
GROUP BY c.name
ORDER BY c.name ASC
;
-- 고객의 이름과 고객이 주문한 도서의 이름을 구하세요:
SELECT c.name, b.bookname FROM Orders o
INNER JOIN Customer c ON c.custid = o.custid
INNER JOIN Book b ON b.bookid = o.bookid
;
-- 가격이 20,000원인 도서를 구매한 고객의 이름과 도서의 이름을 구하세요:
SELECT c.name, b.bookname FROM Orders o
INNER JOIN Customer c ON c.custid = o.custid
INNER JOIN Book b ON b.bookid = o.bookid
WHERE B.price = 20000
;
-- OUTER JOIN
-- 도서를 구매하지 않은 고객을 포함하여, 고객의 이름과 주문한 도서를 구하세요:
SELECT * FROM Customer c
LEFT OUTER JOIN Orders o ON c.custid = o.custid
;
-- subquery
-- 가장 비싼 도서의 이름을 구하세요:
SELECT *
FROM Book b
WHERE b.price = (
SELECT MAX(b.price) FROM Book b
);
# b.price = 뒤에는 스칼라 값이 나와야 하니까 스칼라값을 구하는 아래에 있는 쿼리를 가져와서 사용하는 것.
SELECT MAX(b.price) FROM Book b;
# 이건 그냥 가장 비싼 값을 스칼라로 구하는 쿼리
-- 도서를 구매한 적이 있는 고객의 이름을 구하세요:
SELECT c.name FROM Customer c
WHERE C.custid IN(
SELECT o.custid FROM Orders o
);
-- 대한미디어에서 출판한 도서를 구매한 고객의 이름을 구하세요: (사실 join으로도 풀수 있다.)
-- 작성 순서는 주석 달린 숫자 순서대로. 바로 이렇게 떠올리는게 어렵기 때문에 완성된 코드를 볼때 쿼리별로 뜯어서 이해하고 다시 조립 해보고 하면서 공부하기.
SELECT c.name FROM Customer c
WHERE c.custid IN (
SELECT o.custid FROM Orders o
WHERE o.bookid IN (
SELECT b.bookid FROM Book b WHERE b.publisher = '대한미디어'
)
); # 3. 결국 이 코드로 완성시킨다.
SELECT o.custid FROM Orders o
WHERE o.bookid IN (
SELECT b.bookid FROM Book b WHERE b.publisher = '대한미디어'
); # 2. 얘를 위에 조립해서
SELECT b.bookid FROM Book b WHERE b.publisher = '대한미디어'; # 1. 얘를 위에 조립하고
-- 상관 부속 질의
-- 출판사 별로 출판사의 평균 도서 가격보다 비싼 도서를 구하세요: 주석은 써있는 숫자 순으로 읽가
SELECT b.bookname FROM Book b
WHERE b.price > (
SELECT AVG(b2.price) FROM Book b2 WHERE b2.publisher = b.publisher
);
-- 2. 그래서 WHERE를 b2.publisher = b.publisher로 한다. 이건 서브 쿼리 밖에서 독립적으로 실행할 수 없다. 여기에는 b가 없으니까.
-- 도서를 출력할때 출판사별 평균값에 맞춰서 조건에 맞는 값만 출력을 해야하기 때문에 평균값이 출판사에 따라 바뀌어야한다. 그렇기때문에 연산을 할때 가격이 평균값 보다 큰지 판단할 변수 b와 평균을 구하기 위한 변수 b2가 필요하다.
-- b2는 WHERE로 조건에 맞는 출판사를 가져와서 평균값을 구한다. 그리고 변수 b는 b2로 구한 평균값이랑 Book 테이블을 비교해서 평균보다 큰 값만 출력하는데, 그렇게 출력하고나면 다음 행으로 넘어가기 때문에 출판사의 이름이 바뀌고
-- WHERE b2.publisher = b.publisher 를 해서 새로 바뀐 출판사의 값과 같은 출판사를 가져와서 다시 연산을 수행한다.
-- FROM은 반복문 느낌인데, 이건 2중 for문 느낌으로 이해하면 된다.
SELECT AVG(b2.price) FROM Book b2 WHERE b2.publisher = '굿스포츠';
-- 1. 이대로 가져가면 출판사가 그냥 굿스포츠로 고정이 됨.