everyday com-eat
작성일
2022. 1. 23. 01:07
작성자
갱수터
728x90

서브 쿼리

- SQL문 내에서 하위에 존재하는 쿼리 ( SQL문 안에 SQL )

 

 

스칼라 서브쿼리

- select문에 있는 서브쿼리 (1행만 반환)

SELECT
	g.g_name
	,g.g_price
FROM
	(SELECT
		*
		,(	SELECT
				ROUND(AVG(g_price), 0) AS avg_price
			FROM
				tb_goods) AS avg_price
	FROM
		tb_goods) AS g
WHERE
	g.g_price > g.avg_price;

 

인라인 뷰

- from절에 있는 서브쿼리 -> 집합

- 속도가 가장 빠름

SELECT
	g.g_name
	,g.g_price
FROM
	tb_goods AS g
	JOIN
	(	SELECT
			ROUND(AVG(g_price), 0) AS avg_price
		FROM
			tb_goods) AS g1 /* on절 쓸때 필요해서 as필요 */
WHERE
	g.g_price > g1.avg_price;

 

서브쿼리

- where절에 있는 서브쿼리

SELECT
	*
FROM
	tb_goods AS g
WHERE
	g.g_price > (	SELECT
						ROUND(AVG(g1.g_price), 0) AS '평균단가'
					FROM
						tb_goods AS g1);

 

단일행 서브쿼리

SELECT
	*
FROM
	tb_member AS m
WHERE
	m.m_level = (SELECT 
					l.level_num
				FROM 
					tb_member_level AS l
				WHERE /* where 조건을 안줘서 다중행이 나오게 되면 오류*/
					l.level_name = '관리자');

 

다중행 서브쿼리, 다중행 연산자

- in : (or)리턴되는 값 중에서 조건에 해당하는 값이 있으면 참

SELECT
	*
FROM
	tb_member AS m
WHERE
	m.m_level IN (	SELECT 
						l.level_num /*여기가 하나밖에 못옴*/
					FROM 
						tb_member_level AS l);


any, some : (or) 앞에 비교 연산자 올 수 있음

SELECT
	*
FROM
	tb_member AS m
WHERE
	m.m_level > ANY(SELECT 
						l.level_num
					FROM 
						tb_member_level AS l
					WHERE
						l.level_num > 1);
/*
l.level_num > 2 or l.level_num > 3 or l.level_num > 4 라는 조건이 생김
결국 만족하는건 l.level_num =3인 경우
*/


- all : (and)

SELECT
	*
FROM
	tb_member AS m
WHERE
	EXISTS(	SELECT 
				l.level_num
			FROM 
				tb_member_level AS l
			WHERE
				l.level_num > 1);


- exists : 참, 거짓만 판별 , 비교 조건이 하나라도 존재한다면(or), in과 동일하지만 속도가 더 빠르다

SELECT
	*
FROM
	tb_member AS m
WHERE
	EXISTS(	SELECT 
				l.level_num
			FROM 
				tb_member_level AS l
			WHERE
				l.level_num > 1);
SELECT
	*
FROM
	tb_member AS m
WHERE
	EXISTS(	SELECT 
				l.level_num /*의미가 없음*/
			FROM 
				tb_member_level AS l
			WHERE
				l.level_num > 1 AND l.level_num = m.m_level);
/*AND l.level_num = m.m_level 앞 테이블과 연관있는 서브쿼리임을 조건으로 줘야함*/

 

 

더보기

실습1. mysql 행번호 출력하기 - 세션 변수 이용
(mysql 8버전 이상 window함수 row_number() 함수 사용가능 )

SELECT
	(@rowNum := @rowNum+1 ) AS 'row' ->select문 안에는 반복문이 도니깐
	,g.*
FROM
	tb_goods AS g
	JOIN 
	( SELECT @rowNum := 0) AS r;
    /* '='비교 연산자와 차이두기위해 ':='로 표기*/

>> 세션 변수를 select문으로 지정하면, 제일 겉 select문이 반복될때마다 증감되는 형식이므로 카운트번호로 쓸 수 있다.

 

실습2. 상품테이블의 단가 중 단가가 높은 상품 순서대로 순위를 포함하여 상품테이블을 출력하시오.

(@gprice라는 변수를 추가해야한다고 힌트주심) 

SELECT
	(@rank := @rank + 1) AS 'rank'
	,g.g_code
	,g.g_name
	,g.g_price
FROM
	tb_goods AS g
	JOIN
	( SELECT @rank := 0) AS r;

 

풀이

SELECT	
	if(@gprice=g.g_price,@rank:=@rank,@rank:=@rank + 1) AS 'rank'
	,g.g_code
	,g.g_name
	,@gprice:=g.g_price AS 'g_price'
FROM
	tb_goods AS g
	JOIN
	( SELECT @rank := 0, @gprice := 0) AS r
ORDER BY g.g_price DESC;

>> 상품의 가격이 윗행의 가격과 같으면 @rank를 같은 값으로 메기고, 다르다면 1추가시키라는 조건을줌

select에 쓴 컬럼은 숨길 수 없다해서 그냥 as로 별칭줘서 g_price인척

 

선생님 풀이

SELECT	
	(CASE
	WHEN @gprice = g.g_price THEN @rank
	WHEN @gprice := g.g_price THEN @rank := @rank +1
	END) AS 'rank'
	,g.g_code
	,g.g_name
	,g.g_price
FROM
	tb_goods AS g
	JOIN
	( SELECT @rank := 0, @gprice := 0) AS r
ORDER BY g.g_price DESC;

>> if문으로 깔끔하지 않았떤 쿼리문도 훨씬 가독성 좋아지고, 필요없던 컬럼수도 줄어들었다

:= 는 대입연산자이지만 case문에서는 실행되면 true라고 판단하기 때문에 가장 윗줄의 조건이 거짓이고 대입연산자가 실행되어 원하는 결과값을 얻을 수 있다.

 

 

실습3. dense_rank가 아닌 rank로 (동점자 있을시 다음 순위는 동점자 수만큼 + 처리 ex. 1 2 3 3 5)

(@checkCount라는 변수를 추가해야한다고 힌트주심)

 

풀이

SELECT
	(@checkCount := @checkCount + 1) AS 'no'
	,(CASE
	WHEN @gprice = g.g_price THEN @rank 
	WHEN @gprice := g.g_price  THEN @rank := @checkCount
	END) AS 'rank'
	,g.g_code
	,g.g_name
	,g.g_price
FROM
	tb_goods AS g
	JOIN
	( SELECT @rank := 0, @gprice := 0, @checkCount := 0) AS r
ORDER BY g.g_price DESC;

>> 어떻게 해야할지 고민하다가 동점자가 나와서 rank가 중복되고 나면 다시 checkCount 번호로 돌아오면 될것같다는 생각이 들어서 case문으로 코드 짜봄

 

선생님 풀이

SELECT
	(CASE
	WHEN @gprice = g.g_price THEN @rank 
	WHEN @gprice := g.g_price  THEN @rank := @checkCount + 1
	END) AS 'rank'
	,g.g_code
	,g.g_name
	,g.g_price
	,(@checkCount := @checkCount + 1) AS checkCount
FROM
	tb_goods AS g
	JOIN
	( SELECT @rank := 0, @gprice := 0, @checkCount := 0) AS r
ORDER BY g.g_price DESC;

>> 선생님은 checkCount 컬럼을 rank 컬럼보다 뒤쪽에 둬서 조건문이 약간 다르지만 나와 접근한 방식은 같았다.

그리고 checkCount컬럼을 아예 없애고 싶으면 제일 겉 select문을 서브쿼리로 만드는 인라인뷰를 사용해서 select에 내가 원하는 rank,g_code,g_name,g_price값만 선택하면 된다고 알려주심

 

checkCount컬럼없애기

SELECT
	result.rank
	,result.g_code
	,result.g_name
	,result.g_price
FROM
(SELECT
	(CASE
	WHEN @gprice = g.g_price THEN @rank 
     /* @gprice가 0일때 false가 나옴.. else문 추가 */
	WHEN @gprice := g.g_price  THEN @rank := @checkCount + 1    
	ELSE 
		@rank := @checkCount + 1
	END) AS 'rank'
	,g.g_code
	,g.g_name
	,g.g_price
	,(@checkCount := @checkCount + 1) AS checkCount
FROM
	tb_goods AS g
	JOIN
	( SELECT @rank := 0, @gprice := 0, @checkCount := 0) AS r
ORDER BY g.g_price DESC) AS result;

>> 그런데 인라인뷰로 만들면 case문에서 대입연산자를 사용한게 문제가 생길 수 있기 때문에 else문으로 처음 dense_rank방식의 쿼리문을 한번 더 써줘야 완벽하게... 완성이다

 

 

실습4. 회원 별 구매이력 중 구매금액이 가장 높은 금액의 상품명을 추출하여 회원아이디와 이메일과 함께 조회 하시오.

SELECT
	m.m_id AS '회원아이디'
	,m.m_email AS '이메일'
	,g.g_name AS '상품명'
	,o.o_amount * g.g_price AS '구매금액'
FROM
	tb_member AS m
	inner JOIN 
	tb_order AS o
	ON 
	m.m_id = o.o_id
	inner JOIN 
	tb_goods AS g
	ON
	o.o_g_code = g.g_code
GROUP BY m.m_id;

>> 저번에도 틀렸던것같은데 이번에도 틀림..ㅎ

 

정답

SELECT
	m.m_id AS '회원아이디'
	,m.m_email AS '이메일'
	,g.g_name AS '상품명'
	,MAX(o.o_amount * g.g_price) AS '구매금액'
FROM
	tb_member AS m
	inner JOIN 
	tb_order AS o
	ON 
	m.m_id = o.o_id
	inner JOIN 
	tb_goods AS g
	ON
	o.o_g_code = g.g_code
GROUP BY m.m_id;

>> 데이터를 보지말고 의미를 생각하라고... 데이터에 중복값이 없고 정답이랑 똑같이 나와서 풀었다고 좋아하고 있었다...^^

더보기

실습1. 회원 별 로그인 평균 횟수보다 많이 로그인한 회원 아이디와 로그인 횟수를 조회 하시오

/* 평균 횟수 구하는 방법*/
select 
	avg(result.loginCnt) as '평균로그인횟수'
from 
		(SELECT
			m.m_id as '회원 아이디'
			,m.m_name as '회원 이름'
			, count(l.login_id) as loginCnt
		FROM
			tb_member AS m
			INNER join
			tb_login AS l
			on 
			m.m_id = l.login_id
			and
			m.m_level > 1
		group by m.m_id) as result;

>> 평균 횟수도 못구하고 있어서 선생님이 코드를 짜줌... 처음에 having절도 써보고 서브쿼리를 where절에도 써보고 했는데 못함 

 

 선생님 풀이

SELECT
	m.m_id AS '회원아이디'
	,m.m_name AS '회원이름'
	,COUNT(1) AS '로그인횟수'
FROM
	tb_member as m
	INNER join
	tb_login AS l
	on
	m.m_id = l.login_id
	and
	m.m_level >1
GROUP BY m.m_id
HAVING 로그인횟수 >(SELECT
						avg(result.loginCnt) AS avgloginCnt
					from
						(SELECT
							count(1) as loginCnt
						FROM
							tb_member AS m
						INNER join
							tb_login AS l
						on 
							m.m_id = l.login_id
						and
							m.m_level > 1
						group by m.m_id) AS result);

>> having절을 이용해서 조건문 써주기... 서브쿼리를 같은 코드를 반복해서 쓰는걸 이해 못했음 


변수 활용해서 풀어보기 ▼힌트

cnt logincnt sumlogincnt
1 3 3
2 7 10
3 5 15
4 7 22
5 4 26

 

풀이

SELECT
	re.m_id AS '회원아이디'
	,re.m_name AS '회원이름'
	,re.loginCnt AS '로그인횟수'
	,(@cnt := @cnt+1) AS cnt
	,(@sumlogincnt := @sumlogincnt+re.loginCnt) AS sumlogincnt
FROM
	(SELECT
		m.m_id
		,m.m_name
		,COUNT(1) AS loginCnt
	FROM
		tb_member AS m
		INNER join
		tb_login AS l
		on
		m.m_id = l.login_id 
		AND 
		m.m_level >1		
	GROUP BY l.login_id) AS re
	JOIN
	(SELECT @cnt:= 0, @sumlogincnt:=0) AS cnt
GROUP BY re.m_id
HAVING re.loginCnt >= MAX(sumlogincnt/cnt);

>>처음에 세션 변수를  AS re 셀렉트문 안에 넣어서 1,2,3,4 증가하는게 아니라 loginCnt와 똑같이 나와서 해결을 못하고 있었는데 선생님이 지금 1행으로 못보고 있으니깐 이행(세션변수 선언행)을 따로 빼서 1행으로 나오게 해야한다고 말해주셔서 풀었다...

 

선생님 풀이 > 필요없는 컬럼 인라인뷰로

SELECT
	result.m_id
	,result.m_name
	,result.loginCnt
FROM
	(SELECT
		re.m_id
		,re.m_name
		,re.loginCnt
		,(@cnt := @cnt+1) AS cnt
		,(@sumlogincnt := @sumlogincnt+re.loginCnt) AS sumlogincnt
	FROM
		(SELECT
			m.m_id
			,m.m_name
			,COUNT(1) AS loginCnt
		FROM
			tb_member AS m
			INNER join
			tb_login AS l
			on
			m.m_id = l.login_id 
			AND 
			m.m_level >1		
		GROUP BY l.login_id) AS re
		JOIN
		(SELECT @cnt:= 0, @sumlogincnt:=0) AS cnt) AS result
WHERE
	result.loginCnt > (@sumLoginCnt / @cnt);

 

 

실습2. 회원 별 구매이력 중 구매금액이 가장 높은 상위 30%만 조회하시오.

풀이1

SELECT
	result.m_id AS '아이디'
	,result.orderprice AS '구매액'
FROM
	(SELECT
		r1.m_id
		,r1.orderprice
		,@rank:=@rank+1 AS rank
	FROM
		(SELECT
			m.m_id
			,max(o.o_amount*g.g_price) AS orderprice
		FROM
			tb_member AS m
			INNER join
			tb_order AS o
			on
			m.m_id = o.o_id
			INNER join
			tb_goods AS g
			on
			o.o_g_code = g.g_code
		GROUP BY m.m_id) AS r1
		JOIN
		(SELECT @rank :=0) AS r2
	ORDER BY r1.orderprice desc) AS result
WHERE result.rank <= @rank*0.3;

>> 상위 30% 뽑는걸 못해서 where절 쓰는데 한참 걸림... 1. 문제에서 테이블을 확인 잘하기 tb_member 테이블 굳이 필요없음 2. 동점자 체크 안함 으로 다시 풀어보기...

 

풀이2

SELECT
	result.orderId
	,result.amt
FROM
	(SELECT
		o1.orderId
		,o1.amt
		,(case
			when @amtValue = o1.amt then @rank
			when @amtValue := o1.amt then @rank := @checkCnt+1
			else
				@rank := @checkCnt+1
			end) AS rank	
		,@checkCnt:=@checkCnt+1
	FROM
		(SELECT
			o.o_id AS orderId
			,SUM(o.o_amount*g.g_price) AS amt
		FROM
			tb_order AS o
			INNER join
			tb_goods AS g
			on
			o.o_g_code = g.g_code
		GROUP BY o.o_id
		ORDER BY amt DESC) AS o1
		JOIN
		(SELECT @rank:=0, @amtValue:=0, @checkCnt:=0) AS v) as result
WHERE result.rank < @rank*0.3;

>> 이 문제는 동점자가 없기 때문에 첫문제 풀이와 답이 똑같이 나왔음

728x90

'{ "Hello World!" }; > DataBase' 카테고리의 다른 글

(DB) eXERD 사용 방법  (0) 2022.01.26
(DB) 뷰(VIEW)  (0) 2022.01.26
(DB) 정규화  (0) 2022.01.20
(DB) 데이터베이스 설계  (0) 2022.01.14
(DB) JOIN, UNION  (0) 2021.12.29