2025-04-08 MY SQL

성현규·2025년 4월 8일
post-thumbnail

📌1. sql 코딩테스트

📌 1-1. 조건에 맞는 도서와 저자 리스트 출력하기

  • DATE() 함수는 날짜를 잘라내지만, 내부적으로 데이터 타입은 DATE 로 유지함.
  • MySQL 은 ORDER BY 시 원래 컬럼을 기준으로 정렬하려는 최적화 동작을 한다.
  • 반면, DATE_FORMAT() 은 문자열로 바뀌어서 강제로 정렬이 적용된다.
  • 그래서 DATE() 는 가끔 정렬에서 먹히지 않는 것처럼 보일 수 있다.
  • 알고 싶은 것의 주어에 해당하는 걸 FROM에 넣고 조인하면 편하다. ("내가 쓴 책들" 을 보기 위해 -> 저자조인)
  • 관점의 조인
SELECT B.BOOK_ID, A.AUTHOR_NAME, DATE_FORMAT(B.PUBLISHED_DATE, '%Y-%m-%d') AS PUBLISHED_DATE
    FROM AUTHOR A 
    JOIN BOOK B  ON A.AUTHOR_ID = B.AUTHOR_ID
    WHERE B.CATEGORY = '경제'
    ORDER BY PUBLISHED_DATE;

📌 1-2. 성분으로 구분한 아이스크림 총 주문량

SELECT I.INGREDIENT_TYPE, SUM(TOTAL_ORDER) AS TOTAL_ORDER
    FROM FIRST_HALF F
    JOIN ICECREAM_INFO I ON F.FLAVOR = I.FLAVOR
    GROUP BY I.INGREDIENT_TYPE
    ORDER BY TOTAL_ORDER;

📌 1-3. 상품 별 오프라인 매출 구하기

  • 일대다 관계
  • "상품별 총 판매 금액" 을 알고 싶다. -> 상품을 기준으로 판매 데이터를 모아서 집계
  • 결론: 상품 테이블에서 출발해서 판매 내역을 붙이고 집계하는 흐름이다.
SELECT P.PRODUCT_CODE AS PRODUCT_CODE, SUM(O.SALES_AMOUNT * P.PRICE) AS SALES
    FROM PRODUCT P
    JOIN OFFLINE_SALE O ON P.PRODUCT_ID = O.PRODUCT_ID 
    GROUP BY P.PRODUCT_CODE
    ORDER BY SALES DESC, PRODUCT_CODE ASC;

📌 1-4. 3월에 태어난 여성 회원 목록 출력하기

SELECT MEMBER_ID, MEMBER_NAME, GENDER, DATE_FORMAT(DATE_OF_BIRTH, '%Y-%m-%d') AS DATE_OF_BIRTH
    FROM MEMBER_PROFILE 
    WHERE MONTH(DATE_OF_BIRTH) = 3 
        AND GENDER = 'W'
        AND TLNO IS NOT NULL
    ORDER BY MEMBER_ID ASC;

📌 1-5. 루시와 엘라 찾기

SELECT ANIMAL_ID, NAME, SEX_UPON_INTAKE
    FROM ANIMAL_INS 
    WHERE NAME IN ('Lucy', 'Ella', 'Pickle', 'Rogan', 'Sabrina', 'Mitty');

📌 1-6. 가격대 별 상품 개수 구하기

  • 만원 단위로 그룹화 하기
  • HAVING 은 필터링용 이지, 그룹핑용이 아니다.
  • SELECT 별칭을 GROUP BY에서 못쓴다. -> 논리연산 순서 때문에
  • FLOOR(): 내림함수
  • 같은 계산이 여러번 등장하고 그것이 기준일때 WITH 구문을 사용한다.
WITH PRICE_DATA AS (
    SELECT
        CASE
            WHEN FLOOR(PRICE / 10000) * 10000 >= 10000 THEN FLOOR(PRICE / 10000) * 10000
            ELSE 10000
            END AS PRICE_GROUP
        FROM PRODUCT
)

SELECT PRICE_GROUP, COUNT(PRICE_GROUP) AS PRODUCTS
    FROM PRICE_DATA  -- 여기는 WITH절거를 가져다가 써야함.
    GROUP BY PRICE_GROUP
    ORDER BY PRICE_GROUP;

📌 1-7. 연도 별 평균 미세먼지 농도 조회하기

SELECT YEAR(YM) AS YEAR, 
        ROUND(AVG(PM_VAL1), 2) AS 'PM10', 
        ROUND(AVG(PM_VAL2), 2) AS 'PM2.5'
    FROM AIR_POLLUTION 
    WHERE LOCATION2 = '수원'
    GROUP BY YEAR(YM)
    ORDER BY YEAR

📌 1-8. 물고기 종류 별 잡은 수 구하기

  • N.FISH_NAME 은 하나의 "열(column)" 을 가리키지만, GROUP BY 로 묶인 하나의 그룹 안에서는 N.FISH_NAME 이 여러 값(row) 을 가질 수 있다.
  • 그런데 SQL 은 그룹마다 어떤 FISH_NAME 을 출력해야 할지 알 수 없으므로 오류가 발생한다.
  • 해결하려면: 집계 함수 (MAX(), MIN(), GROUP_CONCAT() 등) 를 써서 하나로 모아주거나, GROUP BY 에 N.FISH_NAME 을 포함해서 완전히 일대일 대응 으로 만들어야 한다.
  • GROUP BY가 늘어나면 소분류가 늘어나는 것을 의미한다.
SELECT COUNT(F.ID) AS FISH_COUNT, N.FISH_NAME AS FISH_NAME
    FROM FISH_NAME_INFO N
    JOIN FISH_INFO F ON F.FISH_TYPE = N.FISH_TYPE
    GROUP BY N.FISH_TYPE, N.FISH_NAME
    ORDER BY FISH_COUNT DESC;

📌 1-9. ROOT 아이템 구하기

SELECT I.ITEM_ID AS ITEM_ID, I.ITEM_NAME AS ITEM_NAME
    FROM ITEM_INFO I
    JOIN ITEM_TREE T ON I.ITEM_ID = T.ITEM_ID
    WHERE T.PARENT_ITEM_ID IS NULL
    ORDER BY ITEM_ID;

📌 1-10. 월별 잡은 물고기 수 구하기

  • GROUP BY는 이미 존재하는 월만 그룹화한다. 굳이 걸러주지 않아도 된다.
  • DATE_FORMAT(TIME, '%c') 이러면 문자열이 되므로 정렬할때 문자열 정렬로 되어 1다음에 12가 온다.
    • 0 → 숫자 연산이니까, SQL 이 문자열을 숫자로 자동 변환하려 한다. (암묵적 변환)
  • CAST나 CONVERT 쓰는게 표준이긴하다.
  • MONTH(TIME)은 숫자형으로 반환하여 0이 붙지 않는다.
SELECT COUNT(ID) AS FISH_COUNT, MONTH(TIME) AS MONTH
    FROM FISH_INFO 
    GROUP BY MONTH(TIME)
    ORDER BY MONTH;

📌 1-11. 조건에 부합하는 중고거래 상태 조회하기

SELECT BOARD_ID, WRITER_ID, TITLE, PRICE,
    CASE 
        WHEN STATUS = 'SALE' THEN '판매중'
        WHEN STATUS = 'RESERVED' THEN '예약중'
        WHEN STATUS = 'DONE' THEN '거래완료'
    END AS STATUS
    FROM USED_GOODS_BOARD 
    WHERE CREATED_DATE = '2022-10-05'
    ORDER BY BOARD_ID DESC;

📌 1-12. 조건에 맞는 사원 정보 조회하기

  • 기본적으로 관계가 가장 뚜렷한 테이블부터 JOIN
SELECT SUM(G.SCORE) AS SCORE, E.EMP_NO, E.EMP_NAME, E.POSITION, E.EMAIL
    FROM HR_EMPLOYEES E
    JOIN HR_GRADE G ON E.EMP_NO = G.EMP_NO
    GROUP BY E.EMP_NO
    ORDER BY SCORE DESC
    LIMIT 1;
  • 만약 만점자가 여러명이라면? -> 서브쿼리 사용
  • SQL 에서는 FROM 절에 오는 서브쿼리 는 반드시 별칭(alias)을 붙여줘야 한다. // 일시적으로 만들어진 테이블 취급을 하기 때문이다.
SELECT SUM(G.SCORE) AS SCORE, E.EMP_NO, E.EMP_NAME, E.POSITION, E.EMAIL
    FROM HR_EMPLOYEES E
    JOIN HR_GRADE G ON E.EMP_NO = G.EMP_NO
    GROUP BY E.EMP_NO
    ORDER BY SCORE DESC
    HAVING SCORE = (
        SELECT MAX(TOTAL_SCORE) 
        FROM (
            SELECT SUM(G.SCORE) AS total_score
            FROM HR_EMPLOYEES E
            JOIN HR_GRADE G ON E.EMP_NO = G.EMP_NO
            GROUP BY E.EMP_NO
        ) AS SUBQUERY
    )
    ORDER BY E.EMP_NO;

📌 1-13. 특정 물고기를 잡은 총 수 구하기

SELECT COUNT(F.ID) AS FISH_COUNT
    FROM FISH_NAME_INFO N
    JOIN FISH_INFO F ON N.FISH_TYPE = F.FISH_TYPE
    WHERE N.FISH_NAME IN ('BASS', 'SNAPPER');

📌 1-14. 대장균의 크기에 따라 분류하기

  • 틀릴 이유가 너무 없는데 틀린다면 그건 오타 때문이다.
SELECT ID, CASE
        WHEN SIZE_OF_COLONY <= 100 THEN 'LOW'
        WHEN SIZE_OF_COLONY > 1000 THEN 'HIGH'
        ELSE 'MEDIUM'
        END AS SIZE
    FROM ECOLI_DATA 
    ORDER BY ID ASC; 

📌 1-15. 재구매가 일어난 상품과 회원 리스트 구하기

  • GROUP BY 의 본질: → 중복 제거
  • SELECT 절에 집계 함수가 없으면, 그냥 각 그룹당 하나의 행만 남는다.
  • 개수는 세지 않고, "묶인다"는 것만 일어나는 것!
SELECT USER_ID, PRODUCT_ID
    FROM ONLINE_SALE 
    GROUP BY USER_ID, PRODUCT_ID
    HAVING COUNT(PRODUCT_ID) >= 2
    ORDER BY USER_ID ASC, PRODUCT_ID DESC;

📌 1-16. 자동차 평균 대여 기간 구하기

  • SQL은 선언형 언어라 내가 명시적으로 "순회해!" 라고 쓰지 않아도, 집계 함수(AVG, SUM, COUNT 등)를 사용하는 순간 SQL 엔진은 자동으로 그룹 내의 각 행을 반복해서 연산한다. -> C언어를 통해 내부에서 절차적 코딩을 수행한다.
  • GROUP BY 의 인수가 2개일 때, 첫 번째 인수의 "중복 제거 효과" 는 풀리기 때문에 아래에서 CAR_1D를 중복제거해서 내보내려면 CAR_ID만 GROUP_BY에 남겨두어야한다.
SELECT CAR_ID, ROUND(AVG(DATEDIFF(END_DATE, START_DATE) + 1),1) AS AVERAGE_DURATION
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY 
    GROUP BY CAR_ID
    HAVING  ROUND(AVG(DATEDIFF(END_DATE, START_DATE) + 1)) >= 7  
    ORDER BY AVERAGE_DURATION DESC, CAR_ID DESC;

📌 1-17. 노선별 평균 역 사이 거리 조회하기

  • ORDER BY에서 TOTAL_DISTANCE를 사용하면 CONCAT때문에 제대로 처리되지 않을 수 있다.
  • 처리가 들어간 별칭일 경우 한번더 확인해야한다.
SELECT ROUTE, 
        CONCAT(ROUND(SUM(D_BETWEEN_DIST), 1), 'km') AS TOTAL_DISTANCE,
        CONCAT(ROUND(AVG(D_BETWEEN_DIST), 2), 'km') AS AVERAGE_DISTANCE
    FROM SUBWAY_DISTANCE 
    GROUP BY ROUTE
    ORDER BY SUM(D_BETWEEN_DIST) DESC;

📌 1-18. 분기별 분화된 대장균의 개체 수 구하기

  • 특정 규칙으로 그룹 묶기
  • GROUP BY에서는 AS로 정의를 못함. 특정 규칙으로 그룹을 묶으려면 그 규칙을 FROM에서 서브쿼리로 정의하고 원본 SELECT에서 필요한 부분들을 같이 첨부한다.
SELECT QUARTER, COUNT(ID) AS ECOLI_COUNT
    FROM (SELECT ID, CASE
                WHEN MONTH(DIFFERENTIATION_DATE) BETWEEN 1 AND 3 THEN '1Q'
                WHEN MONTH(DIFFERENTIATION_DATE) BETWEEN 4 AND 6 THEN '2Q'
                WHEN MONTH(DIFFERENTIATION_DATE) BETWEEN 7 AND 9 THEN '3Q'
                WHEN MONTH(DIFFERENTIATION_DATE) BETWEEN 10 AND 12 THEN '4Q'
                END AS QUARTER
            FROM ECOLI_DATA) AS SUBQUERY
    GROUP BY QUARTER
    ORDER BY LEFT(QUARTER,1) ASC;

📌 1-19. 업그레이드 된 아이템 구하기

  • 가장 작은 조건을 만들고 그것을 서브쿼리로 넣으면서 코드 확장하기
SELECT T.ITEM_ID, I.ITEM_NAME, I.RARITY  
    FROM ITEM_INFO I
    JOIN ITEM_TREE T ON I.ITEM_ID = T.ITEM_ID
    WHERE PARENT_ITEM_ID IN (
        SELECT T.ITEM_ID
        FROM ITEM_INFO I
        JOIN ITEM_TREE T ON I.ITEM_ID = T.ITEM_ID
        WHERE I.RARITY ='RARE'
        )
    ORDER BY T.ITEM_ID DESC;
  • 업그레이드의 업그레이드까지 하려면
  • 재귀구문을 사용해야한다.
  • WITH RECURSIVE = 지정한 Anchor 식 + Recursive 식을 데이터가 더 이상 추가되지 않을 때까지 자동으로 반복 실행하는 SQL 문법이다.
WITH RECURSIVE item_hierarchy AS (
    -- 1. 시작: RARE 아이템들
    SELECT I.ITEM_ID, I.ITEM_NAME, I.RARITY
    FROM ITEM_INFO I
    WHERE I.RARITY = 'RARE'

    UNION ALL -- 위 아래의 결과물을 한 테이블처럼 행단위로 합쳐짐. (UNION과 다르게 중복제거를 하지 않음.)

    -- 2. 재귀: 이전 단계에서 찾은 아이템들의 업그레이드 아이템
    SELECT I.ITEM_ID, I.ITEM_NAME, I.RARITY
    FROM ITEM_INFO I
    JOIN ITEM_TREE T ON I.ITEM_ID = T.ITEM_ID
    JOIN item_hierarchy IH ON T.PARENT_ITEM_ID = IH.ITEM_ID -- 이전단계가 현재단계의 부모여야한다. 이 조건이 맞다면 두 값은 같으므로 테이블을 합칠 수 있다.
    -- 위에서 ON이 조건문의 역할을 한것이다.
)

SELECT DISTINCT ITEM_ID, ITEM_NAME, RARITY -- 위에서 중복을 제거하지 않았으므로 DISTINCT를 이용해서 중복을 제거한다.
FROM item_hierarchy
WHERE RARITY IS NOT NULL
AND ITEM_ID NOT IN (SELECT ITEM_ID FROM ITEM_INFO WHERE RARITY = 'RARE')
ORDER BY ITEM_ID DESC;

📌 2. SQL 수업

  • 필드 다 보여주는 명령어
  • CALL SP_TABLE_INFO_STUDY();
  • 프로그래머스 문제들
  • CALL SP_TABLE_INFO_PROGRAMMERS();

📌 2-1. 데이터 검색하기

  • WHERE는 기본적으로 만족하는 '행'만 조회할때 사용한다.
  • AND 가 OR보다 우선하여 AND와 OR이 같이 쓰인 경우 괄호를 사용하여 순서를 잘 정리해야한다.
  • NOT BETWEEN, NOT LIKE, NOT IN 모두 가능하다.
SELECT ID, NAME, CREDIT
    -> FROM SUBJECTS
    -> WHERE CREDIT NOT IN (2,3);

📌 2-2. SQL함수 (1) - 문자열 함수

  • SQL에는 여러 행의 값을 하나로 요약하는 집계함수와 하나의 행에 대해 하나의 결과값을 반환하는 단일 행 함수가 있다.
함수설명예시결과
UPPER(str)문자열을 모두 대문자로 변환UPPER('hello')'HELLO'
LOWER(str)문자열을 모두 소문자로 변환LOWER('HELLO')'hello'
CHAR_LENGTH(str)문자열의 문자 수 반환CHAR_LENGTH('가나다')3
CONCAT(str1, str2, ...)문자열을 이어붙임CONCAT('Hello', '', 'World')'HelloWorld'
SUBSTRING(str, pos, len)문자열에서 일부를 추출 (1부터 시작)SUBSTRING('abcdef', 2, 3)'bcd'
LEFT(str, n)왼쪽에서 n글자 추출LEFT('abcdef', 3)'abc'
RIGHT(str, n)오른쪽에서 n글자 추출RIGHT('abcdef', 2)'ef'
TRIM(str)문자열 양쪽의 공백 제거TRIM(' hello ')'hello'
REPLACE(str, from, to)문자열 일부를 다른 문자로 변경REPLACE('banana', 'na', 'ny')'bannya'
INSTR(str, substr)문자열에서 substr이 처음 등장하는 위치 반환 (1부터 시작)INSTR('apple', 'l')4
LPAD(str, len, pad_str)왼쪽을 pad_str로 채워 총 길이를 len으로 맞춤LPAD('world', 10, '*')'*****world'
RPAD(str, len, pad_str)오른쪽을 pad_str로 채워 총 길이를 len으로 맞춤RPAD('world', 10, '*')'world*****'
FORMAT(number, decimal_plc)천 단위 콤마와 소수점 자리수 지정FORMAT(12345, 2)'12,345.00'

2-2-1. 학과명을 소문자로 변환하고 10글자 이상인 경우만 출력하기

SELECT LOWER(NAME) AS DNAME
    -> FROM SUBJECTS
    -> WHERE CHAR_LENGTH(NAME) >= 10;

2-2-2. 과목명의 앞 3글자와 학점을 결합하여 출력하기

SELECT CONCAT('과목코드: ', LEFT(NAME, 3), '-', CREDIT)
    -> FROM SUBJECTS

2-2-3. 전화번호 중 가운데 4자리 추출하여 출력하기

SELECT SUBSTRING(PHONE, 5, 4)
    -> FROM STUDENTS;

2-2-4. 학과명을 오른쪽으로 15자 출력, 전화번호는 왼쪽에 - 붙여서 10자 고정폭으로 값을 합쳐 출력

SELECT CONCAT(RPAD(DEPARTMENT_ID, 15, ' '), LPAD(PHONE , 10, '-'))
    FROM STUDENTS;

2-2-5. @ 앞 아이디를 대문자로 만들고 급여를 원단위로 환산하여 특정형태로 출력하기

  • INSTR: 특정 문자가 있는 위치 반환
SELECT CONCAT(UPPER(SUBSTRING(EMAIL, 1, INSTR(EMAIL, '@')-1)), ': ', FORMAT(SAL*10000, 2)) AS EMAILSAL
    FROM PROFESSORS;

2-3. SQL함수 (2) ‒ 숫자 함수 / 조건, NULL 처리 함수

2-3-1. 숫자처리 함수

함수설명예시결과
ABS(x)절댓값 반환ABS(-5)5
POWER(x, y)x의 y제곱 계산POWER(2, 3)8
ROUND(x, d)소수점 d자리까지 반올림ROUND(3.1415, 2)3.14
CEIL(x) / CEILING(x)올림값 반환 (소수점 위로)CEIL(3.2)4
FLOOR(x)내림값 반환 (소수점 아래로)FLOOR(3.8)3
TRUNCATE(x, d)소수점 d자리까지 자름TRUNCATE(3.1415, 2)3.14
MOD(x, y)x를 y로 나눈 나머지MOD(10, 3)1
SIGN(x)x가 양수면 1, 0이면 0, 음수면 -1 반환SIGN(10)1
RAND()0 이상 1 미만의 난수 반환RAND()예: 0.5728

2-3-2. 조건문

함수설명예시결과
IF(expr, t, f)expr이 참이면 t, 거짓이면 f 반환IF(grade=4, '졸업반', '재학생')'졸업반'
IFNULL(expr1, expr2)expr1이 NULL이면 expr2 반환, 아니면 expr1 반환IFNULL(email, '없음')'없음' (email이 NULL인 경우)
NULLIF(expr1, expr2)두 값이 같으면 NULL 반환, 다르면 expr1 반환NULLIF(grade, 1)NULL (grade가 1인 경우)
CASE WHEN ... THEN ... ELSE ... END복수 조건을 처리하는 일반적인 조건 분기 구문CASE WHEN gender = '남' THEN 'M' ELSE 'F' END'M' (gender가 '남'인 경우)

2-3-3. 건강지수와 건강등급 만들기

  • 서브쿼리 활용
  • 원본 SELECT에서 STUDENTS의 열을 쓰고 싶으면 서브쿼리에서 한번 언급해주고 넘어와야 쓸 수 있음.
  • 기본적으로 서브쿼리에 있는 값밖에 사용할 수 없음.
SELECT HI AS '건강지수',
    CASE
        WHEN HI >= 35 THEN '우수'
        WHEN HI >= 30 THEN '양호'
        ELSE '주의'
        END AS '건강등급'
FROM (SELECT TRUNCATE(POWER(HEIGHT,2)/WEIGHT,2) AS HI
        FROM STUDENTS 
        ) AS SUBQUERY; 

2-3-4. 몸무게가 기준과 가장 가까운 사람 5명 뽑기

SELECT NAME, ABS(WEIGHT - 60) AS '차이', FLOOR(WEIGHT/10)*10 AS '몸무게구간'
    FROM STUDENTS
    ORDER BY 차이
    LIMIT 5;

2-3-5. 학생들을 장신과 단신으로 구분하기

SELECT NAME, CASE
    WHEN WEIGHT >= 180 THEN '장신'
    WHEN WEIGHT >= 160 THEN '보통'
    ELSE '단신'
    END AS '키등급',
    IF(MOD(ID,2) = 0, '짝수','홀수') AS '번호유형'
FROM STUDENTS;

2-3-6. 성별정보 출력하기

SELECT NAME, CASE
        WHEN GENDER = '남' THEN 'MALE'
        WHEN GENDER = '여' THEN 'FEMALE'
        WHEN GENDER IS NULL THEN '미입력'
        ELSE '기타'
        END AS '성별정보'
FROM STUDENTS;

2-3-7. 학생들의 키 분류하기

  • NULL로 그냥 지정할 수 있다.
SELECT NAME, IFNULL(IF(HEIGHT >= 170, NULL, '작음'), '경고') AS '키상태'
    FROM STUDENTS;
profile
학생

0개의 댓글