오늘은 Java 없이... 무호흡 DQL 개념정리 및 실습을 진행했다.
과목이 바뀌니까 환기도 되고 좋은 것 같다.
기억이 날라가기 전에 기록으로 정리해본다.
관계형 데이터베이스(RDBMS)란?
관계형 데이터베이스는
엑셀 표(Table)를 아주 엄격한 규칙으로 관리하는 데이터베이스라고 생각하면 이해하기 쉽다.
- 데이터는 항상 표 형태로 저장되고
- 각 표는 명확한 구조(스키마)를 가지며
- 테이블 간의 관계(Relation)를 통해 데이터를 연결한다
이 “엄격함” 덕분에
데이터의 무결성과 일관성을 강하게 보장할 수 있다.
관계형 데이터베이스의 주요 특징
1. 데이터 무결성 (Integrity)
데이터가 깨지지 않고, 논리적으로 말이 되는 상태를 유지하는 것
- DB 차원에서 강하게 보장됨
- 잘못된 데이터 자체가 들어오지 못하게 막는 규칙
예시:
- 주문 테이블의 member_id는
반드시 회원 테이블에 실제로 존재하는 회원 ID여야 한다 - 존재하지 않는 회원으로 주문을 만들 수 없음
이런 제약이 있어야 비즈니스 로직 자체가 성립한다.
2. 데이터 일관성 (Consistency)
어디에서 조회하든, 같은 데이터는 항상 같은 값을 가져야 함
- 시스템 전체에서 데이터가 논리적으로 모순되지 않는 상태
- DB 제약 + 애플리케이션 로직이 함께 보장
예시:
- 계좌 잔액이 10,000원
- 3,000원 출금
- 이후 모든 화면 / 조회 지점에서 7,000원으로 보여야 함
저장된 규칙과 제약 조건이
항상 지켜지는 상태가 바로 데이터 일관성이다.
3. 정형화된 데이터 구조 (Schema)
- 테이블의 컬럼 구조가 미리 정해져 있음
- 각 컬럼은 타입과 제약 조건을 가짐
id INT
name VARCHAR(50)
created_at DATETIME
- 새로운 컬럼을 추가하거나 구조를 바꾸려면
스키마 변경 작업이 필요하다
👉 대신, 구조가 명확해서
데이터 해석이 일관되고 안정적이다.
4. SQL 지원
- 관계형 DB는 SQL(Structured Query Language)을 사용한다
- 데이터 조회(DQL), 삽입/수정(DML), 구조 변경(DDL)을
명확한 문법으로 처리
👉 오늘 실습의 중심이 바로 이 SQL(DQL)
5. 보안과 권한 제어
- 사용자별 접근 권한 제어 가능
- 읽기 / 쓰기 / 수정 권한을 세밀하게 분리
금융, 결제, ERP 같은
신뢰성이 중요한 시스템에서 필수적인 요소
NoSQL 데이터베이스란?
NoSQL은
형식에 덜 얽매이고, 빠르고 유연하게 저장하는 데이터베이스다.
관계형 DB가 “엄격한 표”라면,
NoSQL은 “상황에 맞게 구조가 달라도 되는 저장소”에 가깝다.
NoSQL의 주요 특징
1. 비정형 / 유연한 데이터 구조
- 미리 정해진 테이블 구조가 필수는 아님
- 데이터마다 필드 수가 달라도 허용
{ "id": 1, "message": "hello" }
{ "id": 2, "message": "hi", "emoji": "😊" }
빠른 개발과 변경에 유리
2. 분산 처리 환경
- 한 대의 서버가 아닌 여러 서버가 나눠서 처리
- 일부 서버 장애가 나도 전체 서비스는 계속 동작
3. 높은 가용성
- 장애에 강함
- 일부 노드가 죽어도 다른 노드가 요청 처리
👉 실시간 서비스에 적합
4. 수평적 확장성
- 서버를 “더 좋은 서버”로 바꾸는 대신(수직적 확장)
- 서버를 여러 대로 늘리는 방식
트래픽 증가 → 서버 한 대 추가
👉 NoSQL은 설계 자체가
확장성을 전제로 만들어짐
관계형 DB vs NoSQL 정리
구분관계형 DBNoSQL
| 데이터 구조 | 엄격한 표 구조 | 자유로운 구조 |
| 무결성 / 일관성 | 매우 강함 | 상대적으로 약함 |
| 확장 방식 | 수직 확장 (서버 성능 ↑) | 수평 확장 (서버 수 ↑) |
| 트랜잭션 | 강력함 | 제한적 |
| 대표 사용처 | 금융, 결제, ERP | 로그, 채팅, SNS, 실시간 데이터 |
2-0. DQL (Data Query Language)
데이터 검색 기능에 사용하는 SQL
2-1. NULL은 “값이 없다” ≠ “비교 가능하다”
❌ 잘못된 비교
WHERE comm = NULL;
- NULL은 값이 아니라 상태
- 어떤 연산에서도 = 비교가 성립하지 않음
⭕ 올바른 비교
WHERE comm IS NULL;
WHERE comm IS NOT NULL;
📌 NULL은 항상 IS / IS NOT으로 비교
2-2. NULL이 연산에 미치는 영향 (연봉 계산)
sal * 12 + comm
- comm이 NULL이면 결과 전체가 NULL
- 연산 자체가 무효 처리됨
해결 방법 1️⃣ IFNULL (MySQL)
sal * 12 + IFNULL(comm, 0)
해결 방법 2️⃣ COALESCE (표준 SQL, 권장)
sal * 12 + COALESCE(comm, 0)
📌 COALESCE(a, b, c, …)
→ 왼쪽부터 순서대로 NULL 아닌 첫 값 반환
2-3. IN 연산자 + NULL의 함정 ⚠️
기본 IN
WHERE comm IN (300, 500, 1400);
NOT IN의 문제점
WHERE comm NOT IN (300, 500, 1400);
- comm이 NULL이면 결과에서 제외됨
- “조건에 안 걸리는 게 아니라, 비교 자체가 안 됨”
NULL까지 포함하고 싶다면
WHERE comm NOT IN (300, 500, 1400)
OR comm IS NULL;
📌 NOT IN 사용할 때는 항상 NULL 존재 여부를 의식해야 한다
2-4. BETWEEN은 “이상 / 이하” 포함이다
WHERE sal BETWEEN 2975 AND 3000;
= 👇 완전히 동일
WHERE sal >= 2975 AND sal <= 3000;
📌 날짜에도 동일하게 적용됨
WHERE hiredate BETWEEN '1981-01-01' AND '1981-12-31';
2-5. LIKE 패턴은 자리 수를 정확히 구분한다
패턴의미
| M% | M으로 시작 |
| M_ | M + 딱 한 글자 |
| _M% | 두 번째 글자가 M |
| %M% | M 포함 |
WHERE ename LIKE 'M_';
→ 두 글자 이름만 검색
2-6. 대소문자 구분: BINARY 키워드 (중요)
WHERE ename = 'smith';
- MySQL 기본 설정에서는 대소문자 구분 안 됨
대소문자 구분 강제
WHERE BINARY ename = 'SMITH';
📌 BINARY는 문자열을 바이트 단위로 비교
- 'SMITH' ≠ 'smith'
왜 이런 일이 생길까?
SHOW VARIABLES LIKE 'lower_case_table_names';
- OS / 설정에 따라 문자열 비교 방식이 달라짐
- 그래서 대소문자 민감한 비교는 BINARY로 명시하는 게 안전
2-7. ORDER BY에서 헷갈리는 디테일들
ORDER BY는 SELECT 이후에 실행된다
SELECT empno AS 사번
FROM emp
ORDER BY 사번 DESC;
📌 별칭(alias) 사용 가능
NULL 정렬 위치
ORDER BY comm ASC; -- NULL 먼저
ORDER BY comm DESC; -- NULL 나중
명시적으로 제어하기
ORDER BY (comm IS NULL) ASC, comm ASC;
- (comm IS NULL) → NULL이면 1, 아니면 0
- 결과적으로 NULL을 뒤로 보냄
ORDER BY 컬럼 인덱스 (비권장)
ORDER BY 3 ASC, 2 DESC;
- 동작은 하지만 가독성 최악
- 실무에선 거의 안 씀
2-8. AND / OR 연산자 우선순위 ⚠️
기본 우선순위
AND > OR
헷갈리기 쉬운 예제
WHERE deptno = 10 OR deptno = 20 AND job = 'CLERK';
👉 실제 해석:
deptno = 10
OR (deptno = 20 AND job = 'CLERK')
의도가 이거라면?
WHERE (deptno = 10 OR deptno = 20)
AND job = 'CLERK';
📌 OR가 있으면 무조건 괄호부터 고민
3-0. 단일행 함수 vs 그룹 함수
- 단일행 함수: 입력 행 1개 → 출력 1개
예) ABS(), ROUND(), SUBSTRING(), DATE_ADD(), IF(), CONCAT() - 그룹 함수(집계 함수): 여러 행 → 결과 1개(또는 그룹별 1개)
예) COUNT(), SUM(), AVG(), MAX(), MIN()
3-1. 숫자 함수
ROUND vs TRUNCATE(버림)
SELECT ROUND(12.345, 2); -- 12.35 (반올림)
SELECT TRUNCATE(12.345, 2); -- 12.34 (버림)
- ROUND(x, -1) : 1의 자리에서 반올림 → 10단위로
- TRUNCATE(x, -1) : 1의 자리 버림 → 10단위로
MOD / % (홀짝, 주기, 그룹 분기)
WHERE MOD(empno, 2) = 1;
- 홀짝 / “N개 단위로 묶기” 할 때 자주 씀.
3-2. 문자열 함수
LENGTH vs CHAR_LENGTH (한글/멀티바이트 주의)
SELECT LENGTH('가나다'); -- 바이트 길이(환경에 따라 9 등)
SELECT CHAR_LENGTH('가나다'); -- 문자 개수(3)
- LENGTH는 바이트 기준
- CHAR_LENGTH는 문자 수 기준
📌 “글자 수 제한” 같은 로직이면 CHAR_LENGTH가 더 안전.
SUBSTRING으로 날짜에서 월 뽑기 vs 날짜 함수
WHERE SUBSTRING(hiredate, 6, 2) = '02';
날짜 함수로 더 안정적으로 바꾼다면
WHERE MONTH(hiredate) = 2;
TRIM은 공백(space) 중심
- TRIM()은 보통 앞뒤 공백 제거,
- 탭/개행까지 완벽히 제거하려면 별도 처리(예: REPLACE)가 필요
3-3. 날짜 함수: NOW vs SYSDATE 차이
NOW()
- 쿼리 시작 시점의 시간을 고정해서 씀
- created_at, updated_at, 로그 기록 등에 안정적
SELECT NOW(), SLEEP(2), NOW(); -- NOW 값이 동일하게 나옴
SYSDATE()
- 함수 호출 시점의 현재 시간
- 서버 상태 체크 같은 “진짜 지금”을 볼 때 사용
SELECT SYSDATE(), SLEEP(2), SYSDATE(); -- 값이 달라짐
📌 “같은 쿼리에서 시간이 바뀌면 안 된다” → NOW
📌 “호출 순간의 현재시간이 중요” → SYSDATE
3-4. Date vs Datetime: ‘하루 차이’ 함정
SELECT DATEDIFF('2026-02-05 00:00:00', '2026-02-04 23:59:59'); -- 1
- DATEDIFF는 시간을 무시하고 날짜 단위로 계산
시간까지 포함해서 정확히 계산하려면:
SELECT TIMESTAMPDIFF(DAY, '2026-02-04 00:00:00', '2026-02-04 23:59:59'); -- 0
📌 날짜 필터링도 이 이유로 “범위 조건”이 안전함:
WHERE hiredate >= '2026-02-04' AND hiredate < '2026-02-05'
3-5. 형변환: MySQL의 “암묵적 변환”은 편하지만 위험
SELECT '123abc' + 1; -- 124
SELECT 'abc123' + 1; -- 1 (앞이 숫자가 아니면 0 취급)
암묵적 변환에 기대면 잘못된 값이 나올 수 있다.
→ CAST/CONVERT로 의도를 명시하는 게 안전.
CAST의 핵심 용도
- 문자열 → 숫자(계산)
- 문자열 → 날짜(필터)
- 숫자 → 문자열(출력)
SELECT CAST('123' AS UNSIGNED);
SELECT CAST('20260204' AS DATE);
SELECT CAST(NOW() AS CHAR);
DATE_FORMAT / STR_TO_DATE
- DATE_FORMAT: 날짜 → 문자열(표시 목적)
- STR_TO_DATE: 문자열 → 날짜(파싱 목적)
3-6. 제어 흐름: IF / COALESCE
SELECT IF(sal >= 2500, '고급여', '저급여') AS 등급
FROM emp;
- 간단한 라벨링/파생컬럼에 많이 사용
COALESCE는 “NULL 보정”에서 사실상 필수:
COALESCE(comm, 0)
3-7. 문자열 조작: CONCAT / REPLACE
SELECT CONCAT(ename, '(', job, ')') FROM emp;
SELECT REPLACE(job, 'MAN', 'PERSON') FROM emp;
📌 출력 포맷 만들 때 유용
3-8. GROUP BY + HAVING: 실행 순서가 핵심
실행 순서:
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
WHERE vs HAVING
- WHERE: 그룹 만들기 전 행(row) 필터
- HAVING: 그룹 만든 후 그룹(집계 결과) 필터
✅ 예: “부서별 평균 급여가 2000 이상”은 HAVING
SELECT deptno, AVG(sal)
FROM emp
GROUP BY deptno
HAVING AVG(sal) >= 2000;
COUNT(comm)가 4가 되는 이유 (NULL 자동 제외)
SELECT COUNT(comm) FROM emp; -- NULL 제외
- COUNT(*) : 행 수(전부)
- COUNT(col) : NULL 제외한 개수
3-9. alias(별칭) + ORDER BY: 백틱을 사용하자
SELECT deptno, SUM(sal), AVG(sal) AS `평균급여`
FROM emp
GROUP BY deptno
ORDER BY `평균급여` ASC;
-- ORDER BY '평균급여' ASC; -> 정상동작 X
-- ORDER BY 평균급여 ASC; -> 정상동작 O (비권장)
-- ORDER BY `평균급여` ASC; --> 정상동작 O + 공백처리 O (권장)
📌 별칭에 공백/한글 넣으면 백틱이 필요해지니까, 실무에선 보통 avg_sal처럼 씀.
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 13 - DML, DDL, DCL, TCL (0) | 2026.02.08 |
|---|---|
| [멋사 클라우드 5기] Day 12 - Join, Subquery, Union (0) | 2026.02.06 |
| [멋사 클라우드 5기] Day 10 - 객체 다루기, DBMS 기초(1) (0) | 2026.02.04 |
| [멋사 클라우드 5기] Day 9 - Enum 활용, BigNumber, Lombok, 객체 설계 (1) | 2026.02.03 |
| [멋사 클라우드 5기] Day 8 - 입출력, 직렬화, 스레드, Enum (0) | 2026.02.02 |
