[문과 코린이의 IT 기록장] MSSQL - 서브 쿼리 : IN 연산자, 상관 서브쿼리, 다중 INSERT 여러 방법, 서브쿼리 연습문제, IN, EXISTS 연산자, IN, EXISTS 연산자 연습문제
< 참고 강의 영상 >
MSSQL Server 2016 기반의 데이터베이스 입문에서 활용까지 Part.2 대시보드 - 인프런 | 강의 (inflearn.com)
2022.01.21 - [문과 코린이의, [SQL] 기록] - [문과 코린이의 IT 기록장] MSSQL - 비교연산자, 논리연산자
2022.01.21 - [문과 코린이의, [SQL] 기록] - [문과 코린이의 IT 기록장] MSSQL - 집계 함수, GROUP BY / HAVING
1. 서브 쿼리 : IN 연산자
- SELECT 쿼리문 안에, 다시 한 번 SELECT 쿼리문이 들어 있는 쿼리 형태이다.
- 용도
a. 단일 SELECT 쿼리문 만으로는, 어떤 조건식을 만들기 복잡할 때 쓴다.
b. 서로 다른 테이블에서 데이터 값을 조회하여, 본 쿼리의 조건으로 쓰고자 할 때도 사용한다.
ex. 실습 1 (SELECT)
a. 상품 테이블과 매출 테이블을 각각 만든다.
b. 두 테이블에 필요한 제약조건과 각각 기본키와 외래키를 지정한다.
-- 상품 테이블 생성
CREATE TABLE dbo.productTbl(
p_code CHAR(10) PRIMARY KEY, -- 상품코드 (기본키 지정) : 중복되는 값 삽입 X
: 중
p_name NVARCHAR(16) NOT NULL,
p_date DATE NULL,
p_area NVARCHAR(50) DEFAULT N'생산지미정', -- NVARCHAR이므로 DEFAULT값 앞에 N적어주기
p_money MONEY NULL,
p_num INT IDENTITY NOT NULL
);
-- 매출 테이블 생성 (외래키 지정)
/* 외래키 지정 방법
: FOREIGN KEY REFERENCES [테이블명]([컬럼명])
*/
CREATE TABLE dbo.salesTbl(
s_code CHAR(10) PRIMARY KEY, -- 매출코드
s_branch NVARCHAR(10) NOT NULL, -- 매출지사
s_p_code CHAR(10) FOREIGN KEY REFERENCES dbo.productTbl(p_code), -- 상품코드 (외래키 지정)
s_qty SMALLINT NULL, -- 판매수량
s_date DATE NULL,
s_num INT IDENTITY NOT NULL
);
c. 상품 테이블에 상품을 입력한다.
-- 데이터 입력 (상품 테이블)
INSERT INTO dbo.productTbl VALUES ( 'GD101', '나이키', '2000-12-12', '미국', 900);
INSERT INTO dbo.productTbl VALUES ( 'GD102', '아디다스', '2000-01-13', '독일', 450);
INSERT INTO dbo.productTbl VALUES ( 'GD103', '프로스펙스', '2000-02-14', '한국', 720);
INSERT INTO dbo.productTbl VALUES ( 'GD104', 'FILA', '2000-03-15', '영국', 500);
d. 매출 테이블에 매출 데이터를 쿼리로 입력한다.
-- 데이터 입력 (매출 테이블)
INSERT INTO dbo.salesTbl VALUES ( 'ST0001', '서울', 'GD101', 1700, '2022-01-01');
INSERT INTO dbo.salesTbl VALUES ( 'ST0002', '서울', 'GD102', 1100, '2022-01-02');
INSERT INTO dbo.salesTbl VALUES ( 'ST0003', '세종', 'GD103', 1400, '2022-01-03');
INSERT INTO dbo.salesTbl VALUES ( 'ST0004', '부산', 'GD104', 700, '2022-01-04');
INSERT INTO dbo.salesTbl VALUES ( 'ST0005', '광주', 'GD101', 2900, '2022-01-05');
INSERT INTO dbo.salesTbl VALUES ( 'ST0006', '제주', 'GD103', 2100, '2022-01-06');
INSERT INTO dbo.salesTbl VALUES ( 'ST0007', '제주', 'GD104', 300, '2022-01-07');
e. 테이블 값들을 모두 출력해보고 요구 쿼리를 작성한다
-- 데이터 출력
SELECT * FROM dbo.productTbl ORDER BY p_num;
SELECT * FROM dbo.salesTbl ORDER BY s_num;
- 요구 쿼리(= 요구사항들에 대한 쿼리) : 제약 조건이 잘 수행되는지 확인 : 공급가의 경우, 출력시에는 소숫점 이하를 생략해라. (함수 사용) : 상품의 판매 수량이 1000개 이상인 상품에 대해 출력해라. (서브 쿼리 사용) => IN 연산자 사용 |
-- 서브쿼리(IN)
SELECT -- 출력하고 싶은 값들
'1000개 이상 판매된 브랜드 : ' AS '컬럼명', -- 이와 같이 열 추가 가능
p_code AS'상품코드', -- 하나 끝나면 (,) 꼭 찍어주기
p_name AS'상품명',
p_area AS'생산지',
Replace(p_money,'.00','') AS'공급가' -- 소수점 아래의 .00을 공백으로 바꿔라
FROM dbo.productTbl
WHERE p_code IN( -- 여기에 서브쿼리 작성 (큰 쿼리 안에 Select문을 작성해서 넣기)
-- 이 조건에 해당하는 s_p_code들이 존재하면, p_code에서 출력
SELECT s_p_code-- s_p_code만 가져오면 됨 (비교대상)
FROM dbo.salesTbl
WHERE s_qty >= 1000 -- 상품의 판매 수량이 1000개 이상인 상품 출력
);
/*서브쿼리 : 이 내부의 쿼리(SELECT)문은 따로 실행이 되어진다.
상관쿼리 : 쿼리는 따로 실행이 되지 않는다.*/
- IN 연산자는, 서브쿼리로 수행되어져 나오는 결과(레코드=행)와 연관성이 높다.
- 즉, 서브쿼리가 반환하는 결과 레코드 값이, 단일 값인지 아닌지에 따라, 사용여부가 결정된다.
a. 서브쿼리가 단일 반환값이라면? : IN 연산자를 사용해도 또는 안해도 된다. (= 등의 기타 연산자 사용 가능)
ex. WHERE p_code = 'GD101' or p_code = 'GD103'; 이런식으로 or을 사용해서 표현할 수도 있다. 그렇지만, 서브쿼리를 사용하면 더 손쉽게, 직관적으로 쿼리를 작성할 수 있다. |
b. 하위 쿼리에서 값을 둘 이상 반환한다면? : 나타날 결과물에 대해 고려를 좀 해야한다. (연산자 사용 X. 서브쿼리 사용이 좋음)
- 서브쿼리에 DISTINCT 사용도 가능하다. * 값을 중복되지 않도록 출력 O
ex. SELECT DISTINCT s_p_code FROM dbo.salesTbl WHERE s_qty >= 1000 |
ex. 실습 2 (UPDATE)
- 매출 테이블에서, 판매수량이 제일 적은 최솟값 레코드(행)를 0으로 수정하시오.
UPDATE
dbo.salesTbl
SET
s_qty = 0
WHERE s_qty =( /* = WHERE s_qty IN(); */
SELECT MIN(s_qty)
FROM dbo.salesTbl
);
* 위의 테이블 / 데이터 활용
ex. 실습 3 (DELETE)
- 매출 테이블에서 판매수량이 제일 많은 행을 찾아서 삭제하시오.
DELETE FROM dbo.salesTbl
WHERE s_qty IN(
SELECT MAX(s_qty)
FROM dbo.salesTbl
);
* 위의 테이블 / 데이터 활용
2. 상관 서브쿼리
- 상관 쿼리는, 일반 서브쿼리와는 달리, 서브쿼리 단독으로 실행할 수 없다.
- 실행되는 순서도, 메인(Outer) 쿼리가 먼저 실행이 되서, 서브(Inner) 쿼리쪽으로 들어간다. 이때 메인쿼리는 서브쿼리로 한 행씩 넘긴다.
ex. 김대리는 오늘 부장님으로부터, "매출테이블에서 각 상품(브랜드)별 신발 판매가 각 상품별 평균 판매보다 높은 매출을 출력"해서 뽑아오라는 업무지시를 받았다. 어떻게 쿼리를 짜야하나?
SELECT *
FROM dbo.salesTbl AS S1 -- (1)
WHERE s_qty>( -- (3)
SELECT AVG(s_qty) FROM dbo.salesTbl AS S2 WHERE S2.s_p_code = S1.s_p_code -- (2)
-- 메인이 주는 S1을 받아서, 서브쿼리는 실행됨.
-- 서브쿼리는 메인이 주는 상품코드들(하나의 행들)에 대한, AVG를 구해주는 역할을 함.
-- 즉 메인쿼리에서 넘어온 값을 조건과 비교하여, 최종 결과를 출력한다.
);
* 위의 테이블 / 데이터 활용
3. 다중 INSERT 여러 방법
-- 지금까지 사용한 방법
INSERT INTO dbo.productTbl VALUES ( 'GD101', '나이키', '2000-12-12', '미국', 900);
INSERT INTO dbo.productTbl VALUES ( 'GD102', '아디다스', '2000-01-13', '독일', 450);
INSERT INTO dbo.productTbl VALUES ( 'GD103', '프로스펙스', '2000-02-14', '한국', 720);
-- 다중 ISERT 방법
INSERT INTO 테이블명 (
컬럼 1, 컬럼 2, 컬럼 3, 컬럼 4, 컬럼 5
)
VALUES
('값 1', '값 2', ....),
('값 1', '값 2', ....),
('값 1', '값 2', ....),
('값 1', '값 2', ....),
('값 1', '값 2', ....);
ex.
INSERT INTO dbo.productTbl(
p_code, p_name, p_date, p_area, p_money
)
VALUES
('GD101', '나이키', '2000-12-12', '미국', 900),
('GD102', '아디다스', '2000-01-13', '독일', 450),
('GD103', '프로스펙스', '2000-02-14', '한국', 720);
* 위의 테이블 / 데이터 활용
4. 서브쿼리 연습문제 1
* 위의 테이블 / 데이터 활용
ex 1) 매출 테이블에서 매출일이 가장 오래된 날짜의 상품을 출력하시오.
SELECT *
FROM dbo.salesTbl
WHERE s_date IN(
SELECT MIN(s_date)
FROM dbo.salesTbl
);
ex 2) 매출 테이블에서 각 지사별 평균판매 수량을 출력하시오. 출력은 지점이름과 평균판매만 내림차순으로 하시오.
SELECT s_branch AS 지점이름, AVG(s_qty) AS '평균 판매수량'
FROM dbo.salesTbl
GROUP BY s_branch
ORDER BY '평균 판매수량' DESC;
ex 3) 매출 테이블에서 판매수량이 제주 지점의 최소 매출보다 적게 기록한 지점명, 판매수량, 매출날짜를 출력하시오.
SELECT s_branch AS '지점명', s_qty AS '판매수량', s_date AS '매출날짜'
FROM dbo.salesTbl
WHERE s_qty < (
SELECT MIN(s_qty)
FROM dbo.salesTbl
WHERE s_branch = '제주'
)
ORDER BY '판매수량' DESC; -- 데이터가 많을 경우, 정렬을 해주는 것도 좋은 방법
ex 4) 각 지점별 판매수량 평균을 출력하시오.
SELECT s_branch AS '지점명', AVG(s_qty) AS '판매수량 평균'
FROM dbo.salesTbl
GROUP BY s_branch;
ex 5) 매출 테이블에서 날짜별 판매수량이 각 지점별 평균보다 낮은 지점명과 매출날짜, 판매수량을 출력하시오.
SELECT s_branch '지점명', s_date '매출날짜', s_qty '판매수량'
FROM dbo.salesTbl AS S1
WHERE s_qty <(
SELECT AVG(s_qty)
FROM dbo.salesTbl AS S2
WHERE S2.s_branch = S1.s_branch
);
- 서브쿼리는 WHERE절에서만 사용할 수 있는 것은 아니며, GROUP BY / HAVING절 및 FROM절 안에서도 다양하게 사용할 수 있다. 물론 INSERT / UPDATE / DELETE 쿼리문에서도 물론 다 가능하다. - 괄호 ( ) 안의 쿼리는 서브쿼리를 의미한다. |
5. 서브쿼리 연습문제 2
CREATE DATABASE TESTDB4;
USE TESTDB4;
-- 음악 테이블
CREATE TABLE dbo.tbl_music(
num INT PRIMARY KEY,
title NVARCHAR(30),
singer NVARCHAR(30),
price INT
);
-- 영화 테이블
CREATE TABLE dbo.tbl_movie(
id INT PRIMARY KEY,
director NVARCHAR(30), -- 감독
title NVARCHAR(30), -- 영화제목
release SMALLINT, -- 개봉년도
genre VARCHAR(30), -- 영화 장르
mark1 INT, -- 관객평점
mark2 INT -- 전문가 평점
);
-- 음악 테이블 입력
INSERT INTO dbo.tbl_music
VALUES -- VALUE (X), VALUES(O)
(1, N'흰눈', N'이루', 22000),
(2, N'하루', N'김범수', 33000),
(3, N'Because of you', N'백지영', 44000),
(4, N'레베카', N'양준일', 19800),
(5, N'지금처럼만 사랑하자', N'한경일', 27500);
-- 영화 테이블 입력
INSERT INTO dbo.tbl_movie
VALUES
(1, N'봉준호', N'괴물', 2006, '액션', 88, 75),
(2, N'오리올 파울로', N'더 바디', 2014, '스릴러', 90, 100),
(3, N'제임스 카메룬', N'아바타', 2009, 'SF', 80, 70),
(4, N'오리올 파울로', N'폭풍의 타임', 2018, '스릴러', 94, 100),
(5, N'봉준호', N'기생충', 2019, '드라마', 92, 95),
(6, N'제임스 카메룬', N'에일리언 커버넌트', 2017, 'SF', 84, 77),
(7, N'멜라니 로랑', N'갤버스턴', 2018, '드라마', 88, 100),
(8, N'로버트 스트롬버그', N'말레피슨트', 2014, '환타지(어른용동화)', 95, 90);
-- 데이터 출력
SELECT * FROM dbo.tbl_music;
SELECT * FROM dbo.tbl_movie;
ex 1) 뮤직 테이블의 전체 곡 평균값보다 비싼 곡들을 출력하시오.
SELECT *
FROM dbo.tbl_music
WHERE price > (
SELECT AVG(price)
FROM dbo.tbl_music
);
ex 2) 각 감독들의 관객평점 평균과 전문가평점 평균을 출력하시오.
SELECT director '감독', AVG(mark1) '관객평점 평균', AVG(mark2) '전문가평점 평균'
FROM dbo.tbl_movie
GROUP BY director
ORDER BY director DESC; -- 정렬은 그냥 자연스럽게 해주기
ex 3) 관객평점 평균보다 낮은 점수의 영화들을 출력하시오. (또는 카운트를 세시오)
-- 출력
SELECT *
FROM dbo.tbl_movie
WHERE mark1 < (
SELECT AVG(mark1) FROM dbo.tbl_movie
);
-- 카운트 출력
SELECT COUNT(*)
FROM dbo.tbl_movie
WHERE mark1 < (
SELECT AVG(mark1) FROM dbo.tbl_movie
);
ex 4) 각 감독별로 받은 전문가평점(mark2) 최고값과 관객평점(mark1)의 평균을 비교하여, 높거나 같은게 있으면, 그 감독들의 이름과 최저, 최고 전문가평점을 이름순으로 출력하시오.
-- ex 4.
SELECT director '감독 이름', MIN(mark2) '최저 전문가평점', MAX(mark2) '최고 전문가평점'
FROM dbo.tbl_movie AS S1
GROUP BY director
HAVING MAX(mark2) >= (
SELECT AVG(mark1) FROM dbo.tbl_movie
)
ORDER BY director;
ex 5) 각 영화의 전문가평점(mark2)이 감독별 관객평점(mark1)보다 높은 영화를 출력하시오.
* 하나씩 각 영화를 순회하면서, 감독별 관객평점 평균과 비교하는 것
SELECT *
FROM dbo.tbl_movie AS S1
WHERE mark2 >(
SELECT AVG(mark1) '감독별 관객평점'
FROM dbo.tbl_movie AS S2
WHERE S2.director = S1.director
);
ex 6) 최근(2015년 이상) 영화 위주로 관객평점(mark1)의 평균을 출력하시오.
SELECT AVG(mark1) AS '관객평점 평균'
FROM dbo.tbl_movie AS M1 -- AS(별칭) 안써줘도 됨. 그러나 써줘도 좋음.
where M1.release >= 2015;
ex 7) 최근(2018년 이후) 영화의 감독별 관객평점(mark1) 평균을 구하여, 그보다 높은 전문가평점(mark2)을 받은 영화를 출력하시오.
SELECT *
FROM dbo.tbl_movie AS S1
WHERE mark2 >(
SELECT AVG(mark1) '최근 영화의 관객평점 평균'
FROM dbo.tbl_movie AS S2
WHERE (S2.director = S1.director) AND (S1.RELEASE >= 2018)
-- S1은 메인으로부터 하나하나 받아야 할 값. (왼쪽 조건 지정할 때 주의)
-- 상관 서브쿼리의 목적 : 메인으로부터 넘어오는 값을 받아서 조건을 처리한다. (이를 주의하며 오류는 없는지 확인 필요)
);
ex 8) '제임스 카메룬'의 영화를 출력하되, 그 해(연도)의 관객평점(mark1) 평균을 함께 출력하시오.,
SELECT M.*, ( -- 별칭 붙여주기
SELECT AVG(mark1)
FROM dbo.tbl_movie AS S -- 서브쿼리 별칭 S
WHERE S.release = M.release
) AS '영화 개봉년도의 관객평균'
FROM dbo.tbl_movie AS M -- 메인쿼리 별칭 M
WHERE director = '제임스 카메룬';
ex 9) '제임스 카메룬'의 영화를 출력하되, 관객평점(mark1)과 전문가평점(mark2)의 총합도 같이 출력하시오.
a. 제임스 카메룬 영화 내용들 출력
b. 제임스 카메룬의 해당 영화에 대한, 관객평점 + 전문가평점 출력
SELECT M.*, (
SELECT SUM(mark1+mark2)
FROM dbo.tbl_movie AS S1
WHERE S1.id = M.id
) AS '관객평점 + 전문가평점 총합'
FROM dbo.tbl_movie AS M
WHERE director = '제임스 카메룬';
ex 10) '제임스 카메룬'감독과 '봉준호'감독의 정보를 가져오되, 전체 관객평점 평균을 같이 가져와 비교할 수 있도록 출력하시오.
-- IN 연산자 사용 X
SELECT *,
( SELECT AVG(mark1) FROM dbo.tbl_movie ) AS '전체 관객평점 평균'
FROM dbo.tbl_movie
WHERE (director = '제임스 카메룬') OR (director = '봉준호');
-- IN 연산자를 사용O
SELECT *,
( SELECT AVG(mark1) FROM dbo.tbl_movie ) AS '전체 관객평점 평균'
FROM dbo.tbl_movie
WHERE id IN( -- 제임스 카메룬, 봉준호 감독의 정보만 가져오게 하는 코드 (서브쿼리 사용방법)
-- 서브쿼리는 직관적이지 못함. 그러나 직관적 연산자인 (=)를 사용하게 되면, 하나만 값을 반환할 수 있음.
SELECT id
FROM dbo.tbl_movie
WHERE (director = '제임스 카메룬') OR (director = '봉준호')
);
ex 11) 감독별로 연도에 상관없이 총 만든 영화 갯수를 출력하시오.
SELECT director '감독이름', COUNT(id) '영화 제작 갯수'
FROM dbo.tbl_movie M
GROUP BY director
ex 12) 가장 높은 전문가평점(mark2)을 받은 영화의 정보를 출력하시오. 혹 여러개면 최신 개봉연도 순으로 출력하시오.
SELECT *
FROM dbo.tbl_movie
WHERE mark2 IN(
SELECT MAX(mark2)
FROM dbo.tbl_movie
)
ORDER BY release DESC; -- 정렬 : 다중정렬 가능. (,)으로 구분
6. IN, EXISTS 연산자
ex 1) 2006년 2019년 두 해에 개봉한 영화의 정보를 출력하시오. (IN 연산자 사용)
/* IN / EXITS 연산자 사용 X */
SELECT *
FROM dbo.tbl_movie
WHERE (release = 2006) OR (release = 2019)
/* IN 연산자 사용 O */
-- (1)
SELECT *
FROM dbo.tbl_movie
WHERE id IN(
SELECT id
FROM dbo.tbl_movie
WHERE (release = 2006) OR (release = 2019)
);
-- (2)
SELECT *
FROM dbo.tbl_movie M
WHERE M.release IN(2006,2019);
-- 명시적으로 특정 두 해를 이야기해주면, 그냥 IN 연산자에 넣으면 됨 (다중값을 반환하기 때문에 = 연산자 사용 X. IN 연산자 사용 O)
ex 2) 2006년 2019년 두 해에 개봉한 영화의 정보를 출력하시오. (EXISTS 연산자 사용)
- 괄호 안의 조건에 해당하는, 레코드(행)이 존재하는지 안하는지를 판단해주는 것
* EXISTS절 안의 결과가 존재 : 참 -> 메인쿼리 실행 O
* NOT EXISTS절 안에 결과가 존재 : 거짓 -> 메인쿼리 실행 X
- EXISTS절과 NOT EXISTS절은, 괄호 안의 결과로 반환되는 행이 있는지만 체크(참/거짓)한다. 즉, 어떤 특정한 데이터 결과로 리턴하지는 않는다.
/* EXIT 연산자 사용 O */
SELECT *
FROM dbo.tbl_movie M
WHERE EXISTS(
SELECT * FROM dbo.tbl_movie S WHERE (S.id = M.id) AND release IN(2006,2019)
-- = SELECT id FROM dbo.tbl_movie WHERE (id=M.id) AND (release = 2006 or release = 2019)
-- EXISTS 내부는 값을 리턴 X. TRUE/FALSE만 출력.
-- 따라서 이와 같이 구성 필요(메인으로부터 가져옴)
);
7. IN 연산자 연습문제
ex 1) '제임스 카메룬' 감독의 영화를 출력하되, 그 영화 개봉년도에 같이 개봉한 영화들도 출력하시오.
-- 연도 관련 모든 정보 출력
SELECT *
FROM dbo.tbl_movie M
WHERE release IN(
-- 이 코드들로 인한 리턴값 : 제임스 카메룬 영화 개봉 연도
SELECT release
FROM dbo.tbl_movie S
WHERE S.director = '제임스 카메룬'
);
ex 2) '제임스 카메룬'감독이 만든 영화 장르와 영화제목, 개봉일을 출력하시오. (변수를 사용해서)
DECLARE @d_name VARCHAR(20); -- 변수 선언
SET @d_name = '제임스 카메룬';
SELECT
@d_name+'감독의 영화 장르-->' ' 감독명',
M.genre '장르',
M.title '영화제목',
M.release '개봉연도'
FROM dbo.tbl_movie M
WHERE M.id IN(
SELECT id FROM dbo.tbl_movie S WHERE S.director = @d_name
);
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |