🔌 SPARTA/Courses

스탠다드반 3회차: 실전 SQL

eunjiom 2026. 2. 9. 21:15

WHERE절( 조건 - 필터링)

1. 비교 연산자

  • 예시) ryan 유저 찾기
SELECT *
FROM USERS
WHERE username = 'ryan';

 

2. 논리 연산자

  • AND: 두 조건 모두 만족해야 할 때
SELECT *
FROM POSTS
WHERE user_id = 1 AND post_type = 'video';
  • OR: 두 조건 중 하나라도 만족할 때
SELECT *
FROM POSTS
WHERE user_id = 1 OR post_type = 'video';
  • NOT: 특정 조건을 제외하고 싶을 때

3. BETWEEN, IN, LIKE, IS NULL

 

1) BETWEEN a AND b : 특정 범위에 있는 데이터를 찾을 때

  • 예시) 2022년 1월 1일부터 2022년 12월 31일 사이에 가입한 사용자 찾기
SELECT username, registration_date
FROM USERS
WHERE registration_date BETWEEN '2022-01-01' AND '2022-12-31';

 

2) IN (list) : 여러 값 중 하나라도 일치하는 데이터를 찾을 때(OR처리)

  • user_id가 1, 9, 21 중 하나에 해당하는 사용자 정보 찾기
SELECT *
FROM USERS
WHERE user_id IN (1, 9, 21);

==

SELECT *
FROM USERS
WHERE user_id = 1
    OR users.user_id = 9
    OR users.user_id = 21
;

 

3) LIKE '패턴' : 문자열 패턴으로 데이터를 찾을 때

  • %(0개 이상의 모든 문자)와 _(딱 1개의 문자) 기호를 사용
  • p'로 시작하는 모든 사용자 찾기
SELECT username
FROM USERS
WHERE username LIKE 'p%';
  • #일상 해시테그 게시물 찾기
SELECT post_id, content
FROM POSTS
WHERE content LIKE '%#일상%';

 

4) IS NULL : 값이 비어 있는 데이터를 찾을 때

  • ~가 아닌 것: NOT
    NULL이 아닌 것: IS NOT NULL

JOIN

1. CROSS JOIN

  • 가능한 모든 경우의 수를 조합
  • 가로로 조인 / 5열 12행
SELECT *
FROM EMPLOYEES
CROSS JOIN DEPARTMENTS
;

>> 같은 ID로 부서 필터링 필요

 

2. INNER JOIN ~ ON

  • 매칭되는 데이터만 포함
  • 올바른 조인
SELECT *
FROM EMPLOYEES
JOIN DEPARTMENTS
ON EMPLOYEES.dept_id = DEPARTMENTS.id
;
  • 식별자 추가
SELECT EMPLOYEES.id, EMPLOYEES.name, DEPARTMENTS.name
FROM EMPLOYEES
JOIN DEPARTMENTS
ON EMPLOYEES.dept_id = DEPARTMENTS.id
;
  • 별칭 쓰기
SELECT E.id, E.name, D.name
FROM EMPLOYEES E
JOIN DEPARTMENTS D
ON E.dept_id = D.id
;
  • user_id가 1인 유저가 쓴 피드의 작성자명, 피드내용, 피드작성일 조회
SELECT U.username,P.content, p.creation_date
FROM POSTS P
JOIN USERS U
ON P.user_id = U.user_id
WHERE P.user_id = 1
;

 

3. OUTER JOIN: 데이터 매칭 안 되어도 다 보여줌

 

1) 이너 조인 단점: 배달 예시 / 배달 주문 안 한 회원을 찾을 수 없음

 

2) LEFT (OUTER) JOIN

  • 왼쪽 차집합 +오른쪽과 교집합
SELECT U.user_id, U.username, UP.bio
FROM USERS U
         LEFT OUTER JOIN USER_PROFILES UP
                         ON U.user_id = UP.user_id
ORDER BY U.user_id
  • 프로필 안 쓴 애만 찾기
SELECT U.user_id, U.username, UP.bio
FROM USERS U
         LEFT OUTER JOIN USER_PROFILES UP
                         ON U.user_id = UP.user_id
WHERE UP.bio IS NULL
ORDER BY U.user_id
;

 

4. RIGHT와 FULL

  • RIGHT OUTER JOIN: 오른쪽 데이터 포함
  • FULL OUTER JOIN: 양쪽 테이블 모든 데이터 포함
    명령어 지원x : LEFT JOIN과 RIGHT JOIN을 UNION으로 합치는 방식
SELECT * FROM USERS u LEFT JOIN USER_PROFILES p ON u.user_id = p.user_id
UNION
SELECT * FROM USERS u RIGHT JOIN USER_PROFILES p ON u.user_id = p.user_id;

 

5. SELF JOIN

  • 테이블이 자기 자신과 조인: 테이블 안에 계층적인 관계
  • EX) 팔로우 = USERS 2번 조인
SELECT U.user_id, U.username, U.manager_id, M.username
FROM USERS U
JOIN USERS M
ON U.manager_id = M.user_id
;

Subquery절

1. 단일 행 서브쿼리

  • 오직 하나의 행, 하나의 컬럼 (즉, 하나의 값)
  • 예시) 우리는 '라이언'의 username은 알지만 user_id는 모르는 상황입니다. '라이언'이 작성한 모든 게시물을 찾아주세요
SELECT
    post_id,
    content
FROM
    POSTS
WHERE
    user_id = (SELECT user_id      -- 1. 이 서브쿼리가 먼저 실행됩니다.
               FROM USERS
               WHERE username = 'ryan'); -- 결과: 1 이라는 단일 값
  • 예시) 우리 서비스의 평균 조회수보다 더 높은 조회수를 기록한 게시물은 무엇?
SELECT
    post_id,
    view_count
FROM
    POSTS
WHERE
    view_count > (SELECT AVG(view_count) FROM POSTS); -- 1. 전체 게시물의 평균 조회수를 먼저 계산

 

2. 다중 행 서브쿼리

  • 실행 결과가 여러 개의 행
  •  IN: 목록에 있는 값 중 하나라도 일치하면 참
  • ANY: 목록에 있는 값 중 하나라도 조건을 만족하면 참 (예: `< ANY` -> 최댓값보다 작으면)
  • ALL: 목록에 있는 모든 값을 만족해야만 참 (예: `> ALL` -> 최댓값보다 크면)
  • 예시) '카카오프렌즈' 그룹에 속한 모든 사용자들의 ID 목록을 알려주기 > 그 ID를 가진 사용자들이 작성한 모든 게시물을 찾기
SELECT
    post_id,
    user_id,
    content
FROM
    POSTS
WHERE
    user_id IN (SELECT user_id            -- 1. 이 서브쿼리가 먼저 실행됩니다.
                FROM USERS
                WHERE manager_id = 1) -- 결과: (2, 3, 5, 7...) 이라는 여러 개의 값
;
  • 예시) '#포켓몬' 해시태그가 달린 모든 게시물의 내용을 보여주세요 = 중첩 서브쿼리
SELECT
    post_id,
    content
FROM
    POSTS
WHERE
    post_id IN (SELECT post_id -- 2. #포켓몬 태그가 달린 post_id 목록을 찾는다
                FROM POST_TAGS
                WHERE tag_id = (SELECT tag_id -- 1. '#포켓몬'의 tag_id를 먼저 찾는다
                                FROM HASHTAGS
                                WHERE tag_name = '#포켓몬'))
;

 

3. FROM절 서브쿼리: 인라인 뷰

  • 서브쿼리의 실행 결과는 쿼리가 실행되는 동안에만 존재하는 임시 가상 테이블
  • 예시) 모든 사용자의 username과 함께, 각 사용자가 작성한 총 게시물 수를 나란히 보여주세요
SELECT
    u.username,
    post_info.post_count -- 2. 인라인 뷰에서 계산된 '게시물 수' 컬럼
FROM
    USERS u
INNER JOIN
    (SELECT user_id, COUNT(*) AS post_count -- 1. '사용자별 게시물 수'라는 가상 테이블을 생성
     FROM POSTS
     GROUP BY user_id) post_info -- 반드시 별명(Alias)을 붙여줘야 합니다!
ON
    u.user_id = post_info.user_id; -- 3. USERS 테이블과 가상 테이블을 조인

 

4. SELECT절의 서브쿼리: 스칼라 서브쿼리

  • 하나의 값만 반환(단일 행, 단일 컬럼)
  • 모든 사용자의 username을 조회하면서, 바로 옆에 각 사용자가 작성한 총 게시물 수를 계산해서 보여주세요
SELECT
    u.username,
    (SELECT COUNT(*)
     FROM POSTS p
     WHERE p.user_id = u.user_id) AS "작성 게시물 수" -- 1. 바깥 쿼리의 u.user_id를 참조
FROM
    USERS u
;
  • 예시) 모든 게시물의 content와 함께, 각 게시물별 '좋아요' 수와 '댓글' 수를 각각 계산해서 보여주세요

- 방법 1: 스칼라 서브쿼리 사용 (간결하고 직관적인 방법)

SELECT
    p.post_id,
    p.content,
    (SELECT COUNT(*) FROM LIKES l WHERE l.post_id = p.post_id) AS "좋아요 수",
    (SELECT COUNT(*) FROM COMMENTS c WHERE c.post_id = p.post_id) AS "댓글 수"
FROM
    POSTS p
;

 

- 방법 2: JOIN과 인라인 뷰 사용

SELECT
    p.post_id,
    p.content,
    COALESCE(lc.like_count, 0) AS "좋아요 수",
    COALESCE(cc.comment_count, 0) AS "댓글 수"
FROM
    POSTS p
LEFT JOIN
    (SELECT post_id, COUNT(*) AS like_count FROM LIKES GROUP BY post_id) lc
    ON p.post_id = lc.post_id -- '좋아요 수' 요약 테이블을 조인
LEFT JOIN
    (SELECT post_id, COUNT(*) AS comment_count FROM COMMENTS GROUP BY post_id) cc
    ON p.post_id = cc.post_id -- '댓글 수' 요약 테이블을 조인
;

 

5. 비연관 서브쿼리 vs 연관 서브쿼리

 

1) 비연관 서브쿼리

  • 서브쿼리 독자적 실행 가능, 안쪽 먼저 실행 > 밖 실행
  • 예시)
SELECT *
FROM POSTS
WHERE user_id = (
    SELECT user_id
    FROM USERS
    WHERE username = 'ryan'
)
;

 

2) 연관 서브쿼리

  • 밖 > 안쪽 / 단독실행X
  • 메인쿼리 1번 실행당 서브쿼리도 1번씩 반복실행
  • 예시)
SELECT
    u.username,
    (
    SELECT COUNT(*)
    FROM POSTS p
    WHERE p.user_id = u.user_id) AS "작성 게시물 수" -- 1. 바깥 쿼리의 u.user_id를 참조
FROM
    USERS u

 

6. 존재 여부만 확인: EXISTS, NOT EXISTS

  • EXISTS 예시) 게시물을 한 번이라도 작성한 적이 있는 모든 사용자의 이름을 알려주세요
SELECT
    u.username
FROM
    USERS u
WHERE
    EXISTS (SELECT 1 -- SELECT 절에 무엇이 오든 상관없어요. 존재 여부만 체크!
            FROM POSTS p
            WHERE p.user_id = u.user_id); -- 바깥 쿼리의 사용자가 쓴 게시물이 있는지 확인
  • NOT EXISTS 예시) 가입은 했지만, 게시물은 단 한 개도 작성하지 않은 '유령 회원'을 찾아주세요
SELECT
    u.username
FROM
    USERS u
WHERE
    NOT EXISTS (SELECT 1
                FROM POSTS p
                WHERE p.user_id = u.user_id);

 

ORDER BY절

1. ORDER BY 기본

 

1) 오름차순 정렬: ASC > 기본값

  • 예시) USERS 테이블에서 모든 사용자를 가입일이 빠른 순서(오래된 순)로 정렬해서 보여주세요
-- registration_date를 기준으로 오름차순 정렬합니다.
-- ASC는 기본값이므로 생략할 수 있습니다.
SELECT
    username,
    registration_date
FROM
    USERS
ORDER BY
    registration_date ASC; -- 또는 registration_date; 라고만 써도 동일하게 동작

 

 

2) 내림차순 정렬: DESC

  • 예시) 인스타그램 피드처럼, POSTS 테이블의 게시물들을 가장 최신순으로 정렬해서 보여주세요
-- creation_date를 기준으로 내림차순 정렬합니다.
SELECT
    post_id,
    user_id,
    content,
    creation_date
FROM
    POSTS
ORDER BY
    creation_date DESC;

 

3) 여러 기준으로 정렬하기

  • 예시) 모든 게시물을 종류(post_type)별로 먼저 정렬하고, 만약 종류가 같다면 그 안에서는 최신순(creation_date 내림차순)으로 다시 정렬
-- 1차: post_type 오름차순, 2차: creation_date 내림차순으로 정렬
SELECT
    post_id,
    post_type,
    creation_date
FROM
    POSTS
ORDER BY
    post_type ASC,      -- 1차 정렬 기준 (ASC는 생략 가능)
    creation_date DESC; -- 2차 정렬 기준

 

2. ORDER BY 고급

 

1) 별명(Alias)으로 정렬

  • 각 사용자별로 게시물 수를 계산해서 '게시물수'라는 별명을 붙이고, 그 '게시물수'가 많은 순서대로 정렬
-- 5강에서 배운 GROUP BY를 활용해 사용자별 게시물 수를 구하고,
-- 그 결과(별명: post_count)를 기준으로 내림차순 정렬합니다.
SELECT
    user_id,
    COUNT(*) AS post_count
FROM
    POSTS
GROUP BY
    user_id
ORDER BY
    post_count DESC;

 

2) 컬럼 위치 번호로 정렬

  • 예시) USERS 테이블에서 username, email, registration_date를 순서대로 조회한 다음, 세 번째 컬럼인 registration_date를 기준으로 최신순 정렬
SELECT
    username,             -- 1번째 컬럼
    email,                -- 2번째 컬럼
    registration_date     -- 3번째 컬럼
FROM
    USERS
ORDER BY
    3 DESC; -- SELECT 절의 3번째 컬럼인 registration_date를 기준으로 내림차순 정렬

 

3) CASE 표현식으로 내 마음대로 정렬

  • 예시) 모든 게시물을 조회하는데, 다른 건 다 최신순으로 정렬하되 '라이언(user_id=1)'이 작성한 게시물만 무조건 가장 위로 올려서 보여주세요
-- user_id가 1이면 1순위, 나머지는 2순위로 정렬 우선순위를 부여하고,
-- 같은 순위 내에서는 creation_date를 기준으로 내림차순 정렬합니다.
SELECT
    post_id,
    user_id,
    content,
    creation_date
FROM
    POSTS
ORDER BY
    CASE
        WHEN user_id = 1 THEN 1 -- user_id가 1이면 1순위
        ELSE 2                   -- 나머지는 2순위
    END, -- 1차 정렬 기준: CASE 표현식
    creation_date DESC; -- 2차 정렬 기준: 작성일