TIL/내일배움캠프

쿼리로 데이터 전처리

Kou_ 2026. 4. 28. 14:37

[내일배움캠프] QA/QC_6기 - 사전캠프 7일차

  • TIL
    • SQL로 데이터 전처리 및 피봇테이블 만들기
  • To-Do List
    1. SQL 강의 수강 (Chap5 전체)
    2. SQL 과제
    3. 이전 ADsP 학습 내용 정리
    4. ADsP 강의 수강 (Chap4-3 ~ ) 

SQL

조회한 데이터에 아무 값이 없다면 어떻게 해야할까

  • 데이터가 없을 때 연산 결과 변환 케이스
    • 테이블에 잘못된 값 입력
    • JOIN문 사용 시 값이 없는 경우
    • 사용할 수 없는 데이터가 들어있거나, 값이 없는 경우
  • 방법1. 없는 값 제외
    • MySQL에서는 사용할 수 없는 값(Not given)일 때 해당 값을 0으로 간주
    • 0으로 간주할 경우 평균과 같은 계산에서는 큰 오차가 발생
    • Null로 변환하여 데이터 자체를 제외하는 것이 필요한 경우가 존재
      Not given을 Null로 처리한 평균과 그렇지 않은 평균을 비교하기 위해 작성한 쿼리
      좌측(그냥 평균)이 우측(Null 처리)한 값보다 작은 것을 확인
    • Null 문법 이용해 값을 제외하고 명확하게 연산 지정
      where b.customer_id is not null
      b.customer_id 에 없는 데이터는 제외 (즉, null이 아닌 데이터만 불러오기)
  • 방법2. 다른 값을 대신 사용하기 
    • 다른 값이 있을 때 조건문 이용하기
      if(rating>=1, rating, 15) → rating이 1이상일 때 15로 대체
    • null 값일 때 
      coalesce(b.age, 20) "null 제거" b.age의 null 값을 20으로 대체

조회한 데이터가 상식적이지 않은 값을 가지고 있다면 어떻게 해야할까

  • 상식적이지 않은 데이터의 예시
    • 주문 고객의 연령 데이터에서 일반적으로 2세는 존재할 수 없음
    • 결제의 경우 가게를 창업한 이후의 데이터가 상식적이나, 1960년대의 결제 정보같은 데이터가 있을 수 있음
  • 조건문으로 값의 범위 지정
    • 조건문으로 상식적인 수준의 범위를 지정
      2세, 6세, 82세와같은 값이 각 15세, 80세로 변경된 것을 확인

실습. SQL로 Pivot Table 만들어보기

  • Pivot table : 2개 이상의 기준으로 데이터를 집계할 때, 보기 쉽게 배열하여 보여주는 것
      구분 컬럼1 구분 컬럼2 ...
    집계 기준1 데이터1 데이터3 ...
    집계 기준2 데이터2 데이터4 ...
    ... ... ... ... 
  • 피봇테이블 예시 : 음식점별 시간별 주문건수 피봇테이블 뷰 만들기 (15~20시 사이, 20시 주문건수 기준 내림차순)
    • 음식점별, 시간별 주문건수 집계하기
    • Pivot View 구조 만들기
      • Pivot table로 만들 때 행 데이터를 열로 바꾸는 작업
      • 조건문만 사용할 시 해당 조건이 아닌 행은 null이 되고, GROUP BY를 사용하면 여러 행을 하나로 줄일때 어떤 값을 대표로 할지 모르니 null이 출력
      • 이 null을 무시하기 위해서 집계함수 MAX를 사용해서 대표값을 지정하는 것

집계 기준(가계명, 행)에 따른 각 구분칼럼(주문시간, 열) 형태의 피봇테이블

업무 시작을 단축시켜 주는 마법의 문법 (Window Fuction)

  • Window Function의 사례
    • 각행의 관계를 정의하기 위한 함수로 그룹 내의 연산을 쉽게 만들어줌
      • 한식 식당 중 주문건수가 많은 순으로 순위매기기
      • 한식 식당 전체 주문건수 중 A 식당이 차지하는 비율
    • 기본 구조 : window_function (argument) over (partition by 그룹 기준 컬럼 order by 정렬기준)
  • 실습1 : N번째 까지의 대상을 조회하고 싶을 때, Rank
    • Rank : 특정 기준으로 순위를 매겨주는 기준
    • 실습 : 음식 타입별로 주문 건수가 가장 많은 상점 3개씩 조회
      a 서브쿼리에서 음식종류 및 가게명 별 주문 수 계산, b 서브쿼리에서 a를 바탕으로 랭킹 집계, b에서 집계한 랭킹의 상위 3곳만 조회
      음식종류 별 3위 이내의 가게만 출력
  • 실습2 : 전체에서 차지하는 비율, 누적합을 구할 때 Sum
    • 앞서 배운 합계를 구하는 기능과 동일하나, 누적합이 필요하거나 카테고리별 합계 컬럼과 원본 컬럼을 함께 이용할 때 유용하게 사용 가능
    • 실습 : 각 음식점의 주문건이 해당 음식 타입에서 차지하는 비율을 구하고, 주문건이 낮은 순으로 정렬

날짜 포맷과 조건까지 SQL로 한 번에 끝내기 (포맷 함수)

  • 날짜 데이터의 이해
    • 문자 타입, 숫자 타입과 같이 날짜 데이터도 특정 타입을 가지고 있음
    • 년,월,일,시,분,초 드으이 값을 모두 가지고 있으며, 목적에 따라 '월', '주', '일' 등으로 포맷을 변경할 수 있음
    • 실습1 : 날짜 데이터의 여러 포맷
      • yyyy-mm-dd 형식의 컬럼을 data type으로 변경하기
        DATE(컬럼1) : 컬럼1의 형식을 date 형식으로 변경
        change_date 컬럼명 좌측 시계 모양으로 바뀐 것을 확인
      • data type을 date_format을 이용하여 년, 월, 일, 주로 조회해보기
        DATE_FORMAT(DATE(컬럼1), '%조회하고자 하는 것' : '%형식'으로 지정 가능
        ㅅ일요일(0)부터 월용 토요일(6)으로 나타남
    • 실습2 : 년도별 3월의 주문건수 구하기

실습 문제

  • 문제 : 음식 타입별, 연령별 주문건수 pivot view 만들기 (연령은 10~59세 사이)
    작성한 쿼리
    pivot table
  • 주의 할 점
    • case문 사용 시에 조건마다 , 사용 X

과제

  1. 현재 존재하고 있는 총 부서의 수를 구하는 쿼리를 작성해주세요.
    select count(1)
    from departments
  2. 모든 직원과 그들이 속한 부서의 이름을 나열하는 쿼리를 작성해주세요.
    select e.name, d.name
    from employees e inner join departments d on e.department_id=d.id
  3. '기술팀' 부서에 속한 직원들의 이름을 나열하는 쿼리를 작성해주세요.
    select e.name
    from employees e inner join departments d on e.department_id=d.id
    where d.id='103'
  4. 부서별로 직원 수를 계산하는 쿼리를 작성해주세요.
    select d.name, count(e.name) cnt_employee,
    from employees e left join departments d on e.department_id=d.id
    group by 1
  5. 직원이 없는 부서의 이름을 찾는 쿼리를 작성해주세요.
    select d.name
    from employees e left join departments d on e.department_id=d.id
    where e.id is null
  6. '마케팅팀' 부서에만 속한 직원들의 이름을 나열하는 쿼리를 작성해주세요.
    select e.name
    from employees e inner join departments d on e.department_id=d.id
    where d.id='102'
  7. 모든 주문의 주문 ID와 주문된 상품의 이름을 나열하는 쿼리를 작성해주세요.
    select o.id, p.name
    from pruducts p left join orders o on p.id=o.product_id
  8. 총 매출(price * quantity)이 가장 높은 상품의 ID와 해당 상품의 총 매출을 가져오는 쿼리를 작성해주세요.
    select p.id, sum(o.price*p.quantity) tot_sales
    from pruducts p inner join orders o on p.id=o.product_id
    group by 1
    order by 2 desc

    limit 1
  9. 각 상품 ID별로 판매된 총 수량(quantity)을 계산하는 쿼리를 작성해주세요.
    select p.id, sum(o.quantity) tot_quantity
    from pruducts p inner join orders o on p.id=o.product_id
    group by 1
  10. 2023년 3월 3일 이후에 주문된 모든 상품의 이름을 나열하는 쿼리를 작성해주세요.
    select p.name
    from pruducts p inner join orders o on p.id=o.product_id
    where o.order_date>'2023-03-03'
  11. 가장 많이 판매된 상품의 이름을 찾는 쿼리를 작성해주세요.
    select p.name, sum(o.quantity) tot_quantity
    from pruducts p inner join orders o on p.id=o.product_id
    group by p.id
    order by tot_quantity desc
    limit 1
  12. 각 상품 ID별로 평균 주문 수량을 계산하는 쿼리를 작성해주세요.
    select p.id, avg(o.quantity) avg_quantity
    from pruducts p inner join orders o on p.id=o.product_id
    group by 1
  13. 판매되지 않은 상품의 ID와 이름을 찾는 쿼리를 작성해주세요.
    select p.id, p.name
    from pruducts p left join orders o on p.id=o.product_id
    where o.id is null

SQL 실전

  • SQL의 작동 순서와 작성 순서
    • SQL의 작동 순서
      FROM ON JOIN WHERE GROUB BY HAVING SELECT DISTINCT ORDER BY
    • SQL의 작성 순서
      SELECT FROM WHERE GROUP BY HAVING ORDER BY
  • IF문과 CASE문, 서브쿼리
    • IF는 보통 MySQL에서 사용, CASE는 모든 RDBMS에서 사용 가능
    • 조건에 따라 컬럼 값을 변경, 조건별 다른 계산을 할 때 사용
    • 서브쿼리 vs WITH 문 (공통 테이블 표현식)
      항목
      서브쿼리
      WITH 문 (CTE)
      위치
      SELECT, FROM, WHERE 절 안에 바로 씀
      쿼리의 맨 위에 작성
      재사용
      안됨
      여러 번 참조 가능
      가독성
      복잡해지기 쉬움
      가독성이 좋고 유지보수에 유리
      사용 예
      간단한 조건에 자주 사용
      복잡한 로직 처리 시 선호
    • WITH 문을 추천하는 상황
      • 쿼리가 길고, 중간 계산을 나눠서 작성하고 싶을 때 → 각 단계에 명명 가능, 가동성↑
      • 같은 쿼리를 반복해서 써야할 때 → 중복 제거, 재사용성↑
      • 쿼리가 짧고 단순할 땐 서브쿼리가 간결하고 명확하게 처리 가능
      • 성능 최적화가 중요할 땐 사용하는 RDBMS에 따라 다름
      • 예시
        • 서브쿼리
        • WITH 문
          with 별칭 as ( 필요한 쿼리문 )
  • 문제 풀이가 어렵고, 어디서부터 손대야 할지 모를 때
    • 문제를 2~3개의 질문으로 분해
      • ex) 통과한 사람의 평균 점수
      • 통과의 기준 점수
      • 점수가 속한 칼럼 위치
      • 평균의 사용에는 GROUP BY가 필요한가?
    • SQL 작성 순서 (SELECT  FROM  WHERE  GROUP BY  HAVING  ORDER BY)
    • 우선 조회할 컬럼 등을 확인하기 위해서 select *로 확인하는 연습
  • SQL 쿼리 작성 팁
    1. SQL 작성 전 체크리스트
      • 문제 정의 (문제 분석)
      • 무엇을 구하고 싶은가 문장으로 적기
      • 요청사항을 정확히 아는 것이 중요
      • SQL 작성 전 문제의 요지 분석
      • 문제를 보며 예상 쿼리를 구성해보자
        예시: 월별로 매출 합계를 구하고 싶다
        ~ SELECT, GROUP BY, SUM 무엇을 쓰는가?
    2. 데이터 구조 확인
      쿼리 쓰기 전 반드시 테이블의 컬럼명 확인 (SELECT *)
    3. SQL 쿼리 실행순서에 따라 작성 및 해석
      • FROM
      • JOIN
        • 항상 작은 범위부터 테스트하라
        • 여러 테이블 JOIN 시 먼저 두 개만 조인하여 결과를 확인하라
          ⇒ 조인 조건이 잘못되면 데이터가 폭발적으로 늘어나거나 없어지기 때문이다.
        • JOIN 시 NULL 값이 필요한 경우가 있으니 고려하여 사용하기
      • ON
      • WHERE
        • WHERE은 행을 필터링 함을 기억하라
        • WHERE 필터링을 적용할 때 NULL도 제외되는 것을 기억하라
        • 인덱스에 설정되어있는 컬럼 사용
        • 인덱스의 선두 컬럼에 해당하는 조건을 사용하라
      • GROUP BY
      • 집계함수 (SUM, AVG, MAX 등)
      • HAVING
        • HAVING은 집계된 결과를 필터링 함을 기억하라
      • DISTINCT
      • SELECT
        • 필요한 컬럼만 사용하라
      • ORDER BY
      • LIMIT
    4. 쿼리 작성에 도움되는 것
      • 불필요한 서브쿼리보다는 JOIN, CTE, 윈도우함수를 사용하라
        • CTE: 공통 테이블 표현식, 가상 테이블
      • 날짜 문제의 경우 애매한 경우가 많기에 여러 번 해독해야 함
        • 날짜 계산은 무조건 함수로 하는 것이 좋다.
        • 한국어 및 도메인의 특징에 따라 다르기 때문
          • 대여의 개념
          • 숙박의 개념
          • D-DAY 개념
          • 문자 계산, 날짜끼리의 계산
      • WITH (가상 테이블)
        • 가독성 향상을 위해 사용을 고려할 수 있다.
        • N회 반복 사용 할 때 사용을 고려할 수 있다.
      • WITH RECURSIVE (가상 테이블 재귀)
        • 인위적인 컬럼을 만들 때 유용하게 사용된다.
           ex) 24시간의 시간표를 만들 때
  • GROUP BY vs OVER() 함수 사용 조건 차이
    항목
    GROUP BY
    OVER() (윈도우 함수)
    데이터 집계
    행을 묶어서 줄 수를 줄임
    행의 개수는 그대로
    용도
    전체/그룹별 집계
    각 행 기준으로 누적합, 비율 등 계산
    결과 형태
    묶여서 줄어듦
    줄 수 그대로 유지됨
    • 예시
      • GROUP BY
      • OVER()

SQL 실전 학습 '실습'

의약품 생산 Lot 품질 분석

  • 수분 ≥ 8.0% : 기준 초과
  • 순도 ≤ 92.0% : 기준 미달
  • 중량은 ± 10g 이외면 기준 초과 (500g 정상 기준, 490~510g 이외는 초과)
  • 문제
    • Q1. 전체 로트 중 불량 로트 비율을 계산하라 (소수점 둘째자리까지 반올림된 백분율(%)로 출력하기.
      select 
      round(
      100*sum(case when
      water_content>=8.0
      or purity<=92.0
      or abs(weight-500)>10))
      then 1 else 0 end)/count(1), 2) defact_ratio
      from lot_quality_checks
    • 핵심 SQL 문법 및 함수 정리
      함수/문법
      설명
      정답 쿼리 참조
      집계 함수
      COUNT(*)
      테이블의 전체 행 개수 계산
      COUNT(*) → 전체 로트 수
      COUNT(*) FILTER (WHERE …)
      특정 조건에 맞는 행만 카운트
      COUNT(*) FILTER (WHERE water_content >= 8.0)
      수학 함수
      ABS(x)
      절댓값 반환
      (중량 허용 오차 계산에 사용)
      ABS(weight - 500) > 10
      ROUND(x, n)
      숫자 x를 소수점 n자리까지 반올림
      ROUND(66.6666, 2) → 66.67
      산술 연산
      /
      나눗셈, 불량률 계산에 사용
      불량로트수 / 전체로트수
      *
      곱셈, %로 변환할 때 사용
      (불량/전체) * 100.0
      논리 연산자
      OR
      여러 조건 중 하나라도 맞으면 참
      water_content >= 8.0 OR purity <= 92.0
      별칭
      AS
      결과 컬럼명 지정
      AS defect_rate_percentage
    • Q2. 품질 항목별로 불량 로트 수를 각각 구하라
      select
      sum(case when water_content>=8.0 then 1 else 0 end) defect1
      sum(case when purity<=92.0 then 1 else 0 end) defect2
      sum(case when abs(weight-500)>10 then 1 else 0 end) defect3
      from lot_quality_checks
    • 핵심 SQL 문법 및 함수 정리
      구분
      함수 / 문법
      설명
      정답 쿼리 참조
      조건 분기
      CASE WHEN ... THEN ... ELSE ... END
      조건이 참이면 특정 값을 반환하고,
      거짓이면 다른 값을 반환
      CASE WHEN purity <= 92.0 THEN 1 ELSE 0 END
      집계 함수
      SUM()
      여러 값을 더함 → 조건을 만족하는 로트 수를 세는 데 사용
      SUM(CASE WHEN water_content >= 8.0 THEN 1 ELSE 0 END)
      수학 함수
      ABS()
      절댓값 반환
      중량 허용 오차 계산에 사용
      ABS(weight - 500) > 10
      비교 연산자
      >=, <=, >
      조건 설정에 사용
      purity <= 92.0, weight > 510
      별칭
      AS
      결과 컬럼 이름 지정
      AS water_defects

 차량 센서 진단 및 이상 패턴 분석

  • 분석 목표
    • 센서 이상이 있었던 차량이 어떤 비율로 실제 정비까지 이어졌는지 확인
    • 센서 항목별로 실제 정비 이력과 연결된 케이스 수 분석
    • 센서 이상 없었는데 정비 받은 차량, 또는 이상 있었지만 정비 안받은 차량 등 예외 케이스 탐색
  • 문제
    • Q1. 센서 이상이 1건 이상 있었던 차량 중, 정비 이력이 존재하는 차량의 비율(%)을 구하라.
      with abnormal_vehicles as
      (
      select distinct vehicle_id
      from vehicle_test_logs
      where
      vibration_level>6.0

      or brake_response>300
      or abs(engine_temp-90)>20
      ),
      repaired_vehicles as
      (
      select distinct v.
      from abnormal_vehicles v left join vehicle_repairs r on v.vehicle_id=r.vehicle_id
      )
      select
      count(distinct repaired_vehicles.vehicles_id)*100.0 /
      count(distinct e.vehicles_id) as repair_ratio
      from abnormal_vehicles v left join repaired_vehicles e on v.vehicle_id=e.vehicle_id
    • 핵심 SQL 문법 및 함수 정리
      문법 / 함수
      예시
      설명
      WITH (CTE)
      WITH abnormal_vehicles AS (...)
      공통 테이블 표현식 (CTE). 반복적으로 사용할 서브쿼리를 정의하여 가독성과 재사용성을 높임
      SELECT
      DISTINCT
      SELECT DISTINCT vehicle_id
      중복 제거. 동일 차량의 복수 로그가 있어도 한 번만 카운트
      FROM
      FROM vehicle_test_logs
      데이터를 조회할 테이블 지정
      WHERE
      WHERE vibration_level > 6.0
      조건절. 센서 이상 기준을 지정
      OR
      OR brake_response > 300
      여러 조건 중 하나라도 참이면 결과 포함
      JOIN
      JOIN vehicle_repairs ON v.vehicle_id = r.vehicle_id
      두 테이블을 조건(ON)에 따라 연결
      LEFT JOIN
      LEFT JOIN repaired_vehicles ON ...
      기준 테이블은 모두 포함, 연결 안 되는 항목도 유지
      COUNT
      (DISTINCT)
      COUNT(DISTINCT vehicle_id)
      중복을 제거한 개수를 카운트 (차량 단위 집계에 중요)
      산술 연산
      * 100.0 / ...
      비율(%) 계산을 위한 산술 연산
      별칭 (Alias)
      AS repair_rate_percent
      결과 컬럼에 이름 부여하여 결과 해석 용이하게 함
    • Q2. 센서 항목별로 실제로 정비까지 이어진 건수를 구하라.
      with abnormal_tests as
      (
      select distinct t.vehicle_id,
      (case when vibration_level>6.0  then 1 else 0 end) vibration_flag,

      (case when brake_response>300 1 else 0 end) brake_flag,
      (case when abs(engine_temp-90)>20 then 1 else 0 end) engine_flag
      from vehicle_test_logs t
      ),
      repair_match as
      (
      select a.vehicle_id, r.issue_type
      from abnormal_tests a left join vehicle_repairs r on a.vehicle_id=r.vehicle_id
      )
      select
      sum(case when vibration_level>6.0 and issue_type='진동' then 1 else 0 end) cnt_vibration_repair,

      sum(case when brake_response>300 and issue_type='브레이크' then 1 else 0 end) cnt_brake_repair,
      sum(case when abs(engine_temp-90)>20 and issue_type='엔진' then 1 else 0 end) cnt_engine_repair
      from abnormal_tests a left join repair_match r on a.vehicle_id=r.vehicle_id

    • 핵심 SQL 문법 및 함수 정리
      문법 / 함수
      예시
      설명
      논리 연산자
      AND / OR
      engine_temp < 70 OR engine_temp > 110
      여러 조건을 동시에 평가. AND는 모두 참일 때,
      OR은 하나라도 참일 때 만족
      비교 연산자
      >, <
      값의 크기 비교 (센서 기준 초과/미만 판별에 사용)
    • Q3. 센서 이상이 없었음에도 불구하고 정비를 받은 차량 수를 구하라.
      with normal_vehicles as
      (
      select distinct vehicle_id
      from vehicle_test_logs
      where 
      vibration_level<=6.0

      or brake_response<=300
      or abs(engine_temp-90)<=20
      ),
      repaired_normals as
      (
      select distinct nv.vehicle_id
      from normal_vehicles nv left join vehicle_repairs vr on nv.vehicle_id=vr.vehicle_id
      )
      select
      count(distinct vehicle_id) cnt_normal_repair

      from repaired_normals
    • Q4. 정비 없이 출고된 차량 중, 센서 이상 건수가 2개 이상이었던 차량 수를 구하라.
      with abnormal_counts as
      (
      select vehicle_id,
      (case when vibration_level>6.0  then 1 else 0 end) +

      (case when brake_response>300 1 else 0 end) +
      (case when abs(engine_temp-90)>20 then 1 else 0 end) cnt_abnormal
      from vehicle_test_logs
      ),
      high_abnor_no_repair as
      (
      select distinct a.vehicle_id
      from abnormal_counts a left join vehicle_repairs r on a.vehicle_id=r.vehicle_id
      where a.cnt_abnormal>=2 and r.vehicle_id is null
      )

      select
      count(vehicle_id) high_abnor_without_repair
      from high_abnor_no_repair

출처

전용문, 박현민 지음, 2026 ADsP 데이터분석 준전문가, 위키북스

내일배움캠프 교육 자료

'TIL > 내일배움캠프' 카테고리의 다른 글

파이썬 시작하기 Ⅱ  (0) 2026.05.04
파이썬 시작하기 Ⅰ  (0) 2026.04.30
쿼리로 데이터베이스 다루기  (0) 2026.04.27
AI와 대화? 프롬프팅  (0) 2026.04.26
나는 AI를 잘 사용하고 있을까?  (1) 2026.04.23