안녕하십니까? 8번째 강좌가 시작되었습니다. 이번 강좌에서부터 데이터베이스 관련한 강좌를 다룰 예정입니다. 본격적인 데이터베이스 개발에 앞서서 SQLite DB에 대해서 먼저 알아보도록 하겠습니다.

여담이지만, 제 강좌에 대한 호응도를 보니 일반 안드로이드 소스 개발 보다는 개발에 관련된 주변 정보(아이콘, 도서 소개, 리소스 해킹 등)에 대한 강좌가 더 인기가 있는 것 같습니다. 소스 부분이야 다른 곳에도 많이 있고 그래서 그런가요? 그래도 별 실력도 없는 제 강좌를 좋아해 주시는 분들이 계시니 맘은 뿌듯합니다.

각설하고.. SQLite 다들 잘 아시는지요? 저는 안드로이드 하면서 처음 만졌습니다. 맨날 오라클만 만지다가(하하 제가 얘기했던가요? 저는 모바일 개발자도 아니고, 자바 개발자도 아니고 Oracle SQL, PL/SQL, 모델링만 주로 만지고 있습니다.) 가벼운 SQLite를 다루려고 보니, DDL 문법도 다른 점도 많고, SQL도 그렇고.. 그렇다고 자료도 별로 없고..

SQLite에 대해서 강좌를 쓰다보니, 내용이 너무 많아져서.. SQLite User Guide란 글들을 별도로 작성해서 제 블로그에 올려두었습니다. 본 강좌는 SQLite User Guide 제글의 일부분을 요약한 내용과 일부 Android 관련된 정보를 추가한 문서입니다. 강좌를 보시고 보다 SQLite에 대해서 상세히 알고싶으시면 다음의 링크나 제 블로그의 데이터베이스/SQLite 디렉토리를 클릭하셔서 살펴보시기 바랍니다.
(http://overoid.tistory.com/category/데이터베이스/SQLite)

전체 강좌 목차

[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1
[강좌A06] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part2
[강좌A07] 안드로이드 실전 개발 - 리소스 해킹
[강좌A08] 안드로이드 실전 개발 - SQLite


1. SQLite 소개

SQLite는 아시다시피 iphone이나 android에서 사용하는 파일 베이스 RDB입니다. 주요 특징으로는 Zero Configuration, Portabiliy, Compactness, Simplicity, Flexibility, Liberal Licensing, Reliability 라고 www.sqlite.org 사이트에 나와 있습니다. 좋은 말은 다 있는것 같습니다.

저희가 알아야 될 세부 특징은 다음과 같습니다. 중요합니다.

SQLite는 ANSI92의 기능을 대부분 지원하지만, 아래의 사항은 지원하지 않습니다.

1. RIGHT and FULL OUTER JOIN : LEFT OUTER JOIN만 지원합니다.
2. Complete ALTER TABLE Support : RENAME TABLE과 ADD COLUMN만 지원합니다. DROP COLUMN, ALTER COLUMN, ADD CONSTRAINT 등 다른 기능은 지원하지 않습니다.
3. Writing to VIEWs : SQLite에서 View는 read-only입니다.
4, GRANT and REVOKE : Sqlite에서 읽기/쓰기 권한은 OS 파일 시스템 권한을 사용합니다. 별도로 권한 부여 기능이 없습니다.

2. GUI Tools

SQLite 개발을 효과적으로 하기 위해서는 좋은 툴은 필수겠죠?

SQLite를 지원하는 GUI Tools 리스트는 아래 링크에 나와있습니다.
http://sb2.info/commercial-and-freeware-sqlite-tools-list-2/

무지 많습니다. 그 중 몇개를 골라서 사용해 봤는데.. 제가 추천해 드리고 싶은 툴은 다음과 같습니다.

SQLite Expert (http://www.sqliteexpert.com/)



여러 제품 중에서 가장 무난하게 사용할 수 있는 제품인 것 같습니다. 필요한 기능은 다 있고, UI도 그럭저럭 쓸만합니다. Personal Edition과 Professional Edition으로 나누어지며, Personal Edition은 무료입니다.
(향후 SQLite관련 포스트에서 저는 이 제품으로 테스트 하겠습니다.) Pro Edition의 Crack도 그리 어렵지 않게 구할 수 있습니다.

SQLite Maestro (http://www.sqlmaestro.com/products/sqlite/maestro/)


아마도 제가 본 Sqlite 관련 툴 중에서 가장 강력한 제품인 것 같습니다. 다른 기능은 몰라도 Reverse로 ERD를 만들어 주는 기능은 이 제품만이 가능합니다.

여담이지만 제가 Touch Call(터치콜) App 개발할 당시 Android SDK2.0 기준의 전화번호부 DB를 이용해야 하는데, 구글의 document만으로는 이해하기가 상당히 어려웠습니다. 출판되어 있는 책들도 다 이전 데이터베이스를 기준으로 작성된 책들이고.. 그때 이 제품을 평가판으로 설치해서 애뮬레이터에 있는 contact2 데이터베이스를 가지고 ERD를 만들어 보니… 훨씬 빠르게 이해를 할 수 있었습니다. 

다음 그림은 그 당시 사용했던 contact2 db의 ERD입니다.



좋은 제품이긴 하지만 애석하게도 free 버전은 없습니다. 모두 상용버전 밖에 없으며, 한달 Trial 버전만 사용할 수 있습니다. 현재 버전의 crack은 거의 구하기 힘들며, 이전 버전은 구하실 수는 있을 겁니다.

DeZign (http://www.datanamic.com/dezign/index.html)
현재 SQLite를 Forward/Backward Engineering를 완벽하게 지원하는 모델링 툴은 datanamic의 dezign이란 모델링 툴 밖에 없습니다. 근데, 막상 평가판 설치해보니..저희 정서와 맞지 않는 툴이더군요. 저희는 한글로 논리모델을 만들고 영문으로 물리모델을 만드는데, 이 툴은 그런 개념없이 논리/물리가 함께 처리되는 툴이라 바로 지워 버렸습니다. 조금 불편하더라도 DA#이나 ERWin으로 모델링을 하고, Forward Generation한 스크립트를 수정해서 사용하는 게 더 편리할 듯 합니다.

끝으로 SQLite 강좌에서 사용할 demo db에 대해서 소개를 하겠습니다. 위에서 얘기했듯이 저는 SQLlite Expert Personal 버전을 이용해서 테스트를 하도록 하겠습니다.

SQLite Expert Personal를 실행한 후 File > Open Demo Database를 실행합니다.
Dbdemos란 데이터베이스가 스키마브라우저에 나타납니다.


좌측 스키마 브라우저에 많은 테이블 리스트가 나옵니다. 저는 그중에서 아래 ERD에 나오는 몇 개의 테이블을 주로 사용해서 DML 테스트 등을 진행하도록 하겠습니다.


고객, 직원, 주문, 주문내역 등 간단한 ERD 구조입니다.
참고로 이 ERD는 Dbdemos SQLlite 데이터베이스를 SQLite Maestro를 이용하여 일부 테이블만 designer에 띄운 모습입니다.)

3. System Catalog

시스템 카탈로그를 조회할 수 있는 방법은
sqlite_master 테이블을 직접 조회하시면 됩니다. SQLITE_MASTER 테이블은 READ-ONLY 테이블입니다.

SELECT * FROM SQLITE_MASTER;

SQLITE_MASTER 테이블을 조회하시면 테이블, 인덱스, 트리거등 모든 정보와 DDL문까지 알 수가 있습니다.

SQLITE_MASTER 테이블 조회 외에 PRAGMA 명령어를 통해서도 필요한 정보를 알 수 있습니다.

PRAGMA table_info(table-name);
테이블 정보를 조회하는 명령어입니다.

PRAGMA table_info(COUNTRY);

cid name type notnull dflt_value pk
0 Name CHAR(24) 0  1
1 Capital CHAR(24) 0  0
2 Continent CHAR(24) 0  0
3 Area FLOAT 0  0
4 Population FLOAT 0  0


PRAGMA index_list(table-name); 인덱스 리스트를 볼 수 있습니다.
PRAGMA index_info(index-name); 인덱스 정보를 조회할 수 있습니다.
PRAGMA foreign_key_list(table-name); fk 리스틀 볼 수 있습니다.

4. DATATYPE

Sqlite가 지원하는 데이터 타입은 다음과 같습니다.

1. Null
2. Integer – 부호있는 정수, 실제 값에 따라 1byte에서 8byte까지 가변적으로 저장됨.
3. Real – 실수
4. Text – 문자열
5. BLOB – blob 데이터

실제적으로 저희가 테이블 생성시 DDL상에 VARCHAR(10)이라고 컬럼 사이즈를 정의해도 SQLITE는 TEXT 타입으로 만들어집니다. 그렇기 때문에 10자 이상의 데이터도 삽입이 가능합니다. 그러니, DDL 문 만들 때 구지 다른 데이터 타입을 외울 필요없이 위 타입만 알고 있으면 될 것 같습니다.

재미있는 것은 Data and Time 즉, 날짜 관련 데이터 타입이 따로 없다는 것입니다. DATETIME은 입력되는 값에 따라서 TEXT, REAL, INTEGER 타입으로 저장됩니다. http://www.sqlite.org/datatype3.html 보시면 어떤 데이터 타입이 어떤 식으로 변경되는지 알수가 있습니다.

5. CREATE TABLE


예제1)
CREATE TABLE TEST2 (
     _ID INTEGER NOT NULL,    
     CLASS TEXT NOT NULL,    
     VALUE TEXT,
     CONSTRAINT TEST2_PK PRIMARY KEY (_ID, CLASS)
);

SQLite에서는 ALTER TABLE 문에서 ADD CONSTRAINT 구문이 지원되지 않기 때문에 PRIMARY KEY, UNIQUE, CHECK등의 TABLE LEVEL의 CONSTRAINT는 위 문장처럼 CREATE TABLE 문 제일 하단에 기술해야 합니다.


예제2)
CREATE TABLE TEST1 (
     _ID  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
     TYPE TEXT NOT NULL DEFAULT 'HIGH',
     NAME TEX);

일련번호 채번은 위와 같이 AUTOINCREMENT 구문을 CRATE TABLE 의 해당 컬럼에 기술하면 자동 증가되는 일련번호를 사용할 수 있습니다.
AUTOINCREMENT 문을 가진 DDL이 최초 실행되면 SQLITE는 내부적으로 SQLITE_SEQUENCE 테이블을 생성합니다. 이 테이블은 NAME, SEQ 컬럼을 가진 테이블로 테이블마다 자동 증가되는 최종값을 가집니다. 재미있게도 SQLITE_SEQUENCE 테이블값을 직접 핸들링도 가능합니다.

6. SELECT

SELECT 는 워낙 잘 들 아시는 부분이라  한 두가지만 테스트 해 보도록 하겠습니다.
먼저, 오라클의 dual 테이블 같이 처리되는 구문은 MS-SQL 처럼 FROM 절 없이 사용하면 될 것 같습니다.

예)
SELECT 'A' a


페이징 처리 (ROWNUM, TOP과 유사한 기능)

페이징 처리시 오라클에서는 ROWNUM을 사용하고 MS-SQL에서는 TOP을 주로 사용들 합니다. 물론 요즘은 ROW_NUMBER() 함수를 더 많이 사용하신다구요?
SQLITE에서는 페이징 처리하기가 더 편리합니다. LIMIT와 OFFSET이 그것입니다.

SELECT CUSTNO, COMPANY, CITY
  FROM CUSTOMER 
 ORDER BY CUSTNO
 LIMIT 5 OFFSET 5;


ORDER BY와 상관없이 사용이 가능합니다. LIMIT에 값을 지정하면 화면에 출력할 레코드 개수를 지정할 수 있습니다. OFFSET은 건너뛸 레코드 수를 나타냅니다. 즉, 위의 쿼리는 CUSTNO로 정렬해서 나온 순서에서 6번째부터 5개의 레코드를 가져오는 SELECT문입니다. OFFSET은 생략도 가능합니다.

페이징은 편리하나 오라클의 ROWNUM 처럼 결과에 수치값을 나타낼 방법이 있다면 여러모로 편리한데 그런 기능은 아쉽습니다. 필요시에는 별도의 NUMBER를 가진 테이블을 하나 만들어서 조인해서 사용해야 할 듯 합니다.

7. UPDATE (JOIN)

UPDATE 구문도 워낙 잘 아시니 여기서는 JOIN UPDATE에 대해서만 확인해 보도록 하겠습니다.
테스트를 위해 CUSTOMER 테이블에 컬럼을 2개 추가했습니다.

/* JOIN UPDATE TEST */
-- ALTER TABLE에서 한번여 여러 컬럼 추가도 안됩니다.

ALTER TABLE CUSTOMER
ADD CAPITAL TEXT;

ALTER TABLE CUSTOMER
ADD COLUMN CONTINENT TEXT;

-- 오라클 스타일 JOIN UPDATE TEST => ERROR
UPDATE CUSTOMER C
   SET (CAPITAL, CONTINENT) = (SELECT CAPTIAL, CONTINENT  
                                WHERE COUNTRY K                               
                                  AND K.[Name] = C.COUNTRY)                                 
 WHERE COUNTRY IN (SELECT NAME FROM COUNTRY);

-- MS-SQL 스타일 JOIN UPDATE => ERROR
UPDATE CUSTOMER
   SET CAPITAL = K.CAPITAL,  
       CONTINENT = K.CONTINENT      
FROM CUSTOMER C JOIN COUNTRY K ON C.Country = K.NAME;


찾아보니 JOIN UPDATE 자체를 지원하지 않는답니다. 에구, 이게 안되는 건 좀 치명적인듯. 업데이트시에 어플에서 처리하는 방식밖에 안될 것 같습니다.

8. DELETE

DELETE시에도 LIMIT와 OFFSET 구문을 함께 사용할 수 있습니다. 이 부분은 편리한 듯~ , 참 UPDATE 구문에도 LIMIT와 OFFSET 구문을 함께 사용할 수 있습니다.

DELETE문도 단순 스타일은 워낙 잘 들 아시니 중복 레코드 제거 쿼리 테스트만 해보도록 하겠습니다.


/* 중복 제거 DELETE 문 테스트 */

-- 임시 테이블 생성.
create table t1 (
id integer,
name varchar(10)
);

-- 테스트 데이터 삽입.
insert into t1 values(1,'a');
insert into t1 values(2,'a');
insert into t1 values(2,'b');
insert into t1 values(1,'b');
insert into t1 values(1,'C');

-- 오라클에서 주로 사용하는 방식으로 테스트.
-- ANY 키워드를 지원하지 않아서 에러가 발생함.
DELETE FROM t1 A
WHERE ROWID > ANY (SELECT ROWID
                                     FROM t1 B
                                    WHERE A.id = B.id);

-- DELETE문에서 서브쿼리로 조인을 지원안해서 이것도 에러가 발생함.
DELETE FROM t1 A
WHERE ROWID > (SELECT MIN(ROWID)
                              FROM    t1 B
                             WHERE A.id = B.id);

-- 조금은 부하가 있지만, GROUP BY절을 이용한 NOT IN 서브쿼리로 중복 제거 가능.
DELETE FROM t1
WHERE ROWID NOT IN (SELECT MIN(ROWID)
                      FROM T1                     
                     GROUP BY ID);

9.Core Function

 coalesce(X,Y,...) coalesce() 함수는 Argument 중에서 첫번째로 Not Null인 Argument값을 리턴하는 함수입니다. 만일 모든 인자가 null이면 null을 리턴합니다.
 ifnull(X,Y) ifnull() 함수는 두 인자중에서 첫번째로 Not Null인 인자값을 리턴합니다. 만일, 둘다 null이면 null을 리턴합니다. Ifnull() 함수는 인자가 두개인 coalesce() 함수와 동일합니다.
 length(X) 길이값을 리턴하는 함수입니다. 만일 X 인자가 null이면 null을 리턴합니다.
 like(X,Y)
 like(X,Y,Z)
Like 함수는 “Y LIKE X [ESCAPE Z]”구문과 동일합니다.
 lower(x) 소문자로 변환 합니다.
 upper(X) 대문자로 변환 합니다.
 ltrimX)
 ltrim(X,Y)
ltrim(X)는 X 값 중 왼쪽편의 공백을 제거하는 함수입니다.
ltrim(X,Y)는 X 문자열중에서 Y에 나타난 값을 제일 좌측부터 제거하는 함수입니다.
 
select ltrim(" ZZZZabcZZ ", " aZ")
=> 좌측문자열에서 부터 공백,a,Z 문자열이 있으면 제거하고, 처음으로 공백,a,Z가 아닌 문자열부터 출력합니다. 즉, “bcZZ”가 출력됩니다.
ltrim(" ZZZZabcZZ ", " ") 는 ltrim(" ZZZZabcZZ ")과 동일합니다.
 rtrim(X)
 rtrim(X,Y)
rtrim(X)는 우측편 공백 제거
rtrim(X,Y)는 ltrim(X,Y)와 동일한 방식이지만 우측편부터 매칭되는 글자를 제거합니다. 예를들어 select rtrim(" ZZZZabcZZ ", " Z") 문장은 우측편부터 공백과 Z를 빼고 처음으로 공백과 Z가 아닌 글자, 즉 c 까지 글자가 나타납니다. 결과값: “ ZZZZabc”
 trim(X)
 trim(X,Y)
trim(X)는 양쪽 공백 제거
trim(X,Y) 는 Y에 해당되는 글자를 양쪽 끝에서 부터 제거하고 나머지 글자만 리턴함. 예) trim(" ZZZZabcZZ ", " Z") => “abc” trim은 ltrim과 rtrim을 각각 적용한것과 동일한 결과가 나타납니다.
 max(X,Y,..) 인자값들 중 최대값을 리턴합니다.

create table t1 (coll integer, col2 integer, col3 integer);

insert into t1 values(1,2,3);
insert into t1 values(5,3,1);

select max(col1, col2, col3) from t1;
 min(X,Y,...) 인자값들 중 최소값을 리턴합니다.
 nullif(X,Y) 두 인자가 서로 같으면 null을 리턴, 서로 다르면 X값을 리턴합니다.
nullif('x','y') => ‘x’ , nullif('x','x') => null 리턴
 quote(X) Quote()함수는 single quotation을 escape 해줍니다. ‘값을 ‘’ 로 변경합니다.
Insert나 update 시에 사용하면 유용할 듯 합니다.
select quote("girl's mouse") => 'girl''s mouse'
 random(*) -9223372036854775808 부터 +9223372036854775807 숫자 사이의 임의의 수를 리턴합니다.
 randomblob(N) N으로 지정된 bytes의 랜덤 바이너리 데이터를 생성합니다.
 hex(X) 바이너리 값을 hex 값으로 변경합니다.
select hex(randomblob(16))
 replace(X,Y,Z) X 문자열 중에서 Y문자열을 Z로 변경합니다.
select replace('1/12/2009','1','x') => "x/x2/2009"
 round(X)
 round(X,Y)
반올림 함수. Y는 소수점 자리. Y가 없으면 0으로 처리합니다.
Round(3.5) => 4, round(2.555, 2) => 2.56
 substr(X,Y)
 substr(X,Y,Z)
substr()함수는 X문자열 중에서 Y번째부터 시작해서 Z개수만큼 문자열을 가져오는 함수입니다. Z가 생략되면 Y번째 문자열부터 문자열 끝까지 리턴합니다. Y의 최소값은 1입니다. 자바에서는 0으로 시작하지만 SQLite에서는 1부터 인덱스가 시작합니다. 만일 Y가 –(마이너스)값이면 문자열 우측끝부터 카운팅을 시작합니다.
select substr("string", 1, 3) => str
select substr("string", 0, 3) => st
select substr("string", -1, 3) => g
select substr("string", -3, 3) => ing
select substr("string", 2) => tring
 typeof(X)  X 표현식에 대한 데이터 타입을 리턴합니다. 리턴값은 “null”, “integer”, “real”, “text”, “blob” 중의 하나입니다.

10. Aggregation Function (집합 함수)

avg(X) 그룹내의 Not Null값의 평균값을 리턴합니다. X 컬럼값이 문자열이나 BLOB이면 0으로 간주하고 처리합니다.
count(*)
count(X)
count(X)는 X가 Not Null값을 가진 레코드의 개수 리턴합니다.
count(*)는 그룹내의 모든 rows의 수를 리턴합니다.
group_concat(X)
group_concat(X,Y)
X가 not null인 경우 그룹내의 모든 문자열을 콤마(,)를 구분자로 해서 문자열을 합쳐서 리턴합니다. Y가 주어지면 Y값이 구분자로 처리됩니다.
max(X) 그룹내의 값들중 최대값을 리턴합니다.
min(X) 그룹내의 값들중 최소값을 리턴합니다. 모든값이 Null이면 Null을 리턴합니다.
sum(X)
total(X)
Sum과 total은 그룹내의 Not Null값의 합계를 리턴합니다. X의 모든 값이 Null인 경우 Sum()은 Null을 리턴하고 Total()은 0.0을 리턴합니다.

11. DateTime 관련 Keyword

먼저 SQLite에서는 DateTime관련해서 다음과 같은 세가지 키워드가 있습니다.
CURRENT_TIME : 현재 시간 (형식: 03:22:56) 다만, UTC 기준입니다.
CURRENT_DATE: 현재 날짜 (형식: 2010-08-25) UTC 기준.
CURRENT_DATETIME : 현재 날자 및 시간 (형식: 2010-08-25 03:23:37) UTC 기준.

UTC 기준 날짜 및 시간이라서 사용할때는 로컬시간으로 다시 변환작업이 필요할 듯 보입니다. DATETIME 관련 함수들이 있어 크게 유용해 보이지는 않습니다.

12. DateTime 관련 함수.

DateTime 관련 함수는 다음의 5가지가 있습니다.

date(timestring, modifier, modifier, ...)  : 날짜 함수
time(timestring, modifier, modifier, ...)  : 시간 함수
datetime(timestring, modifier, modifier, ...)  :날짜/시간 함수
julianday(timestring, modifier, modifier, ...)  : 율리우스력 함수
strftime(format, timestring, modifier, modifier, ...)  : 날짜 포맷팅 함수


위 다섯개의 함수 인자 중 timestring 파라미터에로 들어갈 수 있는 날짜 형식은 아래와 같습니다.

1. YYYY-MM-DD
2. YYYY-MM-DD HH:MM
3. YYYY-MM-DD HH:MM:SS
4. YYYY-MM-DD HH:MM:SS.SSS
5. YYYY-MM-DDTHH:MM
6. YYYY-MM-DDTHH:MM:SS
7. YYYY-MM-DDTHH:MM:SS.SSS
8. HH:MM
9. HH:MM:SS
10. HH:MM:SS.SSS
11. now
12. DDDDDDDDDD

strftime 함수에 사용되는 format에 사용할 수 있는 값들은 다음과 같습니다.

%d    day of month: 00
%f     fractional seconds: SS.SSS
%H    hour: 00-24
%j     day of year: 001-366
%J     Julian day number
%m    month: 01-12
%M    minute: 00-59
%s     seconds since 1970-01-01
%S     seconds: 00-59
%w     day of week 0-6 with sunday==0
%W     week of year: 00-53
%Y     year: 0000-9999
%%    %

함수의 modifer에 들어갈 수 있는 값들은 다음과 같습니다.

1. NNN days
2. NNN hours
3. NNN minutes
4. NNN.NNNN seconds
5. NNN months
6. NNN years
7. start of month
8. start of year
9. start of day
10. weekday N
11. unixepoch
12. localtime
13. utc


잘 안 와 닿는듯 합니다. 아래 샘플 코드를 보면 이해가 가실 겁니다.

--UTC 기준의 현재 날짜/시간
select datetime('now');
2010-08-25 04:01:46

-- 로컬 기준의 현재 날짜/시간
select datetime('now','localtime');
2010-08-25 13:02:30

--현재 로컬 기준 시간에서 10분 3.5초를 더한 시간.
select datetime('now','localtime','+3.5 seconds','+10 minutes');
2010-08-25 13:14:15

--현재 로컬 시간에 3.5초를 더하고 날짜는 돌아오는 화요일 (weekday == 0 이 일요일입니다.)
select datetime('now','localtime','+3.5 seconds','weekday 2');
2010-08-31 13:05:39

--현재 달의 마지막 날짜
SELECT date('now','start of month','+1 month','-1 day','localtime');
2010-08-31

--2004-01-01 02:34:56초부터 현재까지의 총 초
SELECT strftime('%s','now') - strftime('%s','2004-01-01 02:34:56');
209785028

--현재날짜/시간 기준에서 올해 9번째달의 첫번째 화요일
SELECT date('now','start of year','+9 months','weekday 2');
2010-10-05

-- 날짜 포맷 스타일 변경
select strftime("%Y/%m/%d %H:%M:%S",'now','localtime');
2010/08/27 09:17:22



이것으로서 간략하게 나마 SQLite에 대해서 알아보았습니다.
보다 상세히 SQLite의 기능에 대해서 알고싶으시면 제 블로그의 SQLite User Guide를 참고하시기 바랍니다.

SQLite User Guide 블로그 포스트

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, 시스템 카탈로그, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger

  1. 이유식 2010.08.30 14:20 신고

    안드로이드 2.1버전에서 foreign key가 적용되나요?

    • 보고픈 2010.08.30 17:23 신고

      제가 알기로 현재 SQLite 버전은 3.7.0.1. 입니다.
      Android 1.5, 1.6, 2.1에는 SQLite 3.5.9 버전이 탑재되어 있습니다.
      Android 2.2에는 SQLite 3.6.22가 탑재되어 있구요.
      FK는 SQLite 3.6.19에 추가되었습니다.

      그러니 Android 2.1에는 FK가 지원되지 않을겁니다.

  2. 2012.12.10 14:37

    비밀댓글입니다

SQLite Databae에 대한 일곱번째 포스트입니다. 이번 포스트에서는 SQLite의 Trigger에 대해서 다뤄보도록 하겠습니다.

SQLite User Guide 포스트 목차

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, 시스템 카탈로그, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)

CREATE TRIGGER

CREATE TRIGGER 문으로 트리거를 생성할 수 있습니다. 다들 아시겠지만, 트리거는 INSERT, UPDATE, DELETE 문장이 실행될 때 자동으로 실행되는 코드입니다. 현재 SQLite는 FOR EACH ROW (각 레코드 마다 트리거 코드가 실행됨) 만 지원합니다. 즉, SQL 문장 단위의 트리거는 지원하지 않습니다.

트리거 코드에서는 각 레코드가 INSERT/UPDATE/DELETE 될때 OLD 및 NEW 키워드를 사용하여 변경전 데이터 혹은 변경되는 데이터를 참조할 수 있습니다. 물론 INSERT에서는 NEW 키워드만, DELETE에서는 OLD 키워드만 유효합니다.

특이하게도 SQLite에서는 View에도 트리거를 설정할 수 있는데, INSTEAD OF 트리거로 만들 수 있습니다. SQLite에서는 View가 Read Only이므로 뷰에 대한 I/U/D 작업이 불가능 하지만,  View에 INSTEAD OF 트리거를 만들어서 트리거에서 I/U/D 작업을 수행하는 것은 가능합니다.

SQLite에서는 트리거를 I/U/D에 대해 각각 만들어야 합니다. 오라클 같은 경우는 하나의 트리거에서 모든 것을 처리할 수 있지만 SQLite에서는 그렇지 않아서 조금 귀챦기는 할 것 같습니다.

샘플 코드를 보면서 트리거에 대해서 확인하도록 하겠습니다.

/* SQLite Trigger Test Code */

-- Trigger test를 위해서 user table과 user_log  table을 생성합니다.
create table user (
  id integer primary key autoincrement,
  name varchar(10),
  password varchar(10)
);

create table user_log (
  log_no integer primary key autoincrement,
  id integer,
  name_before varchar(10),
  name_after varchar(10),
  password_before varchar(10),
  password_after varchar(10),
  change_date text, 
  flag varchar(1)
); 

-- update에 대한 before trigger를 생성합니다.
CREATE TRIGGER user_trigger_before_update BEFORE UPDATE ON user
BEGIN
     INSERT INTO user_log(id, name_before,name_after, password_before, password_after, change_date, flag)     
     VALUES(old.id, old.name, new.name, old.password, new.password, datetime('now','localtime'),'U');
END;

-- insert에 대한 before trigger를 생성합니다.
CREATE TRIGGER user_trigger_before_insert BEFORE INSERT ON user
BEGIN
     INSERT INTO user_log(id, name_before,name_after, password_before, password_after, change_date, flag)     
     VALUES(new.id, null, new.name, null, new.password, datetime('now','localtime'),'I');
END;

-- delete에 대한 before trigger를 생성합니다.
CREATE TRIGGER user_trigger_before_delete BEFORE DELETE ON user
BEGIN
     INSERT INTO user_log(id, name_before,name_after, password_before, password_after, change_date, flag)     
     VALUES(old.id, old.name, null, old.password, null, datetime('now','localtime'),'D');
END;

-- test를 위한 데이터 insert
INSERT INTO USER(name, password) VALUES('overoid','1234');
INSERT INTO USER(name, password) VALUES('test','12345');
UPDATE USER SET NAME = '보고픈'  WHERE ID = 2;
UPDATE USER SET password = 'abcd' WHERE ID =1;
DELETE FROM USER WHERE id = 2;

-- 데이터 확인 (null 데이터를 명확하게 표기하기 위해서..ifnull 함수를 사용함)
SELECT log_no, id, ifnull(name_before,'<null>') name_before, ifnull(name_after,'<null>') name_after,
       ifnull(password_before,'<null>') password_before, ifnull(password_after,'<null>') password_after, change_date, flag
  FROM user_log;

-- result
log_no id name_before name_after password_before password_after change_date flag
1 -1 <null> overoid <null> 1234 2010-08-25 17:02:00 I
2 -1 <null> test <null> 12345 2010-08-25 17:02:02 I
3 2 test 보고픈  12345 12345 2010-08-25 17:02:04 U
4 1 overoid overoid 1234 abcd 2010-08-25 17:02:05 U
5 2 보고픈 <null> 12345 <null> 2010-08-25 17:02:07 D

-- 데이터는 정상적으로 나옵니다.
-- 다만, autoincrement로 생성한 컬럼에 대해서 insert 작업시 실행되는 before trigger에서 NEW.ID 값을
-- 제대로 가져오지 못하고 -1로 입력되는 것을 볼 수 있습니다.

-- 위 문제를 해결하기 위해서 INSERT에 대해서 AFTER 트리거를 생성합니다.
CREATE TRIGGER user_trigger_before_after AFTER INSERT ON user
BEGIN
     INSERT INTO user_log(id, name_before,name_after, password_before, password_after, change_date, flag)     
     VALUES(new.id, null, new.name, null, new.password, datetime('now','localtime'),'I');
END;

-- 데이터를 삽입합니다.
INSERT INTO USER(name, password) VALUES('after','1234');

SELECT log_no, id, ifnull(name_before,'<null>') name_before, ifnull(name_after,'<null>') name_after,
       ifnull(password_before,'<null>') password_before, ifnull(password_after,'<null>') password_after, change_date, flag
  FROM user_log;

-- result
log_no id name_before name_after password_before password_after change_date flag
1 -1 <null> overoid <null> 1234 2010-08-25 17:02:00 I
2 -1 <null> test <null> 12345 2010-08-25 17:02:02 I
3 2 test 보고픈  12345 12345 2010-08-25 17:02:04 U
4 1 overoid overoid 1234 abcd 2010-08-25 17:02:05 U
5 2 보고픈 <null> 12345 <null> 2010-08-25 17:02:07 D
6 -1 <null> after <null> 1234 2010-08-25 17:03:26 I
<null> after <null> 1234 2010-08-25 17:03:26 I

-- after trigger로 변경했더니, autoincrement 컬럼에 대해서 값을 제대로 가져오는 군요.

이것으로 SQLite Trigger에 대한 일곱번째 포스트를 마칩니다. 다음번 포스트에서는 SQLite의 Tranaction 및 Lock에 기능에 대해서 살펴보도록 하겠습니다.

 

SQLite Databae에 대한 여섯번째 포스트입니다. 이번 포스트에서는 SQLite의 DateTime 관련 함수 및 Formatting 에 대해서 다뤄보도록 하겠습니다. 이 부분은 다른 DBMS와 많이 다르고 자주 사용하는 함수들이라 몇가지 표현법은 꼭 익혀야 될 점인것 같습니다.

SQLite User Guide 포스트 목차

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, 시스템 카탈로그, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)


1. DateTime 관련 Keyword

먼저 SQLite에서는 DateTime관련해서 다음과 같은 세가지 키워드가 있습니다.
CURRENT_TIME : 현재 시간 (형식: 03:22:56) 다만, UTC 기준입니다.
CURRENT_DATE: 현재 날짜 (형식: 2010-08-25) UTC 기준.
CURRENT_DATETIME : 현재 날자 및 시간 (형식: 2010-08-25 03:23:37) UTC 기준.

UTC 기준 날짜 및 시간이라서 사용할때는 로컬시간으로 다시 변환작업이 필요할 듯 보입니다. DATETIME 관련 함수들이 있어 크게 유용해 보이지는 않습니다.

2. DateTime 관련 함수.

DateTime 관련 함수는 다음의 5가지가 있습니다.

date(timestring, modifier, modifier, ...)  : 날짜 함수
time(timestring, modifier, modifier, ...)  : 시간 함수
datetime(timestring, modifier, modifier, ...)  :날짜/시간 함수
julianday(timestring, modifier, modifier, ...)  : 율리우스력 함수
strftime(format, timestring, modifier, modifier, ...)  : 날짜 포맷팅 함수


위 다섯개의 함수 인자 중 timestring 파라미터에로 들어갈 수 있는 날짜 형식은 아래와 같습니다.

1. YYYY-MM-DD
2. YYYY-MM-DD HH:MM
3. YYYY-MM-DD HH:MM:SS
4. YYYY-MM-DD HH:MM:SS.SSS
5. YYYY-MM-DDTHH:MM
6. YYYY-MM-DDTHH:MM:SS
7. YYYY-MM-DDTHH:MM:SS.SSS
8. HH:MM
9. HH:MM:SS
10. HH:MM:SS.SSS
11. now
12. DDDDDDDDDD

strftime 함수에 사용되는 format에 사용할 수 있는 값들은 다음과 같습니다.

%d    day of month: 00
%f     fractional seconds: SS.SSS
%H    hour: 00-24
%j     day of year: 001-366
%J     Julian day number
%m    month: 01-12
%M    minute: 00-59
%s     seconds since 1970-01-01
%S     seconds: 00-59
%w     day of week 0-6 with sunday==0
%W     week of year: 00-53
%Y     year: 0000-9999
%%    %

함수의 modifer에 들어갈 수 있는 값들은 다음과 같습니다.

1. NNN days
2. NNN hours
3. NNN minutes
4. NNN.NNNN seconds
5. NNN months
6. NNN years
7. start of month
8. start of year
9. start of day
10. weekday N
11. unixepoch
12. localtime
13. utc


잘 안 와 닿는듯 합니다. 아래 샘플 코드를 보면 이해가 가실 겁니다.

--UTC 기준의 현재 날짜/시간
select datetime('now');
2010-08-25 04:01:46

-- 로컬 기준의 현재 날짜/시간
select datetime('now','localtime');
2010-08-25 13:02:30

--현재 로컬 기준 시간에서 10분 3.5초를 더한 시간.
select datetime('now','localtime','+3.5 seconds','+10 minutes');
2010-08-25 13:14:15

--현재 로컬 시간에 3.5초를 더하고 날짜는 돌아오는 화요일 (weekday == 0 이 일요일입니다.)
select datetime('now','localtime','+3.5 seconds','weekday 2');
2010-08-31 13:05:39

--현재 달의 마지막 날짜
SELECT date('now','start of month','+1 month','-1 day','localtime');
2010-08-31

--2004-01-01 02:34:56초부터 현재까지의 총 초
SELECT strftime('%s','now') - strftime('%s','2004-01-01 02:34:56');
209785028

--현재날짜/시간 기준에서 올해 9번째달의 첫번째 화요일
SELECT date('now','start of year','+9 months','weekday 2');
2010-10-05

-- 날짜 포맷 스타일 변경
select strftime("%Y/%m/%d %H:%M:%S",'now','localtime');
2010/08/27 09:17:22




 

  1. 김승배 2011.03.19 15:18 신고

    DateTime 쿼리 관련해서..
    Select date(birthday) From TableA 와 같이 테이블의 DateTime column을 넣어주면 값이 안나오던데요..
    혹시 해결 방법을 아시나요?


SQLite Databae에 대한 다섯번째 포스트입니다. 이번 포스트에서는 SQLite의 Core 함수에 대해서 다뤄보도록 하겠습니다.

SQLite User Guide 포스트 목차

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, 시스템 카탈로그, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)

1.Core Function

 coalesce(X,Y,...) coalesce() 함수는 Argument 중에서 첫번째로 Not Null인 Argument값을 리턴하는 함수입니다. 만일 모든 인자가 null이면 null을 리턴합니다.
 ifnull(X,Y) ifnull() 함수는 두 인자중에서 첫번째로 Not Null인 인자값을 리턴합니다. 만일, 둘다 null이면 null을 리턴합니다. Ifnull() 함수는 인자가 두개인 coalesce() 함수와 동일합니다.
 length(X) 길이값을 리턴하는 함수입니다. 만일 X 인자가 null이면 null을 리턴합니다.
 like(X,Y)
 like(X,Y,Z)
Like 함수는 “Y LIKE X [ESCAPE Z]”구문과 동일합니다.
 lower(x) 소문자로 변환 합니다.
 upper(X) 대문자로 변환 합니다.
 ltrimX)
 ltrim(X,Y)
ltrim(X)는 X 값 중 왼쪽편의 공백을 제거하는 함수입니다.
ltrim(X,Y)는 X 문자열중에서 Y에 나타난 값을 제일 좌측부터 제거하는 함수입니다.
 
select ltrim(" ZZZZabcZZ ", " aZ")
=> 좌측문자열에서 부터 공백,a,Z 문자열이 있으면 제거하고, 처음으로 공백,a,Z가 아닌 문자열부터 출력합니다. 즉, “bcZZ”가 출력됩니다.
ltrim(" ZZZZabcZZ ", " ") 는 ltrim(" ZZZZabcZZ ")과 동일합니다.
 rtrim(X)
 rtrim(X,Y)
rtrim(X)는 우측편 공백 제거
rtrim(X,Y)는 ltrim(X,Y)와 동일한 방식이지만 우측편부터 매칭되는 글자를 제거합니다. 예를들어 select rtrim(" ZZZZabcZZ ", " Z") 문장은 우측편부터 공백과 Z를 빼고 처음으로 공백과 Z가 아닌 글자, 즉 c 까지 글자가 나타납니다. 결과값: “ ZZZZabc”
 trim(X)
 trim(X,Y)
trim(X)는 양쪽 공백 제거
trim(X,Y) 는 Y에 해당되는 글자를 양쪽 끝에서 부터 제거하고 나머지 글자만 리턴함. 예) trim(" ZZZZabcZZ ", " Z") => “abc” trim은 ltrim과 rtrim을 각각 적용한것과 동일한 결과가 나타납니다.
 max(X,Y,..) 인자값들 중 최대값을 리턴합니다.

create table t1 (coll integer, col2 integer, col3 integer);

insert into t1 values(1,2,3);
insert into t1 values(5,3,1);

select max(col1, col2, col3) from t1;
 min(X,Y,...) 인자값들 중 최소값을 리턴합니다.
 nullif(X,Y) 두 인자가 서로 같으면 null을 리턴, 서로 다르면 X값을 리턴합니다.
nullif('x','y') => ‘x’ , nullif('x','x') => null 리턴
 quote(X) Quote()함수는 single quotation을 escape 해줍니다. ‘값을 ‘’ 로 변경합니다.
Insert나 update 시에 사용하면 유용할 듯 합니다.
select quote("girl's mouse") => 'girl''s mouse'
 random(*) -9223372036854775808 부터 +9223372036854775807 숫자 사이의 임의의 수를 리턴합니다.
 randomblob(N) N으로 지정된 bytes의 랜덤 바이너리 데이터를 생성합니다.
 hex(X) 바이너리 값을 hex 값으로 변경합니다.
select hex(randomblob(16))
 replace(X,Y,Z) X 문자열 중에서 Y문자열을 Z로 변경합니다.
select replace('1/12/2009','1','x') => "x/x2/2009"
 round(X)
 round(X,Y)
반올림 함수. Y는 소수점 자리. Y가 없으면 0으로 처리합니다.
Round(3.5) => 4, round(2.555, 2) => 2.56
 substr(X,Y)
 substr(X,Y,Z)
substr()함수는 X문자열 중에서 Y번째부터 시작해서 Z개수만큼 문자열을 가져오는 함수입니다. Z가 생략되면 Y번째 문자열부터 문자열 끝까지 리턴합니다. Y의 최소값은 1입니다. 자바에서는 0으로 시작하지만 SQLite에서는 1부터 인덱스가 시작합니다. 만일 Y가 –(마이너스)값이면 문자열 우측끝부터 카운팅을 시작합니다.
select substr("string", 1, 3) => str
select substr("string", 0, 3) => st
select substr("string", -1, 3) => g
select substr("string", -3, 3) => ing
select substr("string", 2) => tring
 typeof(X)  X 표현식에 대한 데이터 타입을 리턴합니다. 리턴값은 “null”, “integer”, “real”, “text”, “blob” 중의 하나입니다.

2. Aggregation Function (집합 함수)

avg(X) 그룹내의 Not Null값의 평균값을 리턴합니다. X 컬럼값이 문자열이나 BLOB이면 0으로 간주하고 처리합니다.
count(*)
count(X)
count(X)는 X가 Not Null값을 가진 레코드의 개수 리턴합니다.
count(*)는 그룹내의 모든 rows의 수를 리턴합니다.
group_concat(X)
group_concat(X,Y)
X가 not null인 경우 그룹내의 모든 문자열을 콤마(,)를 구분자로 해서 문자열을 합쳐서 리턴합니다. Y가 주어지면 Y값이 구분자로 처리됩니다.
max(X) 그룹내의 값들중 최대값을 리턴합니다.
min(X) 그룹내의 값들중 최소값을 리턴합니다. 모든값이 Null이면 Null을 리턴합니다.
sum(X)
total(X)
Sum과 total은 그룹내의 Not Null값의 합계를 리턴합니다. X의 모든 값이 Null인 경우 Sum()은 Null을 리턴하고 Total()은 0.0을 리턴합니다.

/* Aggregation Function Test */

create table t1 (
type integer,
id integer,
name varchar(10),
value real,
data text
);

insert into t1 values(1,1,'a',0.0,'1');
insert into t1 values(1,2,'b',3.5,NULL);
insert into t1 values(1,3,NULL,3.1,'2');
insert into t1 values(1,4,'c',3.5,'10');
insert into t1 values(1,5,'d',NULL,NULL);

-- count(*)와 count(X)의 차이 비교
select count(*), count(name), count(data)
from t1
group by type

-- result
count(*) count(name) count(data)
5 4 3

-- group_concat test query
select group_concat(id,' > ') as exp
from t1;

-- result
exp
1 > 2 > 3 > 4 > 5

select group_concat(data,' > ') exp
from t1
group by type

-- result
exp
1 > 2 > 10

/* sum(), tatal() function test */
select sum(data), total(data)
from t1
where id in (2,5)
group by type;

-- result
sum(data) total(data)
<null>    0

생각보다 SQLite에서 지원되는 내장 함수가 너무 적습니다. SQLite는 각 언어별로 확장 기능을 가지고 있긴 하지만, 쉽게 사용하는게 아니라서.. 한번의 SQL로 처리 가능한 것을 상당 부분 프로그램 코드에서 처리해야 하지 않을까 싶습니다. 그럼에도 불구하고 group_concat() 같은 기능의 함수는 Oracle에서도 11g가 되어서야 내장 함수로 제공할 정도로 대부분의 DB에서 제공하지 않는 함수인데.. SQLite에서 구현이 되어 있다는 게 놀라울 따름입니다.

이것으로 SQLite Function 부분을 마치며, 다음 포스트에서는 DateTime 및 DateTime Formatting에 대해서 알아보도록 하겠습니다.

  1. suroMind 2011.04.06 17:00 신고

    좋은 정보 감사합니다. 출처 남기고 퍼가겠습니다.
    http://blog.suromind.com/43


SQLite Databae에 대한 네번째 포스트입니다. DML은 대부분의 개발자들이 기본 SELECT/INSERT/UPDATE/DELETE에 대해서 워낙 잘 들 알고 계시므로 이번 포스트에서는 DML 관련하여 몇가지만 다루도록 하겠습니다.

SQLite User Guide 포스트 목차

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, 시스템 카탈로그, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)

1. EXPLAIN / EXPLAIN QUERY PLAN

실행계획을 볼 수 있는 명령어입니다.
EXPLAIN 명령어는 세부적인 VDBE의 operation code를 볼 수 있습니다.
EXPLAIN QUERY PLAN 은 실행계획을 보여 줍니다.

예제 테스트는 SQLite 첫번째 포스트에서 알려드린 SQLite Expert Personal 버전의 demodb를 이용해서 테스트하도록 하겠습니다.

예제)
EXPLAIN
SELECT K.NAME
      ,C.[CustNo]     
      ,C.[City]     
      ,O.OrderNo     
      ,O.[SaleDate]     
      ,E.[EmpNo]     
      ,i.[ItemNo]     
      ,i.PartNo
  FROM COUNTRY K JOIN CUSTOMER C ON K.[Name] = C.COUNTRY
       JOIN ORDERS O ON C.[CustNo] = O.CustNo
       JOIN ITEMS I ON O.[OrderNo] = I.OrderNo
       JOIN EMPLOYEE E ON O.[EmpNo] = E.EmpNo         
  WHERE K.NAME = 'Canada' 
    AND E.EMPNO = 145;

-- result
addr opcode p1 p2 p3 p4 p5 comment
0 Trace 0 0 0 "" 00 
1 String8 0 1 0 Canada 00 
2 Integer 145 2 0 "" 00 
3 Goto 0 53 0 "" 00 
4 OpenRead 5 5 0 keyinfo(1,BINARY) 00 
5 OpenRead 4 27 0 0 00 
6 OpenRead 1 9 0 8 00 
7 OpenRead 2 18 0 5 00 
8 OpenRead 6 41 0 keyinfo(1,BINARY) 00 
9 OpenRead 3 13 0 3 00 
10 OpenRead 7 39 0 keyinfo(1,BINARY) 00 
11 SeekGe 5 45 1 1 00 
12 IdxGE 5 45 1 1 01 
13 MustBeInt 2 44 0 "" 00 
14 NotExists 4 44 2 "" 00 
-- 중략.

EXPLAIN QUERY PLAN
SELECT K.NAME
      ,C.[CustNo]     
      ,C.[City]     
      ,O.OrderNo     
      ,O.[SaleDate]     
      ,E.[EmpNo]     
      ,i.[ItemNo]     
      ,i.PartNo
  FROM COUNTRY K JOIN CUSTOMER C ON K.[Name] = C.COUNTRY
       JOIN ORDERS O ON C.[CustNo] = O.CustNo
       JOIN ITEMS I ON O.[OrderNo] = I.OrderNo
       JOIN EMPLOYEE E ON O.[EmpNo] = E.EmpNo         
  WHERE K.NAME = 'Canada' 
    AND E.EMPNO = 145;

order from detail
0 0 TABLE COUNTRY AS K WITH INDEX sqlite_autoindex_country_1
1 4 TABLE EMPLOYEE AS E USING PRIMARY KEY
2 1 TABLE CUSTOMER AS C
3 2 TABLE ORDERS AS O WITH INDEX idx_orders_CustNo
4 3 TABLE ITEMS AS I WITH INDEX idx_items_ByOrderNo

VDBE의 OP코드 분석(EXPLAIN 결과) 및 실행계획에 대해서는 향후 포스트에서 상세하게 다루도록 하겠습니다.
맘이 급하십니까? 당장 알기 원하시면 다음의 링크를 참고 하시기 바랍니다.

실행계획/옵티마이저.
http://www.sqlite.org/optoverview.html
http://www.sqlite.org/queryplanner.html

VDBE의 OP코드
http://www.sqlite.org/opcode.html

2. SELECT

SELECT 는 워낙 잘 들 아시는 부분이라  한 두가지만 테스트 해 보도록 하겠습니다.
먼저, 오라클의 dual 테이블 같이 처리되는 구문은 MS-SQL 처럼 FROM 절 없이 사용하면 될 것 같습니다.
예)
SELECT 'A' a

페이징 처리 (ROWNUM, TOP과 유사한 기능)

페이징 처리시 오라클에서는 ROWNUM을 사용하고 MS-SQL에서는 TOP을 주로 사용들 합니다. 물론 요즘은 ROW_NUMBER() 함수를 더 많이 사용하신다구요?
SQLITE에서는 페이징 처리하기가 더 편리합니다. LIMIT와 OFFSET이 그것입니다.

SELECT CUSTNO, COMPANY, CITY
  FROM CUSTOMER 
 ORDER BY CUSTNO
 LIMIT 5 OFFSET 5;

ORDER BY와 상관없이 사용이 가능합니다. LIMIT에 값을 지정하면 화면에 출력할 레코드 개수를 지정할 수 있습니다. OFFSET은 건너뛸 레코드 수를 나타냅니다. 즉, 위의 쿼리는 CUSTNO로 정렬해서 나온 순서에서 6번째부터 5개의 레코드를 가져오는 SELECT문입니다. OFFSET은 생략도 가능합니다.

페이징은 편리하나 오라클의 ROWNUM 처럼 결과에 수치값을 나타낼 방법이 있다면 여러모로 편리한데 그런 기능은 아쉽습니다. 필요시에는 별도의 NUMBER를 가진 테이블을 하나 만들어서 조인해서 사용해야 할 듯 합니다.

USING 구문

일반적으로 EQUAL JOIN 구문에서 ON 이하는 두 테이블간의 조인조건의 컬럼을 기술하는데, 두 컬럼의 컬럼명이 동일한 경우가 많습니다. 이때 간략하게 USING을 사용하여 쿼리를 줄일수 있습니다.

SELECT * FROM A INNER JOIN B ON A.COL1 = B.COL1;

-- 아래와 같이 위 문장을 USING을 사용하여 기술할 수 있음.
SELECT * FROM A INNER JOIN B USING(COL1);

-- 여러 테이블 조인에서의 USING 사용 예)
SELECT * FROM A JOIN B USING (c) JOIN C USING (e) JOIN D USING (g)

3. UPDATE (JOIN)

UPDATE 구문도 워낙 잘 아시니 여기서는 JOIN UPDATE에 대해서만 확인해 보도록 하겠습니다.
테스트를 위해 CUSTOMER 테이블에 컬럼을 2개 추가했습니다.

/* JOIN UPDATE TEST */
-- ALTER TABLE에서 한번여 여러 컬럼 추가도 안됩니다.

ALTER TABLE CUSTOMER
ADD CAPITAL TEXT;

ALTER TABLE CUSTOMER
ADD COLUMN CONTINENT TEXT;

-- 오라클 스타일 JOIN UPDATE TEST => ERROR
UPDATE CUSTOMER C
   SET (CAPITAL, CONTINENT) = (SELECT CAPTIAL, CONTINENT  
                                WHERE COUNTRY K                               
                                  AND K.[Name] = C.COUNTRY)                                 
 WHERE COUNTRY IN (SELECT NAME FROM COUNTRY);

-- MS-SQL 스타일 JOIN UPDATE => ERROR
UPDATE CUSTOMER
   SET CAPITAL = K.CAPITAL,  
       CONTINENT = K.CONTINENT      
FROM CUSTOMER C JOIN COUNTRY K ON C.Country = K.NAME;

찾아보니 JOIN UPDATE 자체를 지원하지 않는답니다. 에구, 이게 안되는 건 좀 치명적인듯. 업데이트시에 어플에서 처리하는 방식밖에 안될 것 같습니다.

4. DELETE

DELETE시에도 LIMIT와 OFFSET 구문을 함께 사용할 수 있습니다. 이 부분은 편리한 듯~ , 참 UPDATE 구문에도 LIMIT와 OFFSET 구문을 함께 사용할 수 있습니다.

DELETE문도 단순 스타일은 워낙 잘 들 아시니 중복 레코드 제거 쿼리 테스트만 해보도록 하겠습니다.


/* 중복 제거 DELETE 문 테스트 */

-- 임시 테이블 생성.
create table t1 (
id integer,
name varchar(10)
);

-- 테스트 데이터 삽입.
insert into t1 values(1,'a');
insert into t1 values(2,'a');
insert into t1 values(2,'b');
insert into t1 values(1,'b');
insert into t1 values(1,'C');

-- 오라클에서 주로 사용하는 방식으로 테스트.
-- ANY 키워드를 지원하지 않아서 에러가 발생함.
DELETE FROM t1 A
WHERE ROWID > ANY (SELECT ROWID
                                     FROM t1 B
                                    WHERE A.id = B.id);

-- DELETE문에서 서브쿼리로 조인을 지원안해서 이것도 에러가 발생함.
DELETE FROM t1 A
WHERE ROWID > (SELECT MIN(ROWID)
                              FROM    t1 B
                             WHERE A.id = B.id);

-- 조금은 부하가 있지만, GROUP BY절을 이용한 NOT IN 서브쿼리로 중복 제거 가능.
DELETE FROM t1
WHERE ROWID NOT IN (SELECT MIN(ROWID)
                      FROM T1                     
                     GROUP BY ID);

5. COMMENT

SQLITE에서는 주석구문으로 블록주석인 /* */ 구문과 단일 행 주석인 -- 을 사용합니다.

6. NULL 처리

NULL값에 대한 처리는 Oracle과 거의 유사하게 동작합니다. SQLITE에서 NULL에 대한 상세한 정보를 얻으실려면 http://www.sqlite.org/nulls.html 를 참조하시기 바랍니다. 샘플코드 따라서 해보시면 금방 이해가 가실듯 합니다.

7. INDEXED BY

실행계획 편에서나 다룰 내용이지만, 여기서 INDEXED BY에 대해서 조금 다루도록 하겠습니다.


테이블명 뒤에 INDEXED BY 구문을 사용하면 지정된 인덱스를 쿼리가 무조건 사용합니다.
지정한 인덱스의 분포도나 이런거 상관없습니다. 무조건 사용합니다. 오라클의 INDEX 힌트와 유사해 보이지만 다릅니다. 오라클의 힌트는 힌트를 잘못 사용하더라도 에러가 발생하지 않지만, SQLITE에서는 SQL 구문이 FAIL 됩니다. 인덱스명을 잘못 사용하는 경우는 물론이고, 인덱스를 타지 않는 상황에서 인덱스를 지정해도 에러가 발생합니다.
아래 쿼리를 보겠습니다.

PRAGMA INDEX_LIST(COUNTRY); /* COUNTRY 테이블의 인덱스 현황입니다. */

seq name unique
0 sqlite_autoindex_country_1 1
--AUTOINCREMENT로 지정된 UNIQUE 인덱스가 하나 존재합니다.

--현재 상황에서 테스트할 쿼리의 실행계획을 보도록 하겠습니다.
EXPLAIN QUERY PLAN
SELECT *
  FROM COUNTRY
 WHERE NAME LIKE 'c%'
   AND CAPITAL LIKE 'a%'

order from detail
0 0 TABLE COUNTRY
--테이블 FULL SCAN하는 실행계획이 생성되었습니다.

-- 인덱스를 추가하겠습니다.
CREATE INDEX COUNTRY_IX1 ON COUNTRY (NAME);
CREATE INDEX COUNTRY_IX2 ON COUNTRY (CAPITAL);

PRAGMA INDEX_LIST(COUNTRY);

seq name unique
0 COUNTRY_IX2 0
1 COUNTRY_IX1 0
2 sqlite_autoindex_country_1 1
-- 추가된 인덱스가 보입니다.
-- 이전에 실행한 쿼리를 다시 실행해 보도록 하겠습니다.

EXPLAIN QUERY PLAN
SELECT *
  FROM COUNTRY
 WHERE NAME = 'c'
   AND CAPITAL = 'a';

order from detail
0 0 TABLE COUNTRY WITH INDEX sqlite_autoindex_country_1
-- 인덱스를 이용하지만, AUTOINCREMENT 컬럼에 대한 인덱스를 타고 검색합니다. 아마도 순차적으로 검색하려고 그런가 봅니다.

-- COUNTRY_IX2를 사용하도록 쿼리를 짠후 실행계획을 보면..
EXPLAIN QUERY PLAN
SELECT *
  FROM COUNTRY INDEXED BY COUNTRY_IX1
 WHERE NAME = 'c'
   AND CAPITAL = 'a'

-- 인덱스를 사용하는 쿼리로 변경이 되었습니다.
order from detail
0 0 TABLE COUNTRY WITH INDEX COUNTRY_IX1

-- 재미있는 상황이 있군요.
EXPLAIN QUERY PLAN
SELECT *
  FROM COUNTRY INDEXED BY COUNTRY_IX2
 WHERE NAME LIKE 'c%'
   AND CAPITAL LIKE 'a%'

/*
= 검색을 LIKE 검색으로 바꾸니 Cannot use index : COUNTRY_IX2란 에러가 발생했습니다. 일반적으로 오라클이나 MS-SQL은 단방향 LIKE 검색의 경우 인덱스를 태울 수 있습니다. SQLITE에서는 에러가 나는걸 보니 LIKE 검색은 인덱스를 이용하지 않나 봅니다. (이 부분은 향후 실행계획 부분에서 다시 자세히 보도록 하겠습니다.) */

본 포스트에서는 일반적인 DML에 대한 강좌 형식 보다는 SQLite에 대한 특징적인 면들을 살펴보았습니다. 그래서 insert 구문은 아예 다루지도 않았습니다. SQLite에 대한 기본 SQL 문법에 대해 더 궁금하시면 다음 사이트를 살펴보시기 바랍니다. http://www.sqlite.org/lang.html

다음 포스트에서는 SQLite Core Function 사용법에 대해 다뤄 보도록 하겠습니다. 

SQLite User Guide 세번째 포스트입니다.  이번에는 CREATE TABLE, CREATE INDEX 등의 DDL 문에 대해 알아보도록 하겠습니다.

SQLite User Guide 포스트 목차

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, 시스템 카탈로그, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)

SQLite DDL문은 특별한 게 없어서 대부분 잘 아시리라 생각됩니다. DDL문에 대해 전혀 모르신다구요? 상관없습니다. GUI 툴을 사용하셔서 테이블이나 인덱스를 만드시고 DDL문을 코드에 붙이셔도 됩니다. 본 포스트에서는 모든 DDL문을 다루기 보다는 주요한 몇가지와 SQLite DB에서의 특징적인 면을 살펴보도록 하겠습니다.

1. CREATE TABLE


예제1)
CREATE TABLE TEST2 (
     _ID INTEGER NOT NULL,    
     CLASS TEXT NOT NULL,    
     VALUE TEXT,
     CONSTRAINT TEST2_PK PRIMARY KEY (_ID, CLASS)
);

SQLite에서는 ALTER TABLE 문에서 ADD CONSTRAINT 구문이 지원되지 않기 때문에 PRIMARY KEY, UNIQUE, CHECK등의 TABLE LEVEL의 CONSTRAINT는 위 문장처럼 CREATE TABLE 문 제일 하단에 기술해야 합니다.


예제2)
CREATE TABLE TEST1 (
     _ID  INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
     TYPE TEXT NOT NULL DEFAULT 'HIGH',
     NAME TEX);

일련번호 채번은 위와 같이 AUTOINCREMENT 구문을 CRATE TABLE 의 해당 컬럼에 기술하면 자동 증가되는 일련번호를 사용할 수 있습니다.
AUTOINCREMENT 문을 가진 DDL이 최초 실행되면 SQLITE는 내부적으로 SQLITE_SEQUENCE 테이블을 생성합니다. 이 테이블은 NAME, SEQ 컬럼을 가진 테이블로 테이블마다 자동 증가되는 최종값을 가집니다. 재미있게도 SQLITE_SEQUENCE 테이블값을 직접 핸들링도 가능합니다.

/* SQLITE_SEQUENCE TEST */

--위에서 만든 TEST1 테이블에 데이터를 2개 넣었습니다.
INSERT INTO TEST1(NAME) VALUES('A');
INSERT INTO TEST1(NAME) VALUES('B');

SELECT * FROM SQLITE_SEQUENCE;

-- 현재 SEQ = 2인 SQLITE_SEQUENCE 테이블을 30으로 업데이트합니다.
UPDATE SQLITE_SEQUENCE
SET SEQ = 30
WHERE NAME = 'TEST1';

-- 다시 레코드를 INSERT한 후 TEST1 데이터를 조회해보니 마지막에 들어간 레코드값은 31로 들어갑니다.
INSERT INTO TEST1(NAME) VALUES('B');

SELECT * FROM TEST1;

_ID TYPE NAME
1 HIGH A
2 HIGH B
31 HIGH B

TEMP TABLE

/* TEMP TABLE CREATION */
CREATE TEMP TABLE COUTRY_TEMP
AS
SELECT *
  FROM COUNTRY     
 WHERE 1 = 2;

CREATE TABLE 구문 사이에 TEMP, TEMPORARY 명령어를 넣으시면 임시 테이블이 생성됩니다. 임시테이블은 Connection이 끊어지면 테이블이 없어집니다. 위 예에서 WHERE 1=2 라고 기술하시면 WHERE 조건이 거짓이 되어 테이블 생성시 COUNTRY 테이블과 동일한 테이블이 만들어지지만 데이터는 들어가지 않습니다.
앞의 PRAGMA 편 포스트에서 시스템 카탈로그를 조회할 수 있는 테이블이 SQLITE_MASTER라고 했는데, 임시테이블을 만들면 이 임시테이블은 SQLITE_MASTER로 조회해도 나타나지 않습니다.
임시테이블은 SQLITE_TEMP_MASTER를 조회하시면 조회가 가능합니다.

COLLATE

추가적으로 COLLATE에 대해서도 알아보도록 하겠습니다. TABLE 정의시 COLLATE를 지정할 수 있습니다. 값은 BINARY, RTRIM, NOCASE 구문이 가능한데, 각각의 케이스에 따라서 값을 비교하는 비교연산자의 결과에 영향을 미칠 수 있습니다. (http://www.sqlite.org/datatype3.html)

COLLATE로 지정가능 한 값.

BINARY – 내부적으로 텍스트 인코딩과 상관없이 memcmp() 함수를 이용하여 텍스트를 비교합니다.
NOCASE – BINARY와 유사하지만 영문자의 경우 대소문자를 동일하게 처리, 즉, 대소문자 구별을 하지 않습니다.
RTRIM – BINARY와 동일하지만 비교하는 문자열의 끝부분에 공백이 있는 경우 이를 제거하고 비교합니다.
디폴트값은 binary 입니다.

COLLATE에 대한 기본 처리 룰은 다음과 같습니다.
  • 이항 비교 연산자(=, <, >, <= 및 >=)의 경우 피연산자 중 하나가 열이면 해당 열의 기본 데이터 정렬 유형에 따라 비교에 사용되는 데이터 정렬 시퀀스가 결정됩니다. 두 피연산자가 모두 열이면 왼쪽 피연산자의 데이터 정렬 유형에 따라 사용되는 데이터 정렬 시퀀스가 결정됩니다. 두 피연산자가 모두 열이 아니면 BINARY 데이터 정렬 시퀀스가 사용됩니다.

  • BETWEEN...AND 연산자는 >= 및 <= 연산자가 있는 두 표현식을 사용하는 것과 같습니다. 예를 들어 x BETWEEN y AND z 표현식은 x >= y AND x <= z와 동일합니다. 따라서 BETWEEN...AND 연산자는 위와 같은 규칙에 따라 데이터 정렬 시퀀스를 결정합니다.

  • IN 연산자는 = 연산자와 같은 방법으로 사용할 데이터 정렬 시퀀스를 결정합니다. 예를 들어 x IN (y, z) 표현식에 사용되는 데이터 정렬 시퀀스는 x가 열인 경우 x의 기본 데이터 정렬 유형입니다. 그렇지 않은 경우에는 BINARY 데이터 정렬이 사용됩니다.

  • SELECT 문에 포함된 ORDER BY 절에는 정렬 작업에 사용할 데이터 정렬 시퀀스를 명시적으로 할당할 수 있습니다. 이렇게 하면 명시적인 데이터 정렬 시퀀스가 항상 사용됩니다. 그렇지 않으면 ORDER BY 절에서 정렬하는 표현식이 열인 경우 해당 열의 기본 데이터 정렬 유형에 따라 정렬 순서가 결정됩니다. 표현식이 열이 아니면 BINARY 데이터 정렬 시퀀스가 사용됩니다.

/* COLLATE TEST */

CREATE TABLE t1(
    x INTEGER PRIMARY KEY,
    a,                 /* collating sequence BINARY */
    b COLLATE BINARY,  /* collating sequence BINARY */
    c COLLATE RTRIM,   /* collating sequence RTRIM  */
    d COLLATE NOCASE   /* collating sequence NOCASE */
);

                                 /* x   a      b        c       d */
INSERT INTO t1 VALUES(1,'abc','abc', 'abc  ','abc');
INSERT INTO t1 VALUES(2,'abc','abc', 'abc',  'ABC');
INSERT INTO t1 VALUES(3,'abc','abc', 'abc ', 'Abc');
INSERT INTO t1 VALUES(4,'abc','abc ','ABC',  'abc');

/* Text comparison a=b is performed using the BINARY collating sequence. */
/* a, b 컬럼이 모두 BINARY로 정의되어 있으므로 BINARY모드로 비교합니다. */
SELECT x FROM t1 WHERE a = b ORDER BY x;
--result 1 2 3

/* Text comparison a=b is performed using the RTRIM collating sequence. */
/* 명시적으로 COLLATE를 지정하면 해당 COLLATE로 비교합니다. */
SELECT x FROM t1 WHERE a = b COLLATE RTRIM ORDER BY x;
--result 1 2 3 4

/* Text comparison d=a is performed using the NOCASE collating sequence. */
/* 서로 컬럼의 COLLATE가 다른 경우 선행 컬럼의 COLLCATE를 사용해서 비교합니다. */
SELECT x FROM t1 WHERE d = a ORDER BY x;
--result 1 2 3 4

/* Text comparison a=d is performed using the BINARY collating sequence. */
/* 서로 컬럼의 COLLATE가 다른 경우 선행 컬럼의 COLLCATE를 사용해서 비교합니다. */
SELECT x FROM t1 WHERE a = d ORDER BY x;
--result 1 4

/* Text comparison 'abc'=c is performed using the RTRIM collating sequence. */
SELECT x FROM t1 WHERE 'abc' = c ORDER BY x;
--result 1 2 3

/* Text comparison c='abc' is performed using the RTRIM collating sequence. */
SELECT x FROM t1 WHERE c = 'abc' ORDER BY x;
--result 1 2 3

/* Grouping is performed using the NOCASE collating sequence (Values
** 'abc', 'ABC', and 'Abc' are placed in the same group). */
/* 그룹바이절에 지정된 컬럼의 COLLATE를 사용해서 처리합니다. */
SELECT count(*) FROM t1 GROUP BY d ORDER BY 1;
--result 4

/* Grouping is performed using the BINARY collating sequence.  'abc' and
** 'ABC' and 'Abc' form different groups */
SELECT count(*) FROM t1 GROUP BY (d || '') ORDER BY 1;
--result 1 1 2

/* Sorting or column c is performed using the RTRIM collating sequence. */
/* ORDER BY절에 선행컬럼에 지정된 컬럼의 COLLATE를 사용해서 정렬하는군요 */
SELECT x FROM t1 ORDER BY c, x;
--result 4 1 2 3

코드 출처 : http://www.sqlite.org/datatype3.html

SQLite COLLATE 처리 원칙만 알면 대충 예상은 가능합니다. 그렇다면, 다음의 쿼리는 결과가 어떻게 나올까요?

SELECT d, c FROM t1 GROUP BY d, c ;

SELECT c, d FROM t1 GROUP BY c, d ;

직접 한 번 해보시기 바랍니다. 위 쿼리의 경우 2개를 동시에 비교해서 처리합니다. 테스트 해 본 결과 Sqlite에서는 Group by가 Sort Group by 방식을 사용하는 듯 합니다. (Group By 절에 지정된 순되로 레코드가 정렬되어서 나옵니다.) Group by나 Order시에 같은 Collate이면 rowId 순으로로 처리되는 것 같네요.

에구.. 복잡합니다.
개발할 때는 디폴트 값인  binary 모드로만 DDL을 생성해서 처리하는 게 예측하기 편리하고 좋을 것 같습니다.

ROWID

CREATE TABLE 관련하여 끝으로 ROWID에 대해서 알아보도록 하겠습니다.
SQLite의 모든 Table의 열은 unique한 64bit singed integer key를 내부적으로 가집니다. 이 값을 “ROWID”, “OID“, “_ROWID_”라고 부르며, SELECT 절에 기술하면 데이터 조회도 가능합니다.

만일 테이블 생성시에 컬럼의 정의를 INTEGER PRIMARY KEY라고 기술하면 이 컬럼(INTEGER PRIMARY KEY) 컬럼을 ROWID에 대한 ALIAS로 사용하게 됩니다. 즉, 컬럼과 ROWID가 같아집니다.
다음의 테스트를 통해 확인해 보도록 하겠습니다.

/* ROWID Alias Test */
create table t
(
x INTEGER PRIMARY KEY ASC,
v text
);

insert into t values(1,'a');
insert into t values(2,'a');
insert into t values(5,'a');

select rowid, x, v from t;

x x_1 v
1 1 a
2 2 a
5 5 a


x컬럼을 INTEGER PRIMARY KEY로 정의한 테이블 생성후, 데이터를 몇개 넣고 조회를 해보면 rowid라고 SELECT절에 기술한 컬럼의 명칭은은 x 로 표기되어 있습니다. 두번째 x는 x 이름이 중복되므로 _1을 붙여서 나왔습니다.

2. ALTER TABLE



ALTER TABLE은 컬럼추가 및 테이블명 변경외에는 안됩니다. 이 점은 중요합니다. 우리가 안드로이드 개발시 SQLite를 사용해야 하며, 컬럼 추가 이외에 DB 구조를 변경해야 한다면, upgrade 처리시에 여러가지를 고려해야 할 것입니다.

3. CREATE VIEW

CREATE VIEW에서 TEMP/TEMPORARY를 지정해서 뷰를 임시로 만들 수가 있습니다. 다른 DBMS에서는 못 본 것 같은데… 이런 기능도 있군요.

4. ANALYZE



ANALYZE는 테이블의 통계정보를 수집하는 명령어입니다. 우리가 사용하는 DB의 데이터가 많거나 속도가 안 나온다면 명령어를 실행해 보시기 바랍니다. 뒤에 파라미터에 값을 주지 않고 “ANALYZE” 명령어만 실행하면 전체 테이블, 인덱스에 대해서 통계정보가 생성됩니다. 생성된 통계정보는 SQLITE_STAT1 이란 테이블이 생성되면서 이곳에 저장됩니다. 문제는 ANALYZE 명령어가 데이터베이스 컨텐츠 변경시에 자동으로 실행되지는 않습니다.


ANALYZE
SELECT * FROM sqlite_stat1;

5. REINDEX



인덱스 리빌드 하는 명령어입니다. 단편화가 많이 된 인덱스를 다시 생성해 줍니다

6. VACUUM

VACUUM 명령어는 데이터베이스를 완전히 cleanzing 하는 명령어입니다. 데이터베이스 Object를 Drop하더라도 재사용을 위해 빈 공간은 남겨집니다. 이런 빈공간 및 DML 작업으로 인한 단편화를 제거하기 위해서 VACUUM 명령어를 실행할 수 있습니다. 실행하면 파일 사이즈도 줄여줍니다.

VACUUM 명령어를 실행하면 SQLITE는 전체 OBJECT를 TEMP 공간에 복사한 후 다시 복사하여 전부 새롭게 생성합니다. 이때 ROWID는 변경될 수도 있습니다.

본 포스트에서 간략하게나마 필요한 DDL문은 대충 다 다룬 것 같습니다. 일부 DDL과 뒤에 포스트에서 별도로 다룰  TRIGGER, TRANACTION 관련 DDL은 다루지 않았습니다.
DDL에 관한 전체 명령어 리스트 및 내용은 http://www.sqlite.org/lang.html  문서를 참고하시기 바랍니다.

  1. 제눅스 2010.10.14 21:49 신고

    좋은 글 감사합니다.


SQLite User Guide 두번째 포스트입니다.
SQLite Document를 보다 보니 PRAGMA란 지시어(확장 명령어)가 나옵니다. 오라클에서도 PL/SQL 개발할 때 자율 트랜젝션이나 에러 정의 할 때 등 몇몇 부분에 PRAGMA 지시어를 사용할 수 있긴 합니다만 SQLite만의 독특한 기능들이라서 별도로 포스트를 구성했습니다.

전체 PRAGMA 지시어 (http://www.sqlite.org/pragma.html ) 중에서 자주 사용할 법한 몇가지만 살펴보도록 하겠습니다.

SQLite User Guide 포스트 목차

1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)

이전 포스트에도 밝혔듯이 테스트는 SQLite Expert Personal 제품에 들어있는 demodb를 사용하도록 하겠습니다.

PRAGMA 명령어

PRAGMA case_sensitive_like = boolean;

SQLite는 기본적으로 like 검색시에 대소문자를 무시합니다. ‘A’ LIKE ‘a’ 가 true가 됩니다. PRAGMA 에서 이 설정을 바꿀수 있습니다.

PRAGMA case_sensitive_like = true;

/* Default 설정에서 대소문자 처리에 대한 Equal(=) SELECT 테스트 */
SELECT *
  FROM COUNTRY   
 WHERE NAME = 'Brazil';

-- Result
Name Capital Continent Area Population
Brazil Brasilia South America 8511196 150400000

SELECT *
  FROM COUNTRY   
 WHERE NAME = 'brazil';

결과없음.

위 PRAGMA 설정은 LIKE 검색에 한해서만 적용되는 듯합니다. WHERE조건에 equal 검색은 대소문자를 정확히 구분합니다. (Collate 설정에 따라서 바꿀 수 있습니다. 뒤쪽에 나옴)

/* Default 설정 - Like 검색시 대소문자 구별 안함 에서 LIKE 검색 테스트*/

SELECT *
  FROM COUNTRY   
 WHERE NAME LIKE '%brazil%';

-- Result
Name Capital Continent Area Population
Brazil Brasilia South America 8511196 150400000

/* Default 설정 - Like 검색시 대소문자 구별 안함 에서 %없이 LIKE 검색 테스트*/
SELECT *
  FROM COUNTRY   
 WHERE NAME LIKE 'brazil';

-- Result
Name Capital Continent Area Population
Brazil Brasilia South America 8511196 150400000

이번에는 LIKE 검색에  % 연산자를 빼고 검색했습니다.(두번째 쿼리문) 오라클의 경우 이런식의 쿼리가 날아가면 Optimzer가 query를 like 검색을 =로 변경해 버립니다. 하지만, SQLite의 경우 그렇지 않고 like 검색 되는 것 같습니다. (equal과 거의 유사하지만 대소문자만 가리지 않는 방식).

명령어로 대소문자 비교를 하도록 설정을 변경 한 후 테스트 해보도록 하겠습니다.


PRAGMA case_sensitive_like = 'TRUE'

SELECT *
  FROM COUNTRY   
 WHERE NAME LIKE '%brazil%';

-- Result 없음.

역시나 설명되로 PRAGMA 명령어로 설정을 변경했더니 대소문자를 따져서 LIKE 검색을 하는군요.

PRAGMA count_changes;
PRAGMA count_changes
= boolean;

count_changes 값이 설정되면 INSERT, UPDATE, DELETE 시에 변경된 레코드 수(Integer)값을 받으며, false로 설정되어 있으면 받지 않는다고 합니다. GUI 툴에서는 툴 자체가 메시지를 표시하기 때문에 제대로 테스트가 안되더군요. 나중에 틈날 때 코드레벨에서 테스트를 해봐야 겠습니다.

PRAGMA encoding;
PRAGMA encoding = "UTF-8";
PRAGMA encoding = "UTF-16";
PRAGMA encoding = "UTF-16le";
PRAGMA encoding = "UTF-16be";

데이터베이스의 인코딩을 바꿀수 있는 옵션입니다. 거의 쓸 일은 없을 것 같습니다.

PRAGMA table_info(table-name);
테이블 정보를 조회하는 명령어입니다.

PRAGMA table_info(COUNTRY);

cid name type notnull dflt_value pk
0 Name CHAR(24) 0  1
1 Capital CHAR(24) 0  0
2 Continent CHAR(24) 0  0
3 Area FLOAT 0  0
4 Population FLOAT 0  0


PRAGMA index_list(table-name); 인덱스 리스트를 볼 수 있습니다.
PRAGMA index_info(index-name); 인덱스 정보를 조회할 수 있습니다.
PRAGMA foreign_key_list(table-name); fk 리스틀 볼 수 있습니다.

시스템 카탈로그를 조회할 수 있는 방법은 위 PRAGMA 명령 말고도 sqlite_master 테이블을 직접 조회해도 됩니다. SQLITE_MASTER 테이블은 READ-ONLY 테이블입니다.

SELECT * FROM SQLITE_MASTER;

SQLITE_MASTER 테이블을 조회하시면 테이블, 인덱스, 트리거등 모든 정보와 DDL문까지 알 수가 있습니다.

DATATYPE

Sqlite가 지원하는 데이터 타입은 다음과 같습니다.

1. Null
2. Integer – 부호있는 정수, 실제 값에 따라 1byte에서 8byte까지 가변적으로 저장됨.
3. Real – 실수
4. Text – 문자열
5. BLOB – blob 데이터

실제적으로 저희가 테이블 생성시 DDL상에 VARCHAR(10)이라고 컬럼 사이즈를 정의해도 SQLITE는 TEXT 타입으로 만들어집니다. 그렇기 때문에 10자 이상의 데이터도 삽입이 가능합니다. 그러니, DDL 문 만들 때 구지 다른 데이터 타입을 외울 필요없이 위 타입만 알고 있으면 될 것 같습니다.

재미있는 것은 Data and Time 즉, 날짜 관련 데이터 타입이 따로 없다는 것입니다. DATETIME은 입력되는 값에 따라서 TEXT, REAL, INTEGER 타입으로 저장됩니다. http://www.sqlite.org/datatype3.html 보시면 어떤 데이터 타입이 어떤 식으로 변경되는지 알수가 있습니다.

이것으로 SQLite User Guide 두번째 포스트를 마치겠습니다. 다음 포스트에서는 SQLite 사용에 꼭 필요한 DDL문(CREATE TABLE, CRATE INDEX 등)에 대해서 알아보도록 하겠습니다.


SQLite는 Android 및 iPhone에 탑재되면서 많이들 사용하고 있는 파일베이스 RDB입니다. 공부를 하는데, 생각보다 많은 자료가 없더군요. 공부하면서 정리한 자료인데.. 많은 도움 되시면 좋겠습니다.

앞으로 제가 포스트할 User Guide의 목차는 다음과 같습니다.

SQLite User Guide 포스트 목차
1. SQLite User Guide - 소개. GUI Tools 등
2. SQLite User Guide - PRAGMA, DATA TYPE
3. SQLite User Guide - DDL(CREATE, DROP 등)
4. SQLite User Guide - DML (SELECT, INSERT, UPDATE, DELETE 등)
5. SQLite User Guide - Function (내장함수, Aggregation 함수)
6. SQLite User Guide - DateTime 함수, DateTime Formatting
7. SQLite User Guide - Trigger
8. SQLIte User Guide - Tranaction, Lock
9. SQLite User Guide - Performance, Optimizer
10. SQLite User Guide - VDBE(Virtual Database Engine)

SQLite의 특징.

Sqlite의 특징을 먼저 살펴보도록 하겠습니다.
1. Zero Configuration – sqlite는 별도의 설치 및 설정이 필요 없습니다. 파일 하나만 있으면 모든걸 할 수 있습니다.

2. Portabiliy – 이식성. 거의 모든 OS에서 다 돌아갑니다. 최대 2 terabytes까지 지원하며, 기본 encoding으로 UTF-8과 UTF-16을 지원합니다.

3. Compactness – 아주 가볍고 작습니다. Header file, library, 관계형 데이터를 다 포함해도 사이즈가 얼마 안됩니다.

4. Simplicity – sqlite는 아주 단순하고 사용하기 편리한 programming library를 제공하고 있으면서도 다양한 언어로 확장이 가능하도록 만들어졌습니다.

그밖에 Flexibility , Liberal Licensing(공짜죠), Reliability, Convenience 등이 나와 있습니다.
에구 좋은 말은 다 있는 것 같습니다.

위 내용은 일반적인 내용인것 같고, 보다 중요한 몇 가지를 짚고 넘어가겠습니다.

SQLite는 ANSI92의 기능을 대부분 지원하지만, 아래의 사항은 지원하지 않습니다.

1. RIGHT and FULL OUTER JOIN : LEFT OUTER JOIN만 지원합니다.

2. Complete ALTER TABLE Support : RENAME TABLE과 ADD COLUMN만 지원합니다.
DROP COLUMN, ALTER COLUMN, ADD CONSTRAINT 등 다른 기능은 지원하지 않습니다.

3. Writing to VIEWs : SQLite에서 View는 read-only입니다.

4, GRANT and REVOKE : Sqlite에서 읽기/쓰기 권한은 OS 파일 시스템 권한을 사용합니다. 
별도로 권한 부여 기능이 없습니다.


SQLite 아키텍처


SQLite의 내부 아키텍처는 위 그림과 같습니다.  코어, SQL 컴파일러, 백앤드가 주요 핵심요소이고, 이외에 액세서리 역할을 하는 유틸리티와 테스트 코드로 이뤄져 있습니다.

GUI Tools for SQLite

SQLite를 지원하는 GUI Tools 리스트는 아래 링크에 나와있습니다.
http://sb2.info/commercial-and-freeware-sqlite-tools-list-2/

무지 많습니다. 그 중 몇개를 골라서 사용해 봤는데.. 제가 추천해 드리고 싶은 툴은 다음과 같습니다.

SQLite Expert (http://www.sqliteexpert.com/)


  
여러 제품 중에서 가장 무난하게 사용할 수 있는 제품인 것 같습니다. 필요한 기능은 다 있고, UI도 그럭저럭 쓸만합니다. Personal Edition과 Professional Edition으로 나누어지며, Personal Edition은 무료입니다.
(향후 SQLite관련 포스트에서 저는 이 제품으로 테스트 하겠습니다.) Pro Edition의 Crack도 그리 어렵지 않게 구할 수 있습니다.

SQLite Maestro (http://www.sqlmaestro.com/products/sqlite/maestro/)


아마도 제가 본 Sqlite 관련 툴 중에서 가장 강력한 제품인 것 같습니다. 다른 기능은 몰라도 Reverse로 ERD를 만들어 주는 기능은 이 제품만이 가능합니다.

여담이지만 제가 Touch Call(터치콜) App 개발할 당시 Android SDK2.0 기준의 전화번호부 DB를 이용해야 하는데, 구글의 document만으로는 이해하기가 상당히 어려웠습니다. 출판되어 있는 책들도 다 이전 데이터베이스를 기준으로 작성된 책들이고.. 그때 이 제품을 평가판으로 설치해서 애뮬레이터에 있는 contact2 데이터베이스를 가지고 ERD를 만들어 보니… 훨씬 빠르게 이해를 할 수 있었습니다. 

좋은 제품이긴 하지만 애석하게도 free 버전은 없습니다. 모두 상용버전 밖에 없으며, 한달 Trial 버전만 사용할 수 있습니다. 현재 버전의 crack은 거의 구하기 힘들며, 이전 버전은 구하실 수는 있을 겁니다.

DeZign (http://www.datanamic.com/dezign/index.html)
현재 SQLite를 Forward/Backward Engineering를 완벽하게 지원하는 모델링 툴은 datanamic의 dezign이란 모델링 툴 밖에 없습니다. 근데, 막상 평가판 설치해보니..저희 정서와 맞지 않는 툴이더군요. 저희는 한글로 논리모델을 만들고 영문으로 물리모델을 만드는데, 이 툴은 그런 개념없이 논리/물리가 함께 처리되는 툴이라 바로 지워 버렸습니다. 조금 불편하더라도 DA#이나 ERWin으로 모델링을 하고, Forward Generation한 스크립트를 수정해서 사용하는 게 더 편리할 듯 합니다.

끝으로 앞으로 저의 SQLite 관련 포스트에서 사용할 demo db에 대해서 소개를 하겠습니다. 위에서 얘기했듯이 저는 SQLlite Expert Personal 버전을 이용해서 테스트를 하도록 하겠습니다.

SQLite Expert Personal를 실행한 후 File > Open Demo Database를 실행합니다.
Dbdemos란 데이터베이스가 스키마브라우저에 나타납니다.


좌측 스키마 브라우저에 많은 테이블 리스트가 나옵니다. 저는 그중에서 아래 ERD에 나오는 몇 개의 테이블을 주로 사용해서 DML 테스트 등을 진행하도록 하겠습니다.


고객, 직원, 주문, 주문내역 등 간단한 ERD 구조입니다.
참고로 이 ERD는 Dbdemos SQLlite 데이터베이스를 SQLite Maestro를 이용하여 일부 테이블만 designer에 띄운 모습입니다.)

SQLIte User Guide 첫번째 포스트를 이만 마치며, 다음 포스트에서는 SQLite 의 PRAGMA 명령어 및 시스템 카탈로그 등에 대해서 알아보도록 하겠습니다.

 

  1. BEOM 2010.09.09 11:40 신고

    좋은 정보 감사합니다~

  2. 고냉지채소 2010.11.10 17:52 신고

    좋은 글 감사합니다. 덕분에.. SQLite에 대해서 궁금했던 부분들 많이 해소 했습니다.
    복 받으실꺼에요..^^

  3. 안승진 2011.03.23 11:43 신고

    정말. 잘 배우고 갑니다.^^ 이런 글이 있다니!! 이해도 쉽고, 읽기도 편하고!! 너무 좋아요!

  4. 김병훈 2011.11.22 00:16 신고

    감사드립니다

  5. Miyo 2012.01.13 11:50 신고

    좋은정보 감사합니다~~


안녕들 하십니까?

오늘의 강좌 주제는 안드로이드 App Resource(이미지, XML) Hacking 입니다.

정말 잘 만들어진 App을 보면서 이 App은 어떻게 UI를 구성했을까? 고민해 본적은 없습니까? 하하~ 저는 그런 고민을 많이 했습니다. 단순한 UI야 쉽게 만들 수 있지만, 복잡한 UI를 보면서.. UI를 만들기 위해서 어떤 Layout을 사용했으며, 어떤 View를 사용했을까? 고민하다가 찾아본 프로그램에 대한 소개입니다.


전체 강좌 목차

[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1
[강좌A06] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part2
[강좌A07] 안드로이드 실전 개발 - 리소스 해킹

참고로 안드로이드 소스 역컴파일은 현재는 불가능합니다. 어셈블리 레벨로 볼 수는 있지만, 그거 분석하는거 공부할려면 짜증납니다. 언젠가는 자바로 역컴파일 해주는 프로그램이 나오지 않을까? ㅋㅋ 안 나올수도 있습니다.

 

기본적으로 Android App을 빌드하면 .apk 파일이 만들어집니다. 이 파일은 압축파일로 압축을 풀면 이미지는 볼 수 있으나, xml 파일의 경우에는 binary로 저장되기 때문에 editor에서 볼 수가 없습니다. xml을 볼 수 있다면.. resource 설계에 상당한 도움이 될 것입니다

~ 시작하겠습니다.

 

1. 먼저 apk 파일을 구해야 겠지요.

핸드폰으로 앱을 다운/설치 하시면.. 기본적으로 apk 파일은 /data/app 디렉토리에 저장됩니다. 근데, 우리가 구매한 핸드폰은 기본적으로 root 계정이 막혀있으며, /data 디렉토리에 퍼미션이 없어서 접근 자체가 불가능합니다. 루팅을 하지 않는 한 /data 디렉토리의 apk를 볼 수 있는 방법이 없습니다. <개발자 폰이 있으면 가능할텐데…> 하지만, 구글 검색어: “android apk rapidshare” 이정도 치면 누군가 apk를 모아서 rapidshare 사이트에 올려둔 게 많습니다. 특정 App 이름을 검색어에 추가하면 그 앱을 구할 수도 있습니다. 최신 App은 거의 없구요. 일종의 불법 app 사용이겠네요. 것도 원래 무료 App이 많습니다.  폰에 설치해서 사용도 가능합니다. 사용하기 위해서 이짓하기는 좀 귀챦습니다. 무료인 경우에는 개발사 홈페이지에서 바로 다운이 가능한 곳도 많습니다. 향후 Google App 인증이 적용된 App들이 나오면 설치는 불가능하게 되겠죠?

저희는 TStore에서 다운받겠습니다. tstore에서 pc manager로 다운 받으시면 local pc apk 파일이 다운됩니다. (참고로 tstore에서는 개발자에게 ARM 관련 library를 제공하기 때문에 ARM이 걸려있는 apk는 폰에 설치해도 실행이 안됩니다. 어플 실행 부분에 체크하는 로직이 들어있기 때문입니다. 저희는 리소스만 볼 것이므로 상관은 없습니다.)

 


앱에서 아무 프로그램이나 pc Manager로 다운받으신 후 우측마우스 클릭 > 파일이 저장된 폴더 열기를 하시면 apk 파일이 저장된 폴더가 열립니다. 저는 R-2 Player라는 라디오 프로그램을 다운 받았습니다. 제가 강좌하고 있는 Hangul To English 앱을 다운 받으셔서 테스트 하셔도 됩니다.

 

2. 압축을 풀자.

apk
파일은 압축 파일이므로 압축을 풀 수가 있습니다. 다만, 알집으로는 못 풀고 오픈소스 진영에서 만든 압축 프로그램인 7-zip으로 푸셔야 합니다. 7-zip은 홈페이지
(http://www.7-zip.org/) 에서 다운받으시면 됩니다.


위 그림을 보시면 /res/drawable 디렉토리에 이미지들이 보이네요. 보통 개발할 때 drawable 디렉토리에는 xml만 넣고, drawable-hdpi 폴더에 큰이미지, drawable-mdpi에 작은 이미지를 넣는데, 이 앱은 drawable에 이미지랑 xml 모두 다 들어있군요.

3. xml 파일 보기.

압축 푼 파일에서 xml 파일을 열어 보겠습니다.


~ binary 파일로 되어 있어서 xml 파일 자체를 볼 방법이 없군요.

이제 이 xml을 볼 수 있는 프로그램을 다운 받아 봅시다.

오픈소스로 만든 AXMLPrinter2 라는 자바로 개발된 프로그램입니다.


http://code.google.com/p/android4me/downloads/list  

에서 AXMLPrinter2.jar 파일을 다운 받아서 아무 디렉토리에 넣습니다. 저는 d:\test 디렉토리에 apk 압축을푼 파일(apk 디렉토리명도 r2_player로 변경했습니다) AXMLPrinter2.jar 파일을 넣어두겠습니다.

이제 준비가 끝났습니다. 위 압축 푼 파일 중에서 D:\test\r2_player\res\layout-hdpi\player_fwvga.xml 파일을 변환한다고 할 때..

cmd 창에서 다음과 같이 입력하시면 작업이 끝납니다.

java –jar [AXMLPrinter2.jar위치] [binary 포맷 xml 파일 위치] > [변환될 파일 위치]


java –jar D:\test\AXMLPrinter2.jar D:\test\r2_player\res\layout-hdpi\player_fwvga.xml > D:\test\r2_ player\res\layout-hdpi\player_fwvga.xml.txt



변환 작업이 완료되었습니다.

변환된 파일 위치에 가보시면 변환된 파일이 존재합니다. 열어보도록 하겠습니다.



잘 변환이 되었습니다.
그런데 이 작업에는 두가지 문제가 존재합니다.

하나는 변환 해야 될 파일이 많은 경우 엄청난 노가다를 해야 합니다.
두번째는, 현재 변환된 XML을 보시면 android:layout_width=”-2”로 실제 상수값이 들어가 있습니다. -2developer.android.com 에서 찾아보니
WRAP_CONTENT를 말합니다. 이것을 우리가 보기좋게 변환하려면 AXMLPrinter2 프로그램을 소스를 수정하거나 혹은 XML -> XML로 변환하는 XSLT 하나 만들면 될 것 같은데만들까 했는데.. 도저히 시간이 나지 않는군요. 누군가 해 주시면 감사하겠습니다. 아무도 안 해주심..하하 내년쯤에나 만들어 볼까 합니다.

 

첫번째 이슈인 변환해야 될 파일이 많은거..이것은 제가 도저히 참을 수가 없어서 간단히 VB스크립트로 조금 일을 간단하게 처리할 수 있도록 만들었습니다.

 

처음에는 vbsWindow Shell을 이용하여 프로그램을 직접 실행하도록 만들었으나 cmd 창이 개수만큼 떴다 사라지고 해서 오히려 불편해서 그냥 batch 파일을 만들어 주는 스크립트를 만들었습니다.

스크립트
소스입니다.



Const AXMLPRINTER_FILE = "D:\test\AXMLPrinter2.jar"

Const APP_ROOT_DIR = "D:\test\r2_player"

Const SAVE_FILE = "D:\test\print_xml_r2_player.bat"

 

Dim fso, tf

Set fso = CreateObject("Scripting.FileSystemObject")

 

 

If Not fso.FolderExists(APP_ROOT_DIR) Then

             Print("App Root Folder not exist. Check APP_ROOT_DIR Constants")

             WScript.Quit 0

End If

 

If Not fso.FileExists(AXMLPRINTER_FILE) Then

             Print("AXMLPrinter2.jar file not exist. please check AXMLPRINTER_FILE Constants")

             WScript.Quit 0

End If

 

Set tf = fso.CreateTextFile(SAVE_FILE, True)

 

call ShowSubfolders(APP_ROOT_DIR)

 

tf.Close

 

 

Sub ShowSubFolders(Folder)

             Set folder = fso.GetFolder(Folder)

    Set files = folder.Files

 

             For each file In files

                            If InStr(file.Name, ".") <> 0 Then

            fext = Right(file.Name, Len(file.Name) - InStrRev(file.Name, "."))

                  End If

 

                            If UCase(fext) = "XML" Then

                                        runCmd = "java -jar " & AXMLPRINTER_FILE & " " & file.Path & " > " & file.Path & ".txt"

                                        Print(runCmd)

                                        tf.WriteLine(runCmd)

 

                            End If

    Next

 

    For Each Subfolder in Folder.SubFolders

        ShowSubFolders Subfolder

    Next

End Sub

 

Sub Print(x)

   WScript.Echo x

End Sub

 


소스는 간단합니다.
디렉토리를 뒤져서 xml 파일인 경우 우리가 실행할 명령어를 텍스트 파일로 만들어 주는 스크립트입니다. vb 스크립트 아시면 금방 이해가 가실겁니다.

 

위 스크립트를 test 디렉토리에 저장한 후 처음 세줄인 상수값을 자신에게 맞게 수정합니다.

 

Const AXMLPRINTER_FILE = "D:\test\AXMLPrinter2.jar" 'AXMLPrinter2.jar 파일 경로 및 파일명

Const APP_ROOT_DIR = "D:\test\r2_player"                  '압축푼 apk 파일 루트 경로 (이 경로 하위 디렉토리를 뒤집니다.)

Const SAVE_FILE = "D:\test\print_xml_r2_player.bat"   '스크립트가 생성할 파일명

 

자 이제 실행은 cmd 창에서 cscript d:\test\print_xml.vbs 라고 명령 내리시면 apk 루트 하위의 모든 xml를 뒤져서 필요한 배치 스크립트를 만들어 줍니다. wscript d:\test\print_xml.vbs 라고 입력하셔도 되나, 이 경우 스크립트에서 메시지가 출력될 때 팝업창으로 나와서 조금 불편합니다.


위 그림과 같이 입력하면 배치파일이 만들어 집니다.

이제 만들어진 배치 파일을 실행하시면 모든 xml 파일을 텍스트 파일로 변환해 줍니다. 일이 한결 편해졌습니다.

이로써 7번째 안드로이드 실전 강좌를 마치겠습니다.

다음 강좌에서는 DB 부분을 살펴볼 것입니다. DB 관련 툴도 살펴보고 저희 Hangul to English 앱의 DB 부분 소스도 개발하겠습니다. 제 강좌를 구독하시면서 아직 Hangul To English App을 설치해 보지 않으신 분은.. 한번 설치해보시기 바랍니다. 그래도 직접 해보면 개발하는 앱에 대한 이해를 높일 수 있으니 ..

 

끝으로 스크립트 소스 첨부합니다. 필요하신분은 다운받으시기 바랍니다.

 

 

 

  1. 지니님 2010.08.20 19:33 신고

    안녕하세요 강좌 잘 보고 있습니다. 서핑하다 우연히 들어왔는데 이렇게 좋은 강좌가 있었네요. 앞으로도 좋은 강좌 많이 부탁드립니다.^^

  2. onjo 2010.08.21 18:39 신고

    좋은 정보 감사합니다.. ^^

  3. 나그네 2010.08.25 11:36 신고

    좋은 강좌 감사합니다.

    너무나 많은 도움이 된거 같네요!

    앞으로도 잘 부탁드립니다 (__)

  4. seso 2010.12.19 16:04 신고

    좋은 정보 감사드립니다~!!

  5. 지니파파 2010.12.21 17:00 신고

    좋은 정보 감사합니다.

  6. tony 2011.05.04 19:28 신고

    최고에요0.0

  7. 빛나는 야옹이 2011.06.29 12:10 신고

    열심히 따라했는데요... 안되네요... xml파일 내용이 텅~ 비어있어요,,, 왜 그런건가요?

  8. 빛나는 야옹이 2011.06.29 12:12 신고

    꼭 해보고 싶은데 ㅠㅠ *

  9. 뽀잉뽀잉 2011.10.26 21:48 신고

    와우! 이런정보가 있다니!!
    정말 많은 도움이 되었어요!!

  10. eeaa3 2012.02.27 21:56 신고

    소스구하기에 좋습니다. 책에서는 이부분을 왜 그냥지나쳣는지 ㅎㅎ


Part2 가 시작되었습니다.

전체 강좌 목차
[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part2

 

이제부터는 버튼 관련 리소스 xml을 정의해 보도록 하겠습니다.

Info_grayed.png
info_clicked.png  

1번 이미지는 앱의 정보를 볼 수 있는 이미지 버튼 스타일의 ImageView 입니다. (디퐅트 이미지:info.png, 클릭 이미지:info_clicked.png) 클릭시 팝업 대화상자가 나타날 겁니다. 디폴트로 회색 이미지였다가 클릭시 배경색이 블루로 변경됩니다. . 이 이미지는 iconWorkshop에 있는 이미지를 32*32로 맞추고, grayscale로 변환하여 회색 배경의 이미지로 만들었습니다.

 

Android에서 클릭시 이미지를 변경되는 효과를 주기 위해서 물론 코딩으로도 처리할 수 있지만, xml로 간단히 구현할 수 있습니다. 아래 코드처럼 xml 문서를 만드신 후 drawable 디렉토리에 저장합니다.


button_info.xml


<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true"

android:state_pressed="false"

android:drawable="@drawable/info_clicked" />

    <item android:state_focused="true"

android:state_pressed="true"

android:drawable="@drawable/info_clicked" />

    <item android:state_focused="false"

android:state_pressed="true"

android:drawable="@drawable/info_clicked" />

    <item android:drawable="@drawable/info"/>

</selector>


Selector element 하위에 item element가 들어갑니다. Item element의 attribute를 보시면 다음의 4가지로 나누어져 있습니다.
android:state_focused="true" android:state_pressed="false" 포커스를 가지고 있으면서 누르지 않은 상태.(보통 클릭 후 상태)
android:state_focused="true" android:state_pressed="true" 포커스를 가지고 있으면서 누른 상태 android:state_focused="false" android:state_pressed="true" 포커스는 없으면서 누른 상태 아무런 속성 설정이 없는 것은 디폴트 상태. 입니다.

Custom EditText 같은 경우에는 위 4가지 속성에 따라서 배경이미지를 다르게 줄 수 있으나, 저희는 클릭시에만 다른 이미지를 보여 줄 것이므로, 디폴트 상태값에는 info.png를 할당하고, 나머지 클릭 상태값에는 info_clicked.png를 설정했습니다.
그림에서 4번 이미지와 5번 이미지는 clipboard copy 및 record delete를 위한 버튼(이미지)입니다. 버튼 클릭시 배경색을 주황색으로 바꿔 주도록 효과를 주기 위해서 이미지를 제작하신 후 button_info.xml과 동일한 스타일로 xml 문서를 만들어 drawable 디렉토리에 저장합니다.

button_copy_clipboard.xml


<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true"

android:state_pressed="false"

android:drawable="@drawable/copy_clipboard_clicked" />

    <item android:state_focused="true"

android:state_pressed="true"

android:drawable="@drawable/copy_clipboard_clicked" />

    <item android:state_focused="false"

android:state_pressed="true"

android:drawable="@drawable/copy_clipboard_clicked" />

    <item android:drawable="@drawable/copy_clipboard" />

</selector>


button_delete_record.xml

<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_focused="true"

android:state_pressed="false"

android:drawable="@drawable/delete_record_clicked" />

    <item android:state_focused="true"

android:state_pressed="true"

android:drawable="@drawable/delete_record_clicked" />

    <item android:state_focused="false"

android:state_pressed="true"

android:drawable="@drawable/delete_record_clicked" />

    <item android:drawable="@drawable/delete_record" />

</selector>


다음은 6번 이미지를 위한 xml을 보도록 하겠습니다. 저희 앱에 구지 필요 없는 항목이긴 하지만, UI를 좀 더 미려하게 보이게 하기 위해서 추가했습니다. 코드는 다음과 같습니다.

shape_title_indicator.xml


<?xml version="1.0" encoding="UTF-8"?> 

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> 

    <stroke android:width="2dip"  android:color="#19CD00" />

    <corners android:bottomRightRadius="1dip" android:bottomLeftRadius="1dip" 

             android:topLeftRadius="1dip" android:topRightRadius="1dip"/> 

    <padding android:left="1dip" android:top="1dip"

             android:right="1dip" android:bottom="1dip" />

</shape>



이 코드는 기본 도형들을 xml로 정의하는 방법입니다. Shape를 line으로 지정하여 선을 만들수도 있으며 oval 로 지정하여 타원형을 만들수도 있습니다.

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line">

   <stroke android:width="1dp" android:color="#FF000000"

           android:dashWidth="1dp" android:dashGap="2dp" />

</shape>



<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">

    <solid android:color="#00000000"/>

    <stroke android:width="4dp" android:color="#99000000"

            android:dashWidth="4dp" android:dashGap="2dp" />

    <padding android:left="7dp" android:top="7dp"

             android:right="7dp" android:bottom="7dp" />

    <corners android:radius="4dp" />

</shape>


Stroke 는 테두리를 그리는 것을 말합니다. dashWidth와 dashGap은 점선을 그릴 때 사용합니다.
Solid는 단색으로 채워 넣는 것을 말하며, gradient는 단색대신 그라디언트 효과를 줄 수도 있습니다

<gradient android:startColor="#FFFF0000" android:endColor="#80FF00FF"

.        android:angle="270"/>


Corners는 가장자리를 둥글게 처리하는 것을 말합니다. Padding은 안쪽 여백을 주는 것입니다. 저희 코드가 이해가 가십니까? 저희 코드는 두께가 2dip인 둥근 사각형입니다. 여백을 주었기 때문에 ImageView 사이즈보다 약간 작게 나오게 됩니다. 그래야 우측 텍스트와 크기를 비슷하게 맞출수 있어서 패딩으로 조정했습니다. 실제 ImageView에 xml을 할당할 때 width를 4dip밖에 주지 않을 것이므로 두꺼운 모서리가 둥근 직선으로 보이게 됩니다.


다음은 문자열을 편집하도록 하겠습니다. Motodev에서 res/values/string.xml 파일을 더블 클릭하여 엽니다. 아래 그림과 같이 편집합니다.


Motodev를 사용하기 때문에 여러 언어를 표 형태의 편집창에서 바로 편집할 수 있어서 편리합니다. 문자열 입력값은 XML 문서 안에 정의되므로 XML 문법에 허용되지 않는 문자열은 입력하시면 에러가 납니다. 저희 앱의 Info 팝업창을 만들 때(뒤쪽 편에 있습니다.) 엔터가 들어간 긴 문장을 TextView에 표시하기 위해서 xml에 문자열 값을 CDATA 섹션에 넣어서 처리를 해 보려고 했으나 제대로 처리가 되지 않았습니다. 특수문자들은 CDATA 안에 넣어서 처리할 수 있습니다. 엔터가 들어간 여러줄의 텍스트를 표현하려면 아직 제가 해본 바로는 각각의 문장을 문자열로 만들어서 프로그램에서 Html.fromHtml() 메소드 처리시 문장마다 <br/>태그를 넣어주거나, 레이아웃에서 여러 개의 TextView를 만들어 사용하시는 것도 한가지 방법입니다.

그리고 label_title_main의 값처럼 <u><b>태그를 추가하여 타이틀 텍스트에 밑줄과 볼드체효과를 줄 수 있습니다. <u><b><i>태그값은 string의 value값으로 바로 정의할 수 있습니다.

이제 drawable에 들어갈 항목을 모두 정의했습니다. 이제부터 메인 레이아웃을 작성하도록 하겠습니다. 프로젝트 생성시 자동으로 만들어진 res/layout/main.xml 파일을 열어서 다음과 같이 편집합니다.

Main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

         <!-- 상단 타이틀바 -->                     

         <LinearLayout android:layout_width="fill_parent"

                       android:layout_height="wrap_content"

                       android:orientation="horizontal"

                       android:background="@drawable/layout_box_bg">

                  <ImageView android:layout_width="4dip"              

                             android:layout_height="20dip"

                             android:layout_marginLeft="3dip"

                             android:layout_gravity="center_vertical"

                             android:src="@drawable/shape_title_indicator"/>

                                                       

                  <TextView android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:layout_marginLeft="6dip"

                           android:layout_gravity="center_vertical"

                           android:layout_weight="1"

                           android:paddingTop="4dip"

                           android:paddingBottom="2dip"

                           android:textColor="#FFFFFF"

                           android:textSize="16sp"

                           android:text="@string/label_title_main"/>  

                                  

                  <ImageView android:id="@+id/button_info"

                           android:layout_height="32dip"

                           android:layout_width="32dip"

                           android:layout_gravity="right|center_vertical"

                           android:background="@null"

                           android:src="@drawable/button_info"/>                                 

                                                          

         </LinearLayout>

        

         <!-- 상단 한글/영문 입력창  -->

         <LinearLayout android:layout_width="fill_parent"

                       android:layout_height="wrap_content"

                       android:orientation="vertical"

                       android:background="@drawable/layout_box_bg_large">

                       

                  <LinearLayout android:layout_width="fill_parent"

                                android:layout_height="wrap_content"

                                android:orientation="horizontal"  >

                           <TextView android:layout_width="55dip"

                                     android:layout_height="wrap_content"

                                      android:layout_gravity="center_vertical"

                                     android:layout_marginLeft="10dip"

                                     android:textColor="#FFFFFF"

                                     android:textSize="16sp"

                                     android:text="@string/label_password"/>

                           <EditText android:id="@+id/text_password"

                                     android:layout_width="wrap_content"

                                     android:layout_height="38dip"

                                     android:textSize="12sp"

                                     android:layout_weight="1"/>

                           <ImageButton android:id="@+id/button_remove"

                                   android:layout_height="38dip"

                                   android:layout_width="38dip"

                                    android:scaleType="centerCrop"

                                   android:layout_marginLeft="3dip"

                                   android:layout_gravity="right"

                                 android:src="@drawable/remove"/>                                                                  

             </LinearLayout>           

             

             <LinearLayout android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:orientation="horizontal" >

                          

<TextView android:layout_width="55dip"

                                      android:layout_height="wrap_content"

                                      android:layout_gravity="center_vertical"

                                      android:layout_marginLeft="10dip"

                                      android:textColor="#FFFFFF"

                                      android:textSize="16sp"

                                      android:text="@string/label_english"/>

 

                           <EditText android:id="@+id/text_english"

                                      android:layout_width="wrap_content"

                                      android:layout_height="38dip"

                                     android:textSize="12sp"

                                     android:layout_weight="1"

                                              />

                      <ImageButton android:id="@+id/button_copy"

                                   android:layout_height="38dip"

                                   android:layout_width="38dip"

                                   android:scaleType="centerCrop"

                                   android:layout_marginLeft="3dip"

                                   android:layout_gravity="right"

                                   android:src="@drawable/copy"/>             

             </LinearLayout>                     

         </LinearLayout>

        

         <Button android:id="@+id/button_save"

                 android:layout_width="fill_parent"

                 android:layout_height="wrap_content"

                 android:layout_marginTop="4dip"

                 android:drawableLeft="@drawable/save"

                 android:gravity="center"

                 android:text="@string/save"/>       

                

         <!-- list Layout -->

         <LinearLayout android:orientation="vertical"

                  android:layout_width="fill_parent"

                  android:layout_height="fill_parent"

                  android:background="@null"

                  android:layout_weight="1">

                          

                  <ListView android:id="@+id/android:list"

                           android:layout_width="fill_parent"

                           android:layout_height="fill_parent"

                           android:drawSelectorOnTop="false"/>

        

                  <TextView android:id="@+id/list_empty"

                           android:layout_width="fill_parent"

                           android:layout_height="fill_parent"

                           android:gravity="center_horizontal|center_vertical"

                           android:textSize="20sp"

                           android:paddingTop="50dip"

                           android:text="" />

        

         </LinearLayout>           

</LinearLayout>




LinearLayout만을 사용하여 UI 요소들을 배치했습니다.

상단 타이틀바 영역을 보시면 가운데 TextView 항목만 android:layout_weight="1" 로 주어졌습니다.  화면이 커지고 작아졌을 때 가운데 TextView만 가중치를 주어 늘어나고 줄어들도록 처리했습니다. 다른 부분들도 마찬가지입니다.

 

하단의 리스트뷰 정의 부분을 보시면 <ListView android:id="@+id/android:list" 아이디를 설정하는 부분이 다른 것과 좀 다릅니다. 저희는 메인 화면에 리스트가 들어갈 것이고, 코딩을 좀 더 간단히 처리하기 위해서 ListActivity에서 상속받을 것입니다. ListActivity에서 상속을 받아서 리스트 처리를 할 때는 반드시 아이디를 위와 같이 기술하셔야 합니다.

 

나머지 레이아웃 소스는 그 동안 책을 보셨다면 대부분 아실 내용입니다. 책을 안 보셨더라도 속성명만 가지고도 대충 알 수 있을 겁니다. 저는 여기서 상세한 설정 정보에 대해서 설명하기 보다는 ImageView ImageButton의 사이즈 설정에 대해서 애기를 좀 하겠습니다.

 

먼저 레이아웃 소스 중 타이틀영역의 Info 버튼 이미지 부분 입니다.

<ImageView android:id="@+id/button_info"

           android:layout_height="32dip"

           android:layout_width="32dip"

           android:layout_gravity="right|center_vertical"

           android:background="@null"

           android:src="@drawable/button_info"/>


ImageView
사이즈가 가로, 세로 모두 32dip 입니다.
저희 이 info 이미지를 hdpi 폴더에 48*48 크기로 만들어 두었습니다. ImageView src 속성에 지정되는 이미지는 로딩시에 pre-scaling이 됩니다. hdpi 폰 에서는 저희가 만들어둔 hdpi 폴더의 이미지 사이즈 그대로 48*48 크기의 이미지가 로딩되며, mdpi에서는 48/1.5 = 32 , 32*32 크기 이미지로 축소되어서 로딩됩니다. 그리고 ImageView가 화면에 그려질 때 scaleType 속성값에 지정된 값에 따라서 다시 스케일이 조정될 수도 있지만, scaleType을 지정하지 않으면 디폴트값이 center 속성값이 지정됩니다. center는 따로 스케일 조정을 하지 않는 옵션이므로 ImageView에는 저희가 계획한대로 hdpi에서는 48*48 크기로, mdpi에서는 32*32 크기로 이미지가 화면에 그려집니다.


다음은 ImageButton 부분 최종 소스입니다.

<ImageButton android:id="@+id/button_copy"

             android:layout_height="38dip"

             android:layout_width="38dip"

             android:scaleType="centerCrop"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>


 

ImageButton 이게 골 때리는 겁니다. ImageView는 우리가 원하는 대로 정확하게 동작합니다. 하지만 이미지 버튼은 그렇지가 않습니다.

 

먼저 아래의 소스를 보겠습니다. 최종 소스와 다른 점은 ImageButton View의 가로 세로사이즈가 32dip이며 scaleType을 지정하지 않았기 때문에 center 값으로 동작합니다. , 이미지를 draw할 때 별다른 scale 작업을 하지 않습니다. 저희가 이 ImageButton에 사용할 아이콘을 Info icon과 동일하게 hdpi 폴더에 48*48 사이즈로 만들어 두었습니다.

 

32*32 size ImageButton의 레이아웃을 설정했을 때.

 <ImageButton android:id="@+id/button_copy"

             android:layout_height="32dip"

             android:layout_width="32dip"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>


ImageView
와 동일한 방식으로 쉽게 생각하여 hdpi 화면에서 48*48 사이즈로 이미지를 로딩하면

~ 이 이미지처럼 버튼 형태의 디자인보다 이미지가 더 커져 버립니다.

 

왜 이럴까요? 버튼의 경우에는 버튼처럼 보이게 하기 위해서 버튼 배경 이미지가 뒤에 깔립니다. 그래서 이런 모습으로 보입니다. 버튼을 나타내는 ImageButton View의 크기를 우리가 제작한 이미지 보다 크게 그려야 합니다. 참고로 이미지 버튼 xml 설정값에서 android:background="@null" 이라고 설정을 추가하면 ImageButton의 버튼 배경이미지가 없어지고, ImageView와 동일한 형태가 되어 버립니다.

 

아래 코드는 위의 단점을 보완하기 위해서 scaleType은 설정하지 않고 ImageButton의 크기만 키웠습니다. 어찌 될까요?

<ImageButton android:id="@+id/button_copy"

             android:layout_height="38dip"

             android:layout_width="38dip"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>

좀 낫습니다. 그래도 너무 꽉 차 보이는 군요. 제가 원하는 바는 아닙니다. 그렇다면 ImageButton의 경우 내부에 그림이 그려질 정확한 영역은 얼마일까요? 아래 그림은 WVGA800 화면의 내용을 Android SDK Tools 폴더 하위에 있는 hierarchyviewer.bat 툴로 살펴본 모습니다. 해당 ImageButton을 클릭해보니 width, height값이 57(우리가 Layout에서 38dip를 지정했고, hdpi 모드에서는 38 * 1.5 = 57이 됩니다)이며, padding 값이 mPaddingBottom=17, mPaddingTop=10, mPaddingLeft=16, mPaddingRight=17 값으로 나타납니다.

좀 알기쉽게 그림으로 나타내면 다음과 같습니다.


물론, 이것도 저희가 원하는 바는 아닙니다. 다음은 scaleType=”centerCrop” 로 설정한 소스입니다.


<ImageButton android:id="@+id/button_copy"

             android:layout_height="38dip"

             android:layout_width="38dip"

             android:scaleType="centerCrop"

             android:layout_marginLeft="3dip"

             android:layout_gravity="right"

             android:src="@drawable/copy"/>


scaleType=”centerCrop”은 이미지(아이콘)를 중앙에 그릴영역 (30 * 24) 에 맞춘 후 원 이미지의 비율에 맞게 가로(24px) 30px로 늘립니다.(minus padding). 아래 그림과 같이 57 * 57 ImageButton 30*30 아이콘이 생성됩니다. 거의 우리가 원하는 디자인이 된 것 같습니다.

이밖에 scaleType에 대한 다른 옵션에 대한 상세 내용들은 레퍼런스들을 참고하시기 바랍니다.

 

결론적으로 ImageButton에 아이콘을 올릴 경우 다음과 같은 전략을 사용하실 수 있습니다.

 

1. ImageButton View의 사이즈를 38*38 mdpi 기준 레이아웃에서 설정했을 때 res/drawable-hdpi/ 폴더에 30*30 아이콘을 제작해서 넣어둔 후, scaleType를 지정하지 않고(Default : center) 아이콘을 로드하면 pre-scaling 만으로 ImageButton을 그려 넣기 때문에 편리합니다. (32*32로 만들어서 넣어 두어도 보기에 괜챦습니다.)

 

2. 우리 소스처럼 scaleType=”centerCrop”으로 설정하여 보기 좋게 만듭니다. 이 방식은 pre-scaling 이후 draw 시점에 auto-scaling을 한 번 더 하므로 약간의 성능에서 손실을 보지만, 이미지도 몇 개 안되고..크게 무리는 없을 것 같습니다. 저희는 이미 30*30 아이콘이 아닌 48*48로 만들었기 때문에 2번 방식을 사용하도록 하겠습니다.

 

List row Layout

 

다음은 리스트 중 하나의 Item에 대한 레이아웃에 대한 이미지 입니다


위 그림에서 보듯이 list가 단 하나의 항목만 가진게 아니라 여러 항목을 가지고 있다면 별도의 layout을 만들어 adapter에서 처리하는 게 편리합니다. 위 그림을 표현하기 위해 간단하게 레이아아웃을 만들면 다음과 같습니다.

 

아래와 같이 소스를 작성하여 res/layout/ 폴더에 넣습니다.


list_item.xml

 

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

           xmlns:android="http://schemas.android.com/apk/res/android"

           android:layout_width="wrap_content"

           android:layout_height="wrap_content">

         <LinearLayout android:layout_width="wrap_content"

                    android:layout_height="wrap_content"

                    android:orientation="vertical"

                    android:gravity="left"

                    android:layout_weight="1">

                  <TextView android:id="@+id/korText"

                           android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:textColor="#FFFFFF"

                           android:textSize="18sp"

                           android:gravity="center_vertical"

                           android:paddingLeft="5dip"

                           android:singleLine="true"/>

         <TextView android:id="@+id/engText"

                           android:layout_width="fill_parent"

                           android:layout_height="wrap_content"

                           android:textColor="#FFF123"

                           android:textSize="20sp"

                           android:gravity="center_vertical"

                           android:paddingLeft="5dip"

                           android:singleLine="true"/>                

                 

    </LinearLayout>

   

    <ImageView android:id="@+id/copyClipboard"

                              android:layout_width="48dip"

                              android:layout_height="48dip"

                              android:layout_marginLeft="5dip"

                              android:layout_marginRight="0dip"

                              android:background="@null"

                              android:src="@drawable/button_copy_clipboard"

                              android:gravity="center_vertical"/>

                             

    <ImageView android:id="@+id/deleteImage"

                              android:layout_width="48dip"

                              android:layout_height="48dip"

                              android:layout_marginLeft="0dip"

                              android:layout_marginRight="0dip"

                              android:background="@null"

                              android:src="@drawable/button_delete_record"

                              android:gravity="center_vertical"/>

</LinearLayout>


 

모 특별한 내용은 없는 것 같아서 소스에 대한 설명은 생략합니다.

 

끝으로 첫번째 레이아웃 이미지에서 Info(1) 버튼 클릭시 아래 그림과 같이 AlertDialog 를 띄울 예정입니다.


alertDialog 화면을 구성할 때 message(단순문자열)말고 view 자체를 add 할 수가 있습니다. Information 화면은 다른 App을 만들 때도 거의 유사한 형태로 사용될 수 있는 재사용성이 높은 UI라서 Layout 뿐만 아니라 string 정보도 별도로 xml을 구성하도록 하겠습니다. 일부 개발자 분들 중 문자열 리소스는 res/values/strings.xml 파일에만 작성해야만 되는 줄로 알고 계신 분들도 많은 것 같은데..꼭 그렇지는 않습니다. stings.xml 과 동일한 형식으로 xml 문서를 만드신 후 /res/values/폴더에 다른 이름으로 저장하시면 됩니다. 앱의 복잡도가 올라가면서 문자열 리소스에 등록한 문자열이 많은 경우에 한 파일로만 작성하시면 관리하기 힘이 듭니다. 모듈별로 만드는 것도 나쁘지 않다고 봅니다.

 

저희 앱의 Info 는 앞에서 얘기했듯이 재사용성이 높으므로 stings-info.xml 문서를 만들어 values/ 폴더와 values-ko/ 폴더에 넣습니다.

 

res/values/strings_info.xml

 <?xml version="1.0" encoding="utf-8" standalone="no"?>

<resources>

         <string name="msg_info_desc">Hangul to English is utility app for converting fro hangul keyboard to english keyboard.</string>

    <string name="msg_info_support_email_desc">Support Email </string>

    <string name="msg_info_support_email">jinook@paran.com</string>

    <string name="msg_info_thanks">Thanks.</string>

    <string name="msg_info_support_url">http://overoid.tistory.com</string>

    <string name="msg_info_support_url_desc">Support Site</string>

    <string name="msg_info_close_button">close</string>

</resources>


res/values-ko/strings_info.xml

<?xml version="1.0" encoding="utf-8" standalone="no"?>

<resources>

         <string name="msg_info_desc">Hangul To English 프로그램(App)은 사용자가 보다 한글로 입력된 패스워드에 해당되는 영문자를 쉽게 찾아주는 앱입니다.</string>

    <string name="msg_info_support_email_desc">문의사항, 건의사항은 이메일로 보내주세요.</string>

    <string name="msg_info_support_email">jinook@paran.com</string>

    <string name="msg_info_thanks">감사합니다.</string>

    <string name="msg_info_support_url">http://overoid.tistory.com</string>

    <string name="msg_info_support_url_desc">상세 메뉴얼 및 프로그램 관련 정보는 아래 사이트를 참고해 주시기 바랍니다.</string>

    <string name="msg_info_close_button">닫기</string>

</resources>

 
그리고 AlertDialog에 넣을 View의 내용을 Layout 파일로 만들어 res/layout/ 폴더에 넣어둡니다.
코딩은 차차 하게 될 것입니다.

res/layout/info.xml


<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

           xmlns:android="http://schemas.android.com/apk/res/android"

           android:layout_width="fill_parent"

           android:layout_height="fill_parent"

           android:padding="10dip"

           android:orientation="vertical">

          

           <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:textSize="12sp"

              android:text="@string/msg_info_desc"/>

             

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:layout_marginTop="20dip"

              android:textSize="12sp"

              android:text="@string/msg_info_support_url_desc"/>

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:textSize="12sp"

              android:linksClickable="true"

              android:autoLink="web"

              android:clickable="true"

              android:text="@string/msg_info_support_url"/>

             

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:layout_marginTop="20dip"

              android:textSize="12sp"

              android:text="@string/msg_info_support_email_desc"/>

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:textSize="12sp"

              android:linksClickable="true"

              android:autoLink="email"

              android:clickable="true"

              android:text="@string/msg_info_support_email"/> 

             

          <TextView android:layout_width="fill_parent"

              android:layout_height="wrap_content" 

              android:layout_marginTop="20dip"

              android:textSize="12sp"

              android:text="@string/msg_info_thanks"/>                  

          

</LinearLayout>


LinearLayout 하위에 TextView만 잔뜩 가지고 있는 Simple View입니다. 중간에 제 블로그 및 email 링크를 위해 몇가지 속성이 추가되었습니다. Android:autoLink email 혹은 web으로 주면 자동으로 링크가 만들어집니다. 또한, android:linksClickable=”true”로 설정하시면 앱 화면에서 링크를 클릭하면 자동으로 웹사이트로 이동하거나 이메일 어플이 실행됩니다. 별다른 코드도 필요없습니다.

 

아래 그림은 최종적으로 만든 layout을 별도의 코딩없이 애뮬에 띄운 모습입니다.
                               [HVGA , 영문 모드에서 실행한 화면]


                              [WVGA800, 한글 모드에서 실행한 화면]


드디어 장문의 레이아웃편이 끝이 났습니다. 파트2에 Syntax Highlight 적용하는데.. 이상하게 먹어서 결국은 다시 빼 버렸네요. 에구..시간만 허비했습니다. 어차피 소스 파일로 제공할 건데...왜 적용하느라 애 먹었는지 ...

리소스편까지 작업된 소스 파일입니다. 필요하신 분 다운받아 가시기 바랍니다.


 

다음번 강좌에서는 리소스 XML 을 들여다 보는 방법에 대해서 간단히 알려드린 후 DB(sqlite)로 넘어가도록 하겠습니다.


  1. 신단수 2010.08.17 17:17 신고

    안드로이드 개발에 대한 막연함만 가지고 있었는데..
    강좌 처음부터 보니 감이 잡힙니다.
    단비와 같은 자료 제공과 강좌입니다.
    좋은 정보 감사드려요...

  2. 이유식 2010.08.26 12:03 신고

    안드로이드를 공부하고 있는 학생입니다.
    졸작으로 어플을 만들고 있는데 제가 원하는 리스트 뷰에 대한 내용이 여기에
    딱 있어서 많은 도움이 되었습니다.
    죄송하지만,, 리스트 아이템 추가 이벤트와 리스트에서 삭제 이벤트에 대한
    팁만 조금 알려주시면 안되나요?
    위에 save text에 대한 이벤트는 버튼이 있는 곳에서 이벤트 처리를 하면 될것 같고요,,
    그 밑에 리스트 아이템으로 오는 것에서 삭제 이벤트는 어떻게 지정해야 되나요?

    • 보고픈 2010.08.26 12:56 신고

      리스트의 내용을 보통 Adapter에서 처리하는데요.
      삭제 이벤트는 Adapter내의 getView() 메소드 내에서 이벤트 핸들러를 추가하시면 됩니다.
      상세한 소스 코드는 다음 다음번 포스트 정도에서 나올것 같습니다.

  3. AnzLove 2010.09.05 03:30 신고

    이미지버튼 때문에 골치아파하고 있는데, 도움이 많이 되네요. ^^
    감사합니다.


오늘의 안드로이드 실전 개발 강좌는 전 시간에 이어 저희가 개발할 앱의 레이아웃 및 리소스들을 만들어 보도록 하겠습니다

전체 강좌 목차
[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작
[강좌A05] 안드로이드 실전 개발 - 레이아웃 및 리소스 : Part1

추천해 드린 책들 중 레이아웃 및 리소스 부분은 먼저 공부하셨는지 모르겠습니다. 최소한 그 부분은 먼저 공부하시고 제 강좌를 보시기 바랍니다. 대충 훑어 보시면 됩니다. 블로그에서 대부분의 안드로이드 책에 있는 내용까지 모두 다룰려면 거의 책을 써야 되는 상황이라 아주 초보적인 내용은 다루지 않기 때문에 책을 보신 후 블로그 포스트를 보시면 설명을 이해하는데 도움이 될 것입니다.

먼저 Motodev 실행하시고 (eclipse에 SDK 설치하신 분도 상관없습니다) Motodev > New > New Android Project 실행하시고, 아래 그림에 보이듯이 



프로젝트 정보를 설정후 생성합니다.

프로젝트 명은 “Hangul2English”

Target SDK : Android 2.0 (국내 출시용이므로 2.0 으로 해도 무방합니다. Google Market에 해외 사용자들도 포함해서 Release 하실려면 적어도 1.6으로 만드셔야 합니다. 아직 1.5 사용자도 많긴 하지만, 1.5와 1.6사이에 변경된 게 많아서.. 1.6정도로 타협하시면 될 것 같습니다.)

Package Name : com.overoid.hangul2english (제 블로그의 앞자리를 땄습니다.)

프로젝트를 생성하셨으면 아래 그림과 같이 패키지 및 디렉토리를 추가하여 구성합니다.

com.overoid.hangul2english.data – sqlite 관련된 소스가 들어갈 예정입니다.

com.overoid.hangul2english.util – utility class

 

화면이 많아서 소스가 복잡해지면 저는 activity / adapter / view 용 패키지를 따로 만들어서 사용하기도 합니다. 구글에서 만든 패키지들를 보면 아무리 복잡해도 별도의 패키지를 안 만들고 메인 폴더 하위에 수십개의 클래스 파일이 다 들어가 있습니다. 머리가 좋은 모양입니다. 저는 파일이 너무 많아지면 이게 그건지 저게 그건지 너무 헷갈립니다. 본 프로젝트는 워낙 화면이 간단한지라 나눌 필요는 없을 것 같습니다.

 

만일, 개발하시려는 앱이 많은 화면과 뷰를 가지고 있다면 아래와 같이 패키지를 나누셔도 좋을것 같습니다. (그냥 제 취향입니다.)

 

com.overoid.hangul2english.activity

com.overoid.hangul2english.activity.adapter

com.overoid.hangul2english.activity.view

 

res\ 하위에 drawable 디렉토리를 추가합니다. drawable 에는 xml로 된 리소스(이미지 등) 파일이 들어갈 예정입니다.

 

values-ko 디렉토리도 추가한 후 values/strings.xml 파일을 복사해서 붙여넣습니다. 한글 문자열을 저장할 파일입니다. values-ko 라고 디렉토리를 만들어서 사용하시면 android os가 폰 설정된 언어셋을 읽어서 언어에 맞게 strings.xml 파일을 로드해서 처리합니다.

 

기본 작업이 끝났습니다.

이제 처음으로 작업할 내용은 이미지나 버튼을 위한 리소스(이미지 등) 작업을 먼저 하겠습니다. 나중에 해도 상관없습니다. 제 개발 스타일입니다.

.

저희가 개발할 앱의 UI 기획서를 다시 보겠습니다.


이 화면 요소 중 1, 2, 3, 4, 5, 7 bitmap(png) 이미지 파일을 로드 한 것 입니다. 6번도 이미지이긴 하지만 xml로 정의해서 로딩시에 그린것입니다.

먼저 UI 구성에 필요한 이미지를 제작 혹은 구하셔서 res\drawable-hdpi 디렉토리에만 저장합니다. (아이콘 제작은 이전 포스트(http://overoid.tistory.com/7) 을 참고하세요)

여기서 잠깐..

안드로이드가 여러 크기의 화면을 어떻게 지원하는지 잠깐 돌아보겠습니다.

원문은 http://www.kandroid.org/guide/practices/screens_support.html 여기에 있습니다. 이 문서는 반드시 읽어 보시기 바랍니다. 아주 중요한 문서입니다. 그렇지 않으면 나중에 복잡한 UI를 여러 화면 사이즈에 대응해야 하는 앱을 만들 때 고생하십니다.

 

제가 소개해 드린 알짜만 골라 배우는 안드로이드 프로그래밍 2” 에도 원문 문서를 기반으로 상세히 관련 내용이 추가되어 있습니다. 오호.. 오늘 찾아보니 번역하신 분도 있군요. http://materer.tistory.com/archive/201005 이 문서입니다.

 

여기에서는 위 내용 중 꼭 필요한 일부분만 간략하게 설명하도록 하겠습니다.

먼저 용어 설명입니다.


스크린사이즈(Screen Size) : 스크린 사이즈는 스크린의 대각선 크기 값으로 물리적인 크기를 나타냅니다. 안드로이드는 스크린 사이즈를 large, normal, small 로 나눕니다.

가로세로비(Aspect ratio) : 가로세로비는 스크린의 물리적인 넓이와 높이 비율을 말합니다. 안드로이드에서는 리소스 제한자인 long, notlong을 이용하여 화면 비율에 대한 layout 리소스를 제공합니다.

해상도(Resolution) : 스크린이 가지고 있는 전체 픽셀수를 나타냅니다. 해상도가 보통 넓이 * 높이로 표현되기는 하지만 해상도가 특정 가로세로비(Aspect ratio)를 의미하지는 않습니다. 안드로이드에서는 해상도를 직접 처리하지는 않습니다.

밀도(density) : 스크린 해상도를 기반으로 물리적 넓이와 높이안에 얼마나 많은 픽셀이 들어있는가를 나타냅니다. Lower density의 스크린에서는 같은 넓이와 높이안에 더 적은 수의 픽셀이있고, Higher density의 스크린에서는 같은 넓이와 높이안에 더 많은 수의 픽셀이 있습니다.  안드로이드에서 density는 아주 중요한 개념입니다. 만일, UI 요소들을 pixel 단위로 크기를 지정하면 낮은 density 화면에서는 더 크게 보이고, 높은 density 화면에서는 더 작게 보입니다. Androiddensity high, medium, low 로 나누며, 플랫폼에서는 실제 스크린밀도에 맞게 리소스들의 사이즈를 조정합니다.

Density Independent Pixel(dip) : density와 상관없이 레이아웃의 위치를 표현할 때 사용하는 가상의 pixel 단위입니다.. Density-independent pixel default density 160dip 에서의 물리적 pixel과 같다. Mediaum Density(160), mdpi 화면에서는 1pixel = 1dip이며, 다른 dip에서 픽셀변환공식은 pixels = dips * (density / 160) 로 처리됩니다.



다음 표는 Android에서 지원되는 Screen Size Density 간의 관계를 나타낸 내용입니다.

   Low density (120), ldpi  Medium density (160), mdpi  High density (240), hdpi
 Small screen  QVGA (240x320),
 2.6"-3.0" diagonal
   
 Normal screen  

W QVGA (240x400),
  3.2"-3.5" diagonal

 

 FWQVGA (240x432),
 3.5"-3.8" diagonal
 HVGA (320x480),
3.0"-3.5" diagonal
 

  WVGA (480x800),
  3.3"-4.0" diagonal

 

FWVGA (480x854),
3.5"-4.0" diagonal
 Large screen    

  WVGA (480x800),
  4.8"-5.5" diagonal

 

 FWVGA (480x854), 
 5.0"-5.8" diagonal
 


이 표에서 BaselineHVGA, Normal Screen, Medium density 이며, dip pixel 1:1로 매칭되는 조건입니다. 대부분의 국내 폰들은 WVGA(400*800) hdpi 입니다.

 

실제 개발시 장치 종류마다 리소스 한정자를 사용하여 별도의 리소스를 지정할 수도 있습니다.
밀도(ldpi, mdpi, hdpi, nodip)

가로세로비(long, notlong)

스크린사이즈(small, normal, large)

 

리소스를 디렉토리명으로 구분한 샘플입니다.

Res/layout/layout1..xml -> Noraml 스크린 사이즈 레이아웃

Res/layout-small/layout1.xml -> Small 스크린 사이즈 레이아웃

Res/layout-large/layout1.xml -> Large 스크린 사이즈 레이아웃

Res/drawable-ldpi/icon.png -> Low density를 위한 아이콘

Res/drawable-mdpi/icon.png -> Medium density를 위한 아이콘

Res/drawable-hdpi/icon.png -> High density를 위한 아이콘

Res/drawable-nodpi/res.xml -> density와 무관한 리소스

 

무슨 말들인지 어렵습니다. 하하~ 쉽다구요. 여러분들은 머리가 저보다 훨씬 좋으신 겁니다. 위에서 얘기한 것중 이것만 기억하십시요. 안드로이드는 폰이 다양해서 여러 UI를 동시 처리하기 위해서 신경써야 한다. 그중 제일 중요한 것은 density이며, dpi라는 단위를 사용해야 한다.

 

이처럼 다양한 크기 및 density를 지원해야 하기 때문에 안드로이드 UI 개발은 iPhone UI 개발보다 훨씬 복잡하고 귀챦습니다. 그렇기 때문에 안드로이드가 내부적으로 UI를 처리하는 방식을 이해하고, 개발할 앱의 타겟을 잘 잡아서 만들어야 합니다.


하지만 국내 TStore에 올라오는 어플들을 보면 국내 출시된 폰이 거의 큰 화면 위주라 이런거 신경안쓰고 큰 화면 기준으로 개발된 앱들도 더러 있는 것 같습니다. 어떻게 아냐구요? 다음 강좌에 나올 리소스 해킹편을 보시면 다른 앱들의 리소스를 들여다 보시면 이해가 되실 겁니다.

 

How Android Supports Multiple Screens

 

안드로이드에서는 런타임시에 아래 3가지 방식 중 하나로 다양한 화면을 지원합니다. 아주 중요한 내용입니다.

 

1.  Pre-Scaling (보통 bitmap 이미지 처리시에..)


Pre-Scaling
은 로딩시점에 크기를 조절합니다. CPU에 이득이 있다고 알려져 있습니다.

Pre-Scaling은 폰의 Density를 기준으로 동일한 dpi 디렉토리에 지정된 리소스를 로딩하며 이때는아무런 크기 변환없이 보여줍니다. 즉 폰이 hdpi density라면 res/drawable-hdpi 디렉토리 하위의 리소스를 먼저 찾아서 있다면 아무런 크기 변환없이 그대로 보여주게 됩니다. 만일, 매칭되는 리소스가 없다면 디폴트 리소스(basline)를 로딩하고 로딩시에 적합한 density로 크기 변환을 합니다. 예를들어 res/drawable-mdpi/ 100*100 아이콘만 존재한다고 할 때 만일 폰의 사양이 hdpi라면 안드로이드는 drawable-mdpi 하위의 아이콘을 읽을때 자동으로 크기를 확대해서 150 * 150 bitmap을 만듭니다. 반대로 drawable-hdpi/ 150*150 아이콘만 존재할 때 mdpi 폰에서 읽으면 자동으로 100*100 아이콘으로 크기를 변환합니다. drawable-hdpi drawable-mdpi에 모두 이미지가 존재한다면 별도의 스케일 변환 작업이 필요없으니 좀 더 성능에 유리합니다.

 

2. Auto-scaling (pixel dimensions and coordinates)


Auto-scaling
은 그리는 시점에 크기를 조절합니다. 메모리에 이득이 있다고 알려져 있습니다. 주로 Pixel좌표, Pixel Dimesion, Application에서 사용된 Pixel 수식 등에 적용되며, 리소스가 아닌 웹이나 SD카드에서 Bitmap 데이터를 가져왔을 때도 적용됩니다. 쉽게 얘기하면 App에서 (10,10)에서 (100,100) 좌표로 4각형을 그리도록 구현했다면 High-Density(240dpi) 화면을 가진 Device에서는 그리는 시점에 자동으로 스케일을 변환해서 (15,15) (150,150) 좌표에 사각형을 그리게 된다.

 

3. Compatibility Mode(호환모드)


Large
스크린을 지원 안하는 앱을 Large 스크린에서 실행하면 검은 배경에 원래 크기만큼만 표시합니다. 보기에는 별로입니다. 앱이 싼티 납니다.


.. 여전히 복잡하고 어렵군요. 원문 보시면 좀 더 상세하지만 복잡한 내용들로 가득 차 있습니다.

중요한 점은 안드로이드가 여러 화면사이즈를 지원하기 위해서 런타임시에 자동으로 크기를 바꾼다는 것을 기억하십시요.

 

제가 사용하는 기본 레이아웃 디자인 룰은 다음과 같습니다.

 

1. 레이아웃 디자인시에는 HVGA 기본 스크린 사이즈를 중심으로 DIP 단위만을 사용해서 디자인합니다. (px dip 1:1 이라서 화면 크기에 대응하여 사이즈 결정하기가 좋습니다.) 320 * 480 화면 기준으로 들어갈 이미지나 UI 요소들 각각의 가로 사이즈를 px로 계산한 후 코딩시에는 그 값의 단위를 dip로만 입력하면 됩니다.

 

2. AbsoluteLayout을 사용하지 않습니다. , 화면의 절대 좌표 보다는 상대 좌표를 사용해야 합니다. 이거 사용해서 디자인하면..나중에 감당 안됩니다.

3. Bitmap HDPI 기준으로 만듭니다. 그래야 자동으로 크기가 조정 되더라도 보기에 좋습니다. , HVGA를 기준으로 계산했을 때 100 * 100 이미지가 필요하다면 HDPI 기준으로 크기의 1.5배인 150 *150 크기로 실 이미지를 제작하시면 됩니다. 만일, 여력이 된다면 hdpi mdpi 기준으로 각각 만드는 것도 나쁘지는 않습니다. 위에서 언급했듯이 성능에 유리합니다.

 

4. 각 장치별로 레이아웃 및 이미지를 모두 별도로 만들어서 세밀하게 조정할 수도 있지만, 그런 경우 관리가 힘듭니다. 가급적 레이아웃과 이미지를 적게 사용해서 통일된 UI를 구성하는 것이 좋습니다.

 
자 이제 저희 앱 개발로 돌아가겠습니다.

기획서 상의 1, 2, 3, 7 번 이미지는 HVGA상에서 32 * 32 크기로 랜더링 될 것입니다. 즉 코딩시에 가로 세로 사이즈 값에 32dip라고 입력할 것입니다. 그래서 실제 이미지는 위 가이드에 따라서 48 * 48 (32 * 1.5 = 48) 사이즈로 제작해서 drawable-hdpi 에 저장합니다.

4, 5번 이미지는 리스트에 들어갈 이미지 버튼입니다. 약간 큰 리스트로 구성할 계획이라서 코딩시 크기값은 48dip 가 되며, 실 이미지 제작은 hdpi 기준으로 64 * 64 크기로 제작합니다.

 

그리고 1, 4, 5번은 클릭시에 이미지 버튼에 효과를 주기 위해서 이미지를 쌍으로 준비합니다. iconWorkshop에서 배경에 색칠을 한다던지, GrayScale로 변경하여 만드시면 됩니다.

 

6번 이미지는 이미지로 제작한 게 아니라 xml로 정의하여 런타임시에 그린 이미지입니다.

8, 9번은 nine-patch 이미지입니다. Nine-patch 이미지는 이미지를 9분할하여 이미지 사이즈 변경시에 이쁘게 형태를 유지할 수 있도록 하는 이미지 입니다. 파일명.9.png 이런식으로 파일명이 구성되어 있습니다. 레이아웃에 지정할 때는 파일명 부분만(9.png를 제외한) 지정하시면 됩니다.  이 두 이미지는 제가 이것저것 보다가 찾은 이미지인데, 안드로이드 블랙 배경에 잘 어울리는 듯 합니다.

 

참고로 Android SDK 하위 폴더를 보시면 data/res/drawable-hdpi/ 애뮬레이터에서 사용하는 모든 이미지들이 있습니다. 좋은 것들도 많고 도움이 많이 됩니다. 구글에서 제공되는 기본 앱 디자인 스타일과 유사한 인터페이스를 제공하려면 이 이미지들을 이용하시면 됩니다.

 

이제 이미지가 모두 준비되었습니다.

다음 화면은 hdpi 폴더에 저장된 이미지 내용입니다. 위에서 언급한 내용과 이전 포스트에서 제작한 아이콘이 저장되어 있습니다.



내용이 너무 길어 읽으시는 분들이 지루할까? 여기서 레이아웃 Part1을 마치고, 잠시 후에 Part2로 찾아뵙겠습니다.


  1. 소영 2010.09.26 12:02 신고

    아 정말 정말 정말 많은 도움이 되었어요!!
    에뮬레이터에서 기본적으로 제공되는 버튼 같은 것만 사용하다가

    이제 한번 직접 만든 이미지를 사용해서 상용화되는 앱을 만들려다보니까

    가장먼저 골치 아픈 것이 기종에 따라 다른 스크린 사이즈에서 이미지를 리사이즈 하는 것이더라구요..ㅠㅠ

    사실 아직도 -_-.. 그렇지만 여기서 정말 좋은 정보 많이 얻고 가요! 도전해봐야겠어요! ㅎㅎ

  2. 희망안드 2011.01.25 06:56 신고

    성격이 깔끔하시고 세심한 면이 많으신듯 해요
    하나하나 글쓰시는게 많은 도움이 되고
    집중력을 높이네요
    대단히 감사합니다
    강좌 계속 하시면서 책도 쓰심 대박 나실듯
    계속 강좌 부탁드려요

  3. 강민 2011.11.20 12:36 신고

    당신은 최고입니다 ㅋ
    너무 강좌가 좋네요
    교육쪽에서 일하시나? ㅋㅋㅋ
    계속 강좌 읽겠습니다 ㅠㅠ


두번째 앱 "Hangul to English"가 드디어 TStore 심사를 통과하고 시장에 나왔습니다.

네~네. 지금 제가 열심히 개발 강좌 쓰고 있는 그 앱입니다.
강좌가 끝날때에는 제 소스가 여러분들에게 모두 오픈 될 것입니다.
뭐~ 대단하지도 않은 소스라 큰 도움은 안되시겠지만, 제가 강좌를 통해 Touch Call 개발하면서, Hangul To English 개발하면서 느꼈던 어려움이나 고생했던 부분들에 대해 책에 잘 안나오는 것들을 중심으로 가능한 많이 설명을 하려고 하니 제 블로그 강좌에 많은 관심 가져 주시기 바랍니다.

[App 소개]

스마트폰 사용하시다가 특정 사이트 패스워드를 한글로 만들었기 때문에 대응하는 영문 자판을 몰라서 제대로 로그인 하지 못한적은 없으십니까? 자판으로 칠 때는 자판을 손이 외우고 있으니 아무 생각없이 패스워드를 입력했는데.. 스마트폰에서는 자판 배열도 틀려서 제대로 한글에 해당 되는 영문 자판을 모르실때 사용할 수 있는 어플입니다.

다시말해 Hangul to English는 한글로 만들어진 텍스트에 해당되는 영문 자판 글자를 찾아주는 App입니다.

[App 특장점]

Hangul To English 는 쉽게 한타를 영타로 변환해 줍니다. 또한, 자주 사용하시는 단어를 DB에 저장할 수 있으며 버튼 클릭으로 Clipboard에 복사가 가능하므로 쉽게 타 프로그램이나 웹사이트에 로그인 할 수 있도록 도와 줍니다.

 
 
 Hangul To English 메인 화면  Hangul To English 소개 화면


[App 사용 가이드]

1. 한글 입력창에 한글을 입력하시면 대응되는 영문 자판 글자가 영문 입력창에 나타납니다.

2. 한글 입력창 옆의 버튼은 입력한 글자를 지워주는 기능 버튼입니다.

3. 영문 입력창 옆의 버튼은 영문으로 나타난 글자를 클립보드에 복사해 주는 기능입니다. 간단히 복사해서 입력하고자 하는 화면에서 텍스트 창을 길게 누르시면 선택옵션에서 붙여넣기를 하시면 됩니다.

4. 텍스트 저장 버튼은 화면에 나타난 한글/영문 텍스트를 DB에 저장해 주는 기능입니다.

5. 저장된 텍스트 목록은 하단 리스트에 나타나며, 리스트에서도 클립보드에 복사하실 수 있으며, X 버튼 클릭으로 바로 삭제도 가능합니다.

6. 메인 타이틀 옆의 Info 버튼을 클릭하시면 저작자 블로그 및 저작자에게 문의할 수 있는 이메일 주소가 나타납니다. HTML 태그를 클릭하시면 폰에서 메일을 작성하거나, 폰으로 블로그 컨텐츠롤 볼 수 있습니다.

[App 다운로드]

Hangul To English는 TStore 에 등록되어 있습니다. 가격은 무료입니다. 앱 소스와 실전 개발 가이드는 블로그를 통해 제공될 예정입니다. 앱에 대한 문의는 블로그나 메일로 보내주시면 됩니다.


다운은 요기서 (http://www.tstore.co.kr/userpoc/game/viewProduct.omp?insDpCatNo=DP04003&insProdId=0000031822&prodGrdCd=PD004401&t_top=DP000504)



[개발후기]

본 앱은 순전히 제가 필요해서 만든 앱입니다.
처음에 만들때는 강좌는 생각지도 않았었고, 네. 그때는 블로그도 없었지요.
한 일주일이면 개발할 수 있을 것 같아 쉽게 시작을 했는데.. 거의 마무리 단계에서 회사 업무가 갑자기 몰리고 일이 한가해 질때 휴가 다녀오느라..개발 완료가 지체되어 버렸습니다.

개발할 당시 TStore에 비슷한 앱이 없었는데, 등록할려고 보니 거의 유사한 앱이 이미 등록이 되어 버렸더군요.
그것도 무료로.. 말입니다.

그래도 유료로 한 500원에 팔면 저희같이 가난한 개발자들이야 S/W에는 돈 100원도 아까워서 잘 구매 안하지만, 일반인들 중에서 별로 푼돈에 연연 안하시는 분들도 많이 계셔서 한 100개 정도는 팔 수도 있겠지만..

그냥 오픈하기로 했습니다. 광고도 빼버리고..
거기다가 오픈하면서 아예 소스랑 앱 실전 개발 가이드까지...



 

'앱스' 카테고리의 다른 글

Touch Call - New Version Released!  (7) 2010.10.18
두번째 App - "Hangul To English" 출시  (3) 2010.08.14
첫번째 Andorid App - Touch Call (터치콜)  (3) 2010.07.31
  1. starcrazy 2010.08.14 01:34 신고

    맞아요 정말 개발일하면 오히려 S/W는 더 안사게 되는듯...특히 회사에서는 간단한거면 만들어 쓰라고 하기도 하고...^^;

  2. acdam 2010.09.05 01:32 신고

    Hangul To English 앱이 이런 사연이있었군요
    덕분에 저희는 앱 실전으로 배울수있어서 다행이네요 ㅎㅎ

  3. 사자고구미 2010.09.21 18:17 신고

    안드로이드사이드 강좌에서 링크타고 왔습니다. 배우기 전 먼저 감사인사드립니다. ^^*


전편에 이어 오늘의 강좌는 아이콘 제작 및 아이콘 관련 자료들에 대해서 좀 알아보도록 하겠습니다.

전체 강좌 목차
[강좌A01] Moteodev Studio를 이용한 안드로이드 개발 환경 구축 가이드
[강좌A02] 안드로이드 개발 참고 서적 소개
[강좌A03] Android 실전 개발 - 아이디어 / 기획 / Wireframe
[강좌A04] 안드로이드 실전 개발 - 아이콘 제작

왠 개발 강좌에서 아이콘 제작이냐? 라고 의아해 하시는 분도 많으시겠지만,
이런 분들은 곁에 훌륭한 디자이너 분들이 계시거나, 본인이 이미 상당한 디자인 감각 및 스킬을 갖추신 분들일겁니다.
 
제가 처음으로 안드로이드 개발을 할 때...
혼자서 코딩 뿐만 아니라 아이콘 제작 등 이것저것 모든 작업을 다 해야 했었습니다. 작업하다 보니 가장 시간이 많이 걸리는 작업이 코딩이 아니라 아이콘이나 디자인 요소 제작이었습니다. 그렇게 시간을 투자한다고 해도 뛰어난 퀄리티의 작품이 나오지도 않는게 더 환장할 노릇이죠.

하여간 오늘은 곁에 뛰어나신 디자이너 분들과 협업할 형편이 못되는 저같은 개발자들을 위해.. 아이콘 관련 툴과 관련 자료들에 대해서 알아보도록 하겠습니다.

1. 만들지 말고 무료 아이콘 중에서 적합한 아이콘을 찾아서 사용하자.

앱에 필요한 아이콘 제작과 관련하여 가장 좋은 방안은 당연히 전문 디자이너가 크리에이티브한 아이콘을 제작해주는 것일 겁니다.  그럴 상황이 안되신다면.. 이미 만들어진 뛰어난 퀄리티의 아이콘을 무료로 사용할 수 있다면 더 좋을겁니다.

아래 사이트들은 대표적인 아이콘 사이트들입니다.
유료도 있으며, 무료 아이콘도 있고  무료 중에서도 상용으로 사용가능한 것도 있고, 비상용인 경우에만 무료인 것도 있습니다. 자신에게 적합한 아이콘이 있다면 라이센스 잘 확인하시고 사용하시면 됩니다.

무료 아이콘 사이트 들

A. http://www.iconarchive.com/


30,000 여개 이상의 최대 icon 사이트 입니다. 웹용 데스크탑용등 다양한 포맷으로 파일등을 제공하고, 유료도 많지만 무료 아이콘으로 상당한 퀄리티를 가진 것도 많습니다. 카테고리별로 정리되어 있으니.. 한 두시간만 투자하면  대충 다 볼 수 있을것 같습니다.
안드로이드에서는 png를 주로 사용합니다. 사이트에서 맘에 드는 아이콘이 ico 파일이라면.. 주저말고 다운 받으시기 바랍니다. 대분분의 아이콘 편집툴에서 원하는 포맷으로 변환이 가능합니다.

B. http://www.freeiconsdownload.com


iconarchive 사이트와 겹치는 아이콘도 많지만 그렇지 않는 것도 상당히 많습니다. 역시, 라이센스를 잘 읽어보시고   다운 받아 사용하시면 됩니다. 위 첨부 이미지에 표기된 라이센스를 보면 Creative Commons Attribute 3.0 LIcense로 되어 있군요. 상세한 라이센스 내용은 웹사이트 찾아 보시면 상당히 많은 내용이 나옵니다. 여기서 간략하게 소개한다면.. 대부분 비상업적 용도에 사용하시는 것은 저작자 표기(사이트 링크 등)만 하시면 그냥 사용하시면 됩니다. CCL 중에서도 상업적 용도에 사용하는 것도 허락이 되어 있다면 저작자 표시만 하시고 상업적으로 사용하셔도 좋습니다. 상업적 이용을 허락하지 않는 것들도 저작자의 허락을 득하면 되기도 하므로 꼭 필요하시면 직접 연락해보시는 것도 괜챦을 것 같습니다.

아래 사이트들은 추가적인 아이콘 사이트 들입니다. 위와 비슷한 것들이 많습니다. 워낙 내용들이 많고 겹치는 아이콘들이 많아..그렇지 않는것 찾기도 힘이 들지만, 의외로 좋은 자료를 찾을 수도 있습니다.

- http://downloadpedia.org/Free_Icons_and_Buttons

- http://www.freeiconsweb.com/

2. 마땅한게 없으면 만들어 보자.

적당한 무료 아이콘이 없거나 독창적인 아이콘을 저작해야 한다면 어쩔수 없이 만들어야 합니다. 물론 Adobe의 포토샵이나 일러스트를 잘 사용하신다면 훨씬 퀄리트 높은 아이콘을 저작하실 수 있습니다.

포토샵이나 일러스트를 이용하신다면 아래 사이트 자료들을 참고하시기 바랍니다.

iPhone icon 제작 가이드

아이폰용으로 만든 아이콘도 안드로이드에서 사용해도 좋은것 같습니다. 삼성에서 만든 갤럭시 핸드폰을 보면 메뉴화면은 꼭 아이폰처럼 구현을 했습니다. 아이폰에서 사용하는 입체감 있는 둥근 사각형 버튼을 배경으로 각 앱 아이콘 하단에 깔았더군요. 상당히 괜챦아 보입니다.

http://www.photoshopvids.com/photshop-tutorials/photoshop-tutorial-how-to-make-iphone-icons/
아이폰용 아이콘을 만드는 방법을 동영상으로 알려주네요.

http://blog.cocoia.com/2010/iphone-ipad-icon-psd-template/
아이폰용 아이콘을 제작을 위한 PSD 템플릿 파일을 제공합니다.

http://www.storm-consultancy.com/blog/design/design-tutorials/how-to-mke-iphone-application-icon-photoshop/
포토샵으로 iphone용 아이콘을 만들 수 있는 방법을 자세히 설명하고 있습니다.

Android icon 관련 자료

http://androgeek.com/android-icon-packs-basics-and-a-list-of-free-icon-sets.html
안드로이드용 아이콘 팩과 무료 아이콘 세트 자료입니다. Android SDK를 설치하셨다면..  SDK 폴더 하위에 data\res\ 폴더에 있는 아이콘 자료들이 대부분 이긴 합니다.

http://developer.android.com/guide/practices/ui_guidelines/icon_design.html
이 사이트는 안드로이드 개발자가 가장 많이 참고해야 되는 사이트죠. 위 링크는 안드로이드용 아이콘 제작 가이드입니다. 에구.. 영어라서 그렇긴 하지만.... 암튼, 참고하시기 바랍니다.

아이콘 저작 툴

Axialis IconWorkshop 6 (http://www.axialis.com/)

최고의 아이콘 편집툴입니다. 가격은 49.95$나 하는군요. 저희같은 가난한 개발자들에게는 무척이나 비싸군요.
무료로 된 아이콘 편집툴들도 많긴 하지만, 이거 못 따라 옵니다. 기능이 많지 않아서 사용하기 어렵지 않고, 그럼에도 샘플이나 꼭 필요한 기능들은 다 있어서 무지 편합니다. 그냥 30일 평가판을 다운받아서 사용하시기 바랍니다. 노력하신다면 시리얼도 구하실 수 있을겁니다.

자.. 이제 부터는 디자이너도 없고, 무료로 적합한 아이콘도 없고, 저처럼 포토샵이나 일러도 잘 사용못하시는 분들은 저화 함께 IconWorkshop 으로 저희 프로젝트에서 사용될 메인 아이콘을 제작하도록 하겠습니다.

먼저 완성된 아이콘 부터 보도록 하겠습니다. 안드로이드용 어플의 High Density에서 사용되는 72*72 사이즈의 아이콘입니다.

네. 저희 프로젝트 Hangul To English 프로그램에서 사용될 메인 아이콘입니다.
한글인 "카"를 영문인 "zk"로 변환해 준다는 퀄리티가 높진 않지만, 어플을 내용을 어느정도는 잘 나타내 준다고
저는 생각하고 있습니다. 하하^^ 물론 제가 만들었으니깐요.

Step 1. 둥근 모서리 사각형 버튼 형태의 배경 작업

먼저 둥근 사각형의 버튼 형태의 배경부터 제작해야 합니다. iconWorkshop을 실행하시면 좌측 패널에 Librarian이란 패널이 보입니다. 일종의 아이콘 관련 라이브러리를 관리하는 기능이라고 생각하시면 될 것 같습니다. Librarian 창이 안 보이신다면 View 메뉴에서 선택하시면 됩니다. iconWorkshop의 Librarian에는 상당히 많은 샘플들이 있습니다. 참고로 Help 파일을 보면 이 샘플들을 가지고 아이콘을 제작하는 방법들을 잘 설명하고 있습니다.

Librarian > Objects > Pack 2 - Realistic Buttons > Rounded Square 를 눌러보시면 둥근 사각형 및 사각형에 이펙트를 줄 수 있는 여러가지 요소들이 나옵니다. 이 이미지들을 합치고 색상을 변형시켜서 여러가지 형태의 모서리가 둥근 사각형을 만들 수 있습니다.

물론 다른 도형들을 가지고 버튼을 제작할 수도 있습니다.

근데, 저희가 제작할 아이콘은.. iconWorkshop의 Librarian > Objects > Pack 5 - Web Illustrations > Background Bases > Glossy > Glossy Blue Rounded.png 에 이미 맘에 드는게 있군요. 앗싸.. 그냥 가져가다 쓰면 되겠습니다. 좌측 패널에서 더블클릭하면 메인 창에 띄워 둡니다. 이것으로 배경작업은 완료되었습니다.

유의하실 점은 iconWorkShop은 Photoshop처럼 Layer를 지원하지 않습니다. 그러므로 일반적으로 작업할때는 여러개의 Project (이미지)를 만들어서 최종 원하는 파일에 복사 - 붙여넣기로 작업을 합니다. 붙여넣기를 하고나면 마지막 붙여넣기 이미지는 컨트롤할 수 있는 핸들이 생기며, 크기 변환이나 색상 변환, 효과 주기 등 다양한 작업을 할 수 있습니다.

Step 2. 텍스트 구현

텍스트 "카", "zk"를 입력하기 위해서 각각의 iconWorkshop에서 New > Bitmap Image, Unix Icon..을 클릭합니다.


새 프로젝트화면에서 Project명은 아무거나 입력하시고(예:카) Color는 RGB Alpha Channel(RGB/A-32 bits)를 선택하시고 사이즈는 배경 버튼이 128 * 128 이었으므로 동일한 사이즈로 생성합니다.

그리고 우측 팔레트에서 먼저 흰색을 클릭((바탕색으로 설정) - 팔레트 하단의 HTML용 색상표에서 #FFFFFF를 입력하셔도 됨) 한 후  팔레트 위의 A 아이콘(텍스트 입력)을 눌러서 글자를 입력합니다.



이쁜 폰트를 찾아서 적당한 크기를 입력하고 확인하면 프로젝트에 해당 글자가 흰색으로 입력됩니다. 사각형의 핸들을 조정하여 적당한 크기 및 위치를 조정합니다.


텍스트가 선택된 상태에서 메인 작업창 상단의 Image Transformation 툴바를 선택하고 Drop Shadow를 선택합니다. 디폴트 옵션으로 OK 버튼 클릭. 그림자 효과를 주어 입체적으로 글자가 보입니다.
이제 팔레트에서 진한 푸른색을 선택한 후 팔레트 위쪽의 상단 Flood Fill (페인트 통)을 선택한 후 텍스트의 흰색부분에 클릭합니다.  텍스트가 테두리는 흰색을 가지면서 가운데는 짙은 푸른색으로 카 텍스트가 완성되었습니다.

zk 텍스트도 동일한 방식으로 작업합니다. zk를 위한 128 * 128 새로운 프로젝트를 만든 후 카 텍스트와 동일하게 작업하시면 됩니다. 다만, zk는 흰색으로 텍스트를 만든 후 노란색으로 입히는 게 아니라 처음부터 노란색 계열로 텍스트를 생성한 후 Drop Shodow에서 디폴트값인 검은색으로 그림자가 지도록 만들겠습니다.

Step 3. 화살표 심볼 만들기.

이제 화살표 심볼을 만들어야 합니다. 이걸 아무것도 없이 그려서 만드려면 저 같이 디자인 꽝인 사람들은 절대 못 만듭니다. 하하.. 그러나 iconWorkshop에서 저같은 사람들을 위해서 상당히 많은 무료 심볼들을 제공합니다.


Librarian > Icons > Free > Basic.icl 을 선택하시면 상당히 많은 아이콘용 심볼들이 있습니다. 저희는 그 중에서 첫번째인 Action redo 심볼을 사용하도록 하겠습니다.

Action redo을 더블클릭하시면 작업창에 32 * 32 아이콘이 나옵니다. 128 * 128 새 프로젝트를 만드시고
Action redo 아이콘을 전체선택(Ctrl + A), 복사 (Ctrl + C) 하신 후 새 프로젝트 창에 붙여넣기(Ctrl + V)
하시고 붙여넣기 옵션 중에 두번째 항목인 Resize Image to fit the editor area 항목을 선택하시면 32*32 아이콘이 128*128 사이즈에 맞게 붙여넣어 집니다. 이 아이콘의 크기 조절 핸들을 드래그 하셔서 적당한 사이즈로 줄여줍니다.



이번에는 색상을 변경하기 위해서 팔레트에서 선택한 색상으로 칠하지 않고, HSB 값을 조정하도록 하겠습니다. 메인 에디터 상단의 상단의 Image Color 툴바 하위의 Hue/Saturation.. 메뉴를 선택하면 위의 그림과 같이 나타납니다. 각각의 값을 조정하여 원하는 색상으로 화살표를 변경합니다.

색상 변경 후 역시 텍스트와 마찬가지고 그림자 옵션을 주어 입체효과를 줍니다.
 

그리고 화살표를 우측으로 회전시켜서 화살표가 카 와 zk 글자가 변환되는 느낌을 줍니다. 이제 각각의 요소가 모두 완성 되었습니다.

Step 4. 각각의 요소를 합쳐서 Icon 완성하기.

이제 지금껏 만든 4개의 프로젝트(일종의 4개의 레이어 효과)를 하나의 새 프로젝트로 합쳐야 합니다. 128 * 128 사이즈의 프로젝트를 만든 후 이전에 만들었던 각각의 프로젝트 화면에서 전체선택(Ctrl + A), 복사(Ctrl + C) 후 합쳐야 될 프로젝트에 붙여넣기(Ctrl + V) 합니다. 붙여넣기 할때는 이전의 작업에서 처럼 Resize 가능한 옵션으로 붙여 넣습니다. 붙여넣으신 후 제일 마지막에 붙여 넣기 한 항목에는 사각형 모서리 부분에 핸들이 나타납니다. 핸들을 조정하여 위치나 크기를 조정하여 작업을 마무리 하시면 됩니다. 잘못 작업하시면 언제든지 취소(Ctrl + Z)를 눌러 취소할 수 있으니 맘 놓고 작업하시면 됩니다.

이제 저희가 계획한 icon이 128 * 128 사이즈로 만들어졌습니다.
안드로이드용 어플에서는 기본적으로 72*72, 48*48, 36*36  사이즈의 아이콘 3개가 필요합니다.
그리고 TStore에 등록하기 위해서는 추가적으로 76*76, 212*212 사이즈의 아이콘이 필요합니다
메인으로 만든 128 * 128 사이즈로 각각 Resize (Image 메뉴 > Image Size .. 클릭 후 값 조정 혹은 신규 프로젝트를 원하는 사이즈로 만든 후 복사/붙여넣기로 생성) 하신 후 Save As.. 하시면 필요한 모든 아이콘 제작이 끝이 납니다.

끝으로 아이콘 제작시 도움이 되는 사이트를 하나 더 소개하겠습니다.

http://msdn.microsoft.com/en-us/library/ms997636.aspx

MS의 MSDN 사이트 컨텐츠인데, Windows XP용 아이콘 제작 가이드 및 Primary Color 등 아이콘 제작에 관해 아주 상세하고 유용한 정보들이 나와 있습니다. 

이것으로 네번째 강좌를 마칩니다. 다음번에는 순서를 바꿔서 UI 레이아웃을 먼저 만들고 (드디어 Motodev 실행하는 군요) 그 히후에 안드로이드 앱에서 이미지, XML 리스소 훔쳐보는 방법에 대해서 알려드리도록 하겠습니다.


  1. MCo더머 2010.08.10 08:18 신고

    오우 작게 써주는 프로그램도 있군여 포토샾으로하면 진짜 힘든데 ㅜㅜ

  2. 남시언 2010.08.11 23:35 신고

    긴글 잘보고 갑니다~ 정말 도움되는 글 계속 얻어가고있어요 RSS 에서요~~ㅎ

    많은 도움 될것 같습니다...
    포스팅에서 정성이 묻어나네요 ㅎㅎ

  3. fireman 2010.08.17 15:57 신고

    ㅋ 일반 프로그래머가 다루기 힘든 부분을 잘 설명해 주셨네요.

    많은 도움이 될 것 같습니다.
    이제 프로그래머도 아이콘 제작 직접 할 수 있을 것 같습니다.

  4. 궁금 2011.07.05 17:59 신고

    내장 되어있는 여러 아이콘 소스는 그냥 상업적으로 사용을 해도 상관이 없는지요 ??
    재판매는 아니고 빌더에 들어갈 아이콘인데 괜찮을까요 ??

    • 보고픈 2011.07.15 15:43 신고

      툴을 정품을 구매했다면 써도 상관없겠죠? 그렇지 않다면 공개되는 제품에는 사용이 좀 어렵지 않을까요?

  5. 예슬 2014.10.13 13:03 신고

    덕분에 일주일간 헤메던 글자깨짐 문제 잘 해결했습니다. 포토샵보다 일러스트레이터보다 글자가 덜 깨지네요.. 진짜 63x63사이즈로 만들었을때 글자깨지는 현상때문에 엄청 고생했거든요.. 좋은 포스팅 감사합니다 ^^ 근데 그래도 63사이즈는 글자가 완전히 선명하기는 어려운 걸까요.. 실제로 앱을 적용하면 여전히 흐린감은 있기에.. 좀 속상하네요 ㅠㅠ

본 내용은 이전에 작성된 SQL Style Guide에 이어진 문서입니다. 별도로 보셔도 상관없으나 함께 보시면 더 많은 도움이 되실 겁니다.   

Naming Conventions

 

File Naming Conventions

PL/SQL이나 SQL에서 코드를 저장할 때 다음과 같은 확장자를 사용한다. Oracle Object 타입별로 확장자를 사용함으로써 Toad 같은 툴에서 해당 에디터를 바로 실행시킬 수 있으며, 향후 Version Control 시스템과 연동하기도 유리하다. 또한, Object의 유지관리 및 editplus 같은 편집기에서 해당 확장자별로 Syntax Coloring 을 적용하기도 유리하다.

File Type

Extension

Package (spec, and optionally body)

pkg

Package Body

pkb

Procedure

prc

Function

fnc

Object type (spec, and optionally body)

typ

Object type body

tyb

Stored Java Procedure

sjp

Stored Java Function

sjf

Trigger

trg

Any other PL/SQL

pls

Any other SQL

sql

표 1 File Name Convention

Variable Names < Identifier elements>

일반적으로 변수명은 다음과 같은 5가지 요소로 구성되어진다. <Scope><Type><Primary Identifier><Modifier><Suffix> 하지만, 본 가이드에서는 <Scope><Type>을 하나의 <Prefix>로 구성하여 <Prefix><Primary Identifier>로 변수명을 구성한다.

변수명의 CASE는 prefix<소문자>_Primary Identifier<Camel Case>와 소문자 + '_'로 구성한다. CamelCase가 원칙이며, 오라클 변수 스타일인 소문자와 underbar(_)만으로 연결된 변수 스타일도 허용한다. 예) v_SiteCode(O), v_site_cd (o)

 

Prefix

 

글로벌 변수 : 'g_' + Primary Identifier

글로벌 변수는 주로 패키지 레벨에서 사용되며, private, public 변수에 상관없이 g 접두어를 사용한다. 예) g_ErrorText, g_SiteCode

로컬변수 : 'v_' + Primary Identifier

로컬변수는 로컬식별자인 l 과 변수식별자인 v로 합쳐서 구성할 수도 있으나, 변수식별자인 v만용 사용하여 작성한다. 예) ), v_SiteCode(O), lv_SiteCode(X), l_SiteCode(X)

파라미터변수 : 'p_' + Primary Identifier

주로 프로시저나 함수의 파라미터에 사용되며 접두어 p를 사용한다. in 파라미터, out 파라미터에 대한 suffix는 사용하지 않는다. 예) p_SiteCode(O), p_ProductModelCode(O), p_SiteCode_out (X)

상수 : 'K_' + Primary Identifier

상수는 접두어K를 사용한다. 일반적으로 c_를 사용할 수도 있으나, c는 cursor 변수인 경우에 사용하며 상수의 경우에는 K_를 사용한다. 상수의 경우에는 일반적인 코딩 관습에 따라 예외적으로 대문자로만 변수명을 구성한다. 예) K_InsertMsg(O), K_ACTION_TYPE(O)

커서 변수 : 'c_' + Primary Identifier

커서변수의 경우에는 접두어 'c'를 사용한다. cursor parameter경우에는 접두어 'cp'를 사용한다. 예) c_Employees, c_ModelCode

레코드변수 : 'r_' + Primary Identifier

테이블이나 커서의 ROWTYPE 변수의 경우에는 접두어 'r을 사용한다. 로컬변수라도 v 접두어와 함께 사용하지 않는다. 예) r_employee

루프변수

루프변수는 일반적으로 i, j 같은 simple 변수명을 사용한다. 그러나 v_month 같이 상황에 따라 의미있는 변수를 사용할 수도 있다. 예) FOR v_month IN 1 .. 12 LOOP

 

Primary Identifier

주 식별자는 변수명의 구성요소 중 가장 중요한 부분으로 의미적으로 명확한 것을 사용한다. 대체로 컬럼명은 약어들로 구성되므로 Primary Identifier로 컬럼명을 사용하기 보다는 Full Name을 사용하는 것을 권고한다. 예) v_PrdMdlCd (X), v_prd_mdl_cd(), v_ProductModelCode(O)

Object Naming Conventions

스키마 레벨의 오브젝트 테이블명, 컬럼명, 프로시저, 함수, 타입, 트리거 등의 Naming에 대한 가이드는 <Prefix><Primary Identifier><Suffix> 구조를 따른다. 테이블명과 컬럼명은 물리모델링 가이드를 따르며, 본 문서에서는 프로시저, 함수, 패키지, 타입명에 대한 규칙만을 정의한다. 데이터베이스 오브젝트명은 모두 대문자와 Underbar(_)로만 작성함을 원칙으로 한다.

함수명 : 'FN' + '_' + 함수 식별자

함수명은 'FN' 접두어를 사용한다. 예)FN_GET_ENG_NAME 단, 패키지내의 함수명은 FN 접두어를 사용하지 않아도 된다. 예) PKG_UTIL.GET_WORKING_DAY

 

프로시저 : 'SP' + '_' + 프로시저 식별자

프로시저명은 'SP' 접두어를 사용한다. 예) SP_SYS_LOG 단, 패키지내의 프로시저명은 FN 접두어를 사용하지 않아도 된다. 예) PKG_CODE.GET_CODE_LIST, PKG_COMMON.GET_LOCAL_TIME

 

패키지 : 'PKG' + '_' + 패키지 식별자

패키지명은 'PKG' 접두어를 사용한다. 예) PKG_DEPLOY 패키지 내의 함수, 프로시저 등의 오브젝트명은 접두어를 사용하지 않는 것을 원칙으로 한다. 예) PKG_COMMON.GET_SYS_TIME(O), PKG_COMMON.GET_USER_NAME(O)

 

타입 : 'T' + '_' + 타입 식별자

타입은 'T' 접두어를 사용한다. 예) T_VC_TAB

 

트리거 : 'TR' + '_' + 트리거 식별자<테이블명>

트리거명은 'TR' 접두어를 사용한다. 트리거가 트리거링 되는 조건에 관한 세부사항 (befor, after/Insert, Update, delete 등)은 트리거명에 넣지 않는다., 예) TR_SYS_USER

 

시퀀스 : 테이블명 + '_' + 'SEQ'

시퀀스 객체는 시퀀스 컬럼을 가진 테이블마다 하나씩 만드는 것을 원칙으로 하며, 접두어를 사용하지 않고, 'SEQ' 접미사를 사용한다. 예)USER_DEPT_SEQ

 

JOB : 'JOB' + '_' + Primary Identifier + ['_' + 스케줄정보]

JOB 객체나 Scheduled Job의 경우에는 'JOB' 접두어를 사용한다. 예) JOB_RSS 부가적으로 JOB 명에 배치주기 정보를 포함해도 무방하다.

 

Object Type

Naming Convention

Function

'FN' + '_' + 함수 식별자

Procedure

'SP' + '_' + 프로시저 식별자

Package

'PKG' + '_' + 패키지 식별자

Type

'T' + '_' + 타입 식별자

Trigger

'TR' + '_' + 트리거 식별자<테이블명>

Sequence

테이블명 + '_' + 'SEQ'

Job

'JOB' + '_' + Primary Identifier

표 2 Object Name Convention

 

Comments

주석은 유지보수 및 코드 설명을 위해 반드시 입력한다. 주석 입력단위는 DDL 이나 DML 스크립트를 파일로 작성할 때 파일레벨의 주석, 그리고 스키마의 OBJECT 레벨의 주석, 패키지내의 함수나 프로지저 등 서브 오브젝트, 그리고 프로그램 코드 중 필요한 부분에 주석을 작성한다.

주석문은 /* */ 를 사용하지 않고, -- 문을 사용하는 것을 원칙으로 한다.

파일레벨 / 스키마 OBJECT 레벨의 주석

파일레벨 이나 스키마 Object 레벨에서는 파일명이나 오브젝트 명 및 설명, 변경정보 등을 기술한다.

-- *****************************************************************************
-- Name : Support_RSS_DDL_20081119.SQL
-- Description : Support_RSS_연동기능 추가
--
-- History
-- 2008.11.19 jinook.lee 1.최초 생성
-- **************************************************************************************

 

함수 / 프로시저 등의 OBJECT 레벨의 주석

함수는 프로시서등의 오브젝트는 위의 기본 주석에 파라미터 정보, 리턴값 정보를 추가로 입력한다.

-- *****************************************************************************
-- Name : FN_URL_STR
-- Description : 문자열에 특수문자 공백을 제거한 문자열을 리턴
-- Param : p_String - 변환전 문자열
-- Return : 변환된 문자열.
--
-- History
-- 2007-10-08 jinook.lee 1.Created
-- ******************************************************************************

 * 끝으로 SQL Style Guide & Code Convention에 대한 출력자료(PDF문서)가 필요하시면 Comment로 남겨주시면 메일로 보내드립니다. 너무 요청자가 많으면 파일을 업로드 해 드리도록 하겠습니다. ㅋㅋ 제 생각에는 별로 요청하시는 분 없을것 같거든요.

'데이터베이스 > SQL' 카테고리의 다른 글

SQL & PL/SQL Naming Convention  (3) 2010.08.06
SQL Style Guide  (1) 2010.08.06
  1. 기려심매 2010.08.12 11:30 신고

    정리가 잘 되어 있네요. 자료좀 부탁드리겠습니다.^^ dreamaker7@naver.com
    감사합니다.

  2. exexit 2010.10.06 19:42 신고

    exexit@naver.com 자료좀 부탁합니다.

  3. 떠비 2011.06.16 17:38 신고

    잘봤습니다. 필요한자료입니다.
    오라클 pl/sql 코딩 가이드 추가 자료 있으면 부탁드리겠습니다.
    감사합니다.

    sonicbaba@hanmail.net

아래 내용은 제가 2008년 프로젝트 당시에 작성한 문서입니다. 당시, 자바나 닷넷은 Code Style Guide 혹은 Code Convention등의 문서가 많았지만, SQL 및 PL/SQL에 대한 문서는 거의 없어서 제가 작성했던 문서입니다. 블로그 만든 기념으로 그 때 문서를 공유합니다. 문서가 길어서 Style Guide 와 Naming Convention 편을 분리해서 작성했습니다. 실제 내용은 프로젝트 개발자를 위한 가이드라서 좀 딱딱한 문체로 기술되었습니다.

프로젝트 팀간의 Formatting 공유를 위해 제가 사용중인 Toad용 Formatting 파일을 함께 첨부합니다. 첨부파일은 Toad Version별로 커스터마이징 되어 있습니다. 자신의 버전에 맞는것을 다운 받아 덮어쓰시면 됩니다. 모, 직접 Toad Formatter를 하나하나 설정해서 사용하셔도 됩니다.

본  문서는 SQL 작성시 필요한 기본적인 Coding Style을 제공함으로써 SQL의 가독성을 높이고 유지보수 편의성을 제공하고자 한다.

Basic Layout

 

Tabs

기본적인 탭 사이즈는 4로 한다. 또한, 탭을 나타낼 때 Tab을 사용하지 않고 Space를 사용한다.

Margin

출력을 위해서는 80컬럼을 기본적으로 사용하나, SQL은 복잡한 Subquery 사용시 라인이 많이 늘어나므로 기본적으로 120 컬럼을 사용한다.

Indenting

기본적인 Indent 사이즈는 4로 Tab 사이즈와 동일하게 한다.

Case

 

SQL은 대문자를 사용하여 작성함을 원칙으로 한다. 아래에 특별히 언급하지 않은 모든 텍스트는 대문자(Uppercase)를 사용하여 작성한다.

Keywords

SQL의 모든 키워드 (예:SELECT, INSERT, BEGIN 등)는 모두 대문자(Uppercase)를 사용한다.

Built-ins / Built-in packages

DBMS에 내장된 함수 (DECODE, SUBSTR, NVL 등) 및 내장 패키지 (DBMS_OUTPUT, DBMS_SQL, UTL_FILE 등)는 모두 대문자(Uppercase)를 사용한다.

Table Name / Column Name

일반적으로 테이블 명 및 컬럼명, Alias 명, User 함수명 등은 소문자를 주로 사용하나 국내에서 테이블명 및 컬럼명이 약자를 주로 사용하므로 대문자가 가독성이 높아 모두 대문자(Uppercase)로 기술하는 것을 원칙으로 한다.

 

PL/SQL Variables

PL/SQL의 변수명은 예외적으로 대문자를 사용하지 않으며, 접두어 및 Camel 표기법을 사용한다. 상세한 내용은 Naming Rule 부분을 참조한다.

Lists and Operators

 

Variable Declarations

변수선언은 변수명과 변수타입명의 정렬방식은 세로줄라인을 맞추는 Fixed 방식을 사용한다.

Alignment

(Fixed)

var1                        NUMBER(20);
my_string                 VARCHAR2(15);
v_C22 CONSTANT VARCHAR2(20) := 'GLOBAL';

(Compact)

var1 number(20);
my_string varchar2(15);
v_C22 CONSTANT VARCHAR2(20) := 'GLOBAL'; 

 

Parameter Declarations

함수, 프로시저, 패키지 등의 PL/SQL에서의 파라미터 선언 스타일은 각 변수별로 한 라인씩 작성하는 Stacked 방식을 사용한다. 정렬방식은 각 변수별로 변수명, 타입등의 세로줄을 맞추는 Fixed 방식을 사용한다.

단, 파라미터 변수가 2~3개이어서 한 라인을 넘지 않는 경우에는 변수를 라인별로 작성하지 않고 한 라인에 기술할 수 있다.

Style

(Stacked)

P1 IN NUMBER,
P2 OUT VARCHAR2,
P3_VAR OUT NUMBER

(Wrapped)

P1 IN NUMBER, P2 OUT VARCHAR2, P3_VAR OUT NUMBER, P4 IN VARCHAR2,
P5 IN NUMBER

 

Alignment

(Fixed)

P1           IN     NUMBER,
P2           OUT VARCHAR2,
P3_VAR  OUT NUMBER

(Compact)

P1 IN NUMBER,
P2 OUT VARCHAR2,
P3_VAR OUT NUMBER

 

Parameters

함수나 패키지등을 호출할 때 파라미터 변수를 입력하는 방식은 기본적으로는 Wrapped 방식 – 모든 파라미터 변수를 한줄에 입력하여 호출하도록 한다.

단, 파라미터로 입력하는 내용이 너무 길어질 경우 여러줄로 입력하여 한눈에 파악할 수 있도록 작성한다.

Named Parameters의 경우에는 여러줄로 나누어서 호출하는 것을 원칙으로 하며, 변수명과 인자값은 각 세로줄을 맞추는 Fixed 방식을 사용한다.

Style

(Wrapped)

MY_PROCEDURE((3 + 4) * A, VAR1, VAR2, VAR3);

(Stacked)

MY_PROCEDURE((3 + 4) * A
                               ,VAR1 
                               ,VAR2 
                               ,VAR3));

(Stack only on line overflow)

SELECT REPLACE(SYS_CONNECT_BY_PATH(PRD_IA_NAME, '>'), '>', P_DELIM)
INTO v_Path
FROM CATEGORY
WHERE PRD_IA_CD = :p_ProductIACode
AND PRD_IA_CD = GET_PIA_CD(p_SiteCode
                                                        ,p_ProductIACode
                                                        ,p_StartIATypeCode
                                                        ,p_ProductIaCode);

 

Aligment for Named Parameters (p => x)

(Fixed)

VAR1             => 1,
VARIABLE2   => 2 + A,
X                    => SIN(P2)

(Compact)

VAR1 => 1,
VARIABLE2 => 2 + A,
X => SIN(P2)

 

Parentheses

함수 호출시 괄호 및 스페이스 처리에 대한 규칙은 호출하는 인자들 간에만 공백을 주고, 함수명, 프로지서명과는 공백을 주지 않는다. 또한, 첫번째 변수와 마지막 변수와 괄호 사이에도 공백을 넣지 않는다.

Parentheses

MY_PROC(PART1, PART2)

Compact

MY_PROC (PART1, PART2)

파라미터, 컬럼리스트 이전에 공백 넣기

MY_PROC( PART1, PART2 )

괄호 안쪽에 공백 넣기

MY_PROC ( PART1, PART2 )

전부 공백 주기

 

Commas

컬럼리스트, 파라미터 리스트 등에서 ,(콤마)는 컬럼 앞에 둔다. 일반적으로 컬럼 뒤에 많이 기술하나 ,(콤마)를 앞쪽에 두면 콤마의 누락을 쉽게 파악 할 수 있다.

Commas

 ITEM1, 
,ITEM2, 
,ITEM3

콤마 + 리스트

(정렬은 단어기준으로 정렬, 첫번째 컬럼은 한칸 들여쓰기)

ITEM1, 
ITEM2,
ITEM3

리스트 + 콤마

  ITEM1
, ITEM2
, ITEM3

콤마 + 공백 + 리스트

        ITEM1
,       ITEM2
,       ITEM3

콤마 + 공백(여러 ) + 리스트

- CDM Style

 

And – Or

Where 조건에서 AND, OR 연산자를 사용할 때 기본적으로 각 연산자를 기준으로 한라인씩 기술하는 Stacked 방식을 사용하여 기술한다.

또한, AND, OR가 동시에 사용되는 경우에는 OR 부분을 명확히 하기 위해서 ( )를 사용하여 표기한다. 그리고 연산자를 좌측에 두고 컬럼리스트가 좌측 정렬되도록 정렬한다.

Style

(Stacked)

A > B
AND C < F(U,V)
AND X =Z

 

(Wrapped)

A > B AND C < F(U,V)
AND X =Z

 

 

Arrangement

         A > B
AND C < F(U,V)
AND X =Z

연산자를 좌측에, 컬럼 기준으로 정렬

A > B
AND C < F(U,V)
AND X =Z

좌측 정렬, 연산자를 좌측에

A > B AND
C < F(U,V) AND
X =Z

연산자를 우측에

A > B          AND
C < F(U,V) AND
X =Z

연산자를 우측, 연산자를 기준으로 정렬

 

Sample (AND, OR 동시 사용시)

SELECT PRD_IA_CD 
  FROM MODEL
 WHERE SITE_CD = 'kr'
   AND  PRD_MDL_CD = 'CODE'
       OR PRD_MDL_NAME = 'NAME')

SELECT PRD_IA_CD
  FROM MODEL
 WHERE SITE_CD = 'kr'
       AND PRD_MDL_CD = 'CODE'
   OR PRD_MDL_NAME = 'NAME'

SELECT PRD_IA_CD
  FROM MODEL
 WHERE SITE_CD = 'kr'
  AND(PRD_MDL_CD = 'CODE'
  OR PRD_MDL_NAME = 'NAME')

 

Plus-Minus-Mul-Div-Conat

사칙연산 및 문자열 연결 연산자의 경우 연산자를 기준으로 여러줄로 작성하지 않고, 한 줄로 기술한다.

Style

(Wrapped)

EXPR1 – F(ARG200) + EXPR3 – EXPR4

(Stacked)

EXPR1
- F(ARG200)
+ EXPR3

 

Specific Statements

 

:= Assignments

변수에 값을 할당할 때는 할당연산자(:=)를 기준으로 정렬한다.

Alignment

(Fixed)

v_SiteCode                 := 'kr'
g_ProductModelCode := 'AAAAB';
x                                 := func(p_Val);

(Compact)

v_SiteCode := 'kr'
g_ProductModelCode := 'XXXXX';
x := func(p_Val);_

 

SELECT / FETCH / EXECUTE

SELECT 문을 기술할 때 SELECT, INTO, FROM WHERE 등의 키워드는 우측 정렬을 원칙으로 한다.

Alignment

SELECT COL1, COL2
    INTO VAR1, VAR2
  FROM MY_TAB1, MY_TAB2;

SELECT COL1, COL2
INTO      VAR1, VAR2
FROM    MY_TAB1, MY_TAB2;

 

SELECT문의 컬럼 리스트는 한줄에 하나의 컬럼을 기술하는 것을 원칙으로 한다. 단, SQL의 조회하고자 하는 컬럼의 개수가 적어 한 줄에 기술이 가능한 경우에는 한 줄에 써도 무방하다.

Style of SELECT / INTO and FETCH / EXECUTE lists

(Stacked)

SELECT FIRST_COL
              ,SECOND_COL 
             ,THIRD_COL …

(Wrapped)

SELECT FIRST_COL, SECOND_COL,
THIRD_COL …

 

SELECT 컬럼 리스트와 동일하게 FROM, ORDER BY, GROUP BY RETURNING 리스트 도 한 줄에 하나의 컬럼을 기술하는 것을 원칙으로 한다. SELECT 문과 동일하게 해당절의 컬럼 개수가 적어 한 줄에 기술이 가능한 경우에는 한 줄에 써도 무방하다.

Style of FROM, ORDER BY , GROUP BY and RETURNING lists

(Stacked)

ORDER BY FIRST_COL
                 , SECOND_COL
                 ,THIRD_COL

(Wrapped)

ORDER BY FIRST_COL, SECOND_COL,
THIRD_COL

 

INSERT

INSERT문도 SELECT 문과 동일한 스타일을 따른다. 즉 INSERT문을 기술할 때 INSERT INTO, VALUES 등의 키워드는 우측 정렬을 원칙으로 한다.

Alignment

INSERT INTO MY_TAB (COL1, COL2)
        VALUES (VAL1, VAL2);

INSERT INTO MY_TAB
(COL1, COL2)
VALUES (VAL1, VAL2);

 

INSERT 문의 컬럼이나 VALUES리스트는 SELECT문의 컬럼 리스트와 동일하게 한줄에 하나의 컬럼을 기술하는 것을 원칙으로 한다. 단, SQL의 조회하고자 하는 컬럼의 개수가 적어 한 줄에 기술이 가능한 경우에는 한 줄에 써도 무방하다.

Style of COLUMN and VALUES lists

(Stacked)

INSERT INTO MYTAB
               ( FIRST_COL
                ,SECOND_COL
                ,THIRD_COL …

(Wrapped)

INSERT INTO MYTAB 
               ( FIRST_COL, SECOND_COL
               , THIRD_COL …

 

UPDATE

UPDATE문도 SELECT 문과 동일한 스타일을 따른다. 즉 UPDATE문을 기술할 때 UPDATE, SET, WHERE 등의 키워드는 우측 정렬을 원칙으로 한다.

Alignment

UPDATE MY_TAB 
       SET COL1 = EXPR1 
              ,COL2 = PXPR2

UPDATE MY_TAB
SET COL1 = EXPR1,
        COL2 = PXPR2

 

DELETE

DELETE문도 SELECT 문과 동일한 스타일을 따른다. 즉 DELETE문을 기술할 때 DELETE, WHERE 등의 키워드는 우측 정렬을 원칙으로 한다.

Alignment

DELETE FROM MY_TAB
             WHERE …

DELETE FROM MY_TAB
WHERE …

 

 

Table Alias

 

Join문에서 Table Alias를 부여할 때 일반적으로 A, B, C 순으로 작성하는 경우가 많다. 이것보다는 테이블을 파악하기가 용이한 의미있는 약어를 사용한다. 단, 약어는 3자를 넘기지 않는다. 1글자로 구분이 가능한 경우에는 한 글자로 사용한다.

예)
DEPT => D
MODEL_CATEGORY_INF => M OR MC
HISTORY => H

Table Alias

SELECT COLS
 FROM EMPLOYEES E 
     ,COMPANIES C
     ,PROFILES P
     ,SALES S
 WHERE C.COM_ID = E.EMP_COM_ID
   AND P.PRO_COM_ID = C.COM_ID 
  AND S.SAL_COM_ID(+) = P.PRO_COM_ID

SELECT COLS
 FROM EMPLOYEES EMP
     ,COMPANIES COM
     ,PROFILES PRO
     ,SALES SAL
 WHERE COM.COM_ID = EMP.EMP_COM_ID
   AND PRO.PRO_COM_ID = COM.COM_ID
  AND SAL.SAL_COM_ID(+) = PRO.PRO_COM_ID

SELECT COLS
  FROM EMPLOYEES A 
      ,COMPANIES B
      ,PROFILES C
      ,SALES D
 WHERE B.COM_ID = A.EMP_COM_ID
   AND C.PRO_COM_ID = B.COM_ID
  AND D.SAL_COM_ID(+) = C.PRO_COM_ID

 

Toad Formatting

 

Use Formatting Tool

모든 개발자들이 공통적으로 위에서 정의한 SQL Style을 적용하기 위해서 Toad의 Formatting Tool을 사용한다. 다른 Tool들도 Formatting 기능을 제공하기는 하나, Toad가 포맷팅 기능은 가장 강력하다. 그러므로 개발자들이 다른 툴을 주로 사용하더라도 최종 SQL문에 대해서는 Toad의 포맷팅 기능을 사용하여 SQL 스타일을 맞추면 된다.

 

Formatting Setup

본 문서와 함께 제공되는 FmtPlus.opt 파일을 PC의 동일파일의 경로에 덮어쓴다.
예) C:\Users\Administrator\AppData\Roaming\Quest Software\Toad for Oracle\10.5\User Files\FmtPlus.opt에 존재함. 개인 개발자들의 정확한 위치는 Toad > View > Formatting Options 메뉴를 실행하면 열린 Formatter Window의 타이틀에 전체 경로가 나타난다. 그 부분을 참고하면 된다.

 

프로젝트 수행시 프로젝트에 맞는Formatting 옵션을 설정하고자 한다면, Toad Formatter에서 프로젝트에 적합하도록 수정한 후 해당 파일을 개발자에게 공유해서 동일한 포맷팅을 사용하도록 하면 된다.

 

그림 1 Toad Formatter 실행 화면

Toad에서 Formatting하기

아래 그림과 같이 Formatting을 하고자 하는 블록을 선택한 후 툴바를 클릭하면 SQL이 포맷팅 된다.
Edit > Format Code 를 선택해도 동일하다. 단축키는 Shift + Ctrl + F 이다.

 

그림 2 Formatting 이전 SQL

 

그림 3 Formatting 실행 이후

Exceptions

Toad로 대부분 필요한 수준의 Formatting이 되어지긴 하나, 일부 SQL에 대해서는 Formatting 한 결과가 오히려 한눈에 파악하기가 어려운 경우도 있으며, Toad v9.x에서는 MERGE 문 같은 경우는 제대로 처리조차 못하고 있다. 이런 경우에는 일반적인 관습을 따라서 개발자가 한눈에 파악하기 좋게 SQL을 작성하도록 한다.

 

Connect By 구문 작성시

CONNECT BY 구문 작성시 키워드의 글자 길이가 길어서 Formatting 툴을 사용하면 키워드를 기준으로 우측정렬 하는 현재 스타일에서 오히려 보기에 안 좋을 수도 있다. 이 경우에는 예외적으로 CONNECT BY의 Full Keyword를 기준으로 정렬을 해도 되고, CONNECT 키워드만을 기준으로 정렬을 해도 무방하다.

Connect By 등 Keyword의 글자 길이가 긴 경우의 예외처리

before

SELECT PRD_IA_CD
FROM CATEGORY
WHERE PRD_IA_TYP_CD = '02'
START WITH SITE_CD = :SITE AND PRD_IA_CD = :IA_CDODE
CONNECT BY PRIOR SITE_CD = SITE_CD
AND PRIOR UPPER_PRD_IA_CD = PRD_IA_CD;

(포맷팅후)

    SELECT PRD_IA_CD
      FROM CATEGORY
     WHERE PRD_IA_TYP_CD = '02'
START WITH SITE_CD = :SITE
       AND PRD_IA_CD = :IA_CDODE
CONNECT BY PRIOR SITE_CD = SITE_CD
       AND PRIOR UPPER_PRD_IA_CD = PRD_IA_CD;

(예외허용)

SELECT PRD_IA_CD
  FROM CATEGORY 
 WHERE PRD_IA_TYP_CD = '02'
 START WITH SITE_CD = :SITE
        AND PRD_IA_CD = :IA_CDODE
 CONNECT BY PRIOR SITE_CD = SITE_CD
        AND PRIOR UPPER_PRD_IA_CD = PRD_IA_CD;

 

MERGE 문 등의 예외 처리

Merge문 작성시 Toad v.9.x버전에서는 Formatting 툴을 이용하면 WHEN MATCHED THEN 구문 이후 UPDATE 문에 대해 기본적인 UPDATE 문에서 정한 Style이 적용되지 않는다. WHN NOT MATCHED THEN 이후의 INSERT 문도 마찬가지다. Toad v.10.x 에서는 제대로 포맷팅 된다.

이처럼 오라클의 버전업에 따른 신규 기능의 경우, 제대로 포맷팅이 안될 경우에는 Formatting 툴을 이용하지 않고 보기 좋게 스타일 가이드에 맞게 작성한다.

'데이터베이스 > SQL' 카테고리의 다른 글

SQL & PL/SQL Naming Convention  (3) 2010.08.06
SQL Style Guide  (1) 2010.08.06
  1. KimSean 2011.02.15 16:04 신고

    좋은 자료네요^^;

+ Recent posts