IMG-LOGO
공지사항 :

SQL  SELECT

lmkfox - 2025-09-26 06:41:01 8 Views 0 Comment

1. 전체 문법(개요) — 실행 논리 순서

SQL의 문법적 순서는 다음과 같지만, DBMS가 실제로 실행하는 내부 처리 순서는 약간 다릅니다(예: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT 순으로 처리된다고 이해하면 됩니다).

SELECT [DISTINCT] select_list
FROM table_source
[JOIN ...]
[WHERE predicate]
[GROUP BY grouping_columns]
[HAVING group_predicate]
[WINDOW ...]        -- 일부 DB에서 사용
[ORDER BY ...]
[LIMIT n [OFFSET m] | FETCH FIRST n ROWS ONLY]


2. SELECT 절 (컬럼·표현식)

  • 컬럼, 리터럴, 함수, 표현식을 지정할 수 있음.

  • 컬럼별 별칭: expr AS alias 또는 expr alias

SELECT id, username AS name, CONCAT(first_name, ' ', last_name) full_name
FROM users;

  • *(와일드카드): 모든 컬럼 선택 — 디버깅 외에는 사용 지양(네트워크·파싱 비용, 명확성 문제).

  • DISTINCT: 중복 제거

SELECT DISTINCT country FROM users;

  • TOP(SQL Server), LIMIT(MySQL/Postgres) 차이 존재 — 아래 LIMIT 참고.


3. FROM 절과 테이블 소스

  • 테이블, 뷰, 파생 테이블(서브쿼리), CTE(공통표현식 WITH) 사용 가능.

  • 테이블 별칭 권장:

FROM users u
JOIN orders o ON u.id = o.user_id

  • 파생 테이블(derived table):

SELECT t.user_id, t.cnt FROM (
  SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id
) t WHERE t.cnt > 10;


4. JOIN(조인) — 종류와 사용

  • INNER JOIN: 일치하는 행만

  • LEFT (OUTER) JOIN: 왼쪽 테이블의 모든 행 + 일치하는 오른쪽 행(없으면 NULL)

  • RIGHT (OUTER) JOIN: 오른쪽 우선(왼쪽과 반대)

  • FULL OUTER JOIN: 양쪽 모두(모든 RDBMS가 지원하지 않음; MySQL 과거 버전은 미지원—UNION으로 대체)

  • CROSS JOIN: 카티시안 곱

  • 예:

SELECT u.id, u.username, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

  • 조인 성능 핵심: 조인 키에 적절한 인덱스가 있어야 효율적. 조인 순서와 인덱스 사용을 EXPLAIN으로 확인.


5. WHERE 절 — 필터링과 SARGability

  • WHERE는 행 필터링. 인덱스 사용 가능 여부(= sargable)에 크게 좌우됩니다.

  • 인덱스 활용을 방해하는 패턴:

    • 컬럼에 함수 적용: WHERE LOWER(name) = 'kim' (인덱스 미사용) — 대신 WHERE name = 'Kim' COLLATE ... 또는 function-based index 사용(지원 시).

    • 선행 와일드카드: WHERE col LIKE '%abc' — 인덱스 무효, LIKE 'abc%'는 인덱스 사용 가능.

  • NULL 처리: IS NULL, IS NOT NULL 사용.


6. GROUP BY / HAVING — 집계

  • GROUP BY는 집계 단위(집단)를 만들고, HAVING은 그룹 필터(집계 결과 기반)를 수행.

  • 예:

SELECT user_id, COUNT(*) AS cnt
FROM orders
WHERE status = 'paid'
GROUP BY user_id
HAVING COUNT(*) >= 10;

  • MySQL 주의: ONLY_FULL_GROUP_BY 모드가 꺼져 있으면 SELECT에 그룹화되지 않은 컬럼 사용 가능하지만(비결정적 결과) 권장하지 않음 — 정식 SQL은 그룹화되지 않은 컬럼의 사용을 허용하지 않습니다.


7. ORDER BY, LIMIT / OFFSET, 페이징

  • ORDER BY column [ASC|DESC]로 정렬.

  • LIMIT n OFFSET m (MySQL/Postgres) — OFFSET이 큰 경우 성능 저하(전체 스캔 후 건너뜀).

  • 키셋(무거운 OFFSET 회피) 페이징(권장):

SELECT * FROM orders WHERE user_id = ? AND created_at < ? ORDER BY created_at DESC LIMIT 20;

  • SQL Server: TOP n 또는 OFFSET ... FETCH.

  • MySQL 8+: FETCH FIRST n ROWS ONLY도 지원(표준적 표현).


8. 서브쿼리(내부 쿼리) — 유형과 차이

  • 스칼라 서브쿼리: 한 행·한 칼럼 반환 — SELECT (SELECT MAX(...))

  • 행 서브쿼리: (a,b) = (SELECT ..)

  • 테이블 서브쿼리(From에 사용): 파생테이블

  • 상관 서브쿼리(correlated): 외부 쿼리의 컬럼을 참조 — 실행 비용 높음

SELECT u.id, u.username,
  (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count
FROM users u;

  • EXISTS vs IN

    • EXISTS(SELECT 1 FROM t2 WHERE t2.x = t1.x) — 존재 여부 체크(일치하는 행 찾자마자 종료).

    • IN (SELECT col FROM t2) — 내부 결과를 집합으로 만든 뒤 비교; NULL/중복 처리 주의.

    • 성능은 데이터 분포·인덱스에 따라 달라짐. 보통 EXISTS가 유리할 때가 많음(특히 서브쿼리가 상관 서브쿼리일 때).


9. 윈도우 함수(Window functions)

  • PARTITION BY / ORDER BY와 함께 집계 아닌 행 단위의 누적/순위 계산 가능(예: ROW_NUMBER(), RANK(), SUM() OVER(...)).

  • 예: 사용자별 최신 3개 주문

SELECT *
FROM (
  SELECT o.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
  FROM orders o
) t
WHERE rn <= 3;

  • MySQL 8.0 이상, Postgres, Oracle 등 최근 RDBMS는 지원.


10. CTE(Common Table Expressions) — WITH 절

  • 가독성·재사용성 향상, 재귀 쿼리 가능(WITH RECURSIVE).

  • 재귀 CTE 예(조직도 ancestor 탐색):

WITH RECURSIVE mgrs AS (
  SELECT id, manager_id, name FROM employees WHERE id = 1
  UNION ALL
  SELECT e.id, e.manager_id, e.name
  FROM employees e JOIN mgrs m ON e.manager_id = m.id
)
SELECT * FROM mgrs;


11. 집합 연산(Set operations)

  • UNION (중복 제거), UNION ALL (중복 허용).

  • INTERSECT, EXCEPT는 DBMS마다 지원 여부가 다름(일부 버전의 MySQL은 최근에 추가). 사용 전 DB 버전 확인.


12. 성능(최적화) 핵심 포인트

  • 인덱스 설계: WHERE·JOIN·ORDER BY·GROUP BY에 자주 사용되는 컬럼에 인덱스.

    • 복합 인덱스는 좌측(left-most) 규칙을 따름.

    • 커버링 인덱스: 인덱스만으로 쿼리 해결 가능하면 매우 빠름(index-only scan).

  • EXPLAIN 사용: 쿼리 플랜(사용 인덱스, 예상 스캔 행수 등) 확인.

    • MySQL: EXPLAIN FORMAT=JSON SELECT ...

  • SARGability: 컬럼에 직접 조건을 걸어 인덱스를 활용하라(함수 적용·표현식은 인덱스 무효화 가능).

  • 대량 데이터 페이징: OFFSET 큰 페이징은 비효율 → 키셋 페이징 권장.

  • 통계와 옵티마이저: 최신 통계 없으면 비효율 플랜 선택 — ANALYZE TABLE 등으로 통계 갱신.

  • 쿼리 리팩토링: 복잡한 상관 서브쿼리를 JOIN으로 바꾸면 성능 개선될 때가 많음.


13. 동시성·락(잠금)

  • SELECT는 기본적으로 락을 걸지 않음(읽기).

  • FOR UPDATE: 행을 잠구고 이후 트랜잭션에서 수정할 때 사용(예: 잔액 처리).

START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

  • LOCK IN SHARE MODE(MySQL) / FOR SHARE(다른 DB) 등 공유락 옵션 존재.

  • SKIP LOCKED로 이미 잠긴 행을 건너뛸 수 있음(작업 분산 시 유용).


14. 보안 — SQL 인젝션 방지

  • 절대 문자열 연결로 쿼리 만들지 말고 Prepared Statements / Parameterized queries 사용.

  • 최소 권한 원칙: DB 사용자 계정에 필요한 최소 권한만 부여.


15. 실전 예제 모음

  1. 기본 조회

SELECT id, username FROM users WHERE email = 'a@example.com';

  1. 조인 + 정렬 + 페이징

SELECT u.id, u.username, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.active = 1
ORDER BY o.created_at DESC
LIMIT 20 OFFSET 0;

  1. 집계 + HAVING

SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5
ORDER BY cnt DESC;

  1. 상위 N개 그룹별(윈도우)

SELECT user_id, order_id, total
FROM (
  SELECT user_id, id AS order_id, total,
    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
  FROM orders
) t
WHERE rn <= 3;

  1. 키셋 페이징(권장)

-- 1페이지
SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC LIMIT 20;

-- 다음 페이지(마지막 created_at 값이 last_ts라면)
SELECT * FROM orders WHERE user_id = 123 AND created_at < :last_ts ORDER BY created_at DESC LIMIT 20;

  1. 재귀 CTE(예: 트리 구조)

WITH RECURSIVE subtree AS (
  SELECT id, parent_id, name FROM categories WHERE id = 2
  UNION ALL
  SELECT c.id, c.parent_id, c.name FROM categories c JOIN subtree s ON c.parent_id = s.id
)
SELECT * FROM subtree;


16. 디버깅 도구

  • EXPLAIN / EXPLAIN ANALYZE(DBMS 지원 시)

  • slow query log(느린 쿼리 로그)

  • performance_schema / pg_stat_statements 등 통계 뷰

  • 프로파일링(애플리케이션 레벨) — 쿼리 실행 시간 측정


17. 모범 사례 체크리스트

  • SELECT * 지양 — 필요한 컬럼만 선택.

  • 인덱스는 쿼리 패턴 기반으로 설계.

  • 복잡한 로직은 CTE로 분리해 가독성 확보.

  • 큰 OFFSET 페이지는 피하고 키셋 페이징 사용.

  • 서브쿼리 vs JOIN 성능 비교 — EXPLAIN으로 확인.

  • 항상 파라미터화하여 SQL 인젝션 방지.

  • 쿼리는 작고 명확하게, 필요시 캐싱(use Redis, memcached).


댓글