TIL_241031
- DATE_FORMAT(AO.DATETIME, '%H') == HOUR(AO.DATETIME)



[GROUP BY] 입양시각 구하기(2)

보호소에서는 몇 시에 입양이 가장 활발하게 일어나는지 알아보려 합니다. 0시부터 23시까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하는 SQL문을 작성해주세요. 이때 결과는 시간대 순으로 정렬해야 합니다.

 

WITH RECURSIVE HOURS AS(
    SELECT 0 AS HOUR
    
    UNION ALL
    SELECT HOUR+1
    FROM HOURS
    WHERE HOUR < 23
)

SELECT H.HOUR, COUNT(AO.ANIMAL_ID) AS COUNT
FROM HOURS H
LEFT JOIN ANIMAL_OUTS AO ON H.HOUR = DATE_FORMAT(AO.DATETIME, '%H')
GROUP BY H.HOUR
ORDER BY H.HOUR;

 

I

TIL_240901
- SELECT 1: 참/거짓 여부만을 확인하기 위해 EXISTS / NOT EXISTS 구문과 효율적으로 쓸 수 있는 구문
- NULL과 != 연산 하면 NULL이 반환됨



[Lv.4] 업그레이드 할 수 없는 아이템 구하기

더 이상 업그레이드할 수 없는 아이템의 아이템 ID(ITEM_ID), 아이템 명(ITEM_NAME), 아이템의 희귀도(RARITY)를 출력하는 SQL 문을 작성해 주세요. 이때 결과는 아이템 ID를 기준으로 내림차순 정렬해 주세요.

 

# 첫 번째 시도.. 아무런 데이터가 조회되지 않는다.

SELECT ii.ITEM_ID, ii.ITEM_NAME, ii.RARITY
FROM ITEM_INFO ii
WHERE ii.ITEM_ID NOT IN 
    (SELECT DISTINCT PARENT_ITEM_ID FROM ITEM_TREE)

 

IN은 or 연산, NOT IN은 and 연산을 수행함 (!=)

서브쿼리 조회 결과에 NULL값이 있는데, NULL과 != 연산 하면 NULL이 반환되므로 조회가 안 되는 것.

 

SELECT ii.ITEM_ID, ii.ITEM_NAME, ii.RARITY
FROM ITEM_INFO ii
WHERE ii.ITEM_ID NOT IN 
    (SELECT DISTINCT PARENT_ITEM_ID
    FROM ITEM_TREE
    WHERE PARENT_ITEM_ID IS NOT NULL)
ORDER BY ii.ITEM_ID DESC

 

또 다른 방법으로는, NOT EXISTS 사용하기!

SELECT ii.ITEM_ID, ii.ITEM_NAME, ii.RARITY
FROM ITEM_INFO ii
WHERE NOT EXISTS
    (SELECT 1 
     FROM ITEM_TREE it
     WHERE it.PARENT_ITEM_ID = ii.ITEM_ID)
ORDER BY ii.ITEM_ID desc

 

'SELECT 1'

- EXISTS / NOT EXISTS 절과 자주 쓰이는 구문으로, 상수 '1'을 반환하도록 하지만 이 값은 중요하지 않고 SQL 엔진은 반환하는 값이 있는지 없는지에 관심을 갖는다.

- 서브쿼리가 '1'을 반환한다면 NOT EXISTS절은 TRUE로 평가함

TIL_20240901
- 비트연산자!

 

 

 

[Lv.4] 저자별 카테고리별 매출액 집계하기

2022년 1월의 도서 판매 데이터를 기준으로 저자 별, 카테고리 별 매출액(TOTAL_SALES = 판매량 * 판매가) 을 구하여, 저자 ID(AUTHOR_ID), 저자명(AUTHOR_NAME), 카테고리(CATEGORY), 매출액(SALES) 리스트를 출력하는 SQL문을 작성해주세요.
결과는 저자 ID를 오름차순으로, 저자 ID가 같다면 카테고리를 내림차순 정렬해주세요.

WITH TOTAL_SALES AS
(
    SELECT b.CATEGORY 
        , b.AUTHOR_ID
        , SUM(bs.SALES * b.PRICE) AS TOTAL_SALES
    FROM BOOK b RIGHT JOIN BOOK_SALES bs
        ON b.BOOK_ID = bs.BOOK_ID
    WHERE DATE_FORMAT(bs.SALES_DATE, '%Y-%m') = '2022-01'
    GROUP BY b.AUTHOR_ID, b.CATEGORY
)

SELECT ts.AUTHOR_ID
    , a.AUTHOR_NAME
    , ts.CATEGORY
    , ts.TOTAL_SALES
FROM TOTAL_SALES ts LEFT JOIN AUTHOR a
    ON ts.AUTHOR_ID = a.AUTHOR_ID
ORDER BY ts.AUTHOR_ID, ts.CATEGORY desc

 

SELECT A.AUTHOR_ID, AUTHOR_NAME, CATEGORY, SUM((SALES * PRICE)) AS TOTAL_SALES
FROM BOOK_SALES S
JOIN BOOK B ON S.BOOK_ID = B.BOOK_ID
JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID
WHERE YEAR(S.SALES_DATE) = 2022 AND MONTH(S.SALES_DATE) = 1
GROUP BY CATEGORY, AUTHOR_ID
ORDER BY A.AUTHOR_ID, CATEGORY DESC

 

 

[Lv.4] 식품분류별 가장 비싼 식품의 정보 조회하기

다음은 식품의 정보를 담은 FOOD_PRODUCT 테이블입니다. FOOD_PRODUCT 테이블은 다음과 같으며 PRODUCT_ID, PRODUCT_NAME, PRODUCT_CD, CATEGORY, PRICE는 식품 ID, 식품 이름, 식품코드, 식품분류, 식품 가격을 의미합니다.

SELECT CATEGORY
    , PRICE AS MAX_PRICE
    , PRODUCT_NAME
FROM (
    SELECT CATEGORY
        , PRICE
        , PRODUCT_NAME
        , ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY PRICE DESC) AS RANKING
    FROM FOOD_PRODUCT
    WHERE CATEGORY IN ('과자', '국', '김치', '식용유')
    ) AS r
WHERE RANKING = 1
ORDER BY MAX_PRICE DESC
SELECT CATEGORY
    , PRICE AS MAX_PRICE
    , PRODUCT_NAME
FROM FOOD_PRODUCT
WHERE (CATEGORY, PRICE) IN
	(SELECT CATEGORY, MAX(PRICE)
    FROM FOOD_PRODUCT
    GROUP BY CATEGORY
    HAVING CATEGORY IN ('국', '김치', '식용유', '과자'))
    )
ORDER BY MAX_PRICE DESC
;

 

[Lv.4] 년, 월, 성별 별 상품 구매 회원 수 구하기

다음은 어느 의류 쇼핑몰에 가입한 회원 정보를 담은 USER_INFO 테이블과 온라인 상품 판매 정보를 담은 ONLINE_SALE 테이블 입니다.USER_INFO 테이블은 아래와 같은 구조로 되어있으며 USER_ID, GENDER, AGE, JOINED는 각각 회원 ID, 성별, 나이, 가입일을 나타냅니다.

 

# GENDER가 NULL인 ROW는 포함하지 않기 위해 INNER JOIN

SELECT YEAR(os.SALES_DATE) AS YEAR
    , MONTH(os.SALES_DATE) AS MONTH
    , ui.GENDER AS GENDER
    , COUNT(DISTINCT os.USER_ID) AS USERS
FROM ONLINE_SALE os INNER JOIN USER_INFO ui
    ON os.USER_ID = ui.USER_ID
    AND ui.GENDER IS NOT NULL
GROUP BY YEAR(os.SALES_DATE), MONTH(os.SALES_DATE), GENDER
ORDER BY YEAR, MONTH, GENDER

 

 

[Lv.4] 언어별 개발자 분류하기

다음은 어느 의류 쇼핑몰에 가입한 회원 정보를 담은 USER_INFO 테이블과 온라인 상품 판매 정보를 담은 ONLINE_SALE 테이블 입니다.USER_INFO 테이블은 아래와 같은 구조로 되어있으며 USER_ID, GENDER, AGE, JOINED는 각각 회원 ID, 성별, 나이, 가입일을 나타냅니다.

 

비트연산자

비트 연산자 설명
& (AND) 대응되는 비트가 모두 1이면 1을 반환

b'1000' & b'1111' --- b'1000'
| (OR) 대응되는 비트 중 하나라도 1이면 1을 반환

b'1000' | b'1111' --- b'1111'
^ (XOR) 대응되는 비트가 서로 다르면 1을 반환

b'1000' ^ b'1111' --- b'0111'
~ (NOT) 비트가 1이면 0, 0이면 1로 반전시킴

~b'1000'--- b'0111'
<< (Left shift) 지정한 수만큼 왼쪽으로 이동시킴

b'1100' << 1 --- b'0011'
>> (Right shift) 지정한 수만큼 오른쪽으로 이동시킴

b'1100' >> 2 --- b'0011' 

 

SELECT
    (CASE
        WHEN (SKILL_CODE & (SELECT SUM(CODE) FROM SKILLCODES WHERE CATEGORY = 'Front End'))
        AND (SKILL_CODE & (SELECT CODE FROM SKILLCODES WHERE NAME='Python')) > 0 THEN 'A'
        WHEN (SKILL_CODE & (SELECT CODE FROM SKILLCODES WHERE NAME='C#')) > 0 THEN 'B'
        WHEN (SKILL_CODE & (SELECT SUM(CODE) FROM SKILLCODES WHERE CATEGORY = 'Front End')) > 0 THEN 'C'
        ELSE NULL
    END) AS GRADE, ID, EMAIL

FROM DEVELOPERS
GROUP BY GRADE, ID, EMAIL
HAVING GRADE IS NOT NULL
ORDER BY GRADE, ID

 

 

[Lv.4] 연간 평가점수에 해당하는 평가 등급 및 성과금 조회하기

HR_DEPARTMENT, HR_EMPLOYEES, HR_GRADE 테이블을 이용해 사원별 성과금 정보를 조회하려합니다. 평가 점수별 등급과 등급에 따른 성과금 정보가 아래와 같을 때, 사번, 성명, 평가 등급, 성과금을 조회하는 SQL문을 작성해주세요.

평가등급의 컬럼명은 GRADE로, 성과금의 컬럼명은 BONUS로 해주세요.
결과는 사번 기준으로 오름차순 정렬해주세요.

 

SELECT e.EMP_NO, e.EMP_NAME, 
(CASE
    WHEN AVG(SCORE) >= 96 THEN 'S'
    WHEN AVG(SCORE) >= 90 THEN 'A'
    WHEN AVG(SCORE) >= 80 THEN 'B'
    ELSE 'C' END) AS GRADE, 
(CASE
    WHEN AVG(SCORE) >= 96 THEN E.SAL*0.2
    WHEN AVG(SCORE) >= 90 THEN E.SAL*0.15
    WHEN AVG(SCORE) >= 80 THEN E.SAL*0.1
    ELSE 0 END) AS BONUS
    
FROM HR_EMPLOYEES e
    INNER JOIN HR_GRADE g ON e.EMP_NO = g.EMP_NO
GROUP BY e.EMP_NO
ORDER BY 1;

 

1. A/B 테스트란

- 임의의 집단 A, B 중 한 집단엔 이전 사이트, 나머지 집단엔 새로운 사이트를 노출하여 성과를 비교하는 방식

 

2. 목적

- 상관관계로부터 인과관계를 추론하기 위한 목적으로 시행한다

1) 원인 요소에 개입하여 결과 요소를 원하는 방향으로 변화시키기

2) 결과에 변화가 생겼을 때 원인 요소를 추론하기

 

- 예시로, [디자인 개편] > [매출 증가]의 상관관계가 발견되었다면

디자인의 변화 때문인지, 이외 요소(경쟁 쇼핑몰/ 입고된 상품 등등) 때문인지, 디자인의 변화가 미치는 영향은 얼마인지를 생각해봐야한다

1) A/B로 나누어 각각 이전 디자인 vs 새로운 디자인을 노출시킨다

2) 디자인 개편 이전 vs 이후의 매출만 비교하는 게 아니라

3) A vs B 집단에서도 유의미한 차이가 발생했다면 디자인 변화로 인한 매출 차이만을 발라낼 수 있음!

 

3. 그룹 분리 방식

1) 노출 분산

- 대상 페이지가 렌더링 될 때 일정 비율에 따라 A, B안을 분리하여 노출 (ex. 5:5/ 9:1)

- 가장 통계적 유의성이 높으나(헤비유저에 따른 왜곡 제거) 한 유저가 여러 안을 볼 수 있기 때문에 혼란을 줄 수 있음

- UI/UX 테스트보다는 알고리즘 테스트에 적합함

 

2) 사용자 분산

- 사용자를 A, B 그룹으로 분리하여 고정적으로 다르게 노출

- 헤비유저에 따라 결과값이 왜곡될 수 있음

 

3) 시간 분할

- 시간대에 따라 다르게 A,B안 노출

- 노출 분산, 사용자 분산을 사용할 수 없을 때 대안적으로 활용할 수 있는 방식

 

 

4. 결과 분석

1) AA Test

- A/B 테스트 결과 신뢰도를 높이기 위해 사전에 시행하는 테스트로, 

- 대조군 A, 실험군 B로 셋팅하지만 같은 화면으로 노출하여 A안 vs A안의 실험 결과를 측정한다

- 결과값에 차이가 난다면 해당 왜곡을 해결한 후에 A/B 테스트를 진행한다

 

2) P-Value 검증

  • 1종 오류: 효과가 없는데 있다고 할 오류 (귀무가설이 사실)
  • 2종 오류: 효과가 있는데 없다고 할 오류 (귀무가설이 거짓)

- 일반적으로 1종 오류를 더 경계(과학에서의 증거주의 영향)하고, 이 오류 허용 수준은 5%로 제약한다 (p<0.05)

- p-value는 귀무가설확률 분포에서 구한다 (귀무가설이 맞다는 무죄추정의 원칙)

- p > 0.05라면, 증거의 강도가 충분히 강하지 않은 것이므로 통계적으로 유의하지 않다고 본다

 

5. 주의할 것

  • A/B 집단은 임의적으로 나뉘어야 한다 (외부 변수의 영향을 통제하기 위함)
  • 실험 참가 집단이 모집단을 대표할 수 있어야 한다
  • 너무 많이, 자주 시행하면 손해를 감수해야 한다
  • A/B 테스팅의 결과는 언제까지 유효할지 모른다 (비즈니스 환경은 시시각각 변화하기 때문)
  • A/B 테스팅으로는 전역적인 최적점을 찾을 수 없고, 지역최적점에 수렴할 뿐이다

 

* 출처: 데이터리안(https://datarian.io/blog/a-b-testing, https://datarian.io/blog/dont-be-overwhelmed-by-pvalue)

 

+ Recent posts