입문자를 위한 SQL
1. SQL이란 무엇인가?
SQL은 데이타를 조회하고, 수정하고, 삭제하는 것을 포함하여 데이타를 처리(혹은 관리)하기 위한 프로그래밍 언어이며, Structured Query Language의 약자이다.
한국어 위키백과의 SQL 편에 따르면 SQL(Structured Query Language)은 구조화 질의어로 번역하고 있으나, 구조화된 질의 언어라는 표현이 더 정확할 것으로 생각되며, 통상 한국어로 질문으로 번역되는 Query라는 단어는 데이터를 처리(혹은 관리)하기 위한 일체의 명령으로 이해할 수 있다.
한국어 위키백과에 따르면 SQL은 관계형 데이터베이스 관리 시스템(RDBMS)의 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어라고 정의하고 있다.
여기서 데이타베이스는 데이타의 저장소로 이해할 수 있다. 데이타베이스는 관계형 데이타베이스도 있지만, 객체지향형, 객체관계형과 같은 것들도 있고, 일반적으로 관계형 모델에 기반하지 않은 경우에는 XML 저장소라든가 디렉토리 서비스(ldap)와 같이 부르는 것이 일반적이다.
관계형 이라는 개념은 데이타들이 서로 관계를 가지고 연결되어 있다고 이해할 수 있다.
SQL은 관계형 데이터베이스 관리 시스템(이하 "RDBMS"라고 한다) 제품에 따라, 심지어는 같은 RDBMS 제품이라도 버전에 따라서 약간의 차이가 있을수 있다.
ISO나 ANSI와 같은 SQL 표준이 존재하지만, SQL 표준 전체를 지원하는 RDBMS 제품을 찾기가 어려울 정도이며, 대부분의 상업용 제품들은 표준을 넘어서는 SQL 구문을 지원하고 있다.
2. 기본개념
2.1. 테이블(table)
테이블은 Microsoft Excel 혹은 OpenOffice Calc에서 1개의 Sheet를 연상하면 된다.
테이블은 Microsoft Excel 혹은 OpenOffice Calc와 같이 열과 행으로 구성된다.
Microsoft Excel 혹은 OpenOffice Calc에서의 열은 A, B, C, D...와 같이 자동으로 이름이 붙고 열과 행의 갯수가 정해져 있지 않다.
이에 반해 테이블(table)은 열을 미리 정의해야 하며, 각각의 열의 이름과 숫자, 문자열 등 자료형을 미리 정의해야 하며, 미리 정의된 유형이 아닌 다른 유형의 데이타를 입력할 수 없다.
2.2. 자료형(Data Type)
자료형이란 프로그래밍 언어를 처음 접하는 사람들에게 매우 생소한 개념일 수도 있고, 왜 그렇게 복잡한 개념이 필요한지 의문을 표하는 사람도 있을 수 있다.
프로그래밍 언어의 경우 미리 자료형을 정하지 않고, 프로그램이 실행 될 때 자료형이 결정되는 경우가 있으나, 이런 방식은 관계형 데이타베이스의 본질적인 구조와 어울리지 않는다.
일반적으로 RDBMS에서 지원하는 자료형은 숫자형과 문자(열)형, 날짜형으로 나눌 수 있다.
데이타베이스에 따라서 blob나 clob, 혹은 gemotry 형이나 기타 등등의 다른 자료형도 있다.
숫자형은 사칙연산 따위가 된다. 문자형의 경우 문자열을 연결하거나 나누거나 혹은 문자열 내부에서 특정한 문자열이 발견되는지 탐색하는 등을 할 수 있다. 날짜형의 경우 날짜에서 연도만 추출하거나 할 수도 있고 날짜를 더하거나 뺄 수도 있다.
자료형은 정렬이나 비교를 하는데에도 사용된다.
예를 들어 111과 21의 경우 숫자로 정렬하는 경우 21이 먼저 등장해야 하지만, 문자열로 정렬하는 경우 111이 먼저 등장해야 한다.
저장하는 방식(2진수 표현)도 다를 가능성이 높다.
2.3. Primary Key
Primary Key는 1개 행의 식별자이다.
Primary Key로 구성된 컬럼은 고유한 값을 가져야 하며,
필수적으로 index가 생성되며, null 값을 갖지 못한다.
index 와 null 에 대해서는 아래에서 기술한다.
Primary Key는 1개의 컬럼일 수도 있고, 여러 개의 컬럼일 수도 있다.
3. SQL 시작하기
사람들이 강학상 DML 이라고 부르는 SQL구문에 대해서만 다룬다.
DBA가 되지 않을 것이라면, 심지어는 DBA가 되는 경우라도 하더라도, DML 중에서도 select 구문을 정확히 배워서 확장성 있는 select 구문을 작성하는 능력을 키우는 것이 가장 중요하다.
DML이 익숙한 사람이 DCL이나 DDL을 익히는 데에는 불과 몇 시간이면 충분하지만, select 구문을 작성하는 능력은 평생을 가도 거기서 거기이거나, 시간이 지날 수록 select 구문을 쓸데없이 어렵게만 작성한다는 측면에서 퇴보하는 경우도 많다.
DML은 데이터를 조회(select)하고, 추가(insert)하고, 변경(update)하고, 삭제(delete)하는 구문이다.
3.1. 데이타 조회하기 (select)
select 구문은 다음과 같은 형식으로 구성된다.
select 컬럼명 from 테이블이름 inner join 테이블이름 on 조건 where 조건 group by 컬럼명 having 조건 order by 컬럼명 limit 숫자 offset 숫자
select와 from은 필수이고,
where 와 order by, limit offset은 자주 사용된다.
group by 나 having은 유용하지만 많이 사용되는 편은 아니다.
엄밀하게 from은 필수는 아니다(mysql, postgresql, sqlite 등).
예를 들어 "select 1" 이 실행 된다.
Oracle에서는 "select 1 from dual" 이라고 가상의 테이블(dual)을 사용해야 한다.
3.1.1. select 다음에 오는 컬럼명
select 다음에 오는 컬럼명은 "*" 를 사용하여 모든 컬럼을 지정할 수 있지만, sql 구문이 늘어지더라도 컬럼명을 1개씩 기술하는 것이 실무의 관행이다.
"*" 는 전체 데이터를 조회하기 위해서 사용된다.
다만 프로그래머들이 "select * form 테이블이름" 과 같은 프로그램 코드를 작성하는 것은 이례적이고, 대부분의 코딩 가이드라인에서는 이런 방식을 금지하고 있다.
여러개의 컬럼을 불러오는 경우에는 컴마(",")로 구분한다.
각각의 컬럼에 함수등으로 변형을 가할 수도 있고,
혹은 숫자나 문자열을 직접 기재하거나 함수를 사용할 수 있다.
그리고 이 경우 가상의 컬럼명을 부여하기 위해 as 라는 키워드를 사용한다.
공식적인 명칭은 별칭(alias)이다.
예를 들면 다음과 같은 방식이다(/**/는 주석).
select 1 as number1, /*항상 1이 출력됨.*/ 'a' as char1, /*문자열은 ' 으로 묶어 줌*/ now() as current_datetime, /*오늘 날짜를 구해주는 sqlite 내장함수*/ 컬럼1 + 1 as 컬럼1plus1, /*컬럼1에 1을 더함 */ 컬럼1 + 컬럼2 as 컬럼1plus컬럼2, /*컬럼2에 컬럼2를 더함 */ 컬럼3 || 컬럼4 as 컬럼3plus컬럼4 /*컬럼3에 컬럼4의 문자열을 결합시킴*/ from 테이블이름
반환되는 행과 열이 1개인 것이 분명한 경우 select 구문을 넣을 수도 있다.
select 바로 다음에 "distinct" 라는 구문을 쓰게 되면, 중복되지 않는 데이타만 반환한다.
distinct는 index range scan 을 무력화시키므로 필요한 경우에만 사용해야 한다.
3.1.2. from 다음에 오는 테이블 이름
from 다음에 오는 테이블 이름은 1개 일수도 있고,
여러 개 일수도 있는데, 여러 개인 경우 컴마(",")로 구분한다.
또한 테이블 이름 대신에 select 구문을 넣을 수도 있다.
이 경우도 가상의 테이블명을 부여하기 위해 as 를 사용하기도 한다.
다만 테이블이 여러개인 경우 where 절에서 처리를 해야 하며, 조인에 대해서 소개하면서 같이 설명한다.
3.1.3. where 다음에 오는 조건절
where 다음에 오는 조건절은 1개의 조건일 수도 있고, 여러개의 조건일 수도 있다.
여러개의 조건이 오는 경우에 각각의 조건은 and 혹은 or로 연결되며, 괄호를 사용하기도 한다.
1개의 조건의 경우 다음과 같이 사용한다.
컬럼명 = 값
그러면 컬럼의 값이 일치하는 것만 조회한다.
컬럼명은 함수로 감싸거나 4칙연산 따위를 하는 것도 가능한데, 추천되는 방식은 아니다.
'=' 은 비교연산자라고 부르는데, = 대신에 > < >= <=와 같은 것을 사용할 수 있다.
특별한 방식중에 하나는 "컬럼명 is null" 혹은 "컬럼명 is not null" 인데,
"null이란 값이 존재하지 않음"이라는 뜻이다.
null값은 insert나 update 구문에서 명시적으로 null을 입력한 경우이거나,
insert를 하면서 해당 컬럼을 생략한 경우,
데이타가 insert 혹은 update 된 이후에 테이블에 컬럼이 추가된 경우 등에 입력된다.
null 은 데이타베이스에 따라 개념이 약간 다를 수 있는데, Oracle 의 경우 '' 이 null 이다.
많이 사용되는 방식중 하나는 like라는 연산자를 사용하는 것이다.
like는 반드시 필요한 경우에만 사용해야 한다.
like 연산자의 사용례는 다음과 같다.
컬럼명 like '%값%'
위의 코드는 컬럼값에 "값"이라는 문자열이 등장하는 행만 돌려주라는 의미이다.
만일 오른쪽에 있는 %를 생략하면 "값"으로 끝나는 행만 돌려주라는 의미이고,
왼쪽의 %를 생략하면 "값"으로 시작하는 행만 돌려주라는 의미이다.
이 글을 읽는 독자가 dos나 linux/unix의 dir이나 ls 명령어를 사용해 본 경험이 있다면 "*"과 "%"는 같은 역활을 한다.
like 연산자는 다음과 같이 사용될 수도 있다.
컬럼명 like '%값1%값2%'
그러면 "값1" 가 "값2"가 순차적으로 등장하는 행만 돌려주게 된다.
만일 "값1"이나 "값2" 중 어느 것 하나라도 등장하는 행을 돌려받기 위해서는 다음과 같이 해야 한다.
컬럼명 like '%값1%' or 컬럼명 like '%값2%'
위와 같은 방식은 괄호"()"로 묶는 것이 일반적이다. 예를 들어 컬럼1의 값이 1이고, 컬럼2의 값에 "값1"이나 "값2"를 포함하는 행을 가져오기 위해서는 다음과 같다.
컬럼명1 = 1 and (컬럼명2 like '%값1%' or 컬럼명2 like '%값2%')
where 절에서 사용하는 유용한 연산자 중 하나는 "in" "not in" "exists" "not exists" 와 같은 것들이다.
"in" 연산자의 기본적은 사용법은 다음과 같다.
컬럼명 in ('값1', '값2')
위의 코드는 컬럼값이 "값1" 혹은 "값2"인 행만 가져오라는 의미이며, 다음과 같은 결과를 반환한다.
컬럼명 = '값1' or 컬럼명 = '값2'
"not in"은 "in"과 반대이다.
"in"은 다음과 같이 select 구문과 함께 사용할 수 있다.
select * from 테이블이름1 where 컬럼명1 in (select 컬럼명2 from 테이블이름2)
exists 나 not exists의 경우는 일반적으로 sql의 실행성능 향상을 위한 유용한 도구이며, 위의 sql을 exists를 이용하여 변경하면 다음과 같다.
select * from 테이블이름1 as a where exists (select * from 테이블이름2 as b where b.컬럼명2 = a.컬럼명1)
in 과 exists 를 비교하면, in 이 구문을 작성하기 간편하지만, exists 가 성능에 유리한 경우가 많으므로, 가급적 exists 를 사용하는 습관을 들이는 것이 좋지만, 편견은 금물이다.
where 다음의 조건절에서는 컬럼을 함수 등으로 가공하는 것은 가급적 피하는 것이 좋고, 자료형에 맞는 값을 공급해 주어야 한다.
3.1.4. 인덱스(index)와 옵티마이져(Optimizer)
인덱스(index)는 실행성능 향상을 위해 매우 중요한 역활을 담당하며, RDB 혹은 RDBMS가 성장해온 핵심 동력이다.
인덱스(index)의 원리는 비교적 간단한다.
인덱스는 BTree와 같은 자료구조를 갖는 파일에 저장되는데, BTree 등은 key값을 빠르게 탐색할 수 있도록 고안된 자료구조이다.
B-Tree 가 일반적이고, 정렬이나 비교없이 일치하는 값만을 찾기 위한 Hash 기반 index 도 있고, 또 다른 것들도 있다.
예를 들어 "where 컬럼명 = '값1'"의 경우 인덱스(index)가 존재하지 않는다면, RDB 혹은 RDBMS는 테이블의 데이타를 순차적으로 모두 읽어 컬럼값을 각각의 값을 비교해야 한다.
최악의 경우에 "값1"이라는 값이 없는 경우 테이블의 데이터를 모두 읽은 다음에야 검색된 결과가 없다는 결과를 돌려주게 된다.
만일 인덱스(index)가 있는 경우에는 먼저 인덱스(index)파일을 읽어 "컬럼명 = '값1'" 을 만족하는 테이블 데이타 파일의 물리적인 주소를 가져온 다음, 이 주소를 이용하여 테이블 데이터의 물리적인 주소로 바로 이동하여 테이블 데이타를 가져오게 되고, 이후로 인덱스 파일과 테이블 데이터 파일을 번갈아 읽으면서 테이블 데이타를 가져오게 된다.
인덱스(index)를 이용하면 "컬럼명 = '값1'"을 만족하는 데이타가 소량이거나 없는 경우, 혹은 나중에 살펴볼 limit 구문을 이용하여 결과의 갯수를 작은 수로 한정한 경우에 특히 높은 성능이 보장된다.
B*Tree 계열은 offset 값을 지정하여 스캔할 수 없기 때문에 limit 를 이용한다고 하더라도, offset 값이 커지면 성능 저하를 피할수는 없다.
옵티마이져(Optimizer)는 SQL 구문을 해석하여 최적의 탐색방법(일반적으로 실행계획 - Expain Plan 이라고 부른다)을 찾아내는 역활을 담당한다.
옵티마이져(Optimizer)는 규칙기반 옵티마이져(Rule Based Optimizer)와 비용기반 옵티마이저(Cost Based Optimizer)가 있다.
Rule Based Optimizer의 경우 index 만 참고하여 실행계획을 작성한다.
이 경우 약간의 비효율이 발생하는데, 예를 들어 데이타 자체가 소량인 경우 인덱스를 거칠 필요조차 없으며, 컬럼값이 99%는 같은 값이고, 1%만 각각 다른 값인 경우 99%를 찾기 위해 인덱스 파일과 테이블 데이타 파일을 번갈아 읽는 것은 효율이 떨어진다.
일반적으로 인덱스 파일을 읽는 것은 데이타 파일을 읽는 것에 비해 느리다.
이런 것들을 보완하기 위해 만들어진 것이 Cost Based Optimizer 이다.
Cost Based Optimizer는 Rule Based Optimizer 기반위에서 각종 통계정보를 활용한다.
Rule Based Optimizer 에 익숙한 사람들은 이를 선호하는 경향이 있다.
데이타의 분포도나 데이타의 크기를 잘 알고 있는 프로그래머는 의도적으로 옵티마이저가 인덱스를 사용하지 않도록 할 수 있고, Rule Based Optimizer는 예측 가능하고 인덱스나 테이블 구조가 변경되지 않는 경우 실행계획이 변경되지 않기 때문이다.
Cost Based Optimizer는 프로그램에서 sql을 호출하는 경우 변수가 바인딩되기 이전에 실행계획을 수립하는 것이 일반적이므로 프로그래머가 sql 실행도구를 통해서 실행된 것과 다른 실행계획으로 실행될 수 있으며 이건 매우 괴로운 일이다.
현대적인 RDBMS 대부분은 Cost Based Optimizer 만 지원하고 있다.
3.1.5. order by 다음에 오는 정렬 구문
sql의 구문 상 group by 나 having 절이 order by 보다 먼저 등장하지만, 여기에서는 편의상 order by 절을 먼저 설명한다.
order by 절은 정렬을 위한 것이다.
예를 들어 다음과 같다.
order by 컬럼명1 asc, 컬럼명2 desc
컬럼명 뒤의 asc, desc는 정렬 방식을 지정하는 것이다.
desc라고 하면 역순으로 정렬하라는 의미이고 asc, desc 를 생략하면 asc로 정렬된다.
2개 이상의 컬럼으로 정렬하는 경우 컴마(",")로 구분한다.
또한 컬럼명 대신에 select 절에서 사용된 컬럼의 순번을 지정할 수 도 있다.
3.1.6. limit, offset 구문
limit는 limit 다음에 오는 숫자로 결과의 갯수를 제한하라는 의미이고, offset은 offset 다음에 오는 숫자부터 결과를 가져오라는 의미이다.
limit, offset 구문은 RDB, RDBMS에 따라 지원하지 않는 경우도 있으나, 대부분의 RDB 혹은 RDBMS는 동일한 결과를 얻을 수 있는 구문을 제공한다.
예를 들어 Oracle 의 경우 rownum을 이용할 수도 있고, limit 숫자, 숫자와 같이 offset 만 지원하지 않는 경우(mysql)도 있다.
offset 다음에 오는 숫자가 커지면 성능에 나쁜 영향(index scan을 하더라도 offset 까지는 skip 할 것이므로)이 있다. offset 0 은 성능에 영향이 없겠지만, offset 1000000 은 다른 방법을 강구해야 한다.
3.1.7. group by와 having 구문
group by 구문은 특정 컬럼을 기준으로 컬럼값 별로 그룹핑 하기 위한 것이다.
그룹핑?: 훌륭한 표현이 떠오르지 않는다.
예를 들어 간단한 sql을 먼저 살펴본다.
select 컬럼명, count(*), max(컬럼명1), min(컬럼명1), sum(컬럼명1), average(컬럼명1) /*혹은 avg */ from 테이블이름 group by 컬럼명
위의 sql은 컬럼 값별로 갯수, 컬럼명1의 최대값, 최소값, 합계, 평균을 반환한다.
group by 절을 사용하게 되면, group by 절에서 사용된 컬럼을 제외한 나머지 컬럼은 집계함수라고 불리우는 count, max, min, sum, average 함수와 함께 사용해야 한다.
컬럼명은 다른 것과 마찬가지로 함수로 감쌀수도 있는데, 이 경우 select 절에서의 컬럼명도 group by 절과 같은 형태이어야 한다.
having 구문은 group by 를 사용한 경우에 특별한 조건을 추가할 수 있다.
예를 들어 위의 sql을 조금 변형시키면 다음과 같다.
select 컬럼명, count(*), max(컬럼명1), min(컬럼명1), sum(컬럼명1), average(컬럼명1) from 테이블이름 group by 컬럼명 having count(*) > 1
위의 sql은 count(*)의 값이 1보다 큰 것만 반환한다.
having 절은 where 절과 마찬가지로 여러개의 조건절을 사용할 수 있고 이 경우 각각의 조건절은 and 나 혹은 or를 통해 연결한다.
3.1.8. union all 구문
union all은 select 구문 2개에서 데이터를 병합하여 반환한다.
예를 들어 다음과 같다.
select 컬럼명1 from 테이블이름1 union all select 컬럼명2 from 테이블이름2
union all에서의 주의사항은 2개의 select 구문이 같은 갯수의 열을 반환해야 하며, 각각의 열은 원칙적으로 자료형이 같아야 한다.
실무에서 union을 사용하는 경우도 있는 데, union은 중복을 제거한 결과를 반환한다.
"union all" 이 아닌 "union" 을 사용하는 경우는 중복을 제거해야할 특별한 이유가 있는지 다시 한번 생각해 봐야 하고, "union all" 과 "union" 이 같은 결과를 반환하는 경우에는, "union all"을 사용해야 하는데, 데이타의 양이 많은 경우에 그 실행속도는 수십배에서 최악의 경우에는 union 은 결과를 반환하는 것을 보지 못하게 될수도 있을 만큼 많은 차이가 발생한다.
3.1.9. 기본개념: Foreign Key
Foreign Key 는 다른 테이블과의 연결고리이며, 물리적으로 정의된 것으로 한정되지 않는다.
일반적으로 Foreign Key 로 정의된 column 은 다른 테이블의 Primary Key 의 column 과 같다.
3.1.10. 조인 (join)
조인(join)은 Primary Key 와 Foreign Key 를 이용해서 2개의 테이블을 연결하는 것이며, 관계형 데이타베이스의 핵심이다.
select 다음의 컬럼명 대신에 select 절을 쓰는 것을 포함하여 where 절에서 in(not in), exists(not exists) 구문을 이용하거나 union all 따위를 쓰는 것들도 모두 넓은 의미에서 조인(join)의 일종이라고 할 수 있다.
조인의 가장 간단한 형태는 다음과 같다.
select a.*, b.* from 테이블1 as a, 테이블2 as b where a.컬럼명 = b.컬럼명
위의 sql은 다음과 같이 변경할 수 있다.
select a.*, b.* from 테이블1 as a inner join 테이블2 as b on a.컬럼명 = b.컬럼명
두 번째 sql 구문이 더 정확한 구문이지만, 역사를 거슬러 올라가면 두 번째 sql을 지원하지 않는 경우가 많았기 때문에, 첫 번째 sql 구문이 친숙하게 느껴진다.
조인(join) 중에는 outer join 이라는 개념이 있다.
outer join은 2개의 테이블에서 기준하는 테이블에 데이타가 있으면 결과를 반환한다.
select a.*, b.* from 테이블1 as a left outer join 테이블2 as b on a.컬럼명 = b.컬럼명
위의 sql에서 left는 right로 대체될 수 있는데, 왼쪽에 있는 테이블이 기준이 되는지 오른쪽에 있는 테이블이 기준이 되는지를 나타낸다.
Oracle 에서는 다음과 같이 사용하는 관행이 있다.
select a.*, b.* from 테이블1 as a, 테이블2 as b where a.컬럼명 = b.컬럼명(+)
양쪽의 어떤 테이블이든 데이터가 있는 경우에 결과를 반환하는 것을 full outer join 이라고 부른다. 오래된 버전의 RDB 혹은 RDBMS의 경우 full outer join 을 대부분 지원하지 않았고, 실무에서 full outer join은 사용할 일도 거의 없지만, group by 를 이용하여 full outer join 을 구현할 수 있었다.
3.2. 데이타 추가하기(insert)
insert 구문은 가장 간단한 구문이다.
insert into 테이블이름 (컬럼명1, 컬럼명2, 컬럼명3) values (값1, 값2, 값3)
위의 sql에서 "(컬럼명1, ...)"을 생략할 수 있지만, 이 경우 테이블의 컬럼 순서에 맞춰서 "(값1, ...)"을 모두 넣어주어야 한다.
값이 문자형인 경우 "'" 로 감싸줘야 한다.
insert 구문은 다음과 같이 사용 할 수도 있다.
insert into 테이블이름 (컬럼명1, 컬럼명2, 컬럼명3) values select 값1, 값2, 값3 from 테이블이름2
3.3. 데이타 변경하기(update)
update 구문도 select 구문에 비하면 비교적 간단하다.
update 테이블이름 set 컬럼명 = 값 where 조건
where 절은 select 에서와 동일하다. 조건에 맞는 행을 찾아서 해당 컬럼을 값으로 변경한다. 만일 여러개의 컬럼을 갱신해야 하는 경우 컴마(",")로 연결하면 된다.
값에는 select 구문을 넣을 수도 있다.
이 경우는 조인(join)에서 where 절을 쓰는 방법을 참조하면 된다.
3.4. 데이타 삭제하기(delete)
delete 구문은 조건에 맞는 행을 삭제하는 구문이다.
delete from 테이블이름 where 조건
from은 RDB 혹은 RDBMS에 따라 제거해야 하는 경우도 있다.
where 절은 select나 update에서의 그것과 동일하다.
4. SQL을 잘 만드는 요령
sql을 잘 만드는 요령은 다음과 같다.
첫 번째는 구문을 정확히 알아야 한다.
sql 구문은 여타의 프로그래밍 언어에 비교하면 매우 단순한 편이다.
예를 들어 union 과 union all 을 혼동하면 상상치 못한 결과가 초래될 수도 있다.
두 번째는 유연한 사고를 해야 한다. 같은 결과를 얻기 위해 여러가지 방법이 있다.
세 번째는 두려움에서 벗어나야 한다. 초보자는 다른 사람이 만든 완성된 SQL 에 주눅이 드는 경우가 있는데, 작은 단위로 실행해서 결과를 확인해 가면서 동작을 파악하면 된다.
길이가 긴 SQL 을 작성하는 것을 금기시 할 필요도 없다. SQL은 데이타 처리에 최적화된 언어이다. 단순하고 짧은 SQL 만 고집하다 보면, 불행이 닥칠 수 있다.
네 번째는 ERD는 초보자에게 어려울 수 있다. ERD는 현행화되지 않았을 가능성도 있고, 정확한 ERD는 경험많고 치밀한 사람만이 만들 수 있고, 이런 사람은 극소수에 불과하다.
다섯 번째는 sql도 언어(language)이다. RDB 혹은 RDBMS는 단지 데이터를 저장하는 곳이 아니다. sql을 이용하여 데이타를 가공하는 것을 두려워 하면 안된다. select 절에서 함수를 이용하여 컬럼값을 변형하여 가져오는 것은 물론이고, update select 나 insert select 구문을 적절히 잘 사용하면 매우 높은 생산성이 보장된다.
여섯 번째는 index 의 원리를 잘 이해해야 한다. 옵티마이저도 결국 프로그래머가 만든 것이다. 모두가 데이터를 가져오는 더 효과적인 방법에 대한 고민을 한다. index 내지는 옵티마이저의 원리를 잘 이해하면 높은 성능의 sql을 작성할 수 있다.
프로이거나 프로가 될 것이라면 습관적으로 실행계획을 확인해야 한다. PostgreSQL 이라면 sql 구문 앞에 "explain"를 붙여서 실행하면 되고, SQLite 는 "explain query plan" 을 붙이면 된다.
일곱 번째는 데이타 중심으로 접근해야 한다.
sql 구문이 익숙해 지기 전까지는 데이타 중심으로 접근하기가 쉽지는 않다.
초창기에는 이미 알려진 기법을 학습하는 것도 훌륭한 방법이다.
이미 알려진 기법이란 예를 들어 sum decode 구문이나 (copy_t와 같은) 더미 테이블을 이용하는 방법,
union all을 이용한 full outer join 같은 것들 이다.
다만 이미 알려진 기법을 학습 할 때는 copy & paste 만 하지 말고,
동작 원리를 정확히 이해하는 것이 중요하다.
예를 들어 sum decode 구문은 decode 함수는 오라클에서만 지원되지만
case를 지원하는 다른 RDB 혹은 RDBMS에서는 sum case 구문으로 사용할 수 있다.
5. 마치며
지금까지 간단하게 sql 구문의 전체 윤곽을 살펴보았다.
여기서 살펴본 윤곽은 특정한 RDB 혹은 RDBMS에 의존성이 별로 없다.
개별적인 RDB 혹은 RDBMS는 수 많은 함수를 제공하기 때문에, 수준높은 상상을 위해서는 해당 RDB 혹은 RDBMS에서 제공하는 함수도 익혀야 하고, 사례들도 살펴봐야 한다.