SELECT 기본기: 산술연산자·ALIAS·중복 제거
테이블을 살펴보는 DESC·COLUMN부터 대소문자와 파싱의 관계, SELECT의 세 가지 기능(셀렉션·프로젝션·조인), 산술연산자와 NVL, ALIAS, 리터럴 문자열, DISTINCT 중복 제거까지 SELECT 문의 기본기를 정리했다.
Oracle SQL/DB 학습 노트 시리즈의 두 번째 글이다. 첫 글에서 데이터베이스의 토대(트랜잭션·무결성·SQL 개요)를 정리했다면, 이번에는 본격적으로 SELECT 문을 다룬다.
[ 목차 ]
- SELECT 공부법 — 분석하는 습관
- 테이블 살펴보기 — DESC와 COLUMN
- SQL 문 작성 시 유의점 — 대소문자와 파싱
- SELECT의 세 가지 기능 — 셀렉션·프로젝션·조인
- SELECT 문의 구조 — SELECT LIST와 FROM
- 기본 테이블 분석 — departments와 employees
- 산술연산자와 NULL 처리(NVL)
- ALIAS — 별칭 붙이기
- 리터럴 문자열과 연결 연산자
- 중복 제거 — DISTINCT
- 핵심 정리
ㅇ SELECT 공부법 — 분석하는 습관
데이터베이스를 다루는 언어에서 가장 중요한 것은 결과를 해석하는 능력이다. SELECT는 데이터를 가져와 보여주는 명령문이라, 어떤 결과가 떨어지는지를 분석하는 습관이 곧 실력이 된다. 결과가 나온다고 안심하면 안 된다. 비용과 시간을 감안해 가장 효율적인 방법으로 짜는 것이 궁극적인 목표이고, 특히 SELECT는 성능에 큰 영향을 미친다.
그래서 실습할 때는 다음 순서를 지키는 것이 좋다.
- 코드를 작성하되, 바로 실행하지 않는다
- 코드를 반드시 분석한다 — 직역은 기본, 의역까지
- 결과를 예측한다
- 그다음에 코드를 실행한다
- 예측한 결과와 실제 결과를 비교 분석한다
SELECT 문이 어려운 이유는 두 가지다. DB의 구조를 이해하고 있어야 하고, 떨어지는 결과를 분석할 수 있어야 한다. 이 둘이 약하면 공부가 어려워진다.
한 가지 더. 약어는 풀네임을 알고 쓰는 편이 좋다. conn은 connect, ed는 edit이다. 같은 약어가 환경에 따라 다른 뜻으로 쓰이기도 하는데, 예를 들어 desc는 describe(구조 확인)와 descending(내림차순) 두 가지로 쓰인다. varchar는 variable character의 약자다.
ㅇ 테이블 살펴보기 — DESC와 COLUMN
테이블의 구조 확인 : DESC
1
DESC 테이블이름;
테이블이 어떤 컬럼으로 이루어져 있고, 각 컬럼이 어떤 데이터 타입인지를 보여준다.
NUMBER(4): 숫자 타입이고 4자리까지 쓸 수 있다VARCHAR2(30): 문자 타입이고 30byte까지 쓸 수 있다- 영문은 한 글자당 1byte, 한글은 한 글자당 2byte
출력 컬럼의 폭 조절 : COLUMN
SQL*Plus는 창 폭이 고정되어 있어서, 컬럼 폭(점선 ---)이 창을 넘어가면 결과가 여러 줄로 쪼개져 보기 힘들다. 이때 COLUMN 명령으로 출력 폭을 조절한다. SQL 명령어가 아니라 SQL*Plus의 편집 명령이라, 버퍼(ed)에는 남지 않는다.
문자·날짜는 알파벳 a 포맷으로 폭을 지정한다.
1
COLUMN department_name format a17
department_name 컬럼을 17자리 폭으로 조절하겠다는 뜻이다. 만약 숫자 데이터에 a17을 쓰면 값이 #으로 깨져 나온다.
그래서 숫자는 9를 써서 자릿수만큼 폭을 지정한다.
1
COLUMN department_id format 99999999
9가 8개이므로 8자리 폭으로 조절하겠다는 뜻이다. 보통 department_name, last_name 정도만 폭을 짧게 조정해 두면 대부분의 데이터가 한 줄에 깔끔하게 보인다.
조절을 취소할 때는 clear를 쓴다.
1
COLUMN department_name clear
ㅇ SQL 문 작성 시 유의점 — 대소문자와 파싱
SQL 자체는 대소문자를 구분하지 않는다. 하지만 구분해서 쓰는 것이 좋다. 성능상의 이유 때문이다. 현업의 보통 규약은 다음과 같다.
SELECT같은 명령문은 대문자- 테이블·컬럼 이름은 소문자
대소문자가 성능에 영향을 주는 이유
핵심은 파싱(parsing)과 데이터 딕셔너리(D.D)다. 먼저 메타데이터부터 짚자.
메타데이터 : 데이터를 표현해 놓은 데이터(데이터의 데이터). 어떤 데이터가 어디에 저장되어 있고, 얼마만큼 공간을 할당받았으며, 누구 소유이고, 어떤 제약조건을 가지는지 등의 정보다. 이 수많은 정보가 데이터 딕셔너리(D.D)에 저장된다. 사람으로 치면 뇌에 해당한다.
SQL은 비절차적 언어라, 명령문을 입력하고 엔터를 치면 바로 실행되는 것처럼 보인다. 그러나 내부에서는 컴파일에 해당하는 파싱 작업이 일어난다.
파싱 : 일련의 문자열을 의미 있는 토큰(token)으로 분해하고, 이들로 이루어진 파스 트리(parse tree)를 만드는 과정.
파싱은 세 단계로 진행된다.
- 검증 : 테이블·컬럼이 존재하는지, 문법에 문제가 없는지, 무결성을 위배하지 않는지 확인하는 디버깅 단계
- 실행 계획 수립 : FROM 절의 테이블에서 SELECT LIST 절의 컬럼 데이터를 가져오는 방법을, 비용·시간 측면에서 가장 빠른 방식으로 수립
- 실행 : 그 계획에 따라 실행되어 결과가 출력된다
그런데 실제로는 명령어가 들어오면 파싱부터 하지 않고 D.D를 먼저 뒤진다. D.D에는 이전 파싱 결과와 그 SELECT 문장의 텍스트가 저장되어 있다.
- 똑같은 문장이 있으면 → 그대로 재사용(파싱을 건너뜀 → 검색 속도가 빨라짐)
- 똑같은 문장이 없으면 → 파싱 수행
이때 문장을 비교하는 기준이 아스키 코드다(예: 대문자 A는 65번). 즉 대소문자를 구분한다. 그래서 같은 의미라도 대소문자를 매번 다르게 쓰면 D.D에서 재사용하지 못해 파싱이 새로 진행되고, 그만큼 느려진다.
결론 : 프로그래머는 자신만의 규약을 만들어 일관되게 쓰는 것이 좋다. 그러면 D.D 재사용률이 올라가 성능이 좋아진다. (참고로 DB 내부에서 컬럼·테이블 이름은 대문자로 저장되어 있지만, 그것과 별개로 나만의 규칙을 지키는 것이 핵심이다.)
ㅇ SELECT의 세 가지 기능 — 셀렉션·프로젝션·조인
SELECT 문 : 데이터베이스에 보관된 데이터를 조회하는 데 사용한다.
SELECT가 출력 데이터를 선정하는 방식은 세 가지다.
셀렉션(SELECTION) — 행 단위로 원하는 데이터를 조회하는 방식
프로젝션(PROJECTION) — 열 단위로 원하는 데이터를 조회하는 방식
조인(JOIN) — 두 개 이상의 테이블을 양옆으로 연결해, 마치 하나의 테이블인 것처럼 데이터를 조회하는 방식. 셋 중 가장 까다롭다.
ㅇ SELECT 문의 구조 — SELECT LIST와 FROM
SELECT 문은 크게 두 개의 절로 이루어진다.
- SELECT LIST 절(첫 번째 줄) : 보고자 하는 데이터를 소유한 컬럼을 명시한다. 결과가 출력되는 절로, 여기에 명시한 컬럼의 데이터만 출력된다.
- FROM 절(두 번째 줄) : SELECT LIST에서 출력할 컬럼을 소유한 테이블 이름을 적는 절. SELECT를 쓰면 FROM은 자동으로 따라온다.
SELECT 문을 작성할 때 가장 먼저 할 일은, 출력하려는 데이터를 소유한 컬럼 이름을 정하는 것, 즉 SELECT LIST 절을 결정하는 것이다.
모든 컬럼 검색 : *
1
2
SELECT *
FROM 테이블이름;
*은 모든 컬럼을 검색하라는 옵션이다. 전산학·수학에서는 이 기호(애스터리스크)를 ‘스타(star)’라고 읽는다(A* 알고리즘, C* 대수). SELECT와 * 사이에는 반드시 띄어쓰기를 해야 한다. SQL은 단어를 공백으로 분리해 인식하기 때문에, 붙여 쓰면 한 단어로 인식한다.
특정 열 선택 검색
1
2
SELECT 컬럼1, 컬럼5, 컬럼10
FROM 테이블이름;
- 컬럼 이름이 열머리글로 출력된다(가운데 정렬, 대문자).
- 출력 정렬은 데이터 타입을 따른다 — 문자·날짜는 왼쪽, 숫자는 오른쪽.
- SELECT LIST 절에 명시한 순서대로 출력된다.
맨 처음에 오는 컬럼은 나중에 매우 중요한 역할을 한다. 그러므로 항상 기준이 되는 컬럼이 무엇인지 고민하는 습관을 들이자. ★ (DISTINCT에서 그 효과가 드러난다.)
ㅇ 기본 테이블 분석 — departments와 employees
부서 테이블 (departments)
1
2
SELECT *
FROM departments;
결과 맨 아래에 27 rows selected.가 뜬다.
- 직역 : 이 테이블의 행이 27개
- 의역 : 즉, 우리 회사의 부서가 27개
department_id는 10부터 270까지, 27개 부서가 존재한다. 테이블은 항상 행 단위로 데이터를 관리하며, 한 행에 저장된 데이터들은 하나의 연결된 데이터로 봐야 한다. 예컨대 첫 번째 행은 “10번 부서의 이름은 Administration(경영관리팀)이고, 200번 사원이 관리하며, 1700번 지역에 위치한다”로 읽는다.
사원 테이블 (employees)
1
2
SELECT last_name, hire_date, salary
FROM employees;
결과는 107 rows selected.
- 직역 : 행이 107개
- 의역 : 이 회사에 근무하는 사원이 107명
정리하면 부서는 27개, 사원은 107명이다. 떨어지는 결과가 무엇을 의미하는지 해석할 수 있어야 한다.
ㅇ 산술연산자와 NULL 처리(NVL)
산술연산자
SELECT 문에서는 산술연산(+ - * /)을 할 수 있다. 컬럼과 상수, 컬럼과 컬럼 모두 가능하다(예: salary*12, salary + salary).
다만 산술연산을 한다고 테이블이 바뀌지는 않는다. SELECT는 테이블의 데이터를 원하는 대로 보여줄 뿐, 테이블 구조에 영향을 주지 않는다. 즉 SELECT 문은 트랜잭션에 영향을 주지 않는다.
데이터 타입별로 가능한 연산이 다르다.
- 숫자 :
+ - * /네 가지 모두 가능 - 날짜 :
+,-만 가능(날짜가 ± 된다). 날짜는 입력 시 연/월/일 포맷이지만, 실제로는 시·분·초까지 포함한 소수점 숫자 값으로 저장되기 때문이다. 참고로hire_date + 1,hire_date - 7처럼 연산해도 열머리글은 그대로hire_date로 나온다. - 문자 : 연산 불가(에러)
연산 우선순위는 괄호 > 곱셈·나눗셈 > 덧셈·뺄셈이다. 같은 값이라도 괄호 위치에 따라 의미가 달라진다.
12*salary+100: 연봉(salary*12)에 100을 더함 → 연봉에 대한 인상12*(salary+100): 월급여에 100을 더한 뒤 연봉을 계산 → 월급여에 대한 인상
실습 1. 사원의 이름과 연봉을 구하시오.
1 2 SELECT last_name, salary*12 FROM employees;
NULL 처리 함수 : NVL
NULL을 포함하는 산술식은 결과가 NULL로 평가된다. 그래서 NULL을 대체 값으로 바꿔주는 NVL 함수를 쓴다.
대체 값은 반드시 컬럼의 데이터 타입과 같아야 한다. ★ 문자는 문자로, 날짜는 날짜로, 숫자는 숫자로. 이때 문자·날짜는 작은따옴표를 잊지 말자. 물론 테이블 구조가 바뀌는 것이 아니라, 이 문장이 실행되는 동안만 적용된다.
실습 2. 사원의 이름과 연봉을 구하되, 보너스를 받는 사원은 보너스를 곱해서 출력하시오.
1 2 SELECT last_name, salary*12*(1+NVL(commission_pct,0)) FROM employees;
commission_pct(보너스 비율)에는 NULL이 있으므로 NVL로 처리한다. NVL(commission_pct, 0)은 NULL을 0으로 대체해 NULL을 없앤다. 거기에 1을 더하면 1.xx(보너스 있음) 또는 1.0(보너스 없음) 꼴이 되어, 보너스를 받는 사원만 추가분이 반영되고 나머지는 본래 연봉이 그대로 나온다.
ㅇ ALIAS — 별칭 붙이기
ALIAS(별칭) : 본래 열 이름 대신 붙이는 이름.
예를 들어 salary*12*(1+NVL(commission_pct,0))처럼 열머리글이 복잡할 때, annsal(annual salary) 같은 이름으로 바꿔 가독성을 높인다. 규칙은 다음과 같다.
- 한 컬럼에 하나의 별칭만 지정할 수 있다
- 열 이름 바로 뒤에 쓴다
- 숫자로 시작할 수 없다
방법 1 : 블랭크(빈칸)
1
2
SELECT 복잡해진컬럼 새로운별칭
FROM 테이블이름;
단어 하나로 된 별칭일 때 빈칸으로 구분한다. 단, 공백이 들어간 복수 단어 별칭에는 쓸 수 없다. 예를 들어 salary*12 Annual Salary는 salary*12를 Annual로 별칭한 것으로 해석되고, 그 뒤의 Salary는 컬럼으로 인식되어 “콤마가 빠진 문법 에러”가 된다. SELECT LIST 절에 공백이 나타나면, 먼저 컬럼 이름이 있는지 검색하고 → 없으면 별칭으로 처리한다.
방법 2 : AS 키워드
1
2
SELECT 복잡해진컬럼 AS 새로운별칭
FROM 테이블이름;
블랭크와 같은 역할이지만 AS를 더 선호한다. AS를 만나면 바로 별칭으로 처리되고, 가독성도 좋다. 공백만 있으면 별칭인지 콤마가 빠진 것인지 판단하기 어렵다.
방법 3 : 큰따옴표(" ")
1
2
SELECT 복잡해진컬럼 "새로운 별칭"
FROM 테이블이름;
공백이 들어간 복수 단어, 특수문자, 대소문자 구분, 쓴 포맷 그대로 출력하고 싶을 때 쓴다. 예: SELECT last_name "name", salary*12 "Annual Salary". 다만 큰따옴표는 성능이 조금 떨어진다. AS는 기본값이 대문자로 출력되지만, 큰따옴표는 쓴 포맷대로 바꾸는 작업이 추가되기 때문이다.
가장 안 좋은 방법은
AS와" "를 동시에 쓰는 것이다. 큰따옴표 자체가 이미 별칭임을 알려주므로AS는 군더더기다. 코드는 짧을수록 좋다.
ㅇ 리터럴 문자열과 연결 연산자
연결 연산자 (||)
열 또는 문자열을 다른 열에 연결해, 두 개의 데이터를 하나로 출력하는 연산자다. 두 개의 세로선 ||로 표시한다. 주로 보고서를 만들 때 쓰는데, 현업에서 SQL*Plus로 보고서를 만드는 경우는 드물어 가볍게 보면 된다.
리터럴 문자열
리터럴 문자열 : 명령문·테이블·컬럼 이름·연산자를 제외한, 모든 문자·날짜·숫자 그 자체.
- 문자·날짜는 작은따옴표(
' ')를 붙인다.- 작은따옴표를 안 붙이면 별칭(ALIAS)으로 인식된다.
- 큰따옴표(
" ")를 붙여도 별칭이 된다.
- 숫자는 따옴표를 붙이지 않는다. 테이블·컬럼 이름은 반드시 문자로 시작하므로(숫자로 시작하지 않음), 숫자는 별칭이 아니라 숫자 값으로 바로 인식된다. 만약
'7'처럼 따옴표를 붙이면 문자로 인식해 버린다.
예를 들어 last_name || ' is a ' || job_id처럼 쓰면 “이름 + ‘ is a ‘ + 업무”가 한 문자열로 이어진다.
리터럴 문자열은 나중에 WHERE 조건절에서 매우 중요해지는데, 포인트는 작은따옴표(' ')를 쓴다는 점이다.
정리하면 — 별칭은 큰따옴표(
" "), 리터럴 문자열은 작은따옴표(' ').
ㅇ 중복 제거 — DISTINCT
1
2
SELECT DISTINCT 컬럼이름
FROM 테이블이름;
DISTINCT는 SELECT 명령어 바로 다음에 위치하며, 전체 문장에서 단 한 번만 사용한다.
컬럼 1개
실습 1. 사원들이 근무하는 부서를 알고 싶다.
1 2 SELECT department_id FROM employees;
여기서 department_id를 직역하면 “사원들의 부서 아이디”인데, 의역하면 “사원들이 근무하고 있는 부서번호”다. 숫자가 중복된다는 것은 한 부서에 여러 사원이 근무한다는 뜻이다. 중복을 제거해 보자.
1
2
SELECT DISTINCT department_id
FROM employees;
결과는 12 rows selected. 그렇다면 사원이 근무하는 부서가 12개일까? 아니다.
NULL도 중복 제거 대상이다. NULL이 여러 개 있어도 DISTINCT를 거치면 1개만 남는다. 즉 결과 12행 중 한 칸은 NULL이므로, 실제 사원이 근무하는 부서는 11개다. 행 수를 그대로 받아들이면 착오가 생긴다.
컬럼 2개
1
2
SELECT DISTINCT 컬럼1, 컬럼2
FROM 테이블이름;
앞 컬럼들의 그룹화를 통해 중복을 제거한다. 첫 번째 컬럼의 데이터가 10 20 10 20 10이라면, 같은 것끼리 모아(그룹화) 각 그룹에서 하나만 반환한다 → 10, 20. 마지막 컬럼은 그룹 내에서 중복을 제거한다.
실습 2.
1 2 SELECT DISTINCT department_id, job_id FROM employees;
컬럼 단위로만 보면 20, 20이 겹쳐 중복 제거가 안 된 것처럼 보이지만, 행 단위로 보면 (20, MK_MAN)과 (20, MK_REP)는 서로 다르므로 중복이 아니다. 의역하면 “부서별로 어떤 업무들이 있는가”이다.
컬럼 3개 이상
첫 번째 컬럼으로 그룹화 → 그 안에서 두 번째 컬럼으로 그룹화 → 세 번째 컬럼에서 중복 제거, 식으로 단계가 늘어난다.
만약 PK(프라이머리 키)를 첫 번째 컬럼으로 두면, PK는 값이 모두 유일하므로 각각이 하나의 그룹이 된다. 즉 첫 번째 컬럼이 무엇이냐에 따라 그룹의 개수가 달라질 수 있다.
ㅇ 핵심 정리
| 주제 | 한 줄 정리 |
|---|---|
| DESC | 테이블의 컬럼 구성과 데이터 타입 확인 |
| COLUMN | 출력 폭 조절(문자 a, 숫자 9). SQL*Plus 편집 명령 |
| 대소문자 규약 | 명령문 대문자·이름 소문자로 일관 → D.D 재사용 → 성능 ↑ |
| SELECT 3기능 | 셀렉션(행)·프로젝션(열)·조인(테이블 연결) |
| SELECT 구조 | SELECT LIST(컬럼) + FROM(테이블), 명시 순서대로 출력 |
| 산술연산자 | 숫자 + - * /, 날짜 + -, 문자 불가. 테이블은 안 바뀜 |
| NVL | NULL을 같은 타입의 대체 값으로 치환 |
| ALIAS | 별칭. 블랭크 / AS(선호) / " "(복수 단어·특수문자) |
| 리터럴 문자열 | 작은따옴표(' '). 별칭은 큰따옴표(" ") |
| DISTINCT | 앞 컬럼 그룹화 후 중복 제거. NULL도 하나로 합쳐짐 |
다음 글에서는 원하는 행만 골라내는 WHERE 조건절과 결과를 정렬하는 ORDER BY를 다룬다.



















