최근에 SQLD 자격증을 준비하고 있다. 주변에서 권유를 받았기 때문이기도 하고, 다음 학기에 데이터베이스 수업을 들어야 되기 때문이기도 하다. 그리고 백엔드에서는 어차피 데이터베이스를 이용할 일이 많기 때문에, 데이터베이스 관련 지식을 좀 쌓아두면 좋지 않을까 생각했다.
Django는 기본적으로 SQLite를 DBMS로 사용한다. 그렇기에 SQLite를 이용해 실습하면 SQLD 공부도 하면서, Django를 이용한 서비스 개발에 있어서도 도움이 될 것이라고 생각했다. 또한, SQLite는 가볍고 빠르기 때문에 입문 과정에서 부담없이 실습할 수 있을 것 같았다.
DB Browser for SQLite는 그 이름에서 알 수 있듯 SQLite 그리고 SQLCipher(SQLite의 오픈소스 확장)를 위한 DB 브라우저이다. 다양한 운영체제를 지원하며, 간단하면서도 SQLite 데이터베이스를 조회, 관리하는 데에 필요한 기능을 제공한다.
다운로드는 https://sqlitebrowser.org/dl/ 에서 할 수 있고 설치형 버전과 비설치형 zip 버전을 모두 제공한다. 나는 설치형 버전을 다운로드 받았지만, 선택은 자유이다.

DB Browser for SQLite (앞으로 브라우저라 줄이겠다)를 열면 위와 같은 창이 나타날 것이다. 화면을 보면 알 수 있듯, 한국어를 지원한다! 언어를 바꾸고 싶으면 위의 편집 메뉴에서 환경설정을 클릭하면 된다.
Django 프로젝트에서 사용되는 DB를 열어볼 것이다. 파일을 클릭한다.

데이터베이스 열기를 클릭하고 Django 프로젝트 폴더에 있는 sqlite3 파일을 선택하여 연다.

그러면 이와 같이 데이터베이스 구조가 보일 것이다. Django 프로젝트에서 migrate를 통해 만들었던 테이블들과 스키마가 보이고 있다.
위는 미니 해커톤 때 만들었던 서비스의 DB를 연 것이다. 사실 DB의 구조는 외부에 노출되면 안 된다. 하지만, 어차피 서비스에 연결되어 있던 백엔드 서버도 더 이상 가동중이지 않으며 DB도 개발 과정에 사용한 실습용 DB였었기 때문에 그냥 올리겠다.
이번에는 SQLD 강의에서 배웠던 SELECT문의 SELECT절과 FROM절 그리고 몇몇 함수를 이용해서 이것저것 조회를 해보고자 한다.
먼저 가장 기본적인 것부터 해보고자 한다. 해당 DB에는 movies_movie라는 테이블이 있는데, 이 테이블의 전체 데이터를 조회해보자.
SELECT *
FROM movies_movie;

movies_movie 테이블에는 총 282행의 데이터가 있고, 결과창에는 모든 행과 모든 컬럼이 출력되고 있다.
AS 사용AS를 사용해서 컬럼에 별칭을 지정할 수 있다. 위에서 title_kor이라는 컬럼이 있는데, 이 컬럼을 AS를 사용해서 한국어 제목으로 출력해보겠다.
SELECT title_kor AS '한국어 제목;
FROM movies_movie;

위에서 title_kor로 출력되고 있던 컬럼이 한국어 제목이라는 별칭으로 출력된다.
AS 생략별칭 AS를 생략할 수 있다.
SELECT title_kor '한국어 제목'
FROM movies_movie;

위와 결과가 같은 것을 볼 수 있다.
여러 컬럼을 조회할 때에는 ,으로 구분한다.
SELECT id, title_kor
FROM movies_movie;

4*3과 같은 계산은 굳이 컬럼이 필요하지 않고, 데이터를 가져올 테이블도 필요하지 않다.
SELECT 4*3;

12가 나타난다. 오라클의 경우에는 이럴 때 FROM DUAL로 더미 테이블 DUAL을 사용해야 한다고 하는데, SQLite에서는 굳이 필요없는 듯하다.
약간의 문자열 함수를 실습해봤다.
SELECT title_ori AS '원어 제목', LOWER(title_ori) AS '원어 제목(LOWER)'
FROM movies_movie;
LOWER 함수는 문자열을 소문자로 만드는데, 한국어 제목은 대소문자라는 개념이 없으니, 원어 제목을 사용해 봤다. 비교를 위해 원래의 원어 제목도 나타나게 해 보았다.

2번이나 4번을 보면, 원어 제목에 LOWER가 적용돼 모든 글자가 소문자인 것을 볼 수 있다.
SELECT title_kor AS '한국어 제목', LENGTH(title_kor) AS '한국어 제목 길이'
FROM movies_movie;

여기에서 외계+인 1부는 일반인 기준으로는 6글자지만, LENGTH는 공백을 포함해서 계산하여 7을 출력하는 것을 볼 수 있다.
SELECT title_kor AS '한국어 제목', LENGTH(title_kor) AS '한국어 제목 길이', LENGTH(REPLACE(title_kor, ' ', '')) AS '한국어 제목 길이 (공백 제거)'
FROM movies_movie;
REPLACE 함수는 문자열에서 대상 문자열을 찾아서 치환하는 함수이다. 여기에서는 공백인 ' '을 ''으로 치환하여 공백을 제거한다. 강의에서는 3번째 인수를 생략하면 대상 문자열이 제거된다고 하는데, SQLite에서는 안 되는 것으로 보인다.
이번에는 몇몇 숫자 함수를 사용해봤다.
SELECT showtime AS '상영 시간(분)', ROUND(showtime, -1) AS '상영 시간(분) (일의 자리 반올림)'
FROM movies_movie;
ROUND는 2번째 인수(n이라고 하자)가 양수이면 소수점 n번째 자리까지 나타나게 반올림하라는 의미이고, 음수이면 정수 부분 10의 자리에서 반올림하라는 의미라고 한다.

예상대로라면 상영 시간이 일의 자리에서 반올림되어야 한다. 그런데, 위 화면에서는 그렇지 못하다. SQLite는 ROUND에서 음수 n을 지원하지 않는다고 한다. SQLite가 경량화된 DBMS라 그런지, 이외에도 실습을 하다보면 강의 설명과 다르게 동작하거나, 없는 함수들이 있었다.
오라클의 숫자에 대한 ROUND 설명을 보면 아래와 같이 적혀있다.
ROUND returns n rounded to integer places to the right of the decimal point. If you omit integer, then n is rounded to zero places. If integer is negative, then n is rounded off to the left of the decimal point.
n can be any numeric data type or any nonnumeric data type that can be implicitly converted to a numeric data type. If you omit integer, then the function returns the value ROUND(n, 0) in the same data type as the numeric data type of n. If you include integer, then the function returns NUMBER.
이 설명을 보면, 오라클의 ROUND에서는 두번째 인수로 음수, 0, 양수 모두 가능한 것을 알 수 있다.
SELECT showtime AS '상영 시간(분)', SIGN(showtime-100) AS '-1: 100분 미만 | 0: 100분 | 1: 100분 초과'
FROM movies_movie;
SIGN은 주어진 수가 음수면 -1을, 0이면 0을, 양수면 1을 반환한다. 즉, 부호 정보를 알려주는 함수인 것이다. showtime에서 100을 뺀 후, 그 값의 부호를 바탕으로 상영 시간이 100분 미만인지, 100분인지, 100분 이상인지 출력하는 예시를 하나 만들어 보았다.

SELECT showtime AS '상영 시간(분)', showtime % 10 AS '상영 시간 % 10'
FROM movies_movie;
상영 시간을 10으로 나눈 나머지, 즉 상영 시간의 일의 자리를 출력한다.

오라클에서는 나머지를 구할 때 MOD 함수를 사용할 수 있다고 한다. 하지만 SQL Server나 SQLite에서는 % 연산자를 사용해야 한다.
SQLite에서는 날짜 함수 부분이 들었던 강의와 내용이 많이 달라서, CURRENT_TIMESTAMP만 사용하고 넘어가려고 한다.
SELECT CURRENT_TIMESTAMP;

이 쿼리를 실행시킨 시간은 오후 10시 44분이었다. 하지만, CURRENT_TIMESTAMP에 나타나는 시간은 그렇지 않은 것을 볼 수 있다. SQLite에서 CURRENT_TIMESTAMP는 UTC 기준으로 출력되기 때문이다.
강의에서는 TO_XXX 함수, CONVERT 함수, CAST 함수, FORMAT 함수를 알려줬는데, SQLite에는 TO_XXX, CONVERT 함수가 없고, CAST만이 존재한다. 그리고 FORMAT은 FORMAT('%.2f', 2.5363)처럼 문자열 포매팅을 위한 함수로 쓰이고 있다.
그래서 그냥 CAST만을 실습하고 넘어가고자 한다.
SELECT CAST('100' AS int);

CAST에 의해 문자열 '100'이 int 타입으로 변환되어 100이 되었다.
SELECT COUNT(*)
FROM movies_movie;
movies_movie 테이블의 데이터의 행의 총 개수를 반환한다.

SELECT SUM(showtime) / COUNT(showtime) AS '상영 시간 평균(분)'
FROM movies_movie;
COUNT(*)은 NULL인 데이터를 포함하지만, COUNT(컬럼명), SUM(컬럼명) 등은 NULL인 데이터를 포함하지 않는다. 이 테이블에서는 showtime이 NULL인 행이 없어서 차이가 없지만, NULL인 행이 있다면 차이가 발생할 것이다.

SELECT SUM(IFNULL(showtime, 0)) / COUNT(*) AS '상영 시간 평균(분) (NULL은 0으로 처리)'
FROM movies_movie;
IFNULL은 밑에서 다룰 것이지만, NULL인 값을 치환하는 함수이다. NULL을 0으로 처리해서 상영 시간의 평균을 구해봤다.

showtime이 NULL인 행이 없어서 결과는 같다.
SELECT MIN(showtime) AS '최소 상영 시간(분)', MAX(showtime) AS '최대 상영 시간(분)'
FROM movies_movie;
MIN, MAX 함수는 각각 최소, 최대를 구하는 데에 사용하는 함수이다.

60분짜리 영화면, 조금 보다보면 끝날 것 같다. 227분짜리 영화는 중간에 휴식 시간이 필요하지 않을까?
SELECT AVG(showtime) AS '상영 시간 평균(분)'
FROM movies_movie;
AVG는 평균을 구하는 함수이다.

아까와는 다르게 평균이 정수가 아닌 실수(real) 형태로 나타나고 있다. AVG 함수는 컬럼의 값들이 정수여도 실수 형태로 값을 반환한다.
SELECT IFNULL(NULL, 5);
IFNULL은 대상값이 NULL이라면 지정한 값으로 치환하는 함수이다.

SELECT IFNULL(10, 5);

10은 NULL이 아니므로 그대로 10이 반환된다.
IFNULL은 SQL Server의 ISNULL, 오라클의 NVL에 해당한다.
SELECT COALESCE(NULL, 5, 2);
COALESCE는 주어진 인수들 중 첫번째로 NULL이 아닌 인수를 반환한다.

첫번째로 NULL이 아닌 인수 5가 출력되는 것을 볼 수 있다.
SELECT COALESCE(5, NULL, 2);

이 경우에도 5가 첫번째로 NULL이 아닌 인수이므로 5가 출력된다.

네이버 사전을 찾아보니, coalesce는 합친다는 의미를 가지고 있다고 한다.
SELECT NULLIF(10, 20)
NULLIF는 두 인수가 같으면 NULL을, 그렇지 않으면 첫번째 인수를 반환한다.

SELECT NULLIF(20, 20)

두 인수가 같아 NULL이 반환되었다.
SELECT CASE WHEN showtime < 100 THEN '100분 미만'
WHEN showtime = 100 THEN '100분'
ELSE '100분 초과'
END AS 'CASE문'
FROM movies_movie;
이전의 쿼리문들에 비해 덩치가 좀 크다. 이것은 CASE문이라는 것인데, WHEN 다음에 조건을 지정하고, THEN 다음에 해당 조건을 만족할 시의 값을 지정한다. 여기에서, C++, 자바, 파이썬 등과는 달리 동등 비교가 ==가 아닌 =이라는 점을 주의해야 한다. 앞의 WHEN에 모두 해당하지 않으면 ELSE 다음에 있는 값이 된다. CASE문에는 끝을 나타내는 END가 있어야 한다.

실행 결과는 위와 같다.
SELECT CASE showtime WHEN 90 THEN '90분'
WHEN 100 THEN '100분'
ELSE '기타'
END AS 'CASE문'
FROM movies_movie;
CASE문에서 동등 비교일 때, 비교 대상을 CASE 다음에 지정할 수 있다. 이때 WHEN에는 =가 필요하지 않고, 값만 지정하면 된다. THEN, ELSE는 기존과 같고, END도 필요하다.

DECODE 함수는 SQLite에 없어서 실습해보지 못했다.
SQLite를 이용해 SQL 실습을 진행하며 느낀 것은, SQLite는 경량화되어서 가볍고 빠르지만, 그 대가로 몇몇 함수들을 잃었다는 것이다. 그래서 추가적으로 SQL 문제를 풀 수 있는 사이트 등에서 SQL을 연습할 필요가 있겠다는 생각이 들었다.
검색을 통해 3개의 사이트를 찾았다. 물론 이것말고도 더 있지만, 일단 내 입장에서 이 정도면 충분하지 않을까?
이렇게 해서, DB Browser for SQLite를 이용해 공부한 SQL 내용을 실습해보았다. 위에서 몇번이고 적었지만, SQLite는 경량화되어 가볍고 빠른 대신 지원하지 않는 함수들이 있다. 그래서 SQLite만으로 실습하기보다는 추가 학습의 사이트 등도 함께 이용하는 편이 더 좋을 것이라고 생각한다.