포스트

SELECT 기본기: 산술연산자·ALIAS·중복 제거

테이블을 살펴보는 DESC·COLUMN부터 대소문자와 파싱의 관계, SELECT의 세 가지 기능(셀렉션·프로젝션·조인), 산술연산자와 NVL, ALIAS, 리터럴 문자열, DISTINCT 중복 제거까지 SELECT 문의 기본기를 정리했다.

SELECT 기본기: 산술연산자·ALIAS·중복 제거

Oracle SQL/DB 학습 노트 시리즈의 두 번째 글이다. 첫 글에서 데이터베이스의 토대(트랜잭션·무결성·SQL 개요)를 정리했다면, 이번에는 본격적으로 SELECT 문을 다룬다.

[ 목차 ]

  • SELECT 공부법 — 분석하는 습관
  • 테이블 살펴보기 — DESC와 COLUMN
  • SQL 문 작성 시 유의점 — 대소문자와 파싱
  • SELECT의 세 가지 기능 — 셀렉션·프로젝션·조인
  • SELECT 문의 구조 — SELECT LIST와 FROM
  • 기본 테이블 분석 — departments와 employees
  • 산술연산자와 NULL 처리(NVL)
  • ALIAS — 별칭 붙이기
  • 리터럴 문자열과 연결 연산자
  • 중복 제거 — DISTINCT
  • 핵심 정리




ㅇ SELECT 공부법 — 분석하는 습관

데이터베이스를 다루는 언어에서 가장 중요한 것은 결과를 해석하는 능력이다. SELECT는 데이터를 가져와 보여주는 명령문이라, 어떤 결과가 떨어지는지를 분석하는 습관이 곧 실력이 된다. 결과가 나온다고 안심하면 안 된다. 비용과 시간을 감안해 가장 효율적인 방법으로 짜는 것이 궁극적인 목표이고, 특히 SELECT는 성능에 큰 영향을 미친다.

그래서 실습할 때는 다음 순서를 지키는 것이 좋다.

  1. 코드를 작성하되, 바로 실행하지 않는다
  2. 코드를 반드시 분석한다 — 직역은 기본, 의역까지
  3. 결과를 예측한다
  4. 그다음에 코드를 실행한다
  5. 예측한 결과와 실제 결과를 비교 분석한다

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)를 만드는 과정.

파싱은 세 단계로 진행된다.

  1. 검증 : 테이블·컬럼이 존재하는지, 문법에 문제가 없는지, 무결성을 위배하지 않는지 확인하는 디버깅 단계
  2. 실행 계획 수립 : FROM 절의 테이블에서 SELECT LIST 절의 컬럼 데이터를 가져오는 방법을, 비용·시간 측면에서 가장 빠른 방식으로 수립
  3. 실행 : 그 계획에 따라 실행되어 결과가 출력된다

그런데 실제로는 명령어가 들어오면 파싱부터 하지 않고 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 Salarysalary*12Annual로 별칭한 것으로 해석되고, 그 뒤의 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(테이블), 명시 순서대로 출력
산술연산자숫자 + - * /, 날짜 + -, 문자 불가. 테이블은 안 바뀜
NVLNULL을 같은 타입의 대체 값으로 치환
ALIAS별칭. 블랭크 / AS(선호) / " "(복수 단어·특수문자)
리터럴 문자열작은따옴표(' '). 별칭은 큰따옴표(" ")
DISTINCT앞 컬럼 그룹화 후 중복 제거. NULL도 하나로 합쳐짐

다음 글에서는 원하는 행만 골라내는 WHERE 조건절과 결과를 정렬하는 ORDER BY를 다룬다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.