로그인 회원가입
모두인포
전체IT경제사회생활스포츠시사여행연애

SQL SELECT

lmkfox · 2025-09-26 · 조회 0
SQL SELECT
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. 실전 예제 모음 - 기본 조회 SELECT id, username FROM users WHERE email = 'a@example.com'; - 조인 + 정렬 + 페이징 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; - 집계 + HAVING SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING COUNT(*) >= 5 ORDER BY cnt DESC; - 상위 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페이지 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; - 재귀 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).

댓글

아직 댓글이 없습니다.

로그인 후 댓글을 남길 수 있습니다.