WIL (2024-06-24 ~ 2024-06-28)

Gi Woon Lee·2024년 6월 29일
1

TIL

목록 보기
1/78
post-thumbnail

SQL

🟦 SQL 쿼리 동작 순서

예시 문제

CODEKATA

단계설명
1.FROMUSED_GOODS_BOARD 테이블과 USED_GOODS_USER 테이블을 INNER JOIN 하여 결합
2.WHEREB.STATUS = 'DONE' 조건을 만족하는 행 필터링
3.GROUP BYB.WRITER_ID를 기준으로 데이터 그룹화
4.HAVING가격 합계가 700,000원을 초과하는 그룹 필터링
5.SELECT각 작가 ID와 가격 합계를 선택

🟦 WHERE 절에는 연산자만 올 수 있는가?

SQL 10번 문제 中 WHERE 절에 "is not" 은 안되고 "!=" 은 되던데
한 번 정리할 필요가 있을 듯 하다.

🅰️ WHERE 절에는 연산자가 오는데, "NULL"만 특별하게 취급하여 "IS NOT NULL", "IS NULL"을 사용해야 한다.

# 젊은 동물의 아이디와 이름을 조회, 아이디 칼럼 정렬
-- 젊은 동물의 아이디와 이름을 조회, 아이디 칼럼 정렬
SELECT animal_id, name
FROM animal_ins
WHERE intake_condition != 'aged'
-- WHERE intake_condition IS NOT 'aged' 는 안된다.
ORDER BY animal_id;
/*
select count(user_id) USERS
from user_info
where age = null
*/

select count(user_id) USERS
from user_info
where age is null

# 이번에는 where 절에 is 가 오네..? null 값을 찾을 때만 is 를 사용 할 수 있는거야?
# 조건문 where 절에서 연산자를 사용해야 하는 상황과 is를 사용해야 하는 상황 구분해야 할 듯.

SQL에서 'NULL' 값은 특별하다!
'NULL'을 확인할 때: "IS NULL" or "IS NOT NULL"을 사용해야 한다.
비교연산자(예: =, !=, <, >)로는 비교가 불가능.
쿼리 WHERE age = NULL은 어떠한 결과도 반환하지 않는다.

🟦 JOIN

JOIN 시 기준 칼럼 이름이 달라도 칼럼 내용이 동일하면 JOIN이 가능하다!

CODEKATA

⭐JOIN 시 AND 연산자로 필터링하기⭐

복습1. 복습2.

240626TIL <- 에서 MAX()를 사용했을 때 해당 행에서 MAX() 칼럼만 따로 노는 문제에 대해 다뤘다.
CODEKATA <- 문제를 풀면서 같은 상황에 처했다. 하지만 이번에는 240626TIL 처럼 WHERE = MAX()를 통해 필터링이 불가능했다.

MAX()의 결과가 여러 줄의 행을 가지고 있기 때문이다. WHERE =같은 단일값 필터링이 불가한 것이다.
Subquery returns more than 1 row

정리
- 서브쿼리의 결과가 다중 행을 반환한다면, WHERE 절에 = 연산자를 사용하여 필터링하는 방법을 사용할 수 없다.
- 이 경우, 서브쿼리의 결과를 필터링하기 위해JOIN을 사용하는 것이 가장 일반적이다.

JOIN 함수와 AND 연산자를 통해 필터링하는 과정을 살펴보자.

🔥JOIN_ON_AND🔥 로 JOIN절에서 필터링 조건 걸어버리기.. 신통방통

SELECT MAIN.FOOD_TYPE,
       MAIN.REST_ID,
       MAIN.REST_NAME,
       SUB.FAVORITES
FROM REST_INFO MAIN INNER JOIN 
(
SELECT FOOD_TYPE,
       MAX(FAVORITES) FAVORITES
FROM REST_INFO
GROUP BY FOOD_TYPE
) SUB ON MAIN.FOOD_TYPE = SUB.FOOD_TYPE AND MAIN.FAVORITES = SUB.FAVORITES
ORDER BY 1 DESC
  1. 문제 설명

    음식 종류별로 즐겨찾기가 가장 많은 식당을 구하고, 해당 식당의 음식 종류, ID, 식당 이름, 즐겨찾기 수를 조회합니다. 결과는 음식 종류를 기준으로 내림차순(DESC)으로 정렬합니다.

  2. REST_INFO 데이터 테이블

  1. 쿼리 작성

    다음은 각 음식 종류별로 즐겨찾기 수가 가장 많은 식당을 찾기 위해 서브쿼리와 JOIN을 사용하는 쿼리입니다.

    SELECT R.FOOD_TYPE, R.REST_ID, R.REST_NAME, R.FAVORITES
    FROM REST_INFO R
    JOIN (
        SELECT FOOD_TYPE, MAX(FAVORITES) AS MAX_FAVORITES
        FROM REST_INFO
        GROUP BY FOOD_TYPE
    ) AS T
    ON R.FOOD_TYPE = T.FOOD_TYPE AND R.FAVORITES = T.MAX_FAVORITES
    ORDER BY R.FOOD_TYPE DESC;
  2. 쿼리 설명

    • 서브쿼리 T는 각 FOOD_TYPE별로 최대 즐겨찾기 수(MAX_FAVORITES)를 계산합니다.
    • 메인 쿼리는 REST_INFO 테이블을 서브쿼리 T와 JOIN하여 FOOD_TYPE과 즐겨찾기 수가 일치하는 행을 찾습니다.
    • 결과는 음식 종류를 기준으로 내림차순으로 정렬됩니다.
  3. 쿼리 결과

    위의 쿼리를 실행하면 다음과 같은 결과가 나옵니다.

    FOOD_TYPEREST_IDREST_NAMEFAVORITES
    일식00004스시사카우스230
    양식00003따띠따띠뜨102
    한식00001은돼지식당734
  4. 💡중요포인트💡
    JOIN ON 뿐만 아니라 AND 를 통해 한 번 더 조건을 걸 수 있다.

🟦 order by

다중 정렬 조건: ORDER BY 절에 ","를 사이에 두고 여러개의 정렬 조건이 나열되면 첫 번째 정렬 결과값 속 중복 행에 대하여 다음 정렬이 수행된다.

-- 모든 ID, NAME, DATETIME을 조회, 이름 순 정렬 / 
-- 같은 이름일 경우 날짜 내림차순 정렬
SELECT ANIMAL_ID, NAME, DATETIME
FROM animal_ins
ORDER BY NAME, DATETIME DESC;

SQL에서 ORDER BY 절에 여러개의 정렬 조건을 사용하면 수행 플로우는 다음과 같다.
ORDER BY CONDITION1, CONDITION2
1. 첫 번째 정렬 조건(CONDITION1) (이름 순 정렬)
2. 두 번째 정렬 조건(CONDITION2) (같은 이름? -> 날짜 기준 내림차순 정렬)

🟦 집계함수는 SELECT 문에만 사용하는가?

집계함수: max() min()등의 함수

🅰️ 집계함수는 일반적으로 'SELECT' 문에서 가장 많이 사용된다. 하지만 'HAVING' 절에서 사용될 수 있다.

HAVING:
그룹화된 결과에 대한 조건을 지정할 때 사용되며, GROUP BY 절과 함께 특정 조건을 만족하는 그룹만을 필터링할 때 사용

주요 집계 함수:
MAX(): 주어진 열의 최대값을 반환
MIN(): 주어진 열의 최소값을 반환
SUM(): 숫자 열의 총합을 반환
AVG(): 숫자 열의 평균값을 반환
COUNT(): 특정 조건을 만족하는 행의 수를 계산

🟦 IF 절은 어디에 들어가야 하는가?

🅰️ IF() 함수는 주로 SELECT 문 내에서 칼럼이 들어갈 위치에 사용된다.

  • if 절의 구조

    if(조건, 조건을 충족할 때, 조건을 충족하지 않을 때)

예시

SELECT ANIMAL_TYPE,
       IF(NAME IS NULL, 'No name', NAME) NAME,
       SEX_UPON_INTAKE
FROM ANIMAL_INS
ORDER BY ANIMAL_ID

/* 
	[IF 함수를 대체할 수 있는 CASE함수]
       CASE 
           WHEN NAME IS NULL THEN 'No name'
           ELSE NAME
       END AS NAME,
*/

🟦 DATE_FORMAT() 함수 & Specifier

learnsql.com 참조

날짜 데이터의 구조를 변경하고 싶을 때 사용하는 함수:
DATE_FORMAT()

registration_datetime 칼럼의 날짜 데이터 구성을 변경해보자.

SELECT
  first_name,
  last_name,
  DATE_FORMAT(registration_datetime, '%a, %Y %M %e %H:%i:%s')
    AS format_registration_datetime
FROM student_platform;

SELECT
  first_name,
  last_name,
  DATE_FORMAT(registration_datetime, '%a, %Y %M %e %H:%i:%s')
    AS format_registration_datetime
FROM student_platform;
  • 날짜 데이터 지정자(specifier)는 다음과 같다.
    %a – 요일 이름 약어
    %Y – Year, in 4-digits.
    %M – 월 이름
    %m - 월 (01-12)
    %d - 일 (00-31)
    %e – 일 (1–31)
    %H – 시 (00-23)
    %i – 분 (00-59)
    %s – 초 (00-59).

ex) 2024-06-29 = "%Y-%m-%d"

'%Y-%m-%d' 가 가장 많이 쓰이는 듯.

🟦 WHERE 절 복수 필터링 사용

🅰️ 논리연산자(AND,OR), 혹은 IN연산자를 사용한다.

#sql_19_흉부외과 또는 일반외과 의사 목록 출력하기

SELECT DR_NAME,
       DR_ID,
       MCDP_CD,
       DATE_FORMAT(HIRE_YMD, "%Y-%m-%d") HIRE_YMD
FROM DOCTOR
WHERE MCDP_CD = 'GS'OR MCDP_CD = 'CS'
ORDER BY HIRE_YMD DESC, DR_NAME ASC


# WHERE 절에 들어가는 복수의 조건들 표현 
# 꼭 저렇게 자세히 써야해? 

🤔 WHERE MCDP_CD = 'GS' OR 'CS' 라고 하면 왜 안되는가..?

🅰️ CS는 언제나 참이 되기 때문에 원하는 결과가 나오지 않는다.

따라서 여러 조건을 확인할 때는 논리연산자(AND,OR,NOT)를 각각 사용해주거나 'IN'연산자를 사용할 수 있다.

💡'IN'연산자

SELECT column_name(s)
FROM table_name
WHERE column_name IN (value1, value2, ...);

CODEKATA
ex) 특정 이름들이 포함된 칼럼 조회

WHERE NAME IN ('Lucy','Ella','Pickle','Rogan','Sabrina','Mitty')

SQL구문이 더 간결하고 가독성이 높아진다!

FIND_IN_SET('해당사항',칼럼) > 최소수량

-- '통풍시트', '열선시트', '가죽시트' 중 하나 이상의 옵션이 포함된 자동차 조회

SELECT CAR_TYPE,
       COUNT(CAR_ID) CARS
FROM CAR_RENTAL_COMPANY_CAR
WHERE FIND_IN_SET('통풍시트',OPTIONS) > 0
   OR FIND_IN_SET('열선시트',OPTIONS) > 0
   OR FIND_IN_SET('가죽시트',OPTIONS) > 0 
GROUP BY CAR_TYPE
ORDER BY CAR_TYPE

🟦 MAX() 함수 주의사항

⭐MAX()가 적용된 데이터"만" 최대값인 경우를 주의하라.

-- 가장 비싼 가격의 항목의 정보를 전부 구하기
SELECT PRODUCT_ID,
       PRODUCT_NAME,
       PRODUCT_CD,
       CATEGORY,
       MAX(PRICE) PRICE
FROM FOOD_PRODUCT;
-- 이렇게 작성하면 MAX(PRICE)와는 상관없는 칼럼들이 나온다.

-- 올바른 작성 예시
SELECT *
FROM FOOD_PRODUCT
WHERE PRICE = (SELECT MAX(PRICE) FROM FOOD_PRODUCT);

1번 코드로 작성하면,
PRICE 칼럼에는 최대값이 출력되지만 나머지 칼럼에는 전부 최대값과는 관련없는 데이터가 출력된다.

PRODUCT_IDPRODUCT_NAMEPRODUCT_CDCATEGORYPRICE
최대값아님최대값아님최대값아님최대값아님최대값

2번 코드처럼 WHERE 서브퀘리를 통해 해당 행 정보를 전부 출력해야 한다.

PRODUCT_IDPRODUCT_NAMEPRODUCT_CDCATEGORYPRICE
최대값최대값최대값최대값최대값

🟦 ROUND()

평균 가격을 소수점 둘째 자리까지 반올림

SELECT ROUND(AVG(PRICE), 2) AS avg_price
FROM BOOK;

🟦 CAST()

CAST( AS 형식)

select date_format(cast(date as date), '%Y-%m') m
  • 위 select 절 workflow는 다음과 같다.
  1. date 칼럼의 데이터 형식이 str
  2. str 데이터를 time data로 변경해준다. cast(date as date)
  3. time data로 변경한 칼럼을 대상으로 date_format 함수를 사용하여 format을 원하는 것으로 바꿔준다!

날짜를 다루는 칼럼은 보통 "date"로 이름짓기 때문에 막연하게 time data겠거니~ 하고 검증을 뛰어넘는 경우가 있다.

그럴 경우 date_format 함수 같은 time data를 대상으로 하는 함수를 사용하지 못할 뿐 만 아니라 정렬 과정에서도 오류가 날 수 있기에 주의하자.

🟦 CASE WHEN ~조건~ THEN ~출력~ ELSE END

  • case when 에서 조건을 각각 작성해줘야 한다.
    ❌: when 1 < level <= 10 then '1~10Lv 이하'
    ⭕: when 1 < level and level <= 10 then '1~10Lv 이하'

CODEKATA

---------------------------case함수 구조------------------------
CASE 
    WHEN condition1 THEN result1
    WHEN condition2 THEN result2
    ...
    ELSE default_result
END
-----------------------------예시--------------------------------
-- CASE WHEN 구조 예시
SELECT game_actor_id,
       level,
       CASE 
           WHEN 1 < level AND level <= 10 THEN '1~10Lv 이하'
           WHEN 10 < level AND level <= 20 THEN '10~20Lv 이하'
           WHEN 20 < level AND level <= 30 THEN '20~30Lv 이하'
       END AS level_range
FROM game_actors;

ELSE를 안적으면 안돼?

🅰️쿼리는 실행되지만 확인되지 않는 값들은 'NULL'을 반환한다.

🟦 DISTINCT

DISTINCT 는 ()를 사용하지 않는다!

DISTINCT(NAME) ❌
DISTINCT NAME ⭕

🟦 LIKE

특정 값이 포함된 값 찾기 : WHERE ~ LIKE %~%

EX) "부산" 이 포함된 칼럼 찾기

WHERE name LIKE '%부산%'

💡LIKE 함수는 ' = ' 을 사용하지 않는다!

SELECT ANIMAL_ID,
       NAME,
       CASE WHEN SEX_UPON_INTAKE LIKE "%Spayed%" THEN "O"
            WHEN SEX_UPON_INTAKE LIKE "%Neutered%" THEN "O"
            ELSE "X" END 중성화
FROM ANIMAL_INS

🟦 SUBSTR()

substr(조회 할 칼럼, 시작 위치, 글자 수)


SELECT CATEGORY,
       COUNT(PRODUCT_CODE) PRODUCTS
FROM 
(SELECT SUBSTR(PRODUCT_CODE,1,2) CATEGORY,
       PRODUCT_CODE,
       PRICE
FROM PRODUCT) A
GROUP BY CATEGORY

🟦 DATE DATA

날짜 데이터에 대해

설명함수
날짜에 간격을 더하기DATE_ADD(date, INTERVAL value unit)
날짜에서 간격을 빼기DATE_SUB(date, INTERVAL value unit)
두 날짜 사이의 일 수 계산DATEDIFF(date1, date2)
두 날짜 시간 값 사이의 차이를 특정 단위로 계산TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2)
날짜 값을 지정된 형식으로 변환DATE_FORMAT(date, format)
날짜 또는 날짜 시간 값에서 연도, 월, 일 추출YEAR(date), MONTH(date), DAY(date)
날짜 시간 값에서 시, 분, 초 추출HOUR(datetime), MINUTE(datetime), SECOND(datetime)

💡1. DATE_ADD()

특정 날짜에 주어진 간격을 더합니다.

함수: DATE_ADD(date, INTERVAL value unit)
------
예시: SELECT DATE_ADD('2024-06-28', INTERVAL 10 DAY) AS new_date;
------
결과: 2024-07-08

💡2. DATE_SUB()

특정 날짜에서 주어진 간격을 뺍니다.

함수: DATE_SUB(date, INTERVAL value unit)
------
예시: SELECT DATE_SUB('2024-06-28', INTERVAL 10 DAY) AS new_date;
------
결과: 2024-06-18

💡3. DATEDIFF()

두 날짜 사이의 일(day) 수를 계산합니다.

함수: DATEDIFF(date1, date2)
------
예시: SELECT DATEDIFF('2024-06-28', '2024-06-18') AS days_diff;
------
결과: 10

💡4. TIMESTAMPDIFF()

TIMESTAMPDIFF

KODEKATA_35

두 날짜 또는 날짜 시간 값 사이의 차이를 특정 단위로 계산합니다.

함수: TIMESTAMPDIFF(unit, datetime_expr1, datetime_expr2)
------
예시: SELECT TIMESTAMPDIFF(MINUTE, '2024-06-28 12:00:00', '2024-06-28 14:30:00') AS minutes_diff;
------
결과: 150

💡5. DATE_FORMAT()

날짜 값을 지정된 형식으로 변환합니다.

함수: DATE_FORMAT(date, format)
------
예시: SELECT DATE_FORMAT('2024-06-28', '%Y-%m-%d') AS formatted_date;
------
결과: 2024-06-28

5-1. 날짜 형식 지정자

형식 지정자설명
%a요일 이름 약어
%YYear, in 4-digits.
%M월 이름
%m월 (01-12)
%d일 (00-31)
%e일 (1-31)
%H시 (00-23)
%k시 (0-23)
%l시 (1-12)
%i분 (00-59)
%s초 (00-59)

💡6. YEAR(), MONTH(), DAY(), HOUR(), MINUTE(), SECOND()

-- 연도, 월, 일, 시, 분, 초 추출
SELECT YEAR('2024-06-28') AS year,
       MONTH('2024-06-28') AS month,
       DAY('2024-06-28') AS day;
---
결과: 2024, 6, 28

💡7. DATE() 함수

My SQL

-- 시간 정보를 제외하고 날짜(date)만 조회
SELECT DATE('2017-06-15 09:34:21') AS date_only;
-- 결과: 2017-06-15
DATE("2017-06-15 09:34:21")
2017-06-15

💡8. TIMESTAMPDIFF()함수. 날짜 데이터끼리 계산!

TIMESTAMPDIFF
KODEKATA_35

-- TIMESTAMPDIFF(단위, datetime_expr1, datetime_expr2)
SELECT TIMESTAMPDIFF(MONTH, '2003-02-01', '2003-05-01') AS months_diff;
-- 결과: 3
SELECT TIMESTAMPDIFF(YEAR, '2002-05-01', '2001-01-01') AS years_diff;
-- 결과: -1
SELECT TIMESTAMPDIFF(MINUTE, '2003-02-01', '2003-05-01 12:05:55') AS minutes_diff;
-- 결과: 128885

🟦 테이블 칼럼 정보 "NULLABLE"

  • nullable = FALSE: NULL 값 허용 안 함.
  • nullable = TRUE: NULL 값 허용.

    위 사진에서 TLNO의 Nullable 값이 "TRUE" 이므로 TLNO 칼럼에는 NULL값이 존재할 수 있다.

CODEKATA

🟩 22-조건에 맞는 회원수 구하기

-- USER_INFO 테이블에서 2021년에 가입한 회원 중 
-- 나이가 20세 이상 29세 이하인 회원 수 구하기
SELECT COUNT(USER_ID)
FROM USER_INFO
WHERE DATE_FORMAT(CAST(JOINED AS DATE), '%Y') = 2021 
  AND AGE >= 20 
  AND AGE <= 29;

      

🟩 23-중성화 여부 파악하기

#동물의 아이디와 이름, 중성화 여부를 아이디 순으로 조회하는 SQL문을 작성해주세요. 
#중성화가 되어있다면 'O', 아니라면 'X'라고 표시해주세요.
# Spayed: 중성화된(암컷)
# Neutered: 중성화된(수컷)
# Intact: 손대지 않은


/*
SELECT ANIMAL_ID,
       NAME,
       CASE WHEN SEX_UPON_INTAKE = LIKE "Spayed" THEN "O"
            WHEN SEX_UPON_INTAKE = LIKE "Neutered" THEN "O"
            ELSE "X" END 중성화
FROM ANIMAL_INS
LIKE 함수는 그냥 바로 적어라. = LIKE 하면 안돼~ 
*/ 
SELECT ANIMAL_ID,
       NAME,
       CASE WHEN SEX_UPON_INTAKE LIKE "%Spayed%" THEN "O"
            WHEN SEX_UPON_INTAKE LIKE "%Neutered%" THEN "O"
            ELSE "X" END 중성화
FROM ANIMAL_INS

🟩 26-입양 시각 구하기

SELECT HOUR(DATETIME) HOUR,
       COUNT(ANIMAL_ID) COUNT
FROM ANIMAL_OUTS
WHERE HOUR(DATETIME) >= 9 AND HOUR(DATETIME) <= 19
GROUP BY HOUR
ORDER BY HOUR

🟩 34-카테고리 별 도서 판매량 집계하기

조인, 그룹핑 참고하기 좋음

문제링크

SELECT B.CATEGORY,
       SUM(SALES) TOTAL_SALES
FROM BOOK_SALES S INNER JOIN BOOK B ON S.BOOK_ID = B.BOOK_ID
WHERE DATE_FORMAT(S.SALES_DATE, '%Y-%m') = '2022-01'
GROUP BY B.CATEGORY
ORDER BY CATEGORY

🟩 35-오랜 기간 보호한 동물(2)

KODEKATA_35

SELECT ANIMAL_ID,
       NAME
FROM
(
SELECT GO.ANIMAL_ID,
       GO.NAME,
       TIMESTAMPDIFF(DAY, COME.DATETIME, GO.DATETIME) DURATION # 2. TIMESTAMPDIFF()함수로 기간 차이 구하기
FROM ANIMAL_OUTS GO LEFT JOIN ANIMAL_INS COME 
ON GO.ANIMAL_ID = COME.ANIMAL_ID # 1. 조인
ORDER BY DURATION DESC
) A # 까먹지 마라
LIMIT 2

PYTHON

🟧1. 파이썬 산술연산자

덧셈(+), 뺄셈(-), 곱셈(*) 생략

  • / : 나누기

  • % : 나머지

  • ** : 거듭제곱

  • // : 나눗셈의 몫

    #나눗셈의 몫 예시
    num1 = 10
    num2 = 3
    # result는 3
    
    def solution(num1, num2):
    result = num1//num2
    return result

CodeKata Note

  • NULL 필터링 시, NAME IS NULL 이라고 작성해야 함. NAME IS "NULL" 이라고 하면 오류가 나타난다.

  • if() 함수는 select 문 칼럼 위치에 들어간다.

# sql_11_이름에 el이 들어가는 동물 찾기

select ANIMAL_ID, NAME
from animal_ins
where name like "%EL%" and animal_type = "dog"
order by name

# 이름 순으로 정렬하라고... 문제 똑바로 읽자. 
#sql_16_경기도에 위치한 식품창고 목록 출력하기

SELECT WAREHOUSE_ID,
       WAREHOUSE_NAME,
       ADDRESS,
       IF(FREEZER_YN IS NULL, 'N', FREEZER_YN) FREEZER_YN
FROM FOOD_WAREHOUSE
WHERE WAREHOUSE_NAME LIKE "%경기%"
ORDER BY WAREHOUSE_ID ASC

0개의 댓글