
1. 핵심 모델 : 두 방향의 자르기

SQL의 기본 조회는 2차원 표를 두 방향으로 자르는 것입니다.
| 절 | 하는일 | 방향 | 관계대수 용어 |
| SELECT | 어떤 컬럼을 볼까 | 세로 ↕ | projection (투영) |
| WHERE | 어떤 행을 남길까 | 가로 ↔ | selection (선택) |
SELECT stn, wrn_cd, tm_ef -- ← 세로: 컬럼 선택
FROM tb_xxx
WHERE stn = 108; -- ← 가로: 행 필터링
🔬 실험 1 : 두 방향을 분리해서 체감하기
-- ① 전부
SELECT * FROM tb_xxx LIMIT 5;
-- ② 세로만 자르기: 컬럼 3개, 행은 전부 대상
SELECT stn, wrn_cd, tm_ef FROM tb_xxx LIMIT 5;
-- ③ 가로만 자르기: 컬럼 전부, 행은 조건에 맞는 것만
SELECT * FROM tb_xxx WHERE stn = 108 LIMIT 5;
-- ④ 둘 다
SELECT stn, wrn_cd, tm_ef FROM tb_xxx WHERE stn = 108 LIMIT 5;
- ②는 화면 너비가 줄어듭니다
- ③은 결과 행 수가 줄어듭니다
이 차이를 눈으로 확인하는 게 실험의 목적입니다.
⚠️ LIMIT은 습관으로 붙이세요.
실습 대상 테이블이 12MB만 되어도 SELECT *는 결과를 받아오는 데 한참 걸립니다. 실무 테이블은 수 GB인 경우가 흔합니다.
2. 함정 ① : =를 SELECT 자리에 쓰면?
SELECT stn = 108 FROM tb_xxx LIMIT 5;
에러가 날 것 같지만 정상 실행됩니다. 대신 결과가 이렇습니다.
?column?
----------
f
f
t
f
t
행이 전혀 걸러지지 않았습니다.
❓왜 이렇게 되는가
=는 boolean을 반환하는 연산자입니다.
- SELECT 절은 "각 행마다 이 식을 계산해서 보여줘"라는 뜻
- 그래서 stn = 108의 계산 결과(참/거짓) 가 하나의 컬럼으로 출력됨
- WHERE가 특별한 이유는, 그 boolean이 참인 행만 통과시키는 자리이기 때문
컬럼명이 ?column?으로 나오는 건 이름 붙일 근거가 없어서입니다. AS로 지정할 수 있습니다.
- 교훈: "어떤 컬럼을 보여줄까"와 "어떤 행을 남길까"는 완전히 다른 일입니다.
3. 함정 ② : 큰따옴표와 작은따옴표
SELECT * FROM tb_xxx WHERE wrn_cd = "W"; -- ✗ 에러
SELECT * FROM tb_xxx WHERE wrn_cd = 'W'; -- ✓ 정상
큰따옴표 버전의 에러
ERROR: column "W" does not exist
"값이 없다"가 아니라 "컬럼이 없다" 고 나오는 게 힌트입니다.
따옴표 역할 예시
| 따옴표 | 역할 | 예시 |
| '작은따옴표' | 문자열ㅇ 리터럴 (값) | WHERE name = '홍길동' |
| "큰따옴표" | 식별자 (테이블명, 컬럼명) | SELECT "MyColumn" FROM "MyTable" |
SQL 표준이 정한 규칙이고, PostgreSQL은 이를 엄격히 따릅니다.
(MySQL은 큰따옴표도 문자열로 봐주지만 이건 MySQL의 확장입니다)
💡 진단법: 에러 메시지가 "column"을 언급하는데 나는 값을 쓴 것 같다면 → 따옴표를 의심하세요.
문자열 안에 작은따옴표가 필요할 때
SELECT 'It''s SQL' AS 방법1; -- 작은따옴표 2번 (표준)
SELECT $$It's SQL$$ AS 방법2; -- 달러 인용 (PostgreSQL 확장)
4. 함정 ③ : 쉼표 하나가 컬럼을 삼킨다
이게 가장 잡기 어려운 버그입니다. 에러가 안 나고 결과만 조용히 틀립니다.

-- 쉼표 누락
SELECT grp_cd_id, cd_id, cd_nm ordr
FROM public.tb_cd
WHERE del_yn = 'N';
- 결과: 컬럼이 4개가 아니라 3개만 나오고, 3번째 헤더가 ordr로 찍힙니다.
왜 에러가 안 나는가
SQL에서 AS는 생략 가능합니다.
SELECT cd_nm AS ordr -- 명시적
SELECT cd_nm ordr -- AS 생략 (동일한 의미)
PostgreSQL은 cd_nm ordr를 cd_nm AS ordr로 해석했습니다. 즉 cd_nm 컬럼에 ordr이라는 별명을 붙인 것이지, ordr 컬럼을 조회한 게 아닙니다.
🔬 실험 2 : 두 결과를 나란히 비교
-- 잘못된 버전
SELECT grp_cd_id, cd_id, cd_nm ordr FROM public.tb_cd LIMIT 5;
-- 올바른 버전
SELECT grp_cd_id, cd_id, cd_nm, ordr FROM public.tb_cd LIMIT 5;
버전 컬럼 수 ordr 열의 내용
| 버전 | 컬럼수 | ordr열의 내용 |
| 쉼표 누락 | 3개 | 한글 텍스트 (실은 cd_nm 값) |
| 정상 | 4개 | 숫자 (진짜 ordr 값) |
쉼표 하나가 결과를 바꿉니다. 데이터 타입이 예상과 다르면 이걸 의심하세요.
💡 예방책: 별칭에는 항상 AS를 명시하세요. 그러면 AS가 없는 곳은 전부 쉼표 누락입니다.
5. DISTINCT : 값의 종류 파악하기

조건을 쓰려면 어떤 값이 들어있는지 알아야 합니다.
SELECT DISTINCT wrn_cd FROM tb_xxx;
중복을 제거하고 존재하는 값의 종류만 보여줍니다.
대용량 테이블에서는 느립니다. 전체를 훑어야 하기 때문이죠. 맛보기용 대안:
SELECT DISTINCT wrn_cd FROM (SELECT wrn_cd FROM tb_xxx LIMIT 1000) t;
여러 컬럼을 함께 쓰면 조합 기준으로 중복을 제거합니다.
SELECT DISTINCT grp_cd_id, grp_cd_nm FROM public.tb_cd ORDER BY 1;
6. LIKE와 와일드카드
이름을 정확히 모를 때 씁니다.
패턴 의미 매칭 예
| 패턴 | 의미 | 매칭 예시 |
| 'warning%' | warning으로 시작 | warning_log ✓ / tb_warning ✗ |
| '%warning' | warning으로 끝 | tb_warning ✓ / warning_log ✗ |
| '%warning%' | warning 포함 | 둘 다 ✓ |
| '_arning' | _는 정확히 1글자 | warning ✓ / xxarning ✗ |
🔬 실험 3 : % 위치에 따른 결과 차이
SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'warning%';
SELECT table_name FROM information_schema.tables WHERE table_name LIKE '%warning';
SELECT table_name FROM information_schema.tables WHERE table_name LIKE '%warning%';
제 환경의 테이블은 전부 tb_로 시작하므로 첫 번째는 0건이 나왔습니다. %를 어디 붙이느냐가 결과를 통째로 바꿉니다.
대소문자 무시 검색
SELECT * FROM t WHERE name ILIKE '%kim%'; -- ILIKE (PostgreSQL 확장)
SELECT * FROM t WHERE lower(name) LIKE '%kim%'; -- 표준 방식
ILIKE는 PostgreSQL 확장이라 이식성이 없습니다.
7. ORDER BY : 정렬의 함정
컬럼 순번으로 지정하기

SELECT table_schema, table_name FROM information_schema.tables
ORDER BY 1, 2;
숫자 1, 2는 SELECT에 적은 컬럼의 순번입니다. 다음과 완전히 동일합니다.
ORDER BY table_schema, table_name;
🔬 실험 4 : 정렬 기준 순서 바꾸기

ORDER BY 1, 2; -- 스키마로 먼저 묶고, 그 안에서 이름순
ORDER BY 2, 1; -- 이름순으로 먼저, 스키마는 섞임
정렬 기준의 순서가 결과 모양을 결정합니다.
🔬 실험 5 : 왜 대문자가 전부 앞에 오는가
ORDER BY 2, 1을 실행하니 LSM... 같은 대문자 테이블명이 전부 앞으로 몰렸습니다.

SELECT 'Z' < 'a' AS 대문자Z가_소문자a보다_앞인가;
- 결과: true
알파벳 순으로는 a가 Z보다 앞일 것 같지만, DB는 알파벳이 아니라 문자 코드값으로 비교합니다.
문자
| 문자 | ASCII 코드 |
| A ~ Z | 65 ~ 90 |
| a ~ z | 97 ~ 122 |
대문자가 전부 앞선다는 뜻입니다.
해결 : 대소문자 무시 정렬
SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY lower(table_name), table_schema;
lower()로 소문자로 통일해 비교하면 LSM...이 l 위치로 이동합니다.
📌 부수적 발견: 이 정렬 결과에서 "이 DB에 대문자 테이블명이 존재한다"는 사실을 알게 됐습니다. 이 테이블들은 조회 시 반드시 큰따옴표가 필요합니다.
NULL의 정렬 위치
SELECT unnest(ARRAY[3, 1, NULL, 2]) AS v ORDER BY v;
PostgreSQL은 오름차순에서 NULL을 마지막에 둡니다. 명시적으로 바꿀 수 있습니다.
ORDER BY v NULLS FIRST;
ORDER BY v DESC NULLS LAST;
8. 보너스 : DBeaver 실행 단축키
여러 쿼리를 써놓고 실행했는데 결과가 하나만 나온 적이 있다면 이것 때문입니다.
| 단축키 | 동작 |
| Ctrl + Enter | 커서가 있는 한 문장만 실행 |
| Alt + X | 스크립트 전체 실행 (결과 탭 여러 개 생성) |
select count(*) from public.tb_cd; -- 커서가 여기 없으면 실행 안 됨
select count(*) from public.tb_cd where del_yn = 'N'; -- 이것만 실행됨
9. 정리
개념 요점
| 개념 | 요점 |
| SELECT | 세로 자르기 (projection). 컬럼 선택 |
| WHERE | 가로 자르기 (selection). 행 필터링 |
| = | boolean 반환 연산자. SELECT에 쓰면 값이 출력될 뿐 |
| '...' | 문자열 리터럴 (값) |
| "..." | 식별자 (테이블/컬럼명) |
| 쉼표 누락 | AS 생략으로 해석 → 에러 없이 컬럼이 사라짐 |
| DISTINCT | 중복 제거. 여러 컬럼이면 조합 기준 |
| LIKE / % / _ | 부분 일치 검색 |
| ORDER BY 1,2 | 컬럼 순번 정렬 |
| 문자 정렬 | 알파벳순이 아닌 코드값순. 대문자 우선 |
| lower() | 대소문자 무시 정렬 |
'개발 및 기타 등등 > Database' 카테고리의 다른 글
| DBeaver에서 PostgreSQL 쿼리문 작성해보고 relation does not exist 에러경험하기 (0) | 2026.09.04 |
|---|---|
| DataBase 기본(Postg데이터베이스 구조와 메타데이터 읽기 (0) | 2026.09.03 |
| PostgreSQL 기본 개념과 SQL에 대한 기본지식 파헤치기! (0) | 2026.09.02 |
| DataBase(DB) PostgreSQL + DBeaver 실습하기! [Query(쿼리)문 작성해보기 WHERE로 데이터 더 많이 추가해보기, DB처리순서, NULL제약조건] (1) | 2026.07.19 |
| DataBase(DB) PostgreSQL + DBeaver 실습하기! [Query(쿼리)문 작성해보기 INSERT, WHERE, SELECT] (0) | 2026.07.18 |