'분류 전체보기'에 해당되는 글 2491건
- 2012.11.14 eclipse에서 junit 실행시 OutOfMemory
- 2012.11.09 flex 특수문자 처리
- 2012.10.18 [펌] Ubuntu 기본 설정#
- 2012.10.18 [펌] Disable GUI Boot in Ubuntu 11.10
- 2012.10.17 [링크] Domain-Driven Design
- 2012.10.17 [펌] How to sort a Map<Key, Value> on the values in Java?
- 2012.10.06 [펌] Optimizer의 원리와 Tuning (상)
- 2012.10.03 jQuery Mobile
- 2012.10.03 Aptana Studio Eclipse Plug-in Installation
- 2012.10.03 ExtJS
- 2012.10.02 [펌] 플렉스 에러 메시지와 해결 방법
- 2012.09.25 Jasper Reports & iReport
- 2012.09.21 java 썸네일 만들기
- 2012.09.21 간단한 text 파일 만들기
- 2012.09.21 java d-day
- 2012.09.21 split과 StringTokenizer 차이
- 2012.09.21 간단한 text 파일 만들기
- 2012.09.21 Oracle] 통계 함수
- 2012.09.21 Oracle PIVOT
- 2012.09.21 pivot and unpivot queries in 11g
- 2012.09.21 Oracle over() 구문
- 2012.09.21 oracle pivot 데이터를 가로를 세로로...
- 2012.09.20 [펌] Spring + mybats 환경에서 xml 파일 변경시 서버 재시작 없이 반영 방법 2
- 2012.09.20 http://joke00.tistory.com/18
- 2012.09.20 eclipse assertion 사용해 보기
- 2012.09.20 이클립스에서 개발하다 보면 interface 클래스를 많이 사용하게 되는데..
- 2012.09.20 Integrate Calculations Defined In Spreadsheets Into Java Apps
- 2012.09.20 엑셀 함수 자바로 구현
- 2012.09.20 [ORACLE] 각종 관리상 팁
- 2012.09.18 orcale like 대신 instr
출처 : http://blessedsoft.org/wiki/Wiki.jsp?page=Linux.setup.basic
Ubuntu 기본 설정#
Ubuntu 12.04 서버 버전 설치#
컴퓨터 이름 설정 #
sudo vi /etc/hostname sudo hostname __hostname__
네트워크 설정#
- (옵션) mac addr 변경시 ubunt 기존 설정 삭제
sudo rm /etc/udev/rules.d/70-persistent-net.rules (리부팅)
ip address 설정#
- 고정 ip
$ sudo /etc/network/interfaces auto eth0 iface eth0 inet static address 122.199.xxx netmask 255.255.xxx gateway 122.199.xxx dns-nameservers xxxx
File limit 해제#
$sudo gedit /etc/security/limits.conf * soft nofile 32768 * hard nofile 32768
기본 업데이트#
sudo apt-get update sudo apt-get upgrade
open ssh 서버 설치#
sudo apt-get install openssh-server
방화벽 설정#
netstat -tlnp sudo ufw enable sudo ufw allow 22/tcp sudo ufw status
VNC 설정(VNC 원격 접속이 필요한 경우)#
vnc 설치#
sudo apt-get install vnc4server
사용자 session type을 ubuntu-2d로 변경
(로긴 화면에서 아이디 위의 버튼으로)#
암호 설정#
$ sudo vncpasswd
설정 변경#
$ vi .vnc/xstartup #!/bin/sh # Uncomment the following two lines for normal desktop: unset SESSION_MANAGER gnome-session --session=ubuntu-2d & # exec /etc/X11/xinit/xinitrc [ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup [ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources xsetroot -solid grey vncconfig -iconic &
재시작#
$ vnc4server -kill :1 (기존 공유 죽이기) $ vnc4server -geometry 1024x768
방화벽 풀기 (포트 2 개)#
JDK 설정#
sun jdk 1.6x 설치#
sun jdk download \\ (scp 로 카피)
압축 해제#
chmod 755 jdk-6u33-linux-x64.bin ./jdk-6u33-linux-x64.bin
이동 (디렉토리 이동임, / 입력 주의)#
sudo mkdir /usr/lib/jvm sudo mv jdk1.6.0_33 /usr/lib/jvm/ sudo ln -s /usr/lib/jvm/jdk1.6.0_33 /usr/lib/jvm/jdk1.6
jdk 설정#
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk1.6/bin/javac 1 sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk1.6/bin/java 1 sudo update-alternatives --install /usr/bin/javaws javaws /usr/lib/jvm/jdk1.6/bin/javaws 1 sudo update-alternatives --config javac sudo update-alternatives --config java sudo update-alternatives --config javaws
확인#
java -version rm jdk-6u33-linux-x64.bin
출처 : http://www.techienote.com/2012/01/disable-gui-boot-in-ubuntu-11-10.html
Is it possible to disable GUI / X at boot time? So many Ubuntu 11.10 Desktop edition users are asking the same question. On my ubuntu machine I have done following steps to disable GUI boot in ubuntu 11.10
Step 1 First update your repository by running
sudo apt-get update |
Step 2 There is some bug in old version of lightdm, so we need to upgrade the same. To do so run,
sudo apt-get install lightdm |
Step 3 Now we have to modify grub config.
Step 3a Open /etc/default/grub with your faviourite editor and change
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
to
GRUB_CMDLINE_LINUX_DEFAULT="text"
Step 3b Also comment GRUB_HIDDEN_TIMEOUT=0 This line is for unhiding the GRUB menu
Step 4 Now we will upgrade GRUB configuration
sudo update-grub |
Step 5 Ubuntu 11.10 Desktop edition use lightdm for GUI. We need to disable the same
sudo update-rc.d -f lightdm remove |
Step 6 Now restart your machine. Voilla you will be in console mode
At any time you need after booting your system you want to change to gui mode the run
startx |
If you want to restore Ubuntu’s GUI mode then
Open /etc/default/grub with your faviourite editor and change
GRUB_CMDLINE_LINUX_DEFAULT="text"
to
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
Update GRUB by running
sudo update-grub |
To restore lightdm run
sudo update-rc.d lightdm defaults |
That’s it
카테고리 : Domain-Driven Design
2009/10/15 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 11부 [完] [15]
2009/10/13 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 10부
2009/07/29 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 9부
2009/07/13 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 8부 [2]
2009/06/29 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 7부
2009/06/23 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 6부 [2]
2009/06/15 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 5부
2009/03/25 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 4부 [4]
2009/02/27 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 3부
2009/02/23 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 2부
2009/02/15 Domain-Driven Design의 적용-4.ORM과 투명한 영속성 1부
2009/01/18 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 7부 [2]
2009/01/02 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 6부
2008/12/24 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 5부 [2]
2008/12/17 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 4부
2008/12/13 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 3부
2008/12/09 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 2부
2008/12/05 Domain-Driven Design의 적용-3.Dependency Injection과 Aspect-Oriented Programming 1부
2008/11/30 Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 5부
2008/11/27 Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 4부
2008/11/25 Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 3부 [2]
2008/11/23 Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 2부
2008/11/20 Domain-Driven Design의 적용-2.AGGREGATE와 REPOSITORY 1부
2008/11/17 Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 4부 [4]
2008/11/17 Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 3부
2008/11/16 Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 2부 [4]
2008/11/15 Domain-Driven Design의 적용-1.VALUE OBJECT와 REFERENCE OBJECT 1부 [14]
출처 : http://stackoverflow.com/questions/109383/how-to-sort-a-mapkey-value-on-the-values-in-java
It seems much easier than all of the foregoing. Use a TreeMap as follows:
public class Testing {
public static void main(String[] args) {
HashMap<String,Double> map = new HashMap<String,Double>();
ValueComparator bvc = new ValueComparator(map);
TreeMap<String,Double> sorted_map = new TreeMap<String,Double>(bvc);
map.put("A",99.5);
map.put("B",67.4);
map.put("C",67.4);
map.put("D",67.3);
System.out.println("unsorted map: "+map);
sorted_map.putAll(map);
System.out.println("results: "+sorted_map);
}
}
class ValueComparator implements Comparator<String> {
Map<String, Double> base;
public ValueComparator(Map<String, Double> base) {
this.base = base;
}
// Note: this comparator imposes orderings that are inconsistent with equals.
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
}
}
Output:
unsorted map: {D=67.3, A=99.5, B=67.4, C=67.4} results: {D=67.3, B=67.4, C=67.4, A=99.5}
Optimizer의 원리와 Tuning (상)
Oracle Optimizer의 원리 이해 및 SQL & 애플리케이션의 튜닝(상):
옵티마이저의 질의 처리 단계에 대한 이해
오라클에서 사용하는 옵티마이저(Optimizer)는 크게 RBO(Rule Base Optimizer)와 CBO(Cost Base Optimizer) 2개로 구분된다. 1992년 Oracle 7에서 처음 CBO가 지원된 이래 새로운 기능들이 적용되면서 CBO가 계속 향상되고 있는 데 반해, RBO는 오라클이 더 이상은 추구하지 않는 옵티마이저로서, 현재의 Oracle Database 10g에서도 명맥은 남아 있지만 향후는 더 이상 지원되지 않을 것이다.
옵티마이저의 입장에서 질의(query) 처리는 5단계로 나눌 수 있는데, 옵티마이저는 서브질의와 뷰의 병합(merge) 등을 수행하는‘Query Rewrite’단계와 ‘Query Optimization’단계에 참여한다. 여기서 옵티마이저는 ▲ 데이타를 어떠한 방법으로 액세스할 것이며 ▲ 올바른 결과를 어떻게 제공할 것이며 ▲ 데이타를 얼마나 효과적으로 액세스할 것인가를 결정한다. ‘QEP Generation’단계는‘Query Optimization’단계에서 제공된 정보를 이용해서 질의에 대한 최적의 실행계획(execution plan)을 만들어 내는 단계이다. CBO에서는 질의 실행계획(QEP)을 구하기 위하여 RBO보다 복잡한 단계를 거치게 된다<그림 1>, <표 1>.
소프트 파싱과 하드 파싱
SQL 문장이 옵티마이저에 의해 처리되고 그 결과물로서, SQL 문장이 어떻게 실행될 것인지의 정보, 즉, QEP가 생기게 된다. 이들 정보는 한번 쓰고 버리는 것이 아니라 오라클의 캐쉬(cache) 영역인 SGA의 공유 풀 (shared pool)에 이들 모든 정보를 캐쉬화해 관리한다. 다음 번에 같은 SQL 문장이 사용자에 의해서 실행되면, 이를 재활용하게 된다.
SQL 문장이 실행되면, 우선 SQL 문장 텍스트의 스트링을 해쉬 함수를 통과시켜 결과 값에 해당되는 버킷(어레이형 구조)에 매달린 체인 정보에서 같은 SQL 문장이 존재하는지 찾는 처리절차를 수행하게 된다. 또한 같은 SQL 문장을 찾았어도 여러 버전이 존재할 수 있다. 여러 버전이란, 같은 SQL 문장(대/소문자, 화이트 스페이스 등이 모두 같아야 함)이지만 서로 다른 스키마의 테이블(예, scott의 emp, sys의 emp)이거나, 바인드 변수 를 사용한 경우는 바인드 변수의 타입, 길이 등에 의해서도 서로 다른 버전이 된다는 것이다. 이와 같이 같은 SQL 문장에 같은 버전을 찾았다면 이를 ‘소프트 파싱(soft parsing)’이라고 한다.
그렇지만, 체인을 다 찾았는데 같은 문장을 발견하지 못했다면, 해당 SQL 문장이 Parsing/Optimizing 단계를 거친 결과로 나온 정보를 저장하기 위해 공유 풀로부터 메모리를 확보받고, 기록한 정보를 체인에 매달게 된다. 이를 ‘하드 파싱(hard parsing)’이라고 한다. 당연히 하드 파싱의 작업량이 소프트 파싱의 작업량에 비해 월등히 클 것이다<그림 2>.
이와 같은 소프트 파싱과 하드 파싱의 과정을 생각해 볼 때, 집중적인 SQL 문장이 실행되는 OLTP(초당 수천 ~ 수만 개 이상)에서 하드 파싱이 많다면 어떻게 될까? 한정된 메모리인 캐쉬에 새로운 메모리를 계속 할당하고, LRU 알고리즘에 의해 제거하고, 체인에 매달고 끊는 등의 일들을 반복해야 할 것이다. 또한 하드 파싱은 복잡한 처리과정을 거치므로 많은 자원(CPU)을 사용하게 된다. 그러므로 OLTP 환경에서는 이와 같은 하드 파싱을 가능한 줄이도록 해야 한다. 특히 SQL 실행 규모가 큰 OLTP 업무는 1% 미만을 권장한다.
애플리케이션을 개발할 때 이러한 하드 파싱을 줄이기 위한 방법으로 거의 대부분의 데이타베이스 접속 방식(JDBC, ODBC, ADO, PRO*C 등) 에서 자주 사용되는 SQL 문장들은 바인드 변수 기법들을 사용하여 개발하는 방법들을 제공하고 있다. 또한 일부에서는 소프트 파싱 자체도 줄일 수 있는 기법들을 제공하고 있다. 실제 이러한 기법을 적용해서 튜닝한 결과, 시스템 CPU/메모리 측면에서 40~50% 이상 개선된 사례가 많이 있다. 혹시 현재 운영중인 시스템이 사용자가 많아지면서 CPU 리소스가 급격히 증가해, 라이브러리 캐쉬, 공유 풀 경합 현상이 발생한다면, 이러한 점을 의심해 볼 수 있다.
<표 4>는 SQL 문장을 바인드 변수를 사용한 공유 SQL과, 상수를 결합한 형태로 SQL 문장을 만들어 실행시키는 비공유 SQL을 9,999회 실행시켜 오라클의 공유 풀 메모리 사용현황과 파싱시 CPU 사용시간을 테스트 한 것이다(단, 그 결과치는 실행 서버별로 차이가 있다).
결론적으로 보면, 비공유 SQL 방식의 사용 메모리와 CPU 사용률이 실행 규모에 비례해 증가하고, 실행된 SQL 문장이 기존에 캐쉬화되어 있는 SQL 문장들을 밀어내는 역할을 한다는 것을 알 수 있다.
이와 같은 SQL 문장을 공유하기 위해서 오라클 입장에서 처리해주는 CURSOR_SHARING이라는 파라미터를 제공하기도 한다. 그러나, CURSOR_SHARING은 모든 상수를 다 바인드 변수로 바꿔버리기 때문에 개발자가 의도하지 않은 Literal까지도 바꾸게 되므로, 애플리케이션을 수정할 수 있다면 가능한 애플리케이션 단에서 바인드 변수를 사용하는 것이 효과적이다.
하드 파싱을 줄이기 위해 모든 업무에 바인드 변수 사용방법을 적용하는 것은 잘못된 생각이다. 옵티마이저의 입장에서 보면, 바인드 변수 기법 보다는 Literal을 사용한 비공유 SQL 방식을 좋아한다. Literal SQL 문장일 경우는 상수 값에 따라서 범위를 정확히 알 수 있기 때문에 효과적인 플랜을 결정하는 주요 결정요소로 작용하기 때문이다. 즉 바인드 변수 기법은 옵티마이저의 판단에는 좋지 않지만 SQL 문장이 집중적으로 실행되는 OLTP 환경에서 하드 파싱의 비율을 줄이기 위한 방법인 것이다. 즉, 업무의 특징에 따라서 다른 적용방식이 사용되어야 한다. 다음은 OLTP와 DW의 특징에 따라 다르게 고려되어야 할 사항이다.
• OLTP의특징
- 목표 : 신속한 응답시간, 적은 데이타 처리량
- 파싱 타임을 최소화하고 SQL 등이 공유될 수 있도록 바인드 변수를 사용해야한다.
- 인덱스의 사용률이 높아야 한다.
- 정렬(sorting)을 최소화해야 한다.
- Nested Loop Join(FIRST_ROWS_n) 방식으로 많이 유도한다.
• DW의특징
- 목표 : 최고의 처리량, 방대한 데이타 처리량
- 인덱스의 참조는 중요한 사항이 아니다.
- 정렬 또는 Aggregate함수 등이 중요한 역할을 한다.
- Hash Join 등을 많이 사용하도록 유도한다.
- 파싱 타임 등은 그리 중요하지 않으며, 바인드 변수의 사용이 문제가 될 수 있다.
- 병렬 질의 등의 사용률을 높인다.
Rule Base Optimizer
질의 최적화(query optimization)에서 RBO(Rule Base Optimizer)는 정해진 랭킹(ranking)에 의해 플랜을 결정한다. 같은 랭킹이라면 Where 절의 뒤부터, From절 뒤의 객체가 우선 순위를 갖는다. 한 객체(예 : 테이 블)에서 같은 랭킹의 인덱스가 있다면 가장 최근에 만들어진 인덱스를 사 용한다. 이는 CBO(Cost Base Optimizer)에서도 같이 적용되는 사항이다.
다분히 RBO는 개발자들이 프로그래밍 단계에서 SQL 문장 구조의 인위적인 조정 등으로 인덱스를 사용 못하게 하는 등 개발자가 코딩에 신경을 많이 써야 하는 문제점이 있다. 또한 RBO는 해당 질의에 대한 테이블의 인덱스가 존재한다면 전체 90% 이상의 대상이어도 인덱스를 선택한다는 것이다. 즉, RBO는 무조건 다음과 같은 미리 정해진 룰을 기준으로 플랜을 결정하게 된다. 1992년 Oracle 7에서 CBO가 지원되면서 CBO는 계속적인 신기능의 적용으로 발전해 온 반면, RBO는 더 이상의 기능 향상은 없으며, 향후는 CBO만 지원될 계획이다. 그러므로 RBO에 더 이상의 미련을 갖지 말기 바라며, CBO의 훌륭한 기능들을 적극 활용하길 바란다.
다음은 RBO의 랭킹을 정리한 것이다.
Path 1 : Single Row by Rowid
Path 2 : Single Row by Cluster Join
Path 3 : Single Row by Hash Cluster Key with Unique or Primary Key
Path 4 : Single Row by Unique or Primary Key
Path 5 : Clustered Join
Path 6 : Hash Cluster Key
Path 7 : Indexed Cluster Key
Path 8 : Composite Index
Path 9 : Single-Column Indexes
Path 10 : Bounded Range Search on Indexed Columns
Path 11 : Unbounded Range Search on Indexed Columns
Path 12 : Sort-Merge Join
Path 13 : MAX or MIN of Indexed Column
Path 14 : ORDER BY on Indexed Column
Path 15 : Full Table Scan
특히 Path 8, 9, 10에 주의를 해야 한다. 예를 들면, ‘emp’ 테이블에 ‘A’ 인덱스가 “deptno”로 구성되어 있고, ‘B’ 인덱스가 “deptno + empno”로 구성되어 있다면, 다음과 같은 SQL 문장은 ‘A’ 인덱스를 사용 하게 된다. 조건이 Bounded Range Search(Between)로 왔기 때문에 아래의 SQL 문장에서 (A)와 (B)의 랭킹은 (A) ==> Rank 9 , (B) ==> Rank 10 조건이 되므로 싱글 칼럼 인덱스를 사용한다는 것이다.
그러면, 이제CBO에 대해 살펴보기 전에, 참고로 RBO를 CBO로 전환한 사례를 잠깐 소개하겠다.
현재 RBO를 사용하고 있는 상황에서 마이그래이션시 CBO로 전환하고 싶으나, 막연히 두려운 부분도 많을 것이다. 실제 RBO에서 CBO로 전환하고 나서 가장 효과를 보는 부분은 배치잡 형태이다. 특히 Oracle9i Database 이상의 WORKAREA_SIZE_POLICY=AUTO로 운영하는 곳 이라면 더욱 더 그럴 것이다. 그러나 OLTP의 변화는 조심해야 한다. 아래의 경우는, 이전하면서 옵티마이저 모드를 RBO에서 CBO로 전환한 것 뿐만 아니라, 블록 사이즈와 CBO 옵티마이저에 민감한 db_file_multi block_read_count 값도 크게 늘렸다. 특히 WORKAREA_SIZE_ POLICY=AUTO로 필요한 워킹 메모리(Sort, Hash, Bitmap 등)를 옵티마이저가 판단하에 가능한 충분히 사용하게 하는 방식을 사용하였다.
그러다 보니, CBO에 영향을 주는 소트 메모리와 해쉬 메모리가 풍부하게 되었고, 블록 사이즈도 커졌으며, 풀 테이블 스캔의 정도를 결정하는 db_file_multiblock_read_count 값도 아주 커진 상태이다. 또한 마이그래이션되면서 데이타가 재정리되어 있는 상태이므로, 풀 테이블 스캔과 Sort Merge Join, Hash Join의 경향이 커진 상태이다. 그러므로 배치잡의 경우는 최적의 조건이 되었으나, 기존에 주로 Nested Loop Join을 선호하 던 RBO 환경의 OLTP들은 많은 플랜의 변화에 직면하게 된다. 그러면 이러한 부분을 어떻게 보정해 줄 것인가?
optimizer_index_caching, optimizer_index_cost_adj의 파라미터가 그 해답일 것이다. 가능한 Nested Loop Join를 선호하고, CBO의 옵티마이저 모드가 인덱스에 점수를 더 주어서 인덱스의 비중을 키울 수가 있는 것이다. 물론 이러한 전환형태 말고 옵티마이저 모드를 FIRST_ROWS_n으로 운영하거나, 아웃라인을 이용하는 방법 등도 있을 것이다. 여러 방법이 있겠지만, 필자는 아래와 같은 방법을 선호한다.
다음은 마이그래이션시 RBO에서 CBO로 전환한 사례이다.
Cost Base Optimizer
질의 최적화에서 CBO(Cost Base Optimizer)는 해당 SQL 문장이 참조하고 있는 객체들(테이블, 인덱스 등)에 대한 수집된 통계정보(statistics)의 값과 데이타베이스 파라미터(init.ora) 설정 값을 기초로 가장 적은 비용 (cost)이 발생되는 플랜을 결정하는 옵티마이저 방식이다.
여기서 중요한 사실은 RBO에서는 전혀 사용되지 않았던 통계정보를 CBO에서는 이용한다는 것이다. 이들 통계정보는 DBA에 의해서 또는 자동 수집 기능(Oracle9i Database Release 2, Oracle Database10g)에 의해 객체들의 통계정보를 관리하는 시스템 딕셔널리(Dictionary)에 저장되고, 이 정보를 CBO 옵티마이저가 이용하는 것이다. 이들 정보는 SQL 문장을 실행하는 데 얼마만큼의 I/O 횟수가 발생할 것인가를 계산하기 위한 각종 데이타를 가지고 있다. 여기서 중요한 사실은 I/O 크기는 중요하지 않으며 I/O 횟수가 중요하다는 것이다. 즉, CBO 옵티마이저는 SQL 문장에 대한 여러 가지 경우의 수별로 I/O의 횟수에 비례한 비용을 산출해내고, 이들 비용에서 가장 작은 비용을 갖는 플랜을 결정한다는 것이다. 즉, 비용 은 I/O 횟수에 비례하는 값이라고 보면 쉬울 것이다.
그러나, Oracle Database 10g부터는 비용의 단위 기준이 I/O에서 처리시간으로 바뀌었다(time base). 또한 Oracle9i Database부터 시스템 통계정보(CPU, 디스크 액세스 타임)를 이용해서 I/O로 환산한 방식을 제공하였으나, 이것은 단지 옵션이었다. 그러나, Oracle Database 10g부터는 시스템 통계정보(CPU, 디스크 액세스 타임)를 이용해서 처리시간으로 환산한 방식을 디폴트로 사용하므로 상당히 정확한 플랜을 만들어내며, 실행 예측 시간도 상당히 정확하다.
그러면, 여기서 잠깐 CBO에서 사용되는 통계정보가 저장된 Dictinary 정보 예를 참고로 살펴보자.
[USER|ALL|DBA]_TABLES : Table의 통계정보
NUM_ROWS,BLOCKS,AVG_ROW_LEN,SAMPLE_SIZE,LAST_ANALYZED
[USER|ALL|DBA]_INDEXES : Index의 통계정보
BLEVEL,LEAF_BLOCKS,DISTINCT_KEYS,AVG_LEAF_BLOCKS_PER_KEY,
AVG_DATA_BLOCKS_PER_KEY,CLUSTERING_FACTOR,NUM_ROWS,SAMPLE_SIZE,
LAST_ANALYZED
[USER|ALL|DBA]_TAB_COLUMNS : Column의 통계정보
NUM_DISTINCT,LOW_VALUE,HIGH_VALUE,DENSITY,NUM_NULLS,NUM_BUCKETS,
LAST_ANALYZED,SAMPLE_SIZE,AVG_COL_LEN
[USER|ALL|DBA]_TAB_HISTOGRAMS : Column의Data 분포도 정보
TABLE_NAME,COLUMN_NAME,ENDPOINT_NUMBER,ENDPOINT_VALUE,
ENDPOINT_ACTUAL_VALUE
기타 파티션 / 클러스터 등에 대한 통계정보
그러면“select * from dept where deptno = 10”과 같은 SQL 문장을 실행해야 한다고 가정하자. 여기서 dept Table은 deptno에 대한 인덱스가 있고, 테이블은 전체 10 블록으로 구성되어 있으며, 풀 테이블 스캔일 경우 I/O 단위를 결정하는 파라미터는 DBA가 db_file_multiblock_read_count=8로 지정하여 운영하고 있다고 가정하자 . 여기서 RBO라면 무조건 인덱스를 타는 플랜을 결정하였을 것이다.
그러나, CBO의 경우는 deptno의 인덱스를 이용해 실행하면, 3회(인덱스는 싱글 블록 단위 I/O)의 I/O가 발생한다고 가정하고, 풀 테이블 스캔의 경우는 2회(8블록 + 2블록)의 I/O가 발생한다고 가정하면, CBO에서는 인덱스가 있음에도 I/O 횟수 측면에서 더 효과적인 풀 테이블 스캔을 선택한다는 것이다. 그러므로 CBO는 이와 같이 가능한 정확한(현실 데이타와 맞는) 통계정보와 적절한 데이타베이스의 파라미터인 init.ora에 의해 플랜이 결정되는 것이다.
CBO에서만 가능한 기능들
CBO는 오라클의 신기능을 지원하도록 지속적으로 발전하고 있으며, 다음의 경우는 반드시 CBO에서만 플랜 결정시 검토되거나 무조건 CBO로 동작되는 경우이다. 예를 들어, 파티션 테이블을 사용한다면 통계정보가 없더라도 무조건 CBO로 동작된다는 것이다.
• Partitioned tables (*)
• Index-organized tables
• Reverse key indexes
• Function-based indexes
• SAMPLE clauses in a SELECT statement (*)
• Parallel execution and parallel DML
• Star transformations
• Star joins
• Extensible optimizer
• Query rewrite (materialized views)
• Progress meter
• Hash joins
• Bitmap indexes
• Partition views (release 7.3)
• Hint (*)
• Parallel DEGREE & INSTANCES - ‘DEFAULT’도 해당 (*)
CBO의 옵티마이저에 영향을 줄 수 있는 파라미터
옵티마이저가 플랜을 수립하는 데 영향을 줄 수 있는 파라미터 값이 무엇인지를 알고 있는 것이 무엇보다 중요하다. 실제 옵티마이저가 참조하는 파라미터는 Oracle9i Database 기준으로 보더라도 60여 개에 이른다. 특히 DBA는 이들 옵티마이저의 파라미터 설정에 신중해야 한다. 또한 이들 파라미터의 효과적인 설정은 개발 중이거나, 마이그래이션 중에 업무의 특징을 판단한 다음, 해당 업무에 가장 효과적인 것을 설정해야 한다. 기준이 잘못되면 개발자들은 SQL 문장마다 힌트를 넣기 바쁠 것이고, 많은 인적 자원을 튜닝에 소모해야 할 것이다. 그러므로 대부분의 업무들이 최적화되어 잘 운영될 수 있는 형태로 이들 파라미터를 바꿔가면서 기준을 정하는 것이 중요하다. 물론 이들 값보다도 CBO에서 사용되는 통계정보가 중요하다는 것은 당연한 사실이다. 오라클에서는 개발장비에도 운영장비에 있는 통계정보와 같게 운영할 수 있도록 DBMS_STATS 패키지를 제공한다.
다음은 질의 수행시 옵티마이저가 플랜을 수립하기 위해 참조한 파라미터 중 일부이다(버전마다 다르다).
OPTIMIZER_PERCENT_PARALLEL (Default = 0)
Optimizer_Percent_Parallel의 Parameter는 CBO가 비용을 계산하는 데 영향을 주는 파라미터이다. 즉 수치가 높을수록 병렬성을 이용하여 풀 테이블 스캔으로 테이블을 액세스하려고 한다. 이 값이 0인 경우는 최적의 시리얼 플랜이나 패러렐 플랜을 사용하며, 1~100일 경우는 비용 계산에서 객체의 등급을 사용한다.
OPTIMIZER_MODE (Default=Choose(Oracle7 ~ Oracle9i
Database),ALL_ROWS)
{Choose(<=9i)|Rule(<=9i)|First_rows|First_rows_n(> =Oracle9i)|All_rows}
기본적인 옵티마이저 모드를 결정한다 (왼쪽상자 기사 ‘옵티마이저 모드의 종류 및 특징’ 참조).
HASH_AREA_SIZE, HASH_JOIN_ENABLED (Oracle Database 10g : _ hash_join_enabled=true)
위의 파라미터 값에 따라서 Hash Join으로 유도할 수 있다. Hash Join이 가능하고 해쉬 메모리가 충분하다면, 플랜에 Hash Join의 경향이 커진다.
OPTIMIZER_SEARCH_LIMIT (Default = 5)
옵티마이저에게 조인 비용을 계산할 경우, From절에 나오는 테이블의 개수에 따라서 조인의 경우의 수가 있을 수 있으며, 옵티마이저는 이들 각각의 경우의 수에 대한 조인 비용을 계산하게 된다. 물론 일부 예외사항은 있다. 예를 들어, Cartesian Production Join 등은 우선 순위가 낮으므로 뒤로 미뤄질 것이다. 이 파라미터의 값이 5일 경우 From절에 5개의 테이블에 대해서 모든 조인의 경우의 수를 가지고 비용을 계산하게 되며, 그 개수는 5!=120개의 경우의 수 에 대한 조인 비용을 계산하게 되므로 옵티마이저가 많은 시간을 소모하게 되므로 성능에 영향을 미칠 수도 있다.
SORT_AREA_SIZE , SORT_MULTIBLOCK_READ_COUNT
위의 파라미터의 값에 따라서 Sort Merge Join으로 유도할 수 있다. 소트 메모리가 충분하다면, 플랜에 Sort Merge Join의 경향이 커진다.
DB_FILE_MULTIBLOCK_READ_COUNT
이 파라미터의 수치가 클수록 인덱스 스캔보다는 풀 테이블 스캔의 비중이 높아진다. 이 파라미터는 옵티마이저의 플랜 결정에 민감하게 영향을 주는 값이다. 즉, 이 값이 커지면 풀 테이블 스캔과 병행해서 Sort Merge Join 또는 Hash Join의 경향이 커진다.
OPTIMIZER_INDEX_CACHING (Default = 0)
CBO가 Nested Loop Join을 선호하도록 조절하는 파라미터, Nested Loop Join시 버퍼 캐쉬 내에 이너 테이블의 인덱스를 캐쉬화하는 비율(%)을 지정하므로 Nested Loop Join시 성능이 향상되며, 옵티마이저는 비용 계산시 이 비율을 반영하여 Nested Loop Join을 선호하도록 플랜이 선택된다(0~100). 100에 근접할수록 인덱스 액세스 경로가 결정될 가능성이 높다. 기존의 RBO를 CBO로 전환시 옵티마이저를 RBO 성향으로 보정하는 데 효과적이다.
OPTIMIZER_INDEX_COST_ADJ (Default = 100)
옵티마이저가 인덱스를 사용하는 위주의 플랜으로 풀릴 것인지 또는 가능한 사용하지 않을 쪽으로 풀릴 것인지의 비중을 지정한다. CBO는 RBO처럼 인덱스를 사용하도록 플랜이 주로 만들어지게 되나, 인덱스가 있다고 해서 RBO처럼 인덱스를 이용한 플랜으로 처리되는 것은 아니다. 인덱스를 이용하는 플랜 위주로 하고자 한다면 100(%) 이하를, 가능한 인덱스를 사용하지 않고자 한다면 100 이상을 지정한다(1 ~ 10000). 이 파라미터는 기존의 RBO를 CBO로 전 환시 옵티마이저를 RBO의 인덱스 위주 성향으로 보정하는데 효과적이다.
WORKAREA_SIZE_POLICY (AUTO | MANUAL)
옵티마이저가 [HASH|SORT|BITMAP_MERGE|CREATE_ BITMAP] *_AREA_SIZE를 자동으로 결정하는 PGA 자동 관리 방식으로, 인스턴스에 속한 모든PGA의 메모리의 합이PGA_AGGREGATE_TARGET에서 설정된 메모리를 가능한 넘지 않는 범위 내에서 Workarea(Sort, Hash, Bitmap 등)를 충분히 사용하고자 하는 방식이다. 플랜은 할당된 Workarea를 가지고 플랜을 결정하게 되므로 풍부한 메모리에 의해 Hash Join, Sort Merge Join등을 선호하는 경향이 높다. 내부적으로 히든 파라미터로 *_AREA_SIZE의 값을 가지고 플랜을 결정할 수도 있으나 인위적인 설정 없이는 자동 할당된 메모리로 플랜이 결정된다.
OPTIMIZER_DYNAMIC_SAMPLING (Default = 1(Oracle9i Database), 2(Oracle Database 10g))
더 나은 플랜을 결정하기 위한 목적으로 더 정확한 Selectivity & Cardinality 를 구하기 위한 방법으로 0 ~ 10 레벨이 있으며, 레벨이 높을수록 SQL 문장의 실행 시점에 통계정보를 만들기 위해 테이블의 데이타를 샘플링하기 위한 추가적인 Recursive SQL이 발생된다.
DYNAMIC_SAMPLING(0 ~ 10) 힌트를 통해서도 같은 기능을 할 수 있다. 그러나 내부적으로 추가적인 테이블 액세스 의 비용이 발생하므로OLTP에서는 주로 사용하지 않는다. 특히OLTP 환경에 서 레벨을 디폴트 값 이상 높여 놓지 않도록 한다. Oracle Database 10g의 경우 통계정보가 없다면 ‘다이나믹 샘플링’이 적용된다.
다음은 Oracle Database 10g의 플랜 및 다이나믹 샘플링의 예이다.
옵티마이저 모드의 종류 및 특징
• 인스턴스 레벨 : optimizer_mode = {Choose|Rule|First_rows| First_rows_n| All_rows}
• 세션 레벨 : 인스턴스 레벨에 우선
ALTER SESSION SET optimizer_mode =
{Choose|Rule|First_rows|First_rows_n|All_rows}
• 스테이트먼트 레벨 : 힌트를 사용하며, 인스턴스, 세션 레벨에 우선
• Oracle9i Database에서 FIRST_ROWS_n 옵티마이저 모드가 추가되었음(N : 1, 10, 100, 1000).
• Oracle Database 10g에서는 CHOOSE, RULE 모드는 더 이상 지원되지 않으나, 기능은 남아있다.
• OPTIMIZER_MODE=CHOOSE 일경우
통계정보가 없다면 기본적으로 RBO로 플랜이 결정된다. 그러나, ‘RULE’, ‘DRIVING_SITE’ 힌트 이외의 힌트가 왔다면 CBO로 결정된다(힌트는 룰의 규정을 깨므로 CBO로 동작됨).
• Parallel Degree, Partition Table, SAMPLE절 등이 있으면 무조건 CBO
• OPTIMIZER_MODE=First_rows|First_rows_n|All_rows일 경우 통계 정보의 존재 여부와 관계 없이 무조건 CBO로 처리하려고 함.
통계정보가 없다면 Heuristics Value를 이용하거나, Oracle9i Database 이상일 경우는 다이나믹 샘플링의 레벨에 따라 테이블의 데이타를 샘플링해서 CBO로 플랜이 결정된다. 그러나, 플랜이 비효율적일 수 있다.
• 통계정보가 있으나 옵티마이저 모드가 RULE일 경우, 다른 힌트가 오지 않은 경우와 Parallel Degree, Partition Table, SAMPLE절 등이 나오지 않은 경우는 RBO로 처리된다.
CBO를 위한 통계정보 운영 방법
통계정보는 CBO의 플랜 결정에 사용되는 객체들의 물리적인 구성정보를 나타낸다. 즉, 테이블이 몇 블록으로 구성되어 있으며, 몇 건의 로우들을 가지고 있으며, 평균 로우 길이는 어느 정도이며, 칼럼의 Min/Max 값의 분포, Distinct 값, 인덱스의 레벨, 키(key)당 Leaf Block 수 등의 정보들을 나타낸다. 이들 정보는 CBO의 플랜 결정의 기초 자료로 사용된다. 이들 통계정보를 생성하기 위해서는 ANALYZE 명령어를 이용하거나 DBMS_STATS 패키지를 이용하면 된다. 그러나 2개의 차이점에 주의해야 하며, DBMS_STATS를 지원하는 Oracle8i Database 이상부터는 DBMS_STATS를 사용하기를 권장하고 있다.
Analyze 명령어와 DBMS_STATS의 차이점
• Analyze는 파티션의 통계정보를 각 파티션 테이블과 인덱스에 대해서 수집하고, Global Statistics는 파티션 정보를 가지고 계산하므로, 비정확할 수 있다. 그러므로 파티션 또는 서브파티션이 있는 객체에는 DBMS_STATS를 사용하여야 한다.
• DBMS_STATS은 전체 클러스터에 대해서는 통계정보를 수집하지 않는다. 그러므로 Analyze를 사용한다.
• DBMS_STATS은 CBO와 관련된 통계정보만을 수집한다. 즉, 테이블의 EMPTY_BLOCKS, AVG_SPACE,CHAIN_CNT 등은 수집되지 않는다.
• DBMS_STATS은 사용자가 지정한 통계정보 테이블에 수집된 통계정보를 저장할 수 있고, 딕셔너리로 각 칼럼, 테이블, 인덱스, 스키마 등을 반영할 수 있다.
• DBMS_STATS 은 IMPORT/EXPORT 기능 및 추가적인 기능이 많다. 이 기능을 이용하여 운영 DB의 통계정보를 개발장비의 통계정보로 복사할 수 있으므로 개발장비의 플랜을 운영장비와 같게 만들 수 있다(매뉴얼 참조).
다음은 Analyze 명령어에만 있는 기능이다.
analyze { index/table/cluster } (schema.){ index/table/cluster } validate structure (cascade) (into schema.table);
• Chained Rows 수집 기능
ANALYZE TABLE order_hist LIST CHAINED ROWS INTO
시스템 통계정보 (>= Oracle9i Database)
시스템 통계정보는 객체의 통계정보와 같이 사용되는 정보로서, 기존의 Oracle8i Database까지의 I/O 중심의 플랜 방식에 CPU와 디스크 I/O 속도와 같은 시스템 자원의 효율을 반영하여 보다 효율적인 플랜을 결정하기 위한 방법으로, Oracle9i Database에서 처음 소개되었으며, 옵션 기능으로 DBA에 의해 사용될 수도 있고 사용하지 않을 수도 있었다. Oracle Database 10g에서는 시스템 통계정보가 기본적으로 수집되고 사용된다. 그러므로 기존의 I/O 횟수만 가지고 플랜을 결정하던 부분이 Oracle Database 10g에서는 시스템의 자원의 성능도 고려되어 보다 정확한 플랜을 결정할 수 있게 되었다.
이들 시스템 통계정보는 DBMS_STATS 패키지를 이용해서 수집된다.
Oracle Database 10g에서는 기본적으로 수집되는 값들이 있으며, 또한 사용자가 수집해야 하는 항목도 있다. 다음은 Oracle Database 10g의 시스템 통계정보의 수집형태를 보여주고 있다.
더 자세한 내용 : 오라클 매뉴얼 [Database Performance Tuning Guide and Reference]
http://munduki.tomeii.com/darkhorse/entry/ORACLE-Oracle-Optimizer의-원리-이해-및-SQL-애플리케이션의-튜닝 상
Getting Started with jQuery Mobile
jQuery Mobile provides a set of touch-friendly UI widgets and an AJAX-powered navigation system to support animated page transitions. Building your first jQuery Mobile page is easy, here's how:
Create a basic page template
Pop open your favorite text editor, paste in the page template below, save and open in a browser. You are now a mobile developer!
Here's what's in the template. In the head
, a meta viewport
tag sets the screen width to the pixel width of the device and references to jQuery, jQuery Mobile and the mobile theme stylesheet from the CDN add all the styles and scripts. jQuery Mobile 1.1 works with both 1.6.4 and 1.7.1 versions of jQuery core.
In the body
, a div with a data-role
of page
is the wrapper used to delineate a page, and the header bar (data-role="header"
) and content region (data-role="content"
) are added inside to create a basic page (these are both optional). These data-
attributes are HTML5 attributes are used throughout jQuery Mobile to transform basic markup into an enhanced and styled widget.
<!DOCTYPE html>
<html>
<head>
<title>My Page</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
</head>
<body>
<div data-role="page">
<div data-role="header">
<h1>My Title</h1>
</div><!-- /header -->
<div data-role="content">
<p>Hello world</p>
</div><!-- /content -->
</div><!-- /page -->
</body>
</html>
Add your content
Inside your content container, you can add any standard HTML elements - headings, lists, paragraphs, etc. You can write your own custom styles to create custom layouts by adding an additional stylesheet to the head
after the jQuery Mobile stylesheet.
Make a listview
jQuery Mobile includes a diverse set of common listviews that are coded as lists with a data-role="listview"
added. Here is a simple linked list that has a role of listview
. We're going to make this look like an inset module by adding a data-inset="true"
and add a dynamic search filter with the data-filter="true"
attributes.
<ul data-role="listview" data-inset="true" data-filter="true">
<li><a href="#">Acura</a></li>
<li><a href="#">Audi</a></li>
<li><a href="#">BMW</a></li>
<li><a href="#">Cadillac</a></li>
<li><a href="#">Ferrari</a></li>
</ul>
Add a slider
The framework contains a full set of form elements that automatically are enhanced into touch-friendly styled widgets. Here's a slider made with the new HTML5 input type of range, no data-role
needed. Be sure to wrap these in a form
element and always properly associate a label
to every form element.
<form>
<label for="slider-0">Input slider:</label>
<input type="range" name="slider" id="slider-0" value="25" min="0" max="100" />
</form>
Make a button
There are a few ways to make buttons, but lets turn a link into a button so it's easy to click. Just start with a link and add a data-role="button"
attribute to it. You can add an icon with the data-icon
attribute and optionally set its position with the data-iconpos
attribute.
<a href="#" data-role="button" data-icon="star">Star button</a>
Play with theme swatches
jQuery Mobile has a robust theme framework that supports up to 26 sets of toolbar, content and button colors, called a "swatch". Just add a data-theme="e"
attribute to any of the widgets on this page: page, header, list, input for the slider, or button to turn it yellow. Try different swatch letters in default theme from a-e to mix and match swatches.
Cool party trick: add the theme swatch to the page and see how all the widgets inside the content will automatically inherit the theme (headers don't inherit, they default to swatch A).
<a href="#" data-role="button" data-icon="star" data-theme="a">Button</a>
data-theme="a"data-theme="b"data-theme="c"data-theme="d"data-theme="e"
When you're ready to build a custom theme, use ThemeRoller to drag and drop, then download a custom theme.
Go forth and build stuff
This is just scratching the surface of all the cool things you can build with jQuery Mobile with little effort. Be sure to explore linking pages, adding animated page transitions, and creating dialogs. Use the data-attribute reference to try out some of the other data-
attributes you can play with.
More of a developer? Great, forget everything we just covered (kidding). If you don't want to use the data-
attribute configuration system, you can take full control of everything and call plugins directly because these are all just standard jQuery plugins built with the UI widget factory. Be sure to dig into global configuration, events, and methods. Then read up on scripting pages, generating dynamic pages, and building PhoneGap apps.
Eclipse Plug-in Installation
Installing via Eclipse
Please copy the following Update Site URL to your clipboard and then follow the steps listed below to add this URL to your Available Software Sites list. Attempting to access this URL using your web browser will return an Access Denied error.
- From the Help menu, select Install New Software... to open the Install New Software dialog.
- Paste the URL for the update site into the Work With text box, and hit the Enter (or Return) key.
- In the populated table below, check the box next to the name of the plug-in, and then click the Next button.
- Click the Next button to go to the license page.
- Choose the option to accept the terms of the license agreement, and click the Finish button.
- You may need to restart Eclipse to continue.
Welcome!
If you are reading this, we'd like to thank you for your interest Aptana Studio. Please read through this guide carefully.
For Aptana Studio 2 Upgraders
Aptana Studio 3 is intended to be completely independent from Aptana Studio 2. You can install both Studio 2 and Studio 3 separately and run them both on the same machine, since they will use different workspace directories. If you wish to import your Studio 2 projects into Studio 3, you can follow instructions here.
Prerequisites
Before you install the Release, you should have the following tools installed and working correctly:
- Sun/Oracle Java 1.5.x or later on Mac and Linux. OpenJDK is not yet supported. The Windows installer includes a compatible version of Java.
- Git is required because it is used internally to update your scripting environment. On Windows, we recommend PortableGit from msysgit because we also require its bash shell for executing commands. If you don't have msysgit installed, Studio 3 will offer to install it for its own internal use.
- If you are installing Studio 3 as an Eclipse plugin, you must use Eclipse 3.5 or better.
If you intend to do Ruby on Rails development, you will also need to set up your Ruby and Rails development environment:
- Ruby 1.8.7+ and/or 1.9.1+ (on Windows, we recommend RubyInstaller along with its optional DevKit addon). You can use other ruby runtimes (e.g. jruby) if you like, but we expect to find
ruby
andgem
tools on your path by name. Plus we haven't tested debugging with other interpreters yet, so debugging may not work if you use an alternate runtime. - Rails (2.x and/or 3.0)
- The gem that supports the database(s) of your choice.
- For debugging, you will need to have the
ruby-debug-base
andruby-debug-ide
gems installed.
You should also make sure that your login PATH is set up correctly such that you can execute the ruby
, gem
, rails
, and git
commands correctly. Setting these things up in your .bashrc (or equivalent) isn't sufficient, because it doesn't get evaluated by default when GUI applications like Aptana Studio 3 get launched.
Ruby switching utilities like rvm should also work, provided that you set them up in your login PATH as well.
If getting your login PATH set up in this way isn't practical for you, you can work around the problem by launching Aptana Studio 3 from the command line, using the studio3
command line utility. This utility can be found at the top level of the Aptana Studio 3 installation folder, so you can put that folder in your PATH for convenience. (The command line utility can also be called after Aptana Studio 3 is running, to get it to open source code files for editing.)
Update Your Installation Regularly
The IDE is configured to check for updates from our update site, and we plan on releasing new versions on a regular schedule.
Getting Started With Studio 3
Check the help system for links on geting started and how to use various features.
Providing Feedback and Reporting Bugs
-
Browse Discussions
The active community around Aptana Studio includes documentation, Aptana communications and member conversations. -
Bug Tracker
Found a bug? Use our bug tracker to see the list of open and closed bugs as well as submit new tickets.
- 2012/07/22ExtJS를 활용한 Ria 어플리케이션 구축하기 - 3
- 2012/07/22ExtJS를 활용한 Ria 어플리케이션 구축하기 - 2
- 2012/07/21ExtJS를 활용한 Ria Application 만들기
- 2012/07/20ExtJS 4를 활용한 Ria Application 만들기
출처 : http://benney.tistory.com/category/자바스크립트/Ext%20JS?page=2
에러 메시지 : 에러 메시지는 플렉스 빌더 2의 콘솔로 나오는 메시지가 대부분이고 일부 Runtiome Exception이라고 되어 있는 것은
웹브라우저의 플레시 플레이어 에서 나오는 메시지 이다.
* 에러 레벨 : 에러 레벨은 Warning 과 Error 가 있는데 Warning 은 경고성 메시지로 SWF 파일로 컴파일 되어 실행은 가능한 반면
Error는 컴파일이 되지 않으므로 반드시 원인을 찾아 고쳐야 한다.
Warning은 flex - config.xml 파일의 컴파일 옵션 중 " Show - actionscript - warnings " 를 false 로 설정하면 없어진다.
* 발생 원인 : 발생 원인이 되는 코드 패턴을 설명한 부분이다.
* 코드 예 : 에러 메시지가 나오는 코드의 예를 보여준다
* 해결책 : 에러를 수정하기 위해 해야할 조치를 설명한다.
1) 1008 : Variable my Var 에 형식 선언이 없습니다.
* 에러 레벨 : Warning
* 발생 원인 : my Var에 데이터 타입을 지정하지 않았을 경우 발생
* 코드 예 : Private Var My Var;
* 해결책 : Private Var my var : String; 식으로 데이터 타입을 지정해줌
2) 1084 : var 'my Var' 메소드에 namespace 속성이 없습니다.
이 속성은 'Package - private' namespace 의 기본값을 구성합니다.
* 에러 레벨 : Warning
* 발생 원인 : 변수나 메소드명에 제한자를 지정하지 않았을 떄 발생
* 코드 예 : var myvar : String;
* 해결책 : private var myvar : String; 이런 식으로 제한자를 지정해줌
3) 1084 : identifier 가 xxx 앞에 있어야 합니다.
* 에러 레벨 : Error
* 발생 원인 : xxx 근처에 변수 선언이 잘못되어있거나 예약어로 변수명을 선언했을 경우
* 코드 예 : var 1 myVar : string;
* 해결책 : 변수명이 제한자 + var + 변수명의 식으로 정확히 선언되었는지,
변수명이 영문자나 $ 혹은 _ 로 시작하는지 확인
4) 1120 : Access of undefined property myButton
* 에러 레벨 : Error
* 발생 원인 : 정의되지 않은 속성, 변수명이나 id 값을 참조하려고 할 때
* 코드 예 : <mx : Button id = "myButn" label = "확인">
<mx : Label text = "{myButton.label}"/>
5) 1084 : Rightbrace 가 end of program 앞에 있어야 한다.
* 에러 레벨 : Error
* 발생 원인 : 코드 블록 {와} 표시가 제대로 열고 닫히지 않았을때
* 해결책 : 모든 괄호가 짝지어 있는지 확인한다.
6) 1087 : 프로그램 종료 이후에 문자가 추가로 발견 되었습니다.
* 에러 레벨 : Error
* 발생원인 : 함수 선언 다음에 { 표시가 없을때
* 해결책 : 모든 괄호가 짝지어 있는지 확인한다.
7) 1008 : Variable i 에 형식 선언이 없습니다.
* 에러 레벨 : warning
* 발생 원인 : 실행문 안에 변수의 타입이 선언이 되지 않았을 때
* 코드 예 : for (var i=0; i<1; i++)
* 해결책 : 타입을 선언해 준다. for (var i : int =0; i<1; i++)
8) The end - tag for element type "mx:Mode *" must end with a '>' delimiter
* 에러 레벨 : Error
* 발생 원인 : 태그가 정확히 열고 닫히지 않았을때
* 해결책 : 태그가 제대로 열고 닫혔는지 확인한다.
9) Encountered "<mx:Model>" at line 41, column5. Was expecting one of : /Model....
* 에러 레벨 : Error
* 발생 원인 : 태그가 정확히 열고 닫히지 않았을때
* 해결책 : 태그가 제대로 열고 닫혔는지 확인한다 ( 특히 종료 태그를 빼먹지는 않았는지 확인)
10) The element type "mx:Model: must ne terminated by the matching end-tag "</mx:Model>"
* 에러 레벨 : Error
* 발생 원인 : 태그가 정확히 열고 닫히지 않았을때
* 해결책 : 태그가 동일한 이름으로 열고 닫혔는지 확인한다.
11) invalid character or markup for found in script block. Try surrounding your code with a CDATA block.
* 에러 레벨 : Error
* 발생 원인 : 태그기호 " < " 가 하나 더 있을떄
* 코드 예 : <mx:Model> .... <</mx:model>
* 해결책 : 추가된 " < " 를 찾아서 제거 한다.
12) Encounterd " > \n " at line41, column16.
* 에러 레벨 : Error
* 발생 원인 : xorm rlgh " > " 가 하나 더 있을 때
* 코드 예 : <mx:Model> .... </mx:model>>
* 해결책 : 추가된 " > "를 찾아서 제거 한다.
13) 1120 : Access of undefined property "클래스명"
* 에러 레벨 : Error
* 발생 원인 : 에러메시지에 나오는 클래스명을 import 하지 않았을 떄
* 코드 예 : ArrayUtil.toArray (myXML.result)
* 해결책 : import mx.utils.ArrayUtil ; //문장을 스크립트 블록에 써줌
14) 1061 : 정의되지 ㅇ낳은 메소드 입니다.
* 에러 레벨 : Error
* 발생 원인 : 에러 라인의 객체에서 정의되어있지 않은 메소드를 호출 하려고 할 때
* 코드 예 : Arrayutil.toArrya 2 (myXML.result)
* 해결책 : 코드ㅡ 자동완성 기능이나 API 문서를 통해 그 클래스에서 사용가능한 정확한 메소드 명을 찾아서 정의함
15) Cannot resolve attribute 'titles' for component type mx.containers.Panel.
* 에러 레벨 : Error
* 발생 원인 : 에러라인의 객체에서 정의도어있지 않은 속성을 사용하려고 할 때
* 코드 예 : <mx : Panel titles = "Line Chart ">
* 해결책 : 코드 자동완성 기능이나 API 문서를 통해 그 클래스에서 사용가능한 정확한 속성명을 찾아서 정의함
16 ) Open quote is expected for attribute "title"
* 에러 레벨 : Error
* 발생 원인 : 에러라인의 태그에서 속성값의 시작 따옴표가 정의 되지 않았을 때
* 코드 예 : <mx : panel titles = Line Chart"> or <mx : panel titles = 'Line Chart">
* 해결책 : 속성값의 따옴표를 정확히 입력한다.
18) 1071 : 속성 뒤에 주석 사용이 가능한 지시문이 있어야 합니다.
* 에러 레벨 : Error
* 발생 원인 : 에러라인의 변수 선언이 제대로 되지 않았을 때
* 코드 예 : private; private var myVar : String;
* 해결책 : 해당라인 근처의 변수 선언을 정확히 해준다.
19) 1180 : Call to a possibly undefined method check.
* 에러 레벨 : Error
* 발생 원인 : 정의되지 않은 메소드를 호출하려고 할 떄
* 코드 예 : check ();
* 해결책 : 해당 함수를 호출하는 라인으로 가서 정확한 메소드명으로 고쳐준다.
20) 1084 : rightparen 이 콜론 " : " 앞에 있어야 합ㄴ디ㅏ.
* 에러 레벨 : Error
* 발생 원인 : " : " 근처에서 객체 관련 코드가 잘못 되었을 때 / var, function 등의 키워드를 설정하지 않았을 떄
* 코드 예 : <mx : Button label = "확인 " click = "showMenu(event:Event) " />
*해결책 : 클래스 타입 선언을 제거한다.
21) Data binding will not be able to detect assignments to :bindableVar"
* 에러 레벨 : Warning
* 발생 원인 : 변수가 바인딩 되어 있는데 [bindable] 표시를 하지 않아 발생
* 코드 예 : private var bindableVar : Number=0;
* 해결책 : 바인딩하는 변수를 찾아서 [bindable] private var bindableVar : Number=0; 표시를 해준다.
22) 다음 형식 주석이 컴파일 타입 상수가 아닙니다.
* 에러 레벨 : Error
* 발생 원인 : 컴피일 중에 참조하려는 객체나 속성이 없을때
* 코드 예 : 객체으 ㅣ이름이나 속성이 잘못 되었거나 import 문을 써주지 않았을 때
* 해결책 : 에러가 발ㄹ생한 객체를 찾아서 import 문, 클래스며으, 속성명 등이 정확한지 확인한다.
23 ) int가 관련없는 String 형식으로 암시적 강제 정의 되어있습니다.
* 에러 레벨 : Error
* 발생 원인 : 객체에 값을 설정하려고 하는데 타입이 맞지 않을 때
* 코드 예 : var test : String = "test"; test =1;
* 해결책 : 에러가 발생한 코드를 찾아서 세팅되는 객체의 타입을 맞게 수정해 준다 / test = "1";
24) Definition sub : SubClass could not be found .
* 에러 레벨 : Error
* 발생 원인 : 특정 패키지에 있는 클래스를 찾을 수 없을 때
* 코드 예 : import sub.subclass
* 해결책 : SubClass 에 대한 패키지명, 경로, 클래스명이 정확한지 확인한다.
25) ReferenceError : Error #1069 : String 에서 속성을 찾을 수 없습니다. 기본값이 없습니다.
* 에러 레벨 : Error
* 발생 원인 : 데이터 바인딩을 위해 데이터를 참조하려고 하는데 그 속성명이 없거나, 데이터 바인딩이 되지 않았을 때
* 코드 예 : <mx:LineChart dataProvider = "myAC } " 또는 showDataTips = true">
혹은 데이터 정의가 <apple> 100 </apple> 식으로 되어 있을때
26) typeError : Error #1009 : null 객체 참조의 속성이나 메소드에 액세스 할 수 없습니다.
* 에러 레벨 : Error (Runtime Exception)
* 발생 원인 : 실행중에 참조하려는 객체 속성이나 메소드가 없을때
* 코드 예 : mx.controls.Alert.show (event.result.xxx) ;
* 해결책 : 참조하려는 객체의 정확한 속성명이나 메소드명을 써 준다.
출처: http://blog.naver.com/PostView.nhn?blogId=injae0203&logNo=70071061714
iReport 개발자 매뉴얼.pdf
JasperReports_개발자_매뉴얼-rokmc7980304.pdf
를 보고 아주 많은 도움을 받았습니다.
아직도 위의 매뉴얼을 반도 해 보진 못했지만 간단한 웹보고서를 만들 수 있었습니다.
필요하신 분은 나는나님의 블로그에 방문하셔서 위의 PDF 파일을 다운받아서 공부하시면 보다 심도깊은 보고서를 만드실 수 있다고 생각합니다. 좋은 자료를 공개하신 나는나님에게 다시 한 번 감사의 말씀을 전합니다.
-----------------------------------------------------------------------------
사용된 것들
MS Windows 2000 Advanced
JDK5.0 Update 8
TOMCAT 5.5
PostgreSql 8.1.4
Struts 1.2.9
iReport 1.2.5
JasperReports 1.2.5
Adobe Reader 7.0
AcroEdit 0.9.5.30
이 글을 무리없이 보고 이해할 수 있는 레벨
이글은 제가 생각하기에
웹보고서를 만드는 가장 쉬운 방법을 적은 글입니다.
하지만, 쉽다고는 하지만 아래에 밝힌 실력만큼은 갖추고 있는 분에게만
쉽다는 얘기입니다.
본문에서는 각 분야의 기초적인 설명은 하지 않습니다.
기초설명하다가 본래의 목적을 벗어날까 해서 입니다.
- 우선 톰캣에 웹어플리케이션을 만들 줄 알고 간단하게나마 운용할 줄 알아야 합니다.
- 자바에 대한 공부가 어느정도 되어 있어야 합니다.
명령행에서 컴파일을 할 줄 알아야하고
간단한 소스는 눈으로 보고 어떻게 돌아가는 건지 파악할 줄 알아야 합니다.
- Struts에 대한 지식이 약간(?) 필요합니다.
- 데이타베이스를 다룰줄 알아야 합니다.
이 문서에서는 PostgreSql을 사용하는데 다른 RDBM도 상관없지만
데이타베이스를 만들고 테이블도 만들어서 프라이머리키를 지정할 수 있고
기본적인 SQL문을 작성해서 실행할 줄 알아야 합니다.
-----------------------------------------------------------------
자~~~ 그럼, 본론으로 들어갑니다.
이번엔, 최근에 조사하고 테스트한 웹보고서를 만드는 법에 대해 내용입니다.
거의 3주간 자료찾아보고 테스트하느라 제 PC를 몇 번씩 뒤집었다는...
의외로 자료가 부족해서 시간이 많이 걸렸습니다.
이 자료를 보고 저처럼 쓸데없이 시간을 버리는 것을
조금이나마 줄일 수 있었으면 합니다.
일반 C/S 프로그램에서는 각종 개발툴의 안에 보고서를 만들어 주는 기능이
들어 있거나 관련 라이브러리가 있어서 제작하기가 쉽지만
웹상에서 정형화된 보고서를 유저에게 제공하는 기능을 보기란 그리 흔치 않습니다.
이번에 알아본 것은 iReport라는 프로그램과
JasperAssistant라는 Eclipse 플러그인이었습니다.
이중에 JasperAssistant는 유료이고 Eclipse에서 사용하는 것이기도 하고
실제로 사용해 보니 iReport로도 웬만한 보고서는 만들어 낼 수 있었고
화면 운영방식이 Eclipse 안에서 움직이는 것이 틀릴 뿐이고 기본적인 것은
거의 같기 때문에 JasperAssistant에 대한 설명은 하지 않습니다.
이 문서를 충분히 이해하신 분은 무리없이 JasperAssistant도 다룰 수 있을 겁니다.
하지만 고급기능들까지 해 보질 않아서
둘사이의 장단점을 확실히 꼬집어 내진 못하겠습니다.
이 문서를 이해하면 할 수 있는 것
Java + Tomcat(Struts) + PostgreSql의 환경하에서
간단한 웹보고서를 자신의 웹어플리케이션에서 제공할 수 있게됩니다.
이 문서의 소프트웨어 환경
다음은 이 문서를 작성하면서 설치한 환경입니다.
실력이 있으신 분은 상관없지만
되도록 똑같은 환경에서 하셔야 에러를 최소한으로 줄이실 수
있을 것입니다.
자신의 환경을 감안하면서 적용해 보시기 바랍니다.
운영체제 : MS Windows 2000 Advanced
JDK5.0 (설치하기)
Tomcat 5.5 (설치하기)
PostgreSql 8.1.4 (설치하기)
Test용 Tomcat 웹어플리케이션 MyWeb02 (설치하기)
Adobe Reader 설치<-- 알아서 설치하시길...
꼭! 사전에 설치해 둬야 합니다.
되도록 한국어 버전으로....
위의 소프트웨어들은 제 블로그를 보시면
다운로드 -> 설치 하는 법을 아실 수 있습니다.
(뭐,, 당연히 각자 하셔도 됩니다. 설치시 특별히 어려운 점은 없으니까요)
Struts 다운로드 및 설치
다운로드 URL : http://struts.apache.org/downloads.html
다운로드 파일명 : struts-1.2.9-bin.zip(13.8MB)
설치 : 우선 적당한 곳에 압축을 풀어놓습니다.
압축을 푼 폴더의 밑에 webapps 라는 폴더가 있는데
그 안의 struts-blank.war를 톰캣밑의 webapps에 복사해
넣습니다.
그러면 struts-blank 라는 웹어플리케이션이 자동으로 생깁니다.
스트러츠 사용할 줄 아시는 분은 다 아시는 내용이라고 생각하기 때문에
자세한 설명은 하지 않겠습니다.
* 나중에 이 안의 lib 안의 파일들을 웹어플리케이션에 그대로
복사해 넣을 것입니다.
JasperReports의 다운로드와 설치
JasperReports H.P URL : http://jasperforge.org/sf/projects/jasperreports
JasperReports 다운로드 :
http://sourceforge.net/project/showfiles.php?group_id=36382&package_id=28579
다운로드 파일명 : jasperreports-1.2.5.jar(1.33MB)
jasperreports-1.2.5-applet.jar(196KB)
jasperreports-1.2.5-project.tar.gz(24.1MB)
; (이 파일은 이 문서에서는 사용하지 않음)
jasperreports-1.2.5-project.zip(28.3MB)
JasperReports 설치 : 특별히 설치할 작업은 없습니다.
단지, 미리 jasperreports-1.2.5-project.zip 의 압축을
풀어놓으면 편할지도 모릅니다.(애매하군...)
* 이것도 나중에 이 안의 lib 폴더 안의 파일들을 웹어플리케이션에
전부 복사해 넣을 것입니다.
iReport의 다운로드
다운로드 URL : http://sourceforge.net/project/showfiles.php?group_id=64348
다운로드 파일명 : iReport-1.2.5-windows-installer.exe(30.3MB)
iReport의 설치
다운로드 받은 iReport-1.2.5-windows-installer.exe 을 더블클릭
설치시 어려운점은 없습니다. 그냥 Next 버튼을 몇 번 눌러주면 설치 끝.
설치 위치는 설치시 특별히 바꾸지 않는한
C:/Program Files/JasperSoft/iReport-1.2.5 에 설치 됩니다.
iReport로 템플레이트 파일 만들기
설치시 경로를 바꾸지 않았다면
C:/Program Files/JasperSoft/iReport-1.2.5
에 설치되었을텐데
저의 경우에는 iReport.exe 를 실행하지 않고
iReport.bat 를 실행합니다.
왠지 이 배치파일로 실행해야 제대로 환경을 끌여들여서 실행하는 것 같습니다.
- iReport 화면이 떳으면 제일 처음에 JDBC 파일을 연결시켜 줍니다.
여기서는 PostgreSql 8.1.4를 설치했으므로
PostgreSql 8.1.4의 설치폴더 밑에 JDBC 폴더 안에
JDBC 드라이버가 설치되어 있는 것을 사용합니다.
메뉴에서 Options->Classpath 를 선택하면 윈도우가 하나 뜨는데
그 윈도우의 오른쪽에 있는 Add JAR 버튼을 누릅니다.
C:Program FilesPostgreSQL8.1jdbc 안의 postgresql-8.1-405.jdbc3.jar
를 선택합니다.
Save classp... 라는 버튼을 눌러 저장하고 나옵니다.
- 다음은 사용할 데이타소스를 정의해 줍니다.
메뉴에서 Data->Connections / Datasources 를 선택합니다.
화면 오른쪽의 New 버튼을 선택합니다.
아래 그림처럼 입력합니다.(각자의 환경에 맞추면 됩니다.)
WITH OWNER = postgres
ENCODING = 'EUC_KR'
TABLESPACE = pg_default;
(
rel_id int2 NOT NULL,
rel_name text,
CONSTRAINT "rel_PK" PRIMARY KEY (rel_id)
)
(
uid int4 NOT NULL,
fname text,
telno1 text,
rel_id int2,
CONSTRAINT "tbl01_PK" PRIMARY KEY (uid)
)
insert into rel(rel_id, rel_name) values(2,'친척');
insert into rel(rel_id, rel_name) values(3,'친구');
insert into rel(rel_id, rel_name) values(4,'선배');
insert into rel(rel_id, rel_name) values(5,'후배');
insert into tbl01(uid,fname,telno1,rel_id) values(2,'어머니','022-222-2222',1);
insert into tbl01(uid,fname,telno1,rel_id) values(3,'큰형','033-333-3333',1);
insert into tbl01(uid,fname,telno1,rel_id) values(4,'막내','044-444-4444',1);
insert into tbl01(uid,fname,telno1,rel_id) values(6,'매형','066-666-6666',2);
insert into tbl01(uid,fname,telno1,rel_id) values(8,'용팔이','088-888-8888',3);
insert into tbl01(uid,fname,telno1,rel_id) values(9,'삼순이','099-999-9999',3);
insert into tbl01(uid,fname,telno1,rel_id) values(11,'김선배','200-200-2000',4);
insert into tbl01(uid,fname,telno1,rel_id) values(12,'이선배','300-300-3000',4);
insert into tbl01(uid,fname,telno1,rel_id) values(14,'오연수','234-234-2345',5);
insert into tbl01(uid,fname,telno1,rel_id) values(15,'김희선','345-345-3456',5);
FROM tbl01 a LEFT JOIN rel b ON a.rel_id = b.rel_id
ORDER BY b.rel_name
FROM tbl01 a LEFT JOIN rel b ON a.rel_id = b.rel_id
ORDER BY b.rel_name
이 예제 웹어플리케이션을 실행하기 위해서는 준비과정이 필요합니다.
예제 소스에는 모든 라이브러리 파일은 담지 않았습니다.
파일크기도 커지고 각자 해 보는 것이 좋기 때문입니다.
예제에 필요한 파일 복사해 넣기
/MyWeb02/WEB-INF/lib 에는 첨부파일에도 아무것도 들어있지 않는데
이 폴더에 아래의 파일들을 복사해 넣으십시오.
- JasperReports를 압축해제한 폴더의 밑의 lib 안의 모든 JAR 파일
- jasperreports-1.2.5.jar
- jasperreports-1.2.5-applet.jar
- iTextAsian.jar
- postgresql-8.1-407.jdbc3.jar
- /struts-blank 웹어플리케이션의 lib 안의 모든 파일
참고로 아래는 이 문서 작성시 /MyWeb02/WEB-INF/lib 에 들어있던 파일리스트입니다.
예제 프로그램을 실행시키기 위해서 이 파일들이 모두 필요하진 않습니다.
좀 더 가볍게 실행시키시고 싶은 분은 필요없는 것은 지우셔도 상관없습니다.
제 경우에는 잘 모르기 때문에 하나씩 지우면서 웹 어플리케이션이 실행되나 안되나
시험하면서 정리를 했었는데 나중에 귀찮아서 그냥 전부 넣고 돌립니다.
[/MyWeb02/WEB-INF/lib]
ant-1.5.1.jar
antlr-2.7.5.jar
antlr.jar
commons-beanutils-1.5.jar
commons-beanutils.jar
commons-collections-2.1.jar
commons-digester-1.7.jar
commons-digester.jar
commons-fileupload.jar
commons-javaflow-20060411.jar
commons-logging-1.0.2.jar
commons-logging-api-1.0.2.jar
commons-logging.jar
commons-validator.jar
groovy-all-1.0-beta-10.jar
hibernate3.jar
hsqldb-1.7.1.jar
itext-1.3.1.jar
iTextAsian.jar
jakarta-bcel-20050813.jar
jakarta-oro.jar
jasperreports-1.2.5-applet.jar
jasperreports-1.2.5.jar
jcommon-1.0.0.jar
jdt-compiler-3.1.1.jar
jfreechart-1.0.0.jar
jpa.jar
jxl-2.6.jar
list.txt
mondrian-2.0.1.jar
png-encoder-1.5.jar
poi-2.0-final-20040126.jar
postgresql-8.1-407.jdbc3.jar
servlet.jar
struts.jar
xalan.jar
xercesImpl.jar
xml-apis.jar
그리고 /MyWeb02/reports 에 위에서 iReport로 만든 파일을 넣어주는데
이 문서의 경우 보고서명(파일명)이 report01이었습니다.
그리고 report01.jrxml 파일은 잘 아는 곳에 저장을 해 두었습니다.
이 문서의 경우는 D:/TEST 였습니다.
그 report01.jrxml 과 iReport의 설치폴더 안에 보면 report01.jasper 파일이 있습니다.
위의 2개의 파일을 /MyWeb02/reports 에 넣어줍니다.
즉, report01.jrxml , report01.jasper 를 /MyWeb02/reports에 넣어줍니다.
다음, /MyWeb02/jsp 안에 java 파일이 2개 있습니다.
이 자바파일들을 컴파일해야 합니다.
이것도 컴파일한 클래스 파일을 넣을까 하다가 소스만 넣어 두었습니다.
명령행에서 컴파일 방법
- 우선 클래스패스에 같은 폴더에 들어있는 jasperreports-1.2.5.jar 를 더해 줍니다.
>set classpath=%classpath%;D:/tomcat_webapps/MyWeb02/jsp/jasperreports-1.2.5.jar[엔터]
-> 이 때 클래스패스에
%JAVA_HOME%libtools.jar
%JAVA_HOME%libdt.jar
가 잡혀있는 경우에는 위와 같이 해주면 되지만
이 문서에서의 환경에서는 환경변수에 CLASSPATH가 없었습니다.
그래서 위의 명령행에서 위의 폴더까지 들어가서
아래와 같이 클래스패스를 잡아주었습니다.
>set classpath=.;jasperreports-1.2.5.jar[엔터]
- 컴파일
>javac JRViewerSimple.java[엔터]
>javac EmbeddedViewerApplet.java[엔터]
다음은 서블릿 파일을 컴파일합니다.
이 파일은 /MyWeb02/WEB-INF/src/reports 안에
JasperPrintServlet.java
ReportServlet.java
라는 이름으로 들어 있습니다.
Eclipse 를 사용하시는 분이라면 쉽게 컴파일까지 하실 수 있을겁니다.
여기서는 Eclipse가 없을 때 명령행에서 컴파일하는 방법을 설명합니다.
우선 명령행으로 나가서 /MyWeb02/WEB-INF/src/reports 까지 들어갑니다.
그 다음 여기서도 필요한 클래스를 패스로 잡아주고 컴파일하겠습니다.
다음 명령을 넣어줍니다.
>set classpath=.;C:/Tomcat55/common/lib/servlet-api.jar;../../lib/jasperreports-1.2.5.jar;../../lib/commons-beanutils.jar;../../lib/commons-digester.jar;../../lib/postgresql-8.1-407.jdbc3.jar;../../lib/struts.jar
이 명령에 대한 설명은 생략합니다.
명령행에서 컴파일하는 법은 우선 다른 웹사이트를 참조하시기 바랍니다.
이 문서와 똑같이 진행시켜 왔다면 컴파일이 될 것입니다.
다음은 컴파일
>javac -d ../../classes ReportServlet.java[엔터]
>javac -d ../../classes JasperPrintServlet.class[엔터]
/MyWeb02/WEB-INF/classes/reports 안을 확인해 보면 클래스 파일들이
생성되어 있을 겁니다.
MENU
Applet View
PDF View
cond1 : ___________
cond2 : ___________
이런 화면이 나타납니다.
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
public class CommonUtil {
public CommonUtil(){
}
/**
* 썸네일 만들기
* @param width
* @param height
* @param loadFile
* @param saveFile
* @param zoom
* @throws IOException
*/
public static void createThumbnail(int width, int height, String loadFile, String saveFile, int zoom) throws IOException{
File save = new File(saveFile.replaceAll("/", "\\"+File.separator));
FileInputStream fis = new FileInputStream(loadFile.replaceAll("/", "\\"+File.separator));
BufferedImage im = ImageIO.read(fis);
if (zoom<=0) zoom = 1;
BufferedImage thumb = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = thumb.createGraphics();
g2.drawImage(im, 0, 0, width, height, null);
ImageIO.write(thumb, "jpg", save);
}
/**
* 썸네일 삭제
* @param thumbnail_path
*/
public static void deleteThumbnail(String thumbnail_path){
File file = new File(thumbnail_path);
if(file.exists()){
file.delete();
}
}
public static void main(String args[]){
String loadFile = "http://localhost:8080/2010/07/15/LZHABUhgGy1279183572640.JPG";
String saveFile = "D://dev/test.jpg";
int zoom = 5;
try {
CommonUtil.createThumbnail(124, 144, loadFile, saveFile, zoom);
} catch (IOException e) {
e.printStackTrace();
}
}
}
사용자의 로그정보가 필요해 아래와같이 간단하게 만들어봤다.
public void makeFreePaidLog(HttpServletRequest request, String academyCode, String date) throws IOException, Exception{
StringBuffer sb = new StringBuffer();
SimpleDateFormat formatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS");
Calendar cal = Calendar.getInstance();
String today = formatter.format(cal.getTime());
String str = "[" +today + "]";
sb.append("Browser Info = " + request.getHeader("User-AGENT") + " MIS_ID = " + SessionUtil.getMis_Id(request));
sb.append(", USER_NM = " + SessionUtil.getUserNm(request) + ", ACAD_CD = " + academyCode);
String log = str + sb.toString();
try{
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\FreePaidLog_"+date+".txt", true));
bw.write(log);
bw.newLine();
bw.flush();
bw.close();
}catch(IOException ie){
System.err.println("Error");
System.exit(1);
}
}
위에서 중요한 부분은 new FileWriter()하는부분. 뭐가 문제인지는 모르겠으나 FileWriter 객체 생성할때 다른 class나 새로운 객체를 생성하여 매개인자로 넣으면 에러가난다.
간단하긴 한걸까...
java.util.Calendar cal = java.util.Calendar.getInstance(); //일단 Calendar 객체
int year = 2011; //수능일 기준으로 잡아봤다.
int month = 11;
int date = 10;
long now_day = cal.getTimeInMillis(); //현재 시간
cal.set(year, month-1, date); //목표일을 cal에 set
long event_day = cal.getTimeInMillis(); //목표일에 대한 시간
long d_day = (event_day - now_day) / (60*60*24*1000);
일단 목표일이 현재 일보다는 커야한다는 조건이 붙어야 한다. 이걸 parameter 세개 받아서 며칠남았는지 딱딱 return 시켜주는 공통 method로 만들어 봐야하는데 지금 귀찮음.
String str = "sukeun.park, ,010-0000-0000";
String [] values = "str.split(",")";
for( int x = 0; x < values.length; x++ ){
System.out.println( "문자(열) " + (x+1) + " : " + values[x] );
}
print>
문자(열) 1 : sukeun.park
문자(열) 2 :
문자(열) 3 : 010-0000-0000
2. StringTokenizer
String str = "sukeun.park, ,010-0000-0000";
StringTokenizer tokens = new StringTokenizer(str, ",");
for(int x = 1; tokens.hasMoreElements(); x++;){
System.out.println( "문자(열) " + x + " : " + tokens.nextToken());
}
print>
문자(열) 1 : sukeun.park
문자(열) 2 : 010-0000-0000
차이점 알겠나?
사용자의 로그정보가 필요해 아래와같이 간단하게 만들어봤다.
public void makeFreePaidLog(HttpServletRequest request, String academyCode, String date) throws IOException, Exception{
StringBuffer sb = new StringBuffer();
SimpleDateFormat formatter = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS");
Calendar cal = Calendar.getInstance();
String today = formatter.format(cal.getTime());
String str = "[" +today + "]";
sb.append("Browser Info = " + request.getHeader("User-AGENT") + " MIS_ID = " + SessionUtil.getMis_Id(request));
sb.append(", USER_NM = " + SessionUtil.getUserNm(request) + ", ACAD_CD = " + academyCode);
String log = str + sb.toString();
try{
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\FreePaidLog_"+date+".txt", true));
bw.write(log);
bw.newLine();
bw.flush();
bw.close();
}catch(IOException ie){
System.err.println("Error");
System.exit(1);
}
}
위에서 중요한 부분은 new FileWriter()하는부분. 뭐가 문제인지는 모르겠으나 FileWriter 객체 생성할때 다른 class나 새로운 객체를 생성하여 매개인자로 넣으면 에러가난다.
1. Ranking Family
(1) RANK() - 상위 순으로 등수를 부여하는 경우 정렬 결과를 기준으로 전체 순위를 출력
☞사용법
RANK() OVER(
[PRTITION BY < value expression1>] [,...]
ODER BY<value expression2> [collate clause] [ASC:DESC]
[NULLS FIRST:NULLS LAST])
OVER : 순위를 부여하기 위한 대상 집합의 정렬 기준과 분할 기준 정의
PARTITION BY : value expression1을 기준으로 분할, 생랼하면 전체 집합을 대상으로 순위부여
ODER BY : 각 분할내에서 데이터를 정렬하는 기준 칼럼 지정
NULLS FIRST|NULLS LAST : 정렬 결과에서 NULL값의 위치 지정
(2) DENSE_RANK() - RNAK함수의 변형 동일 순위를 무시한 연속 순위를 출력
RNAK함수는 1등이 2건인 경우 다음순위를 3등으로 부여 하지만, DENSE_RANK 함수는 다음순위를 2등으로 부여한다.
●
질의
SELECT id,score,
rank()over(ORDER BY score ASC)as rank,
dense_rank() over(order by score asc)as dense_rank
From ksdb_score;
결과
ID SCORE RANK DENSE_RANK
--------- ---------- ---------- ----------
200040394 83 1 1
200020182 88 2 2
200231047 89 3 3
200020182 90 4 4
200020183 90 4 4
200020183 92 6 5
200172058 93 7 6
200040394 95 8 7
(3) CUME_DIST()- 그룹 값 내에서 어떤 값의 cumulative distribution(누적분포)을 계산
☞사용법
CUME_DIST(expr)
(4) PERCENT_RANK()-
(5) NTILE() - 출력결과를 사용자가 지정한 그룹 수로 나누어 출력
☞사용법
NITLE(expr) OVER(
[PARTITION BY< value expression1>][,...]
ORDER BY <value espression2> [collate clause] [ASC : DESC]
[NULLS FIRST:NULLS LAST])
●
질의
SELECT Cid,bday,
NTILE(3) OVER(ORDER BY bday) class
FROM ksdb_customer_info;
결과
CID BDAY CLASS
------ -------- ----------
100004 60/05/02 1
100010 72/08/02 1
100011 74/09/21 1
100006 75/04/05 1
100001 75/07/01 2
100002 77/02/01 2
100007 80/01/04 2
100003 80/01/25 2
100009 81/01/30 3
100005 82/06/01 3
100008 85/04/04 3
11 개의 행이 선택되었습니다.
(6) ROW_NUMBER() - 분할별로 정렬된 결과에 대해 순위를 부여하는 기능 분할은 전체 행을 특정 칼럼을 기준으로 분리하는 기능으로 GROUP BY 절에서 그룹화하는 방법과 같은 개념
☞사용법
ROW_NUMBER() OVER(
[PARTITION BY< value expression1>][,...]
ORDER BY <value espression2> [collate clause] [ASC : DESC]
[NULLS FIRST:NULLS LAST])
●
질의
SELECT id,score,
RANK()OVER(ORDER BY score ASC)as rank,
DENSE_RANK()OVER(order by score asc)as dense_rank,
ROW_NUMBER()OVER(order by score asc)as row_number
From ksdb_score;
결과
ID SCORE RANK DENSE_RANK ROW_NUMBER
--------- ---------- ---------- ---------- ----------
200040394 83 1 1 1
200020182 88 2 2 2
200231047 89 3 3 3
200020182 90 4 4 4
200020183 90 4 4 5
200020183 92 6 5 6
200172058 93 7 6 7
200040394 95 8 7 8
8 개의 행이 선택되었습니다.
↑RANK나 DENSE_RANK에서는 점수가 같으면 순위가 같게4,4 나오지만 ROW_NUMBER에서는 순서대로 4,5 로 번호를 부여한다.
2. Aggregate Family
(1) SUM(), AVG(), MAX(), MIN()
☞사용법
AVG([DISTINCT|ALL] expr)
SUM([DISTINCT|ALL] expr)
expr의 데이터 타입은 NUMBER 데이터 타입만 가능
(2) COUNT() - 테이블에서 조건을 만족하는 행의 개수를 반환
COUNT(*)는 NULL을 가진 행과 중복되는 행을 모두 포함하는 행의 수를 계산
COUNT(expression)는 NULL을 가진 행을 제외한 행의 수를 계산하여 반환
(3) STDDEV() VARIANCE() - 인수로 지정된 칼럼에 대해 조건을 만족하는 행을 대상으로 표준편차와 분산을 구하는 함수로 숫자 데이터 타입에만 사용할 수 있으며, NULL 은 계산에서 제외된다.
(4)RATIO_TO_REPORT()
3. Lead/Lag Family
LEAD() LAG() - 동일한 테이블에 있는 다른 행의 값을 참조하기 위한 함수
LAG 분석함수는 현재 행을 기준으로 이전값을 참조
LEAD 분석함수는 현재 행을 기준으로 이후값을 참조
LEAD LAG 분석함수에서 지정하는 인수는 현재행을 기준으로 몇 번째 행을 참조할 것인지를 지정
음수는 사용할 수 없다.
☞사용법
ROW_NUMBER() OVER(
[PARTITION BY< value expression1>][,...]
ORDER BY <value espression2> [collate clause] [ASC : DESC]
[NULLS FIRST:NULLS LAST])
●
질의
SELECT id,score,
LEAD(score, 1)OVER(ORDER BY score)as next_score,
LAG(score,1)OVER(order by score)as prev_score
From ksdb_score;
결과
ID SCORE NEXT_SCORE PREV_SCORE
--------- ---------- ---------- ----------
200040394 83 88
200020182 88 89 83 ←88 이후 점수 : 89
200231047 89 90 88 88 이전 점수 : 83
200020182 90 90 89
200020183 90 92 90
200020183 92 93 90
200172058 93 95 92
200040394 95 93
8 개의 행이 선택되었습니다.
Oracle PIVOT
In Excel, we can do this using TRANSPOSE, a bit of patience & know-how, and ctrl+shift+enter.
In Oracle, if we have aggregate data displaying months by row, the old way was to use a bunch of DECODEs (or similar)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 |
SELECT t. name
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'jan' ,1,0)) jan
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'feb' ,1,0)) feb
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'mar' ,1,0)) mar
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'apr' ,1,0)) apr
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'may' ,1,0)) may
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'jun' ,1,0)) jun
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'jul' ,1,0)) jul
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'aug' ,1,0)) aug
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'sep' ,1,0)) sep
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'oct' ,1,0)) oct
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'nov' ,1,0)) nov
, SUM (DECODE (TO_CHAR (e.start_date, 'mon' ), 'dec' ,1,0)) dec FROM events e, bookings b, resources r, resource_types t WHERE e.event_no = b.event_no AND r.code = b.resource_code AND r.type_code = t.code GROUP BY t. name ;
NAME JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC -------------------- --- --- --- --- --- --- --- --- --- --- --- --- Catering 0 0 2 1 0 0 0 0 0 0 0 0 Stationary 0 0 1 1 0 0 0 0 0 0 0 0 Video equipment 0 0 1 1 1 0 0 0 1 0 0 0 Audio equipment 0 0 0 0 0 0 0 0 0 1 0 0 Computer equipment 0 0 1 0 0 0 0 0 0 0 0 0 Locations 0 0 2 2 2 1 1 1 1 1 0 0
6 rows selected |
Oracle 11g introduced pivot queries.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 |
SELECT * FROM
( SELECT COUNT (*) c, t. name , TO_CHAR(start_date, 'mon' ) mth
FROM events e, bookings b, resources r, resource_types t
WHERE e.event_no = b.event_no
AND r.code = b.resource_code
AND r.type_code = t.code
GROUP BY t. name , to_char(start_date, 'mon' ) ) PIVOT
( SUM (c) -- Add up all my counts
FOR mth -- Transposing the months
IN ( 'jan' as jan
, 'feb' , 'mar' , 'apr' , 'may' , 'jun'
, 'jul' , 'aug' , 'sep' , 'oct' , 'nov' , 'dec' ) );
NAME JAN 'feb' 'mar' 'apr' 'may' 'jun' 'jul' 'aug' 'sep' 'oct' 'nov' 'dec' -------------------- --- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- Catering 2 1 Stationary 1 1 Video equipment 1 1 1 1 Audio equipment 1 Computer equipment 1 Locations 2 2 2 1 1 1 1 1
6 rows selected |
As with most esoteric SQL functions, there are quite a few good examples on the web that I'm not out to emulate - the prime purpose of this post was to help remind me what's going on.
That being said, Tim Hall and Arup Nanda have the most concise articles.
I particularly like Lucas Jellema's example linked by Arup using
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 |
select value from (
(
select
'a' v1,
'e' v2,
'i' v3,
'o' v4,
'u' v5
from dual
)
unpivot
(
value
for value_type in
(v1,v2,v3,v4,v5)
) ) / |
출처 : http://www.grassroots-oracle.com/2012/05/oracle-pivot.html
pivot and unpivot queries in 11g
Pivot queries involve transposing rows into columns (pivot) or columns into rows (unpivot) to generate results in crosstab format. Pivoting is a common technique, especially for reporting, and it has been possible to generate pivoted resultsets with SQL for many years and Oracle versions. However, the release of 11g includes explicit pivot-query support for the first time with the introduction of the new PIVOT and UNPIVOT keywords. These are extensions to the SELECT statement and we will explore the syntax and application of these new features in this article.
pivot
We will begin with the new PIVOT operation. Most developers will be familiar with pivoting data: it is where multiple rows are aggregated and transposed into columns, with each column representing a different range of aggregate data. An overview of the new syntax is as follows:
SELECT ... FROM ... PIVOT [XML] ( pivot_clause pivot_for_clause pivot_in_clause ) WHERE ...
In addition to the new PIVOT keyword, we can see three new pivot clauses, described below.
- pivot_clause: defines the columns to be aggregated (pivot is an aggregate operation);
- pivot_for_clause: defines the columns to be grouped and pivoted;
- pivot_in_clause: defines the filter for the column(s) in the pivot_for_clause (i.e. the range of values to limit the results to). The aggregations for each value in the pivot_in_clause will be transposed into a separate column (where appropriate).
The syntax and mechanics of pivot queries will become clearer with some examples.
a simple example
Our first example will be a simple demonstration of the PIVOT syntax. Using the EMP table, we will sum the salaries by department and job, but transpose the sum for each department onto its own column. Before we pivot the salaries, we will examine the base data, as follows.
SQL> SELECT job 2 , deptno 3 , SUM(sal) AS sum_sal 4 FROM emp 5 GROUP BY 6 job 7 , deptno 8 ORDER BY 9 job 10 , deptno;
JOB DEPTNO SUM_SAL --------- ---------- ---------- ANALYST 20 6600 CLERK 10 1430 CLERK 20 2090 CLERK 30 1045 MANAGER 10 2695 MANAGER 20 3272.5 MANAGER 30 3135 PRESIDENT 10 5500 SALESMAN 30 6160 9 rows selected.
We will now pivot this data using the new 11g syntax. For each job, we will display the salary totals in a separate column for each department, as follows.
SQL> WITH pivot_data AS ( 2 SELECT deptno, job, sal 3 FROM emp 4 ) 5 SELECT * 6 FROM pivot_data 7 PIVOT ( 8 SUM(sal) --<-- pivot_clause 9 FOR deptno --<-- pivot_for_clause 10 IN (10,20,30,40) --<-- pivot_in_clause 11 );
JOB 10 20 30 40 --------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
We can see that the department salary totals for each job have been transposed into columns. There are a few points to note about this example, the syntax and the results:
- Line 8: our pivot_clause sums the SAL column. We can specify multiple columns if required and optionally alias them (we will see examples of aliasing later in this article);
- Lines 1-4: pivot operations perform an implicit GROUP BY using any columns not in the pivot_clause (in our example, JOB and DEPTNO). For this reason, most pivot queries will be performed on a subset of columns, using stored views, inline views or subqueries, as in our example;
- Line 9: our pivot_for_clause states that we wish to pivot the DEPTNO aggregations only;
- Line 10: our pivot_in_clause specifies the range of values for DEPTNO. In this example we have hard-coded a list of four values which is why we generated four pivoted columns (one for each value of DEPTNO). In the absence of aliases, Oracle uses the values in the pivot_in_clause to generate the pivot column names (in our output we can see columns named "10", "20", "30" and "40").
It was stated above that most pivot queries will be performed on a specific subset of columns. Like all aggregate queries, the presence of additional columns affects the groupings. We can see this quite simply with a pivot query over additional EMP columns as follows.
SQL> SELECT * 2 FROM emp 3 PIVOT (SUM(sal) 4 FOR deptno IN (10,20,30,40));
EMPNO ENAME JOB MGR HIREDATE COMM 10 20 30 40 ---------- ---------- --------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- 7654 MARTIN SALESMAN 7698 28/09/1981 1400 1375 7698 BLAKE MANAGER 7839 01/05/1981 3135 7934 MILLER CLERK 7782 23/01/1982 1430 7521 WARD SALESMAN 7698 22/02/1981 500 1375 7566 JONES MANAGER 7839 02/04/1981 3272.5 7844 TURNER SALESMAN 7698 08/09/1981 0 1650 7900 JAMES CLERK 7698 03/12/1981 1045 7839 KING PRESIDENT 17/11/1981 5500 7876 ADAMS CLERK 7788 23/05/1987 1210 7902 FORD ANALYST 7566 03/12/1981 3300 7788 SCOTT ANALYST 7566 19/04/1987 3300 7782 CLARK MANAGER 7839 09/06/1981 2695 7369 SMITH CLERK 7902 17/12/1980 880 7499 ALLEN SALESMAN 7698 20/02/1981 300 1760 14 rows selected.
In this case, all the EMP columns apart from SAL have become the grouping set, with DEPTNO being the pivot column. The pivot is effectively useless in this case.
An interesting point about the pivot syntax is its placement in the query; namely, between the FROM and WHERE clauses. In the following example, we restrict our original pivot query to a selection of job titles by adding a predicate.
SQL> WITH pivot_data AS ( 2 SELECT deptno, job, sal 3 FROM emp 4 ) 5 SELECT * 6 FROM pivot_data 7 PIVOT ( 8 SUM(sal) --<-- pivot_clause 9 FOR deptno --<-- pivot_for_clause 10 IN (10,20,30,40) --<-- pivot_in_clause 11 ) 12 WHERE job IN ('ANALYST','CLERK','SALESMAN');
JOB 10 20 30 40 ---------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 ANALYST 6600 3 rows selected.
This appears to be counter-intuitive, but adding the predicates before the pivot clause raises a syntax error. As an aside, in our first example we used subquery factoring (the WITH clause) to define the base column set. We can alternatively use an inline-view (as follows) or a stored view (we will do this later).
SQL> SELECT * 2 FROM ( 3 SELECT deptno, job, sal 4 FROM emp 5 ) 6 PIVOT (SUM(sal) 7 FOR deptno IN (10,20,30,40));
JOB 10 20 30 40 --------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
aliasing pivot columns
In our preceding examples, Oracle used the values of DEPTNO to generate pivot column names. Alternatively, we can alias one or more of the columns in the pivot_clause and one or more of the values in the pivot_in_clause. In general, Oracle will name the pivot columns according to the following conventions:
Pivot Column Aliased? | Pivot In-Value Aliased? | Pivot Column Name |
N | N | pivot_in_clause value |
Y | Y | pivot_in_clause alias || '_' || pivot_clause alias |
N | Y | pivot_in_clause alias |
Y | N | pivot_in_clause value || '_' || pivot_clause alias |
We will see examples of each of these aliasing options below (we have already seen examples without any aliases). However, to simplify our examples, we will begin by defining the input dataset as a view, as follows.
SQL> CREATE VIEW pivot_data 2 AS 3 SELECT deptno, job, sal 4 FROM emp;
View created.
For our first example, we will alias all elements of our pivot query.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) AS salaries 4 FOR deptno IN (10 AS d10_sal, 5 20 AS d20_sal, 6 30 AS d30_sal, 7 40 AS d40_sal));
JOB D10_SAL_SALARIES D20_SAL_SALARIES D30_SAL_SALARIES D40_SAL_SALARIES ---------- ---------------- ---------------- ---------------- ---------------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
Oracle concatenates our aliases together to generate the column names. In the following example, we will alias the pivot_clause (aggregated column) but not the values in the pivot_in_clause.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) AS salaries 4 FOR deptno IN (10, 20, 30, 40));
JOB 10_SALARIES 20_SALARIES 30_SALARIES 40_SALARIES --------- ----------- ----------- ----------- ----------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
Oracle generates the pivot column names by concatenating the pivot_in_clause values and the aggregate column alias. Finally, we will only alias the pivot_in_clause values, as follows.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) 4 FOR deptno IN (10 AS d10_sal, 5 20 AS d20_sal, 6 30 AS d30_sal, 7 40 AS d40_sal));
JOB D10_SAL D20_SAL D30_SAL D40_SAL ---------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
This time, Oracle generated column names from the aliases only. In fact, we can see from all of our examples that the pivot_in_clause is used in all pivot-column naming, regardless of whether we supply an alias or value. We can therefore be selective about which values we alias, as the following example demonstrates.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) 4 FOR deptno IN (10 AS d10_sal, 5 20, 6 30 AS d30_sal, 7 40));
JOB D10_SAL 20 D30_SAL 40 --------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
pivoting multiple columns
Our examples so far have contained a single aggregate and a single pivot column, although we can define more if we wish. In the following example we will define two aggregations in our pivot_clause for the same range of DEPTNO values that we have used so far. The new aggregate is a count of the salaries that comprise the sum.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) AS sum 4 , COUNT(sal) AS cnt 5 FOR deptno IN (10 AS d10_sal, 6 20 AS d20_sal, 7 30 AS d30_sal, 8 40 AS d40_sal));
JOB D10_SAL_SUM D10_SAL_CNT D20_SAL_SUM D20_SAL_CNT D30_SAL_SUM D30_SAL_CNT D40_SAL_SUM D40_SAL_CNT ---------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- ----------- CLERK 1430 1 2090 2 1045 1 0 SALESMAN 0 0 6160 4 0 PRESIDENT 5500 1 0 0 0 MANAGER 2695 1 3272.5 1 3135 1 0 ANALYST 0 6600 2 0 0 5 rows selected.
We have doubled the number of pivot columns (because we doubled the number of aggregates). The number of pivot columns is a product of the number of aggregates and the distinct number of values in the pivot_in_clause. In the following example, we will extend the pivot_for_clause and pivot_in_clause to include values for JOB in the filter.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) AS sum 4 , COUNT(sal) AS cnt 5 FOR (deptno,job) IN ((30, 'SALESMAN') AS d30_sls, 6 (30, 'MANAGER') AS d30_mgr, 7 (30, 'CLERK') AS d30_clk));
D30_SLS_SUM D30_SLS_CNT D30_MGR_SUM D30_MGR_CNT D30_CLK_SUM D30_CLK_CNT ----------- ----------- ----------- ----------- ----------- ----------- 6160 4 3135 1 1045 1 1 row selected.
We have limited the query to just 3 jobs within department 30. Note how the pivot_for_clause columns (DEPTNO and JOB) combine to make a single pivot dimension. The aliases we use apply to the combined value domain (for example, "D30_SLS" to represent SALES in department 30).
Finally, because we know the pivot column-naming rules, we can reference them directly, as follows.
SQL> SELECT d30_mgr_sum 2 , d30_clk_cnt 3 FROM pivot_data 4 PIVOT (SUM(sal) AS sum 5 , COUNT(sal) AS cnt 6 FOR (deptno,job) IN ((30, 'SALESMAN') AS d30_sls, 7 (30, 'MANAGER') AS d30_mgr, 8 (30, 'CLERK') AS d30_clk));
D30_MGR_SUM D30_CLK_CNT ----------- ----------- 3135 1 1 row selected.
general restrictions
There are a few simple "gotchas" to be aware of with pivot queries. For example, we cannot project the column(s) used in the pivot_for_clause (DEPTNO in most of our examples). This is to be expected. The column(s) in the pivot_for_clause are grouped according to the range of values we supply with the pivot_in_clause. In the following example, we will attempt to project the DEPTNO column.
SQL> SELECT deptno 2 FROM emp 3 PIVOT (SUM(sal) 4 FOR deptno IN (10,20,30,40));
SELECT deptno * ERROR at line 1: ORA-00904: "DEPTNO": invalid identifier
Oracle raises an ORA-00904 exception. In this case the DEPTNO column is completely removed from the projection and Oracle tells us that it doesn't exist in this scope. Similarly, we cannot include any column(s) used in the pivot_clause, as the following example demonstrates.
SQL> SELECT sal 2 FROM emp 3 PIVOT (SUM(sal) 4 FOR deptno IN (10,20,30,40));
SELECT sal * ERROR at line 1: ORA-00904: "SAL": invalid identifier
We attempted to project the SAL column but Oracle raised the same exception. This is also to be expected: the pivot_clause defines our aggregations. This also means, of course, that we must use aggregate functions in the pivot_clause. In the following example, we will attempt to define a pivot_clause with a single-group column.
SQL> SELECT * 2 FROM emp 3 PIVOT (sal 4 FOR deptno IN (10,20,30,40));
PIVOT (sal AS salaries * ERROR at line 3: ORA-56902: expect aggregate function inside pivot operation
Oracle raises a new ORA-56902 exception: the error message numbers are getting much higher with every release!
execution plans for pivot operations
As we have stated, pivot operations imply a GROUP BY, but we don't need to specify it. We can investigate this by explaining one of our pivot query examples, as follows. We will use Autotrace for convenience (Autotrace uses EXPLAIN PLAN and DBMS_XPLAN to display theoretical execution plans).
SQL> set autotrace traceonly explain SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) 4 FOR deptno IN (10 AS d10_sal, 5 20 AS d20_sal, 6 30 AS d30_sal, 7 40 AS d40_sal));
Execution Plan ---------------------------------------------------------- Plan hash value: 1475541029 ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 75 | 4 (25)| 00:00:01 | | 1 | HASH GROUP BY PIVOT| | 5 | 75 | 4 (25)| 00:00:01 | | 2 | TABLE ACCESS FULL | EMP | 14 | 210 | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------
The plan output tells us that this query uses a HASH GROUP BY PIVOT operation. The HASH GROUP BY is a feature of 10g Release 2, but the PIVOT extension is new to 11g. Pivot queries do not automatically generate a PIVOT plan, however. In the following example, we will limit the domain of values in our pivot_in_clause and use Autotrace to explain the query again.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT (SUM(sal) AS sum 4 , COUNT(sal) AS cnt 5 FOR (deptno,job) IN ((30, 'SALESMAN') AS d30_sls, 6 (30, 'MANAGER') AS d30_mgr, 7 (30, 'CLERK') AS d30_clk));
Execution Plan ---------------------------------------------------------- Plan hash value: 1190005124 ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 78 | 3 (0)| 00:00:01 | | 1 | VIEW | | 1 | 78 | 3 (0)| 00:00:01 | | 2 | SORT AGGREGATE | | 1 | 15 | | | | 3 | TABLE ACCESS FULL| EMP | 14 | 210 | 3 (0)| 00:00:01 | ----------------------------------------------------------------------------
This time the CBO has costed a simple aggregation over a group by with pivot. It has correctly identified that only one record will be returned from this query, so the GROUP BY operation is unnecessary. Finally, we will explain our first pivot example but use the extended formatting options of DBMS_XPLAN to reveal more information about the work that Oracle is doing.
SQL> EXPLAIN PLAN SET STATEMENT_ID = 'PIVOT' 2 FOR 3 SELECT * 4 FROM pivot_data 5 PIVOT (SUM(sal) 6 FOR deptno IN (10 AS d10_sal, 7 20 AS d20_sal, 8 30 AS d30_sal, 9 40 AS d40_sal));
Explained.
SQL> SELECT * 2 FROM TABLE( 3 DBMS_XPLAN.DISPLAY( 4 NULL, 'PIVOT', 'TYPICAL +PROJECTION'));
PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------- Plan hash value: 1475541029 ---------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 75 | 4 (25)| 00:00:01 | | 1 | HASH GROUP BY PIVOT| | 5 | 75 | 4 (25)| 00:00:01 | | 2 | TABLE ACCESS FULL | EMP | 14 | 210 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------- Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=1) "JOB"[VARCHAR2,9], SUM(CASE WHEN ("DEPTNO"=10) THEN "SAL" END )[22], SUM(CASE WHEN ("DEPTNO"=20) THEN "SAL" END )[22], SUM(CASE WHEN ("DEPTNO"=30) THEN "SAL" END )[22], SUM(CASE WHEN ("DEPTNO"=40) THEN "SAL" END )[22] 2 - "JOB"[VARCHAR2,9], "SAL"[NUMBER,22], "DEPTNO"[NUMBER,22] 18 rows selected.
DBMS_XPLAN optionally exposes the column projection information contained in PLAN_TABLE for each step of a query. The projection for ID=2 shows the base columns that we select in the PIVOT_DATA view over EMP. The interesting information, however, is for ID=1 (this step is our pivot operation). This clearly shows how Oracle is generating the pivot columns. Many developers will be familiar with this form of SQL: it is how we write pivot queries in versions prior to 11g. Oracle has chosen a CASE expression, but we commonly use DECODE for brevity, as follows.
SQL> SELECT job 2 , SUM(DECODE(deptno,10,sal)) AS "D10_SAL" 3 , SUM(DECODE(deptno,20,sal)) AS "D20_SAL" 4 , SUM(DECODE(deptno,30,sal)) AS "D30_SAL" 5 , SUM(DECODE(deptno,40,sal)) AS "D40_SAL" 6 FROM emp 7 GROUP BY 8 job;
JOB D10_SAL D20_SAL D30_SAL D40_SAL --------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
pivot performance
From the evidence we have seen, it appears as though Oracle implements the new PIVOT syntax using a recognised SQL format. It follows that we should expect the same performance for our pivot queries regardless of the technique we use (in other words the 11g PIVOT syntax will perform the same as the SUM(DECODE...) pivot technique. We will test this proposition with a larger dataset using Autotrace (for general I/O patterns) and the wall-clock (for elapsed time). First we will create a table with one million rows, as follows.
SQL> CREATE TABLE million_rows 2 NOLOGGING 3 AS 4 SELECT MOD(TRUNC(DBMS_RANDOM.VALUE(1,10000)),4) AS pivoting_col 5 , MOD(ROWNUM,4)+10 AS grouping_col 6 , DBMS_RANDOM.VALUE AS summing_col 7 , RPAD('X',70,'X') AS padding_col 8 FROM dual 9 CONNECT BY ROWNUM <= 1000000;
Table created.
We will now compare the two pivot query techniques (after full-scanning the MILLION_ROWS table a couple of times). We will begin with the new 11g syntax, as follows.
SQL> set timing on SQL> set autotrace on SQL> WITH pivot_data AS ( 2 SELECT pivoting_col 3 , grouping_col 4 , summing_col 5 FROM million_rows 6 ) 7 SELECT * 8 FROM pivot_data 9 PIVOT (SUM(summing_col) AS sum 10 FOR pivoting_col IN (0,1,2,3)) 11 ORDER BY 12 grouping_col;
GROUPING_COL 0_SUM 1_SUM 2_SUM 3_SUM ------------ ---------- ---------- ---------- ---------- 10 31427.0128 31039.5026 31082.0382 31459.7873 11 31385.2582 31253.2246 31030.7518 31402.1794 12 31353.1321 31220.078 31174.0103 31140.5322 13 31171.1977 30979.714 31486.7785 31395.6907 4 rows selected. Elapsed: 00:00:04.50 Execution Plan ---------------------------------------------------------- Plan hash value: 1201564532 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1155K| 42M| 3978 (2)| 00:00:48 | | 1 | SORT GROUP BY PIVOT| | 1155K| 42M| 3978 (2)| 00:00:48 | | 2 | TABLE ACCESS FULL | MILLION_ROWS | 1155K| 42M| 3930 (1)| 00:00:48 | ------------------------------------------------------------------------------------ Note ----- - dynamic sampling used for this statement Statistics ---------------------------------------------------------- 170 recursive calls 0 db block gets 14393 consistent gets 14286 physical reads 0 redo size 1049 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 6 sorts (memory) 0 sorts (disk) 4 rows processed
The most important outputs are highlighted. We can see that the query completed in 4.5 seconds and generated approximately 14,000 PIOs and LIOs. Interestingly, the CBO chose a SORT GROUP BY over a HASH GROUP BY for this volume, having estimated almost 1.2 million records.
By way of comparison, we will run the pre-11g version of pivot, as follows.
SQL> SELECT grouping_col 2 , SUM(DECODE(pivoting_col,0,summing_col)) AS "0_SUM" 3 , SUM(DECODE(pivoting_col,1,summing_col)) AS "1_SUM" 4 , SUM(DECODE(pivoting_col,2,summing_col)) AS "2_SUM" 5 , SUM(DECODE(pivoting_col,3,summing_col)) AS "3_SUM" 6 FROM million_rows 7 GROUP BY 8 grouping_col 9 ORDER BY 10 grouping_col;
GROUPING_COL 0_SUM 1_SUM 2_SUM 3_SUM ------------ ---------- ---------- ---------- ---------- 10 31427.0128 31039.5026 31082.0382 31459.7873 11 31385.2582 31253.2246 31030.7518 31402.1794 12 31353.1321 31220.078 31174.0103 31140.5322 13 31171.1977 30979.714 31486.7785 31395.6907 4 rows selected. Elapsed: 00:00:04.37 Execution Plan ---------------------------------------------------------- Plan hash value: 2855194314 ----------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1155K| 42M| 3978 (2)| 00:00:48 | | 1 | SORT GROUP BY | | 1155K| 42M| 3978 (2)| 00:00:48 | | 2 | TABLE ACCESS FULL| MILLION_ROWS | 1155K| 42M| 3930 (1)| 00:00:48 | ----------------------------------------------------------------------------------- Note ----- - dynamic sampling used for this statement Statistics ---------------------------------------------------------- 4 recursive calls 0 db block gets 14374 consistent gets 14286 physical reads 0 redo size 1049 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 4 rows processed
With a couple of minor exceptions, the time and resource results for this query are the same as for the new PIVOT syntax. This is as we expected given the internal query re-write we saw earlier. In fact, the new PIVOT version of this query generated more recursive SQL and more in-memory sorts, but we can conclude from this simple test that there is no performance penalty with the new technique. We will test this conclusion with a higher number of pivot columns, as follows.
SQL> set timing on SQL> set autotrace traceonly statistics SQL> WITH pivot_data AS ( 2 SELECT pivoting_col 3 , grouping_col 4 , summing_col 5 FROM million_rows 6 ) 7 SELECT * 8 FROM pivot_data 9 PIVOT (SUM(summing_col) AS sum 10 , COUNT(summing_col) AS cnt 11 , AVG(summing_col) AS av 12 , MIN(summing_col) AS mn 13 , MAX(summing_col) AS mx 14 FOR pivoting_col IN (0,1,2,3)) 15 ORDER BY 16 grouping_col;
4 rows selected. Elapsed: 00:00:04.29 Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 14290 consistent gets 14286 physical reads 0 redo size 2991 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 4 rows processed
We have generated 20 pivot columns with this example. Note that the above output is from a third or fourth run of the example to avoid skew in the results. Ultimately, the I/O patterns and elapsed time are the same as our original example, despite pivoting an additional 16 columns. We will compare this with the SUM(DECODE...) technique, as follows.
SQL> SELECT grouping_col 2 , SUM(DECODE(pivoting_col,0,summing_col)) AS "0_SUM" 3 , COUNT(DECODE(pivoting_col,0,summing_col)) AS "0_CNT" 4 , AVG(DECODE(pivoting_col,0,summing_col)) AS "0_AV" 5 , MIN(DECODE(pivoting_col,0,summing_col)) AS "0_MN" 6 , MAX(DECODE(pivoting_col,0,summing_col)) AS "0_MX" 7 -- 8 , SUM(DECODE(pivoting_col,1,summing_col)) AS "1_SUM" 9 , COUNT(DECODE(pivoting_col,1,summing_col)) AS "1_CNT" 10 , AVG(DECODE(pivoting_col,1,summing_col)) AS "1_AV" 11 , MIN(DECODE(pivoting_col,1,summing_col)) AS "1_MN" 12 , MAX(DECODE(pivoting_col,1,summing_col)) AS "1_MX" 13 -- 14 , SUM(DECODE(pivoting_col,2,summing_col)) AS "2_SUM" 15 , COUNT(DECODE(pivoting_col,2,summing_col)) AS "2_CNT" 16 , AVG(DECODE(pivoting_col,2,summing_col)) AS "2_AV" 17 , MIN(DECODE(pivoting_col,2,summing_col)) AS "2_MN" 18 , MAX(DECODE(pivoting_col,2,summing_col)) AS "2_MX" 19 -- 20 , SUM(DECODE(pivoting_col,3,summing_col)) AS "3_SUM" 21 , COUNT(DECODE(pivoting_col,3,summing_col)) AS "3_CNT" 22 , AVG(DECODE(pivoting_col,3,summing_col)) AS "3_AV" 23 , MIN(DECODE(pivoting_col,3,summing_col)) AS "3_MN" 24 , MAX(DECODE(pivoting_col,3,summing_col)) AS "3_MX" 25 FROM million_rows 26 GROUP BY 27 grouping_col 28 ORDER BY 29 grouping_col;
4 rows selected. Elapsed: 00:00:05.12 Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 14290 consistent gets 14286 physical reads 0 redo size 2991 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 1 sorts (memory) 0 sorts (disk) 4 rows processed
We can begin to see how much more convenient the new PIVOT syntax is. Furthermore, despite the workloads of the two methods being the same, the manual pivot technique is 25% slower (observable over several runs of the same examples and also a version using CASE instead of DECODE).
pivoting an unknown domain of values
All of our examples so far have pivoted a known domain of values (in other words, we have used a hard-coded pivot_in_clause). The pivot syntax we have been using doesn't, by default, support a dynamic list of values in the pivot_in_clause. If we use a subquery instead of a list in the pivot_in_clause, as in the following example, Oracle raises a syntax error.
SQL> SELECT * 2 FROM emp 3 PIVOT (SUM(sal) AS salaries 4 FOR deptno IN (SELECT deptno FROM dept));
FOR deptno IN (SELECT deptno FROM dept)) * ERROR at line 4: ORA-00936: missing expression
Many developers will consider this to be a major restriction (despite the fact that pre-11g pivot techniques also require us to code an explicit set of values). However, it is possible to generate an unknown set of pivot values. Remember from the earlier syntax overview that PIVOT allows an optional "XML" keyword. As the keyword suggests, this enables us to generate a pivot set but have the results provided in XML format. An extension of this is that we can have an XML resultset generated for any number of pivot columns, as defined by a dynamic pivot_in_clause.
When using the XML extension, we have three options for generating the pivot_in_clause:
- we can use an explicit list of values (we've been doing this so far in this article);
- we can use the ANY keyword in the pivot_in_clause. This specifies that we wish to pivot for all values for the columns in the pivot_for_clause; or
- we can use a subquery in the pivot_in_clause to derive the list of values.
We will concentrate on the dynamic methods. In the following example, we will use the ANY keyword to generate a pivoted resultset for any values of DEPTNO that we encounter in our dataset.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT XML 4 (SUM(sal) FOR deptno IN (ANY));
JOB DEPTNO_XML --------- --------------------------------------------------------------------------- ANALYST <PivotSet><item><column name = "DEPTNO">20</column><column name = "SUM(SAL) ">6600</column></item></PivotSet> CLERK <PivotSet><item><column name = "DEPTNO">10</column><column name = "SUM(SAL) ">1430</column></item><item><column name = "DEPTNO">20</column><column name = "SUM(SAL)">2090</column></item><item><column name = "DEPTNO">30</column> <column name = "SUM(SAL)">1045</column></item></PivotSet> MANAGER <PivotSet><item><column name = "DEPTNO">10</column><column name = "SUM(SAL) ">2695</column></item><item><column name = "DEPTNO">20</column><column name = "SUM(SAL)">3272.5</column></item><item><column name = "DEPTNO">30</colum n><column name = "SUM(SAL)">3135</column></item></PivotSet> PRESIDENT <PivotSet><item><column name = "DEPTNO">10</column><column name = "SUM(SAL) ">5500</column></item></PivotSet> SALESMAN <PivotSet><item><column name = "DEPTNO">30</column><column name = "SUM(SAL) ">6160</column></item></PivotSet> 5 rows selected.
The XML resultset is of type XMLTYPE, which means that we can easily manipulate it with XPath or XQuery expressions. We can see that the generated pivot columns are named according to the pivot_clause and not the pivot_in_clause (remember that in the non-XML queries the pivot_in_clause values or aliases featured in all permutations of pivot column-naming). We can also see that the XML column name itself is a product of the pivot_for_clause: Oracle has appended "_XML" to "DEPTNO".
We will repeat the previous query but add an alias to the pivot_clause, as follows. If we wish to change the column name from "DEPTNO_XML", we use standard SQL column aliasing.
SQL> SELECT job 2 , deptno_xml AS alias_for_deptno_xml 3 FROM pivot_data 4 PIVOT XML 5 (SUM(sal) AS salaries FOR deptno IN (ANY));
JOB ALIAS_FOR_DEPTNO_XML ---------- --------------------------------------------------------------------------- ANALYST <PivotSet><item><column name = "DEPTNO">20</column><column name = "SALARIES ">6600</column></item></PivotSet> CLERK <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES ">1430</column></item><item><column name = "DEPTNO">20</column><column name = "SALARIES">2090</column></item><item><column name = "DEPTNO">30</column> <column name = "SALARIES">1045</column></item></PivotSet> MANAGER <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES ">2695</column></item><item><column name = "DEPTNO">20</column><column name = "SALARIES">3272.5</column></item><item><column name = "DEPTNO">30</colum n><column name = "SALARIES">3135</column></item></PivotSet> PRESIDENT <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES ">5500</column></item></PivotSet> SALESMAN <PivotSet><item><column name = "DEPTNO">30</column><column name = "SALARIES ">6160</column></item></PivotSet> 5 rows selected.
As suggested, the pivot_clause alias defines the pivoted XML element names and the XML column name itself is defined by the projected alias.
An alternative to the ANY keyword is a subquery. In the following example, we will replace ANY with a query against the DEPT table to derive our list of DEPTNO values.
SQL> SELECT * 2 FROM pivot_data 3 PIVOT XML 4 (SUM(sal) AS salaries FOR deptno IN (SELECT deptno FROM dept));
JOB DEPTNO_XML ---------- --------------------------------------------------------------------------- ANALYST <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES "></column></item><item><column name = "DEPTNO">20</column><column name = " SALARIES">6600</column></item><item><column name = "DEPTNO">30</column><col umn name = "SALARIES"></column></item><item><column name = "DEPTNO">40</col umn><column name = "SALARIES"></column></item></PivotSet> CLERK <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES ">1430</column></item><item><column name = "DEPTNO">20</column><column name = "SALARIES">2090</column></item><item><column name = "DEPTNO">30</column> <column name = "SALARIES">1045</column></item><item><column name = "DEPTNO" >40</column><column name = "SALARIES"></column></item></PivotSet> MANAGER <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES ">2695</column></item><item><column name = "DEPTNO">20</column><column name = "SALARIES">3272.5</column></item><item><column name = "DEPTNO">30</colum n><column name = "SALARIES">3135</column></item><item><column name = "DEPTN O">40</column><column name = "SALARIES"></column></item></PivotSet> PRESIDENT <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES ">5500</column></item><item><column name = "DEPTNO">20</column><column name = "SALARIES"></column></item><item><column name = "DEPTNO">30</column><col umn name = "SALARIES"></column></item><item><column name = "DEPTNO">40</col umn><column name = "SALARIES"></column></item></PivotSet> SALESMAN <PivotSet><item><column name = "DEPTNO">10</column><column name = "SALARIES "></column></item><item><column name = "DEPTNO">20</column><column name = " SALARIES"></column></item><item><column name = "DEPTNO">30</column><column name = "SALARIES">6160</column></item><item><column name = "DEPTNO">40</col umn><column name = "SALARIES"></column></item></PivotSet> 5 rows selected.
We can see a key difference between this XML output and the resultset from the ANY method. When using the subquery method, Oracle will generate a pivot XML element for every value the subquery returns (one for each grouping). For example, ANALYST employees only work in DEPTNO 20, so the ANY method returns one pivot XML element for that department. The subquery method, however, generates four pivot XML elements (for DEPTNO 10,20,30,40) but only DEPTNO 20 is non-null. We can see this more clearly if we extract the salaries element from both pivot_in_clause methods, as follows.
SQL> SELECT job 2 , EXTRACT(deptno_xml, '/PivotSet/item/column') AS salary_elements 3 FROM pivot_data 4 PIVOT XML 5 (SUM(sal) AS salaries FOR deptno IN (ANY)) 6 WHERE job = 'ANALYST';
JOB SALARY_ELEMENTS --------- --------------------------------------------------------------------------- ANALYST <column name="DEPTNO">20</column><column name="SALARIES">6600</column> 1 row selected.
Using the ANY method, Oracle has generated an XML element for the only DEPTNO (20). We will repeat the query but use the subquery method, as follows.
SQL> SELECT job 2 , EXTRACT(deptno_xml, '/PivotSet/item/column') AS salary_elements 3 FROM pivot_data 4 PIVOT XML 5 (SUM(sal) AS salaries FOR deptno IN (SELECT deptno FROM dept)) 6 WHERE job = 'ANALYST';
JOB SALARY_ELEMENTS --------- --------------------------------------------------------------------------- ANALYST <column name="DEPTNO">10</column><column name="SALARIES"/><column name="DEP TNO">20</column><column name="SALARIES">6600</column><column name="DEPTNO"> 30</column><column name="SALARIES"/><column name="DEPTNO">40</column><colum n name="SALARIES"/> 1 row selected.
Despite the fact that three departments do not have salary totals, Oracle has generated an empty element for each one. Again, only department 20 has a value for salary total. Whichever method developers choose, therefore, depends on requirements, but it is important to recognise that working with XML often leads to inflated dataset or resultset volumes. In this respect, the subquery method can potentially generate a lot of additional data over and above the results themselves.
unpivot
We have explored the new 11g pivot capability in some detail above. We will now look at the new UNPIVOT operator. As its name suggests, an unpivot operation is the opposite of pivot (albeit without the ability to disaggregate the data). A simpler way of thinking about unpivot is that it turns pivoted columns into rows (one row of data for every column to be unpivoted). We will see examples of this below, but will start with an overview of the syntax, as follows.
SELECT ... FROM ... UNPIVOT [INCLUDE|EXCLUDE NULLS] ( unpivot_clause unpivot_for_clause unpivot_in_clause ) WHERE ...
The syntax is similar to that of PIVOT with some slight differences, including the meaning of the various clauses. These are described as follows:
- unpivot_clause: this clause specifies a name for a column to represent the unpivoted measure values. In our previous pivot examples, the measure column was the sum of salaries for each job and department grouping;
- unpivot_for_clause: the unpivot_for_clause specifies the name for the column that will result from our unpivot query. The data in this column describes the measure values in the unpivot_clause column; and
- unpivot_in_clause: this contains the list of pivoted columns (not values) to be unpivoted.
The unpivot clauses are quite difficult to describe and are best served by some examples.
simple unpivot examples
Before we write an unpivot query, we will create a pivoted dataset to use in our examples. For simplicity, we will create a view using one of our previous pivot queries, as follows.
SQL> CREATE VIEW pivoted_data 2 AS 3 SELECT * 4 FROM pivot_data 5 PIVOT (SUM(sal) 6 FOR deptno IN (10 AS d10_sal, 7 20 AS d20_sal, 8 30 AS d30_sal, 9 40 AS d40_sal));
View created.
The PIVOTED_DATA view contains our standard sum of department salaries by job, with the four department totals pivoted as we've seen throughout this article. As a final reminder of the nature of the data, we will query this view.
SQL> SELECT * 2 FROM pivoted_data;
JOB D10_SAL D20_SAL D30_SAL D40_SAL ---------- ---------- ---------- ---------- ---------- CLERK 1430 2090 1045 SALESMAN 6160 PRESIDENT 5500 MANAGER 2695 3272.5 3135 ANALYST 6600 5 rows selected.
We will now unpivot our dataset using the new 11g syntax as follows.
SQL> SELECT * 2 FROM pivoted_data 3 UNPIVOT ( 4 deptsal --<-- unpivot_clause 5 FOR saldesc --<-- unpivot_for_clause 6 IN (d10_sal, d20_sal, d30_sal, d40_sal) --<-- unpivot_in_clause 7 );
JOB SALDESC DEPTSAL ---------- ---------- ---------- CLERK D10_SAL 1430 CLERK D20_SAL 2090 CLERK D30_SAL 1045 SALESMAN D30_SAL 6160 PRESIDENT D10_SAL 5500 MANAGER D10_SAL 2695 MANAGER D20_SAL 3272.5 MANAGER D30_SAL 3135 ANALYST D20_SAL 6600 9 rows selected.
We can see from the results that Oracle has transposed each of our pivoted columns in the unpivot_in_clause and turned them into rows of data that describes our measure (i.e. 'D10_SAL', 'D20_SAL' and so on). The unpivot_for_clause gives this new unpivoted column a name (i.e "SALDESC"). The unpivot_clause itself defines our measure data, which in this case is the sum of the department's salary by job.
It is important to note that unpivot queries can work on any columns (i.e. not just aggregated or pivoted columns). We are using the pivoted dataset for consistency but we could just as easily unpivot the columns of any table or view we have.
handling null data
The maximum number of rows that can be returned by an unpivot query is the number of distinct groupings multiplied by the number of pivot columns (in our examples, 5 (jobs) * 4 (pivot columns) = 20). However, our first unpivot query has only returned nine rows. If we look at the source pivot data itself, we can see nine non-null values in the pivot columns; in other words, eleven groupings are null. The default behaviour of UNPIVOT is to exclude nulls, but we do have an option to include them, as follows.
SQL> SELECT * 2 FROM pivoted_data 3 UNPIVOT INCLUDE NULLS 4 (deptsal 5 FOR saldesc IN (d10_sal, 6 d20_sal, 7 d30_sal, 8 d40_sal));
JOB SALDESC DEPTSAL ---------- ---------- ---------- CLERK D10_SAL 1430 CLERK D20_SAL 2090 CLERK D30_SAL 1045 CLERK D40_SAL SALESMAN D10_SAL SALESMAN D20_SAL SALESMAN D30_SAL 6160 SALESMAN D40_SAL PRESIDENT D10_SAL 5500 PRESIDENT D20_SAL PRESIDENT D30_SAL PRESIDENT D40_SAL MANAGER D10_SAL 2695 MANAGER D20_SAL 3272.5 MANAGER D30_SAL 3135 MANAGER D40_SAL ANALYST D10_SAL ANALYST D20_SAL 6600 ANALYST D30_SAL ANALYST D40_SAL 20 rows selected.
By including the null pivot values, we return the maximum number of rows possible from our dataset. Of course, we now have eleven null values, but this might be something we require for reporting purposes or "data densification".
unpivot aliasing options
In the pivot section of this article, we saw a wide range of aliasing options. The UNPIVOT syntax also allows us to use aliases, but it is far more restrictive. In fact, we can only alias the columns defined in the unpivot_in_clause, as follows.
SQL> SELECT job 2 , saldesc 3 , deptsal 4 FROM pivoted_data 5 UNPIVOT (deptsal 6 FOR saldesc IN (d10_sal AS 'SAL TOTAL FOR 10', 7 d20_sal AS 'SAL TOTAL FOR 20', 8 d30_sal AS 'SAL TOTAL FOR 30', 9 d40_sal AS 'SAL TOTAL FOR 40')) 10 ORDER BY 11 job 12 , saldesc;
JOB SALDESC DEPTSAL ---------- -------------------- ---------- ANALYST SAL TOTAL FOR 20 6600 CLERK SAL TOTAL FOR 10 1430 CLERK SAL TOTAL FOR 20 2090 CLERK SAL TOTAL FOR 30 1045 MANAGER SAL TOTAL FOR 10 2695 MANAGER SAL TOTAL FOR 20 3272.5 MANAGER SAL TOTAL FOR 30 3135 PRESIDENT SAL TOTAL FOR 10 5500 SALESMAN SAL TOTAL FOR 30 6160 9 rows selected.
This is a useful option because it enables us to change the descriptive data to something other than its original column name. If we wish to alias the column in the unpivot_clause (in our case, DEPTSAL), we need to use standard column aliasing in the SELECT clause. Of course, aliasing the unpivot_for_clause is irrelevant because we have just defined this derived column name in the clause itself (in our case, "SALDESC").
general restrictions
The UNPIVOT syntax can be quite fiddly and there are some minor restrictions to how it can be used. The main restriction is that the columns in the unpivot_in_clause must all be of the same datatype. We will see this below by attempting to unpivot three columns of different datatypes from EMP. The unpivot query itself is meaningless: it is just a means to show the restriction, as follows.
SQL> SELECT empno 2 , job 3 , unpivot_col_name 4 , unpivot_col_value 5 FROM emp 6 UNPIVOT (unpivot_col_value 7 FOR unpivot_col_name 8 IN (ename, deptno, hiredate));
IN (ename, deptno, hiredate)) * ERROR at line 8: ORA-01790: expression must have same datatype as corresponding expression
Oracle is also quite fussy about datatype conversion. In the following example, we will attempt to convert the columns to the same VARCHAR2 datatype.
SQL> SELECT job 2 , unpivot_col_name 3 , unpivot_col_value 4 FROM emp 5 UNPIVOT (unpivot_col_value 6 FOR unpivot_col_name 7 IN (ename, TO_CHAR(deptno), TO_CHAR(hiredate)));
IN (ename, TO_CHAR(deptno), TO_CHAR(hiredate))) * ERROR at line 7: ORA-00917: missing comma
It appears that using datatype conversions within the unpivot_in_clause is not even valid syntax and Oracle raises an exception accordingly. The workaround is, therefore, to convert the columns up-front, using an in-line view, subquery or a stored view. We will use subquery factoring, as follows.
SQL> WITH emp_data AS ( 2 SELECT empno 3 , job 4 , ename 5 , TO_CHAR(deptno) AS deptno 6 , TO_CHAR(hiredate) AS hiredate 7 FROM emp 8 ) 9 SELECT empno 10 , job 11 , unpivot_col_name 12 , unpivot_col_value 13 FROM emp_data 14 UNPIVOT (unpivot_col_value 15 FOR unpivot_col_name 16 IN (ename, deptno, hiredate));
EMPNO JOB UNPIVOT_COL_NAME UNPIVOT_COL_VALUE ---------- ---------- -------------------- -------------------- 7369 CLERK ENAME SMITH 7369 CLERK DEPTNO 20 7369 CLERK HIREDATE 17/12/1980 7499 SALESMAN ENAME ALLEN 7499 SALESMAN DEPTNO 30 7499 SALESMAN HIREDATE 20/02/1981 <<...snip...>> 7902 ANALYST ENAME FORD 7902 ANALYST DEPTNO 20 7902 ANALYST HIREDATE 03/12/1981 7934 CLERK ENAME MILLER 7934 CLERK DEPTNO 10 7934 CLERK HIREDATE 23/01/1982 42 rows selected.
The output has been reduced, but we can see the effect of unpivoting on the EMP data (i.e. we have 3 unpivot columns, 14 original rows and hence 42 output records).
Another restriction with UNPIVOT is that the columns we include in the unpivot_in_clause are not available to us to project outside of the pivot_clause itself. In the following example, we will try to project the DEPTNO column.
SQL> WITH emp_data AS ( 2 SELECT empno 3 , job 4 , ename 5 , TO_CHAR(deptno) AS deptno 6 , TO_CHAR(hiredate) AS hiredate 7 FROM emp 8 ) 9 SELECT empno 10 , job 11 , deptno 12 , unpivot_col_name 13 , unpivot_col_value 14 FROM emp_data 15 UNPIVOT (unpivot_col_value 16 FOR unpivot_col_name 17 IN (ename, deptno, hiredate));
, deptno * ERROR at line 11: ORA-00904: "DEPTNO": invalid identifier
Oracle raises an invalid identifier exception. We can see why this is the case when we project all available columns from our unpivot query over EMP, as follows.
SQL> WITH emp_data AS ( 2 SELECT empno 3 , job 4 , ename 5 , TO_CHAR(deptno) AS deptno 6 , TO_CHAR(hiredate) AS hiredate 7 FROM emp 8 ) 9 SELECT * 10 FROM emp_data 11 UNPIVOT (unpivot_col_value 12 FOR unpivot_col_name 13 IN (ename, deptno, hiredate));
EMPNO JOB UNPIVOT_COL_NAME UNPIVOT_COL_VALUE ---------- ---------- -------------------- -------------------- 7369 CLERK ENAME SMITH 7369 CLERK DEPTNO 20 7369 CLERK HIREDATE 17/12/1980 <<...snip...>> 7934 CLERK ENAME MILLER 7934 CLERK DEPTNO 10 7934 CLERK HIREDATE 23/01/1982 42 rows selected.
We can see that the unpivot columns are not available as part of the projection.
execution plans for unpivot operations
Earlier we saw the GROUP BY PIVOT operation in the execution plans for our pivot queries. In the following example, we will use Autotrace to generate an explain plan for our last unpivot query.
SQL> set autotrace traceonly explain SQL> SELECT job 2 , saldesc 3 , deptsal 4 FROM pivoted_data 5 UNPIVOT (deptsal 6 FOR saldesc IN (d10_sal AS 'SAL TOTAL FOR 10', 7 d20_sal AS 'SAL TOTAL FOR 20', 8 d30_sal AS 'SAL TOTAL FOR 30', 9 d40_sal AS 'SAL TOTAL FOR 40')) 10 ORDER BY 11 job 12 , saldesc;
Execution Plan ---------------------------------------------------------- Plan hash value: 1898428924 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 20 | 740 | 17 (30)| 00:00:01 | | 1 | SORT ORDER BY | | 20 | 740 | 17 (30)| 00:00:01 | |* 2 | VIEW | | 20 | 740 | 16 (25)| 00:00:01 | | 3 | UNPIVOT | | | | | | | 4 | VIEW | PIVOTED_DATA | 5 | 290 | 4 (25)| 00:00:01 | | 5 | HASH GROUP BY PIVOT| | 5 | 75 | 4 (25)| 00:00:01 | | 6 | TABLE ACCESS FULL | EMP | 14 | 210 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("unpivot_view"."DEPTSAL" IS NOT NULL)
The points of interest are highlighted. First, we can see a new UNPIVOT step (ID=3). Second, we can see a filter predicate to remove all NULL values for DEPTSAL. This is a result of the default EXCLUDING NULLS clause. If we use the INCLUDING NULLS option, this filter is removed. Note that the GROUP BY PIVOT operation at ID=5 is generated by the pivot query that underlies the PIVOTED_DATA view.
We will extract some more detailed information about this execution plan by using DBMS_XPLAN's format options, as follows. In particular, we will examine the alias and projection details, to see if it provides any clues about Oracle's implementation of UNPIVOT.
SQL> EXPLAIN PLAN SET STATEMENT_ID = 'UNPIVOT' 2 FOR 3 SELECT job 4 , saldesc 5 , deptsal 6 FROM pivoted_data 7 UNPIVOT (deptsal 8 FOR saldesc IN (d10_sal AS 'SAL TOTAL FOR 10', 9 d20_sal AS 'SAL TOTAL FOR 20', 10 d30_sal AS 'SAL TOTAL FOR 30', 11 d40_sal AS 'SAL TOTAL FOR 40')) 12 ORDER BY 13 job 14 , saldesc;
Explained.
SQL> SELECT * 2 FROM TABLE( 3 DBMS_XPLAN.DISPLAY( 4 NULL, 'UNPIVOT', 'TYPICAL +PROJECTION +ALIAS'));
PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------- Plan hash value: 1898428924 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 20 | 740 | 17 (30)| 00:00:01 | | 1 | SORT ORDER BY | | 20 | 740 | 17 (30)| 00:00:01 | |* 2 | VIEW | | 20 | 740 | 16 (25)| 00:00:01 | | 3 | UNPIVOT | | | | | | | 4 | VIEW | PIVOTED_DATA | 5 | 290 | 4 (25)| 00:00:01 | | 5 | HASH GROUP BY PIVOT| | 5 | 75 | 4 (25)| 00:00:01 | | 6 | TABLE ACCESS FULL | EMP | 14 | 210 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------------------- Query Block Name / Object Alias (identified by operation id): ------------------------------------------------------------- 1 - SEL$D50F4D64 2 - SET$1 / unpivot_view@SEL$17 3 - SET$1 4 - SEL$CB31B938 / PIVOTED_DATA@SEL$4 5 - SEL$CB31B938 6 - SEL$CB31B938 / EMP@SEL$15 Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter("unpivot_view"."DEPTSAL" IS NOT NULL) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=2) "unpivot_view"."JOB"[VARCHAR2,9], "unpivot_view"."SALDESC"[CHARACTER,16], "unpivot_view"."DEPTSAL"[NUMBER,22] 2 - "unpivot_view"."JOB"[VARCHAR2,9], "unpivot_view"."SALDESC"[CHARACTER,16], "unpivot_view"."DEPTSAL"[NUMBER,22] 3 - STRDEF[9], STRDEF[16], STRDEF[22] 4 - "PIVOTED_DATA"."JOB"[VARCHAR2,9], "D10_SAL"[NUMBER,22], "PIVOTED_DATA"."D20_SAL"[NUMBER,22], "PIVOTED_DATA"."D30_SAL"[NUMBER,22], "PIVOTED_DATA"."D40_SAL"[NUMBER,22] 5 - (#keys=1) "JOB"[VARCHAR2,9], SUM(CASE WHEN ("DEPTNO"=10) THEN "SAL" END )[22], SUM(CASE WHEN ("DEPTNO"=20) THEN "SAL" END )[22], SUM(CASE WHEN ("DEPTNO"=30) THEN "SAL" END )[22], SUM(CASE WHEN ("DEPTNO"=40) THEN "SAL" END )[22] 6 - "JOB"[VARCHAR2,9], "SAL"[NUMBER,22], "DEPTNO"[NUMBER,22] 45 rows selected.
The projection of the unpivoted columns is highlighted between operations 1 and 3 above. This does not really provide any clues to how Oracle implements UNPIVOT. Note that a 10046 trace (SQL trace) provides no clues either, so has been omitted from this article.
The alias information is slightly more interesting, but still tells us little about UNPIVOT. It might be a red herring, but when Oracle transforms a simple query, the generated alias names for query blocks usually follow a pattern such as "SEL$1", "SEL$2" and so on. In our unpivot query, the aliases are as high as SEL$17, yet this is a relatively simple query with few components. This could suggest that a lot of query re-write is happening before optimisation, but we can't be certain from the details we have.
other uses for unpivot
Unpivot queries are not restricted to transposing previously pivoted data. We can pivot any set of columns from a table (within the datatype restriction described earlier). A good example is Tom Kyte's print_table procedure. This utility unpivots wide records to enable us to read the data down the page instead of across. The new UNPIVOT can be used for the same purpose. In the following example, we will write a static unpivot query similar to those that the print_table utility is used for.
SQL> WITH all_objects_data AS ( 2 SELECT owner 3 , object_name 4 , subobject_name 5 , TO_CHAR(object_id) AS object_id 6 , TO_CHAR(data_object_id) AS data_object_id 7 , object_type 8 , TO_CHAR(created) AS created 9 , TO_CHAR(last_ddl_time) AS last_ddl_time 10 , timestamp 11 , status 12 , temporary 13 , generated 14 , secondary 15 , TO_CHAR(namespace) AS namespace 16 , edition_name 17 FROM all_objects 18 WHERE ROWNUM = 1 19 ) 20 SELECT column_name 21 , column_value 22 FROM all_objects_data 23 UNPIVOT (column_value 24 FOR column_name 25 IN (owner, object_name, subobject_name, object_id, 26 data_object_id, object_type, created, last_ddl_time, 27 timestamp, status, temporary, generated, 28 secondary, namespace, edition_name));
COLUMN_NAME COLUMN_VALUE -------------- --------------------- OWNER SYS OBJECT_NAME ICOL$ OBJECT_ID 20 DATA_OBJECT_ID 2 OBJECT_TYPE TABLE CREATED 15/10/2007 10:09:08 LAST_DDL_TIME 15/10/2007 10:56:08 TIMESTAMP 2007-10-15:10:09:08 STATUS VALID TEMPORARY N GENERATED N SECONDARY N NAMESPACE 1 13 rows selected.
Turning this into a dynamic SQL solution is simple and can be an exercise for the reader.
unpivot queries prior to 11g
To complete this article, we will include a couple of techniques for unpivot queries in versions prior to 11g and compare their performance. The first method uses a Cartesian Product with a generated dummy rowsource. This rowsource has the same number of rows as the number of columns we wish to unpivot. Using the same dataset as our UNPIVOT examples, we will demonstrate this below.
SQL> WITH row_source AS ( 2 SELECT ROWNUM AS rn 3 FROM all_objects 4 WHERE ROWNUM <= 4 5 ) 6 SELECT p.job 7 , CASE r.rn 8 WHEN 1 9 THEN 'D10_SAL' 10 WHEN 2 11 THEN 'D20_SAL' 12 WHEN 3 13 THEN 'D30_SAL' 14 WHEN 4 15 THEN 'D40_SAL' 16 END AS saldesc 17 , CASE r.rn 18 WHEN 1 19 THEN d10_sal 20 WHEN 2 21 THEN d20_sal 22 WHEN 3 23 THEN d30_sal 24 WHEN 4 25 THEN d40_sal 26 END AS deptsal 27 FROM pivoted_data p 28 , row_source r 29 ORDER BY 30 p.job 31 , saldesc;
JOB SALDESC DEPTSAL ---------- ---------- ---------- ANALYST D10_SAL ANALYST D20_SAL 6600 ANALYST D30_SAL ANALYST D40_SAL CLERK D10_SAL 1430 CLERK D20_SAL 2090 CLERK D30_SAL 1045 CLERK D40_SAL MANAGER D10_SAL 2695 MANAGER D20_SAL 3272.5 MANAGER D30_SAL 3135 MANAGER D40_SAL PRESIDENT D10_SAL 5500 PRESIDENT D20_SAL PRESIDENT D30_SAL PRESIDENT D40_SAL SALESMAN D10_SAL SALESMAN D20_SAL SALESMAN D30_SAL 6160 SALESMAN D40_SAL 20 rows selected.
The resultset is the equivalent of using the new UNPIVOT with the INCLUDING NULLS option. The second technique we can use to unpivot data joins the pivoted dataset to a collection of the columns we wish to transpose. The following example uses a generic NUMBER_NTT nested table type to hold the pivoted department salary columns. We can use a numeric type because all the pivoted columns are of NUMBER. We will create the type as follows.
SQL> CREATE OR REPLACE TYPE number_ntt AS TABLE OF NUMBER; 2 /
Type created.
Using this collection type for the pivoted department salaries, we will now unpivot the data, as follows.
SQL> SELECT p.job 2 , s.column_value AS deptsal 3 FROM pivoted_data p 4 , TABLE(number_ntt(d10_sal,d20_sal,d30_sal,d40_sal)) s 5 ORDER BY 6 p.job;
JOB DEPTSAL ---------- ---------- ANALYST ANALYST 6600 ANALYST ANALYST CLERK CLERK 1045 CLERK 1430 CLERK 2090 MANAGER 3272.5 MANAGER MANAGER 3135 MANAGER 2695 PRESIDENT PRESIDENT PRESIDENT PRESIDENT 5500 SALESMAN 6160 SALESMAN SALESMAN SALESMAN 20 rows selected.
While we have unpivoted the department salaries, we have lost our descriptive labels for each of the values. There is no simple way with this technique to decode a row number (like we did in the Cartesian Product example). We can, however, change the collection type we use to include a descriptor. For this purpose, we will first create a generic object type to define a single row of numeric unpivot data, as follows.
SQL> CREATE TYPE name_value_ot AS OBJECT 2 ( name VARCHAR2(30) 3 , value NUMBER 4 ); 5 /
Type created.
We will now create a collection type based on this object, as follows.
SQL> CREATE TYPE name_value_ntt 2 AS TABLE OF name_value_ot; 3 /
Type created.
We will now repeat our previous unpivot query, but provide descriptions using our new collection type.
SQL> SELECT p.job 2 , s.name AS saldesc 3 , s.value AS deptsal 4 FROM pivoted_data p 5 , TABLE( 6 name_value_ntt( 7 name_value_ot('D10_SAL', d10_sal), 8 name_value_ot('D20_SAL', d20_sal), 9 name_value_ot('D30_SAL', d30_sal), 10 name_value_ot('D40_SAL', d40_sal) )) s 11 ORDER BY 12 p.job 13 , s.name;
JOB SALDESC DEPTSAL ---------- ---------- ---------- ANALYST D10_SAL ANALYST D20_SAL 6600 ANALYST D30_SAL ANALYST D40_SAL CLERK D10_SAL 1430 CLERK D20_SAL 2090 CLERK D30_SAL 1045 CLERK D40_SAL MANAGER D10_SAL 2695 MANAGER D20_SAL 3272.5 MANAGER D30_SAL 3135 MANAGER D40_SAL PRESIDENT D10_SAL 5500 PRESIDENT D20_SAL PRESIDENT D30_SAL PRESIDENT D40_SAL SALESMAN D10_SAL SALESMAN D20_SAL SALESMAN D30_SAL 6160 SALESMAN D40_SAL 20 rows selected.
We can see that the new 11g UNPIVOT syntax is easier to use than the pre-11g alternatives. We will also compare the performance of each of these techniques, using Autotrace, the wall-clock and our MILLION_ROWS test table. We will start with the new 11g syntax and unpivot the three numeric columns of our test table, as follows.
SQL> set autotrace traceonly statistics SQL> set timing on SQL> SELECT * 2 FROM million_rows 3 UNPIVOT (column_value 4 FOR column_name 5 IN (pivoting_col, summing_col, grouping_col));
3000000 rows selected. Elapsed: 00:00:09.51 Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 20290 consistent gets 14286 physical reads 0 redo size 80492071 bytes sent via SQL*Net to client 66405 bytes received via SQL*Net from client 6001 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 3000000 rows processed
The 11g UNPIVOT method generated 3 million rows in under 10 seconds with only slightly more logical I/O than in our PIVOT tests. We will compare this with the Cartesian Product method, but using a rowsource technique that generates no additional I/O (instead of the ALL_OBJECTS view that we used previously).
SQL> WITH row_source AS ( 2 SELECT ROWNUM AS rn 3 FROM dual 4 CONNECT BY ROWNUM <= 3 5 ) 6 SELECT m.padding_col 7 , CASE r.rn 8 WHEN 0 9 THEN 'PIVOTING_COL' 10 WHEN 1 11 THEN 'SUMMING_COL' 12 ELSE 'GROUPING_COL' 13 END AS column_name 14 , CASE r.rn 15 WHEN 0 16 THEN m.pivoting_col 17 WHEN 1 18 THEN m.summing_col 19 ELSE m.grouping_col 20 END AS column_value 21 FROM million_rows m 22 , row_source r;
3000000 rows selected. Elapsed: 00:00:24.95 Statistics ---------------------------------------------------------- 105 recursive calls 2 db block gets 14290 consistent gets 54288 physical reads 0 redo size 42742181 bytes sent via SQL*Net to client 66405 bytes received via SQL*Net from client 6001 SQL*Net roundtrips to/from client 1 sorts (memory) 1 sorts (disk) 3000000 rows processed
The Cartesian Product method is considerably slower than the new 11g UNPIVOT syntax. It generates considerably more I/O and takes over twice as long (note that these results are repeatable across multiple re-runs). However, investigations with SQL trace indicate that this additional I/O is a result of direct path reads and writes to the temporary tablespace, to support a large buffer sort (i.e. the sort that accompanies a MERGE JOIN CARTESIAN operation). On most commercial systems, this buffer sort will probably be performed entirely in memory or the temporary tablespace access will be quicker. For a small system with slow disk access (such as the 11g database used for this article), it has a large impact on performance. We can tune this to a degree by forcing a nested loop join and/or avoiding the disk sort altogether, as follows.
SQL> WITH row_source AS ( 2 SELECT ROWNUM AS rn 3 FROM dual 4 CONNECT BY ROWNUM <= 3 5 ) 6 SELECT /*+ ORDERED USE_NL(r) */ 7 m.padding_col 8 , CASE r.rn 9 WHEN 0 10 THEN 'PIVOTING_COL' 11 WHEN 1 12 THEN 'SUMMING_COL' 13 ELSE 'GROUPING_COL' 14 END AS column_name 15 , CASE r.rn 16 WHEN 0 17 THEN m.pivoting_col 18 WHEN 1 19 THEN m.summing_col 20 ELSE m.grouping_col 21 END AS column_value 22 FROM million_rows m 23 , row_source r;
3000000 rows selected. Elapsed: 00:00:14.17 Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 20290 consistent gets 14286 physical reads 0 redo size 64742156 bytes sent via SQL*Net to client 66405 bytes received via SQL*Net from client 6001 SQL*Net roundtrips to/from client 1000000 sorts (memory) 0 sorts (disk) 3000000 rows processed
We have significantly reduced the elapsed time and I/O for this method on this database, but have introduced one million tiny sorts. We can easily reverse the nested loops order or use the NO_USE_MERGE hint (which also reverses the NL order), but this doubles the I/O and adds 10% to the elapsed time.
Moving on, we will finally compare our collection method, as follows.
SQL> SELECT m.padding_col 2 , t.name AS column_name 3 , t.value AS column_value 4 FROM million_rows m 5 , TABLE( 6 name_value_ntt( 7 name_value_ot('PIVOTING_COL', pivoting_col), 8 name_value_ot('SUMMING_COL', summing_col), 9 name_value_ot('GROUPING_COL', grouping_col ))) t;
3000000 rows selected. Elapsed: 00:00:12.84 Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 20290 consistent gets 14286 physical reads 0 redo size 80492071 bytes sent via SQL*Net to client 66405 bytes received via SQL*Net from client 6001 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 3000000 rows processed
This method is comparable in I/O to the new UNPIVOT operation but is approximately 35-40% slower. Further investigation using SQL trace suggests that this is due to additional CPU time spent in the collection iterator fetches. Therefore, the new UNPIVOT operation is both easier to code and quicker to run than its SQL alternatives.
further reading
For more information on the new PIVOT and UNPIVOT syntax, see the SQL Reference documentation. For some more examples of the use of pivot and unpivot queries, see the Data Warehousing Guide here and here.
source code
The source code for the examples in this article can be downloaded from here.
Adrian Billington, April 2008
WITH TEST AS
(
select 'A' grade, 10 cnt from dual union all
select 'A' grade, 20 cnt from dual union all
select 'A' grade, 15 cnt from dual union all
select 'B' grade, 20 from dual union all
select 'B' grade, 30 from dual union all
select 'D' grade, 15 from dual union all
select 'F' grade, 10 from dual
)
SELECT
grade 등급,
cnt 학생수,
sum(cnt) over () 갯수,
row_number() over(partition by grade order by cnt) 로넘,
rank() over(partition by grade order by cnt) 랭크,
round((cnt / sum(cnt) over ())*100,2) 구성비,
sum(cnt) over (partition by grade order by grade) 누적학생수,
sum(cnt) over (partition by grade,cnt order by grade) 그레카운,
sum(cnt) over (order by grade) 누적,
round((sum(cnt) over (order by grade)/sum(cnt) over ())*100,2) 누적구성비
FROM TEST;
/*
rank() over(partition by grade order by cnt) 랭크,
표현식 over (partition by 컬럼1 order by 컬럼2)
이 의미는
"(컬럼1값이 같은놈들로 묶은것을 컬럼2로 정렬한) 각 파티션별로 표현식을 수행해준다."
*/
복잡하군... :(
[출처] Oracle over() 구문|작성자 18061975
오랔믈 pivot 기능
SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
'TA_IPT_ICG_MNFT_MST' AS title,
COUNT(mrn) AS cnt
FROM TA_IPT_ICG_MNFT_MST
WHERE USE_FG = 'S'
GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
UNION
SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
'TA_ICG_MNFT_MST' AS title,
COUNT(mrn) AS cnt
FROM TA_ICG_MNFT_MST
WHERE USE_FG = 'S'
GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
SELECT *
FROM
(SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
'TA_IPT_ICG_MNFT_MST' AS title,
COUNT(mrn) AS cnt
FROM TA_IPT_ICG_MNFT_MST
WHERE USE_FG = 'S'
GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
UNION
SELECT TO_CHAR(rgs_dt,'YYYYMMDD'),
'TA_ICG_MNFT_MST' AS title,
COUNT(mrn) AS cnt
FROM TA_ICG_MNFT_MST
WHERE USE_FG = 'S'
GROUP BY TO_CHAR(rgs_dt,'YYYYMMDD')
) pivot (SUM(cnt) FOR title IN('TA_IPT_ICG_MNFT_MST','TA_ICG_MNFT_MST'))
ORDER BY 1
출처 : http://sbcoba.tistory.com/
요즘 ORM으로는 하이버네이트, JPA등 많이 사용하고 있으나, 역시 SI 쪽은 mybatis(ibatis)를 많이 사용된다.
문제는 mybatis는 xml로 sql을 관리하고 있는데 보통 조금 바꿀때 마다 서버를 재구동 시켜야 되는 문제가 된다.
시스템이 클 경우 재시작시 오랜 시간 걸리고 session 사용시 또 로그인을 해야 하는듯 개발의 흐름이 끊어지는 문제가 많이 발생한다.
예전에 ibatis를 사용 했을시에도 그런 부분이 많이 불편했었는데, 예전 대우정보시스템의 JCF 프레임워크에서 사용된다고 Refresh 되는 클래스 소스가 한번 공개 된적이 있었다. ( 몇년전인지 기억은 안나지만, 당시 인터넷 검색으로 찾았었다. )
그것이 버전이 문제인지 바로 사용이 안되어서 커스터마이징하고 사용을 잘사용 했었다.
그런데 지금 프로젝트가 mybatis로 진행하기 때문에 예전과 같은 불편함이 또 생기게 되었는데, 이 번에는 그 소스를 mybatis에 맞도로 커스터마이징 하기로 했다.
일단 사전 조건은
JDK 1.5 이상, Spring, mybatis, spring-mybatis 라이브러리가 설치되어 있는 환경에서만 된다.
일단 기존 Spring 에서 mybatis 설정을 보겠다.
보통 sqlSessionFactory를 이렇게 설정 한다.
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" p:configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource"/>
classpath*:패키지경로/**/mapper.xml <- 이부분이 재로딩될 xml 파일 경로
이부분에서 굵게 표시한 class 부분만 새로 만든 클래스로 바꾸면 모든게 해결된다.
<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean" p:mapperLocations="classpath*:패키지경로/**/mapper.xml" p:configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource" />
RefreshableSqlSessionFactoryBean.java
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.core.io.Resource;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영
*
* @author
*
*/
public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean {
private static final Log log = LogFactory .getLog(RefreshableSqlSessionFactoryBean.class);
private SqlSessionFactory proxy;
private int interval = 500;
private Timer timer;
private TimerTask task;
private Resource[] mapperLocations;
/**
* 파일 감시 쓰레드가 실행중인지 여부.
*/
private boolean running = false;
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public void setMapperLocations(Resource[] mapperLocations) {
super.setMapperLocations(mapperLocations);
this.mapperLocations = mapperLocations;
}
public void setInterval(int interval) {
this.interval = interval;
}
/**
*
* @throws Exception
*/
public void refresh() throws Exception {
if (log.isInfoEnabled()) {
log.info("refreshing sqlMapClient.");
}
w.lock();
try {
super.afterPropertiesSet();
} finally {
w.unlock();
}
}
/**
* 싱글톤 멤버로 SqlMapClient 원본 대신 프록시로 설정하도록 오버라이드.
*/
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
setRefreshable();
}
private void setRefreshable() {
proxy = (SqlSessionFactory) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSessionFactory.class },
new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// log.debug("method.getName() : " + method.getName());
return method.invoke(getParentObject(), args);
}
});
task = new TimerTask() {
private Map<Resource, Long> map = new HashMap<Resource, Long>();
public void run() {
if (isModified()) {
try {
refresh();
} catch (Exception e) {
log.error("caught exception", e);
}
}
}
private boolean isModified() {
boolean retVal = false;
if (mapperLocations != null) {
for (int i = 0; i < mapperLocations.length; i++) {
Resource mappingLocation = mapperLocations[i];
retVal |= findModifiedResource(mappingLocation);
}
}
return retVal;
}
private boolean findModifiedResource(Resource resource) {
boolean retVal = false;
List<String> modifiedResources = new ArrayList<String>();
try {
long modified = resource.lastModified();
if (map.containsKey(resource)) {
long lastModified = ((Long) map.get(resource))
.longValue();
if (lastModified != modified) {
map.put(resource, new Long(modified));
modifiedResources.add(resource.getDescription());
retVal = true;
}
} else {
map.put(resource, new Long(modified));
}
} catch (IOException e) {
log.error("caught exception", e);
}
if (retVal) {
if (log.isInfoEnabled()) {
log.info("modified files : " + modifiedResources);
}
}
return retVal;
}
};
timer = new Timer(true);
resetInterval();
}
private Object getParentObject() throws Exception {
r.lock();
try {
return super.getObject();
} finally {
r.unlock();
}
}
public SqlSessionFactory getObject() {
return this.proxy;
}
public Class<? extends SqlSessionFactory> getObjectType() {
return (this.proxy != null ? this.proxy.getClass()
: SqlSessionFactory.class);
}
public boolean isSingleton() {
return true;
}
public void setCheckInterval(int ms) {
interval = ms;
if (timer != null) {
resetInterval();
}
}
private void resetInterval() {
if (running) {
timer.cancel();
running = false;
}
if (interval > 0) {
timer.schedule(task, 0, interval);
running = true;
}
}
public void destroy() throws Exception {
timer.cancel();
}
}
만약에 재로딩 되는 시간을 바꾸고 싶으면
<bean id="sqlSessionFactory" class="패키지경로.RefreshableSqlSessionFactoryBean" p:mapperLocations="classpath*:kr/web/**/mapper.xml" :configLocation="classpath:/MapperConfig.xml" p:dataSource-ref="dataSource" p:interval="1000" />
p:interval="1000" 이부분을 수치를 정해주면된다. ( 디폴트는 500, 단위 ms )
이제 설정을 서버를 시작해서 위에 로케이션 해당하는 mapper.xml ( sql이 있는 xml , 설정에 따라 다름)에서
sql을 바꿔 보자 그리고 클라이언트에서 바뀐지 확인해보자
큰 문제가 없다면 반영될것이라고 생각된다.
아 단 운영 시스템에 사용은 보장 못합니다~ 개발시에서만 사용하세요~
이 소스는 예전에 인터넷에서 나돌던 RefreshableSqlMapClientFactoryBean 소스를 커스터마이징한 소스인데 문제가 있다면 연락주시길바랍니다.
2. 자바_한글_API_par01.exe 를 실행시킨후 저절로 api라는 이름으로 압축이 풀립니다.
그러면.... 이클립스가 설치된 폴더 안에 넣습니다.
난, C:\program files\ eclipse 폴더에 넣습니다. 뭐를? api 폴더를~
3. 이제 이클립스를 실행 시킵니다.
4. 이클립스 실행 후에 프로젝트 중 아무거나 하나 클릭해서, rt.jar 파일 찾는다.
5. properties 에 들어가게 되면
type filter text > Java source Attachment 가 있는데,
여기의 Location Path 부분을 지운다
6. Javadoc Location 에 가면 javadoc URL 설정부분에서....
아까 압축받아서 푼 API 를 넣어준 이클립스 경로를 브라우저 눌러서 찾아서 API/ 까지 등록
7. 자, 이제 원초적인 JAVA API 는 한글로 편히 볼 수 있다 ㅋㅋㅋ
출처 : http://joke00.tistory.com/69
자바 책을 훑어보던 중.
Exception 관련해서 assertion 부분이 나왔다. ( 왜.. 예전엔 미처 몰랐을까;;;;)
assertion 은 디버깅시에 익셉션 에러 검증 구문으로 인용하기 쉽다.
throws 나 try ~ catch 블럭과 동일한 역할을 합니다.
사용법
---------------------------------------------------------------------------------------------
assert [ true or false 를 나타내는검증구문] : [검증에서 false 발생시 출력할 내용]
---------------------------------------------------------------------------------------------
1. 이클립스를 열어 테스트를 하나 만듭니다.
2. 해당 프로젝트를 선택 후 마우스 오른쪽을 누르면 properties 를 눌러 Run/Debug Settings 를 선택합니다.
해당 프로젝트가 보이네요~ edit 를 누릅니다
3. Arguments 탭으로 이동후 VM arguments 를 입력합니다.
-ea 는 assertion 실행
-ea:클래스명 : 해당 클래스만 assetion 을 실행
-ea:... : 현재 패키지 내에 있는 클래스들만 assertion 실행
-ea<package명> : 해당 package 명 내의 클래스들만 assetion 실행
-da 로 했을 경우에는 반대로 해당되는 경우에만 assertion 을 실행시키지 않음
4. ctrl + f11 을 눌러 실행을 시킵니다.
아름과 같은 익셉션 구문과 함께 어디서 익셉션이 났는지를 알려준답니다 ^^
출처 : http://joke00.tistory.com/56
이클립스에서 개발하다 보면 interface 클래스를 많이 사용하게 되는데..
특히 스프링을 사용하거나 프레임 웍을 사용하다 보면 interface 클래스를 많이 쓰게 된다.
이때, 컨트롤을 누르고 메소드를 클릭하게 되면 interface 메소드로 가게 되있는데,
이 implementors 플러그 인은 말 그대로 해당 interface 가 아니라 interface 구현체인 implements 메소드를
찾아갈 수 있게끔 하는 플러그인이다.
꽤 유용할듯~
자, 이제 설치해 봅시다
URL : http://eclipse-tools.sourceforge.net/updates
1. 이클립스의 [help] - [Install New SoftWare] 를 선택합니다. (3.5 갈릴레오 기준)
2. 선택 후 next~
3. 이 플러그인은 다른것들과는 다른 accept 창이 나오니, 당황하지 마시고, 동의하고 next 넘어가면 됩니다.
4. 무지 빠른 속도로 install 되니, 어디 가지 마세요 ^^
5. 설치가 끝난 후 어김없이 뜹니다. "지금 당장 재부팅 하시꺼유?" YES!
6. 자, 한번 테스트 해 봅시다.
getData 라는 interface 메소드가 있는데, 이전 같으면 Ctrl + 누른후 마우스를 가져다 대면 interface 클래스로 이동했는데, 이번엔 선택하도록 나옵니다.
open Declaration -> interface 클래스의 메소드로 이동 ==> F3
open Implementation -> interface 를 implements 한 class 의 메소드로 이동 => Alt + F3
나 이거 캡쳐하느라 애 좀 먹었다규;;; -_-;;;;
단축키 이동이 불편하다면,
[Window] - [preference ] - [General] - [keys] 에서 재 등록 하시면 됩니다.
Quick Start
Have you downloaded? Good. Now let’s get your first compiled spreadsheet up and running. I’ll show you how to…
- create a spreadsheet defining a customized line item price computation,
- make AFC compile it to Java classes,
- use those classes in your application,
- look at the generated code, and
- save and reload the classes from a .jar file.
The Problem
This is a fictional example about finding the total price for a line item with
- a base article price,
- a count of items, and
- the customer category (for automatic rebates).
We will flesh out the following bit of code:
// Compile price finding factory and strategy implementation from spreadsheet: EngineBuilder builder = SpreadsheetCompiler.newEngineBuilder(); builder.loadSpreadsheet( new File( PATH, "CustomPriceFormula.xls" ) ); builder.setFactoryClass( PriceFinderFactory.class ); builder.bindAllByName(); Engine engine = builder.compile(); PriceFinderFactory factory = (PriceFinderFactory) engine.getComputationFactory(); // Use it to compute a line item price: LineItem item = getCurrentLineItem(); PriceFinder priceFinder = factory.newInstance( item ); BigDecimal price = priceFinder.getPrice();
The Spreadsheet
Here’s the spreadsheet-defined formula our user wants the application to use:
A | B | C | D | E | |
1 | Input Values | ||||
2 | Article Price | 500.00 | |||
3 | Item Count | 5 | |||
4 | Customer Category | C | |||
5 | |||||
6 | Intermediate Values | ||||
7 | Categories | A | B | C | D |
8 | Rebates | 6% | 4% | 1% | 0% |
9 | Effective Rebate | =HLOOKUP(B4,B7:E8,2.0) | |||
10 | Base Price | =B2*B3 | |||
11 | |||||
12 | Output Values | ||||
13 | Price | =B10*(1.0-B9) |
Please create this spreadsheet now and save it somewhere under the name CustomPriceFormula.xls
.
The Project
Create a new Java project for this demo application now. Add to it references to the following libraries you obtained from the download:
build/formulacompiler-runtime.jar
build/formulacompiler-compiler.jar
build/formulacompiler-spreadsheet.jar
build/formulacompiler-spreadsheet-excel-xls.jar
build/formulacompiler-decompiler.jar
build/lib/asm-x.y.jar
build/lib/asm-commons-x.y.jar
build/lib/jxl.jar
build/lib/jode-decompiler.jar
Then create a main class in it. Unless your IDE can find and organize imports automatically, you might also want to add these to your main class:
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.math.BigDecimal; import org.formulacompiler.compiler.CompilerException; import org.formulacompiler.compiler.SaveableEngine; import org.formulacompiler.decompiler.ByteCodeEngineSource; import org.formulacompiler.decompiler.FormulaDecompiler; import org.formulacompiler.runtime.Engine; import org.formulacompiler.runtime.EngineException; import org.formulacompiler.runtime.FormulaRuntime; import org.formulacompiler.spreadsheet.EngineBuilder; import org.formulacompiler.spreadsheet.SpreadsheetCompiler;
The Interfaces
In essence, what AFC compiles from the spreadsheet is a computation strategy implementation. So we need the customary factory and doer interfaces that go with a strategy:
public static interface PriceFinder { BigDecimal getPrice(); } public static interface PriceFinderFactory { PriceFinder newInstance( LineItem item ); }
Paste these directly into your main class (they are nested classes in this example, but AFC is just as happy with top-level classes).
We’ll also need the line item, which provides input data to the computation:
public static class LineItem { public BigDecimal getArticlePrice() { return BigDecimal.valueOf( 112.00 ); } public int getItemCount() { return 10; } public String getCustomerCategory() { return "B"; } }
It’s obviously a dummy. Paste it into the main class as well, together with the following dummy getter:
private LineItem getCurrentLineItem() { return new LineItem(); }
The Compilation
We’re ready to compile now. Add the following two methods, that I’ll explain shortly:
private SaveableEngine compile() throws FileNotFoundException, IOException, CompilerException, EngineException { EngineBuilder builder = SpreadsheetCompiler.newEngineBuilder(); builder.loadSpreadsheet( new File( PATH, "CustomPriceFormula.xls" ) ); builder.setFactoryClass( PriceFinderFactory.class ); builder.bindAllByName(); return builder.compile(); } private PriceFinderFactory factoryFor( Engine engine ) { return (PriceFinderFactory) engine.getComputationFactory(); }
Also add a constant for the path where you saved the spreadsheet. In my code, it’s:
public static final File PATH = new File( "src/test/data/org/formulacompiler/tutorials" );
Let’s go through this now:
- First, we get a new engine builder. It gives us a simplified API onto AFC.
- We tell the builder to load the spreadsheet you created earlier on. AFC detects the appropriate loader to use by the file’s extension. It currently supports Microsoft Excel (.xls) and OpenOffice Calc (.ods), and maybe others – check the release notes for details.
- We inform the builder of our factory class. It can usually infer from this the input interface,
LineItem
, and the output interface,PriceFinder
, by itself (if not, you can given them explicitly; you can then also omit the factory interface altogether).
- We tell the builder to bind spreadsheet cells to our interface methods by name. In short, any cell named after a method on the input interface,
LineItem
, gets its value from that method in the compiled formula. A cell named after a method on the output interface,PriceFinder
, is used to implement that method. (The binder is smart about dropping theget
prefix on method names and is not case sensitive.)
- But wait, we don’t have any cell names in this spreadsheet. What’s going on? AFC automatically creates cell names from row titles (string values in column A are applied as names for corresponding cells in column B) when a sheet has no cell names and you use
bindAllByName()
.
- That’s it. We tell the builder to compile the thing. It returns a so-called engine (which we’ll later use to save, reload, and decompile), but the most important method on the engine is the one we use on our second method:
getComputationFactory()
.
The Computation
We now look at how the resulting price finder is used. Since AFC compiles spreadsheets to regular JVM classes, there’s no magic at all when using a precompiled computation:
private BigDecimal compute( PriceFinderFactory factory ) { PriceFinder priceFinder = factory.newInstance( getCurrentLineItem() ); return priceFinder.getPrice(); }
So let’s make this runnable:
public static void main( String[] args ) throws Exception { QuickStart app = new QuickStart(); SaveableEngine engine = app.compile(); PriceFinderFactory factory = app.factoryFor( engine ); BigDecimal price = app.compute( factory ); System.out.println( "The result is " + price ); }
Go ahead. Run it.
The Code
Would you also like to know just what exactly the generated PriceFinder
implementation does? You can. AFC wraps a nifty external library, Jode, to decompile generated code to plain Java source again for just this purpose. Let’s use this:
private void decompile( SaveableEngine engine ) throws Exception { ByteCodeEngineSource source = FormulaDecompiler.decompile( engine ); source.saveTo( new File( "temp/test/decompiled/quickstart" ) ); }
You can change the output folder, of course. Now add the following at the end of the main
method:
app.decompile( engine );
Run it again. Then look into the output folder. You should find the following there. First, the generated computation:
package org.formulacompiler.gen; import java.math.BigDecimal; import org.formulacompiler.runtime.Computation; import org.formulacompiler.runtime.FormulaException; import org.formulacompiler.runtime.internal.Environment; import org.formulacompiler.runtime.internal.RuntimeDouble_v2; import org.formulacompiler.runtime.internal.Runtime_v2; import org.formulacompiler.tutorials.QuickStart; final class $Root implements Computation, QuickStart.PriceFinder { private final QuickStart.LineItem $inputs; final Environment $environment; private String[] $constarr$0; $Root(QuickStart.LineItem lineitem, Environment environment) { $environment = environment; $inputs = lineitem; } final double get$0() { return (get$1() * get$2() * (1.0 - $idx$0(Runtime_v2.fun_MATCH_Ascending(get$3(), $constarr$0(), $environment) - 1))); } public final BigDecimal getPrice() { return BigDecimal.valueOf(Runtime_v2.checkDouble(get$0())); } final String[] $constarr$0() { if ($constarr$0 == null) $constarr$0 = new String[] { "A", "B", "C", "D" }; return $constarr$0; } final double $idx$0(int i) { switch (i) { case 0: return 0.06; case 1: return 0.04; case 2: return 0.01; case 3: return 0.0; default: throw new FormulaException ("#VALUE/REF! because index is out of range in INDEX"); } } final double get$1() { return RuntimeDouble_v2.numberToNum($inputs.getArticlePrice()); } final double get$2() { return (double) $inputs.getItemCount(); } final String get$3() { return Runtime_v2.stringFromString($inputs.getCustomerCategory()); } }
Second, it’s corresponding factory:
package org.formulacompiler.gen; import org.formulacompiler.runtime.Computation; import org.formulacompiler.runtime.ComputationFactory; import org.formulacompiler.runtime.internal.Environment; import org.formulacompiler.tutorials.QuickStart; public final class $Factory implements ComputationFactory, QuickStart.PriceFinderFactory { private final Environment $environment; public $Factory(Environment environment) { $environment = environment; } public final Computation newComputation(Object object) { return new $Root((QuickStart.LineItem) object, $environment); } public final QuickStart.PriceFinder newInstance (QuickStart.LineItem lineitem) { return new $Root(lineitem, $environment); } }
The Runtime
Computations compiled by AFC are plain compiled Java classes. They rely only on a single small runtime library, formulacompiler-runtime.jar
. So it’s good practice to simply save them to a .jar file and reuse them from there across application starts (or to even split the compiling application from the using application). However, they do require a special classloader. AFC therefore provides special methods to save and load engines properly.
First, let’s save our engine:
private void save( SaveableEngine engine ) throws Exception { engine.saveTo( new FileOutputStream( "temp/test/CustomPriceFormula.jar" ) ); }
Loading it back is just as easy:
private Engine load() throws Exception { return FormulaRuntime.loadEngine( new FileInputStream( "temp/test/CustomPriceFormula.jar" ) ); }
Again, add the following to the main method to make it runnable:
app.save( engine ); QuickStart app2 = new QuickStart(); Engine engine2 = app2.load(); PriceFinderFactory factory2 = app2.factoryFor( engine2 ); BigDecimal price2 = app2.compute( factory2 ); System.out.println( "The result is " + price2 );
Alright. Run it.
Summary
As promised, you just…
- created a spreadsheet defining a customized line item price computation,
- made AFC compile it to Java classes,
- used those classes in your application,
- looked at the generated code in Java source form, and
- saved and reloaded the classes from a .jar file.
Hopefully, you also played around with the spreadsheet, compiling different formulas. You really should.
Not bad. Now learn more about the goals, the design, the API, and the supported functions of AFC.
출처: http://realcool.egloos.com/3924002
엑셀의 함수를 자바로 구현할 일이 있어서 찾아본 결과
http://www.formulacompiler.org/doc/reference/index.htm
위의 사이트를 어렵게 찾았습니다.
위의 사이트에서 라이브러리 다운받으시고
필요한 엑셀함수를 호출해서 사용하면 됩니다.
하나만 예로 들어 만들어보면 다음과 같습니다.
엑셀의 chiinv를 예로 들면 다음과 같습니다.
import org.formulacompiler.runtime.internal.RuntimeDouble_v2;
/**
* 카이제곱분포
* @author realcool@empal.com
*
*/
public class Chiinv {
/**
* 카이제곱분포
* @param a
* @param b
* @return
*/
public double chiinv(double _x,double _degFreedom){
return RuntimeDouble_v2.fun_CHIINV(_x, _degFreedom);
}
final double NORMDIST(double input1,double input2,double input3,double input4) {
double d;
if (input3 <= 0.0) {
Runtime_v2.fun_ERROR("#NUM! because sigma <= 0 in NORMDIST");
d = (double) -1;
} else
d = (input4 != 0.0
? (Math.abs((input1 - input2) / input3
* 0.7071067811865476) < 0.7071067811865476
? (0.5
+ 0.5 * RuntimeDouble_v2.fun_ERF((input1 - input2)
/ input3
* 0.7071067811865476))
: (input1 - input2) / input3 * 0.7071067811865476 > 0.0
? 1.0 - 0.5 * (RuntimeDouble_v2.fun_ERFC
(Math.abs((input1 - input2) / input3
* 0.7071067811865476)))
: 0.5 * (RuntimeDouble_v2.fun_ERFC
(Math.abs((input1 - input2) / input3
* 0.7071067811865476))))
: (Math.exp((input1 - input2) * (input1 - input2)
/ (-2.0 * input3 * input3))
/ (2.5066282746310002 * input3)));
return d;
}
public static void main(String[] args){
Chiinv c = new Chiinv();
double d = c.chiinv(0.05, 2);
System.out.println(d);
}
}
Since AFC only supports Microsoft Excel spreadsheet semantics at the moment, this reference uses Excel as a baseline for expected behaviour of the supported functions.
- Cells, Values, Names
- Strings, numbers, dates, booleans; blank cells; cell names; ranges; numeric precision
- Numeric Operators
%
,*
,+
,-
,/
,<
,<=
,<>
,=
,>
,>=
,^
- Numeric Functions
ABS
,ACOS
,ACOSH
,ASIN
,ASINH
,ATAN
,ATAN2
,ATANH
,CEILING
,COMBIN
,COS
,COSH
,DEGREES
,EVEN
,EXP
,FACT
,FLOOR
,GEOMEAN
,HARMEAN
,INT
,LN
,LOG
,LOG10
,MOD
,ODD
,PERMUT
,PI
,POWER
,RADIANS
,RAND
,ROUND
,ROUNDDOWN
,ROUNDUP
,SIGN
,SIN
,SINH
,SQRT
,TAN
,TANH
,TRUNC
- Financial Functions
DB
,DDB
,FV
,IRR
,MIRR
,NPER
,NPV
,PMT
,PV
,RATE
,SLN
,SYD
,VDB
- Statistical Functions
AVEDEV
,BETADIST
,BETAINV
,BINOMDIST
,CHIDIST
,CHIINV
,CHITEST
,CONFIDENCE
,CORREL
,CRITBINOM
,DEVSQ
,EXPONDIST
,FDIST
,FINV
,FISHER
,FISHERINV
,FORECAST
,FTEST
,GAMMADIST
,GAMMAINV
,GAMMALN
,HYPGEOMDIST
,INTERCEPT
,KURT
,LARGE
,LOGINV
,LOGNORMDIST
,MEDIAN
,MODE
,NEGBINOMDIST
,NORMDIST
,NORMINV
,NORMSDIST
,NORMSINV
,PEARSON
,PERCENTILE
,PERCENTRANK
,POISSON
,PROB
,QUARTILE
,RANK
,RSQ
,SKEW
,SLOPE
,SMALL
,STANDARDIZE
,STDEV
,STDEVP
,STDEVPA
,STEYX
,SUMX2MY2
,SUMX2PY2
,SUMXMY2
,TDIST
,TINV
,TRIMMEAN
,TTEST
,VAR
,VARA
,VARP
,WEIBULL
,ZTEST
- String Support
&
,<
,<=
,<>
,=
,>
,>=
,CLEAN
,CONCATENATE
,EXACT
,FIND
,LEFT
,LEN
,LOWER
,MATCH
,MID
,PROPER
,REPLACE
,REPT
,RIGHT
,SEARCH
,SUBSTITUTE
,TRIM
,UPPER
- Boolean Functions And IF
AND
,IF
,NOT
,OR
,true
- Date Functions
DATE
,DAY
,DAYS360
,HOUR
,MINUTE
,MONTH
,NOW
,SECOND
,TIME
,TODAY
,WEEKDAY
,YEAR
- Conversion Functions
CHAR
,CODE
,DATEVALUE
,DOLLAR
,FIXED
,N
,ROMAN
,T
,TEXT
,TIMEVALUE
,VALUE
- Type Functions
ISNONTEXT
,ISNUMBER
,ISTEXT
- Aggregators
AVEDEV
,AVERAGE
,COUNT
,COUNTA
,COVAR
,DEVSQ
,KURT
,MAX
,MIN
,PRODUCT
,SKEW
,STDEV
,STDEVP
,SUM
,SUMIF
,SUMSQ
,VAR
,VARP
- Database Table Aggregators
COUNTIF
,DAVERAGE
,DCOUNT
,DCOUNTA
,DGET
,DMAX
,DMIN
,DPRODUCT
,DSTDEV
,DSTDEVP
,DSUM
,DVAR
,DVARP
,SUMIF
- Lookup Functions
CHOOSE
,HLOOKUP
,INDEX
,LOOKUP
,MATCH
,VLOOKUP
- Limitations
- Things that are known to not work quite as expected yet.
Note On The Sample Expressions
To ensure its correctness, all the example expressions and their results have been cited from automated tests that are run with every release build.
# by | 2009/01/18 19:54 | 자바코드 | 트랙백 | 덧글(2)
--1. Buffer Cache Hit Ratio
SELECT ROUND(((1-(SUM(DECODE(name, 'physical reads', value,0))/
(SUM(DECODE(name, 'db block gets', value,0))+
(SUM(DECODE(name, 'consistent gets', value, 0))))))*100),2) || '%' "Buffer Cache Hit Ratio"
FROM V$SYSSTAT;
--2. Library Cache Hit Ratio
SELECT (1-SUM (reloads)/SUM(pins))*100 "Library Cache Hit Ratio"
From V$LIBRARYCACHE;
--3. Data Dictionary Cache Hit Ratio
SELECT (1-SUM(getmisses)/SUM(gets))*100 "Data Dictionary Hit Ratio"
FROM V$ROWCACHE;
-- 테이블 스페이스 사용량
SELECT a.tablespace_name,
a.total "Total(Mb)",
a.total - b.free "Used(Mb)",
nvl(b.free,0) "Free(Mb)",
round((a.total - nvl(b.free,0))*100/total,0) "Used(%)"
from ( select tablespace_name,
round((sum(bytes)/1024/1024),0) as total
from dba_data_files
group by tablespace_name) a,
( select tablespace_name,
round((sum(bytes)/1024/1024),0) as free
from dba_free_space
group by tablespace_name) b
where a.tablespace_name = b.tablespace_name(+)
order by a.tablespace_name;
--오라클서버의 메모리
select * from v$sgastat
select pool, sum(bytes) "SIZE"
from v$sgastat
where pool = 'shared pool'
group by pool
--cpu를 많이 사용하는 쿼리문과 프로세스아이디,시리얼번호,머신 알아내기
select c.sql_text
,b.SID
, b.SERIAL#
,b.machine
,b.OSUSER
,b.logon_time --이 쿼리를 호출한 시간
from v$process a, v$session b, v$sqltext c
where a.addr = b.paddr
and b.sql_hash_value = c.hash_value
--and a.spid = '675958'
order by c.PIECE
--cpu를 많이 사용하는 쿼리문과 프로세스아이디,시리얼번호,머신 알아내기
select c.sql_text
from v$process a, v$session b, v$sqltext c
where a.addr = b.paddr
and b.sql_hash_value = c.hash_value
and a.spid = '171'
order by c.PIECE
--프로세스 아이디를 이용하여 쿼리문 알아내기
select c.sql_text
,b.SID
, b.SERIAL#
,b.machine
,b.OSUSER
,b.logon_time --이 쿼리를 호출한 시간
from v$process a, v$session b, v$sqltext c
where a.addr = b.paddr
and b.sql_hash_value = c.hash_value
and a.spid = '1708032' --1912870/
order by c.PIECE
--세션 죽이기(SID,SERAIL#)
--ALTER SYSTEM KILL SESSION '8,4093'
--오라클 세션과 관련된 테이블*/
--select count(*) from v$session where machine ='머신이름' and schemaname ='스키마이름'
--현재 커서 수 확인
SELECT sid, count(sid) cursor
FROM V$OPEN_CURSOR
WHERE user_name = 'ilips'
GROUP BY sid
ORDER BY cursor DESC
SELECT sql_text, count(sid) cnt
FROM v$OPEN_CURSOR
GROUP BY sql_text
ORDER BY cnt DESC
select * from v$session_wait
select sid, serial#, username, taddr, used_ublk, used_urec
from v$transaction t, v$session s
where t.addr = s.taddr;
select * from sys.v_$open_cursor
--V$LOCK 을 사용한 잠금 경합 모니터링
SELECT s.username, s.sid, s.serial#, s.logon_time,
DECODE(l.type, 'TM', 'TABLE LOCK',
'TX', 'ROW LOCK',
NULL) "LOCK LEVEL",
o.owner, o.object_name, o.object_type
FROM v$session s, v$lock l, dba_objects o
WHERE s.sid = l.sid
AND o.object_id = l.id1
AND s.username IS NOT NULL
--락이 걸린 세션 자세히 알아보기
select a.sid, a.serial#,a.username,a.process,b.object_name,
decode(c.lmode,2,'RS',3,'RX',4,'S',5,'SRX',8,'X','NO') "TABLE LOCK",
decode (a.command,2,'INSERT',3,'SELECT',6,'UPDATE',7,'DELETE',12,'DROP TABLE',26,'LOCK TABLE','UNknown') "SQL",
decode(a.lockwait, NULL,'NO wait','Wait') "STATUS"
from v$session a,dba_objects b, v$lock c
where a.sid=c.sid and b.object_id=c.id1
and c.type='TM'
--락이 걸린 세션 간단히 알아보기
select a.sid, a.serial#, b.type, c.object_name, a.program, a.lockwait,
a.logon_time, a.process, a.osuser, a.terminal
from v$session a, v$lock b, dba_objects c
where a.sid = b.sid
and b.id1 = c.object_id
and b.type = 'TM';
select a.sid, a.serial#, a.username, a.process, b.object_name
from v$session a , dba_objects b, v$lock c
where a.sid=c.sid and b.object_id = c.id1
and c.type = 'TM'
--락이 걸린 세션을 찾아 내어 세션을 죽이려고 해도 죽지 않는 경우
--아래 쿼리문으로 OS단의 PROCESS ID를 찾아내어 OS에서 죽인다
--kill -9 프로세스아이디
select substr(s.username,1,11) "ORACLE USER", p.pid "PROCESS ID",
s.sid "SESSION ID", s.serial#, osuser "OS USER",
p.spid "PROC SPID",s.process "SESS SPID", s.lockwait "LOCK WAIT"
from v$process p, v$session s, v$access a
where a.sid=s.sid and
p.addr=s.paddr and
s.username != 'SYS'
--위 쿼리문의 결과가 있다면 락이 걸린 세션이 있다는것이므로 아래의 쿼리문으로 세션을 죽인다
ALTER SYSTEM KILL SESSION '11,39061'
alter session으로 죽지않는 프로세스 죽이기
1.oracle이 설치된 서버에 텔넷으로 root로 접속한다
2.su -오라클계정
3.sqlplus '/as sysdba''
4.connect system/sys
5.ALTER SYSTEM KILL SESSION '137,1723'
Full Text Indexing using Oracle Text
Oracle Text, previously know as interMedia Text and ConText, is an extensive full text indexing technology allowing you to efficiently query free text and produce document classification applications. In this article I'll only scratch the surface of this very complex feature.
The examples in this article require access to the CTX_DDL
package, which is granted as follows.
GRANT EXECUTE ON CTX_DDL TO <username>;
CONTEXT Indexes
The CONTEXT
index type is used to index large amounts of text such as Word, PDF, XML, HTML or plain text documents. In this example we will store the data in a BLOB
column, which allows us to store binary documents like Word and PDF as well as plain text. Using a CLOB
is preferable if only plain text documents are being used.
First we build a sample schema to hold our data.
DROP TABLE my_docs; DROP SEQUENCE my_docs_seq; DROP PROCEDURE load_file_to_my_docs; CREATE TABLE my_docs ( id NUMBER(10) NOT NULL, name VARCHAR2(200) NOT NULL, doc BLOB NOT NULL ); ALTER TABLE my_docs ADD ( CONSTRAINT my_docs_pk PRIMARY KEY (id) ); CREATE SEQUENCE my_docs_seq; CREATE OR REPLACE DIRECTORY documents AS 'C:\work';
Next we load several files as follows.
CREATE OR REPLACE PROCEDURE load_file_to_my_docs (p_file_name IN my_docs.name%TYPE) AS v_bfile BFILE; v_blob BLOB; BEGIN INSERT INTO my_docs (id, name, doc) VALUES (my_docs_seq.NEXTVAL, p_file_name, empty_blob()) RETURN doc INTO v_blob; v_bfile := BFILENAME('DOCUMENTS', p_file_name); Dbms_Lob.Fileopen(v_bfile, Dbms_Lob.File_Readonly); Dbms_Lob.Loadfromfile(v_blob, v_bfile, Dbms_Lob.Getlength(v_bfile)); Dbms_Lob.Fileclose(v_bfile); COMMIT; END; / EXEC load_file_to_my_docs('FullTextIndexingUsingOracleText9i.doc'); EXEC load_file_to_my_docs('FullTextIndexingUsingOracleText9i.asp'); EXEC load_file_to_my_docs('XMLOverHTTP9i.asp'); EXEC load_file_to_my_docs('UNIXForDBAs.asp'); EXEC load_file_to_my_docs('emp_ws_access.sql'); EXEC load_file_to_my_docs('emp_ws_test.html'); EXEC load_file_to_my_docs('9ivsSS2000forPerformanceV22.pdf');
Next we create a CONTEXT
type index on the doc column and gather table statistics.
CREATE INDEX my_docs_doc_idx ON my_docs(doc) INDEXTYPE IS CTXSYS.CONTEXT; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_DOCS', cascade=>TRUE);
Finally we query table looking for documents with specific content.
SELECT SCORE(1) score, id, name FROM my_docs WHERE CONTAINS(doc, 'SQL Server', 1) > 0 ORDER BY SCORE(1) DESC; SCORE ID NAME ---------- ---------- ------------------------------------------------ 100 127 9ivsSS2000forPerformanceV22.pdf 1 row selected. Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=2 Bytes=58) 1 0 SORT (ORDER BY) (Cost=4 Card=2 Bytes=58) 2 1 TABLE ACCESS (BY INDEX ROWID) OF 'MY_DOCS' (Cost=2 Card=2 Bytes=58) 3 2 DOMAIN INDEX OF 'MY_DOCS_DOC_IDX' (Cost=0) SELECT SCORE(1) score, id, name FROM my_docs WHERE CONTAINS(doc, 'XML', 1) > 0 ORDER BY SCORE(1) DESC; SCORE ID NAME ---------- ---------- ------------------------------------------------ 74 123 XMLOverHTTP9i.asp 9 125 emp_ws_access.sql 2 rows selected. Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=2 Bytes=58) 1 0 SORT (ORDER BY) (Cost=4 Card=2 Bytes=58) 2 1 TABLE ACCESS (BY INDEX ROWID) OF 'MY_DOCS' (Cost=2 Card=2 Bytes=58) 3 2 DOMAIN INDEX OF 'MY_DOCS_DOC_IDX' (Cost=0)
CTXCAT Indexes
The CTXCAT
index type is best suited to smaller text fragments that must be indexed along with other relational data. In this example the data will be stored in a VARCHAR2
column.
First we create a schema to hold the data.
DROP TABLE my_items; DROP SEQUENCE my_items_seq; EXEC CTX_DDL.DROP_INDEX_SET('my_items_iset'); CREATE TABLE my_items ( id NUMBER(10) NOT NULL, name VARCHAR2(200) NOT NULL, description VARCHAR2(4000) NOT NULL, price NUMBER(7,2) NOT NULL ); ALTER TABLE my_items ADD ( CONSTRAINT my_items_pk PRIMARY KEY (id) ); CREATE SEQUENCE my_items_seq;
Next we populate the schema with some dummy data.
BEGIN FOR i IN 1 .. 1000 LOOP INSERT INTO my_items (id, name, description, price) VALUES (my_items_seq.NEXTVAL, 'Bike: '||i, 'Bike Description ('||i||')', i); END LOOP; FOR i IN 1 .. 1000 LOOP INSERT INTO my_items (id, name, description, price) VALUES (my_items_seq.NEXTVAL, 'Car: '||i, 'Car Description ('||i||')', i); END LOOP; FOR i IN 1 .. 1000 LOOP INSERT INTO my_items (id, name, description, price) VALUES (my_items_seq.NEXTVAL, 'House: '||i, 'House Description ('||i||')', i); END LOOP; COMMIT; END; /
Next we create a CTXCAT
index on the DESCRIPTION
and PRICE
columns and gather table statistics. In order to create the index we must create an index-set with a sub-index for each column referenced by the CATSEARCH
function.
EXEC CTX_DDL.CREATE_INDEX_SET('my_items_iset'); EXEC CTX_DDL.ADD_INDEX('my_items_iset','price'); CREATE INDEX my_items_name_idx ON my_items(description) INDEXTYPE IS CTXSYS.CTXCAT PARAMETERS ('index set my_items_iset'); EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_ITEMS', cascade=>TRUE);
Finally we query table looking for items with a description that contains our specified words and an appropriate price.
SELECT id, price, name FROM my_items WHERE CATSEARCH(description, 'Bike', 'price BETWEEN 1 AND 5')> 0; ID PRICE NAME ---------- ---------- ------------------------------------------------ 1 1 Bike: 1 2 2 Bike: 2 3 3 Bike: 3 4 4 Bike: 4 5 5 Bike: 5 5 rows selected. Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=150 Bytes=6000) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MY_ITEMS' (Cost=2 Card=150 Bytes=6000) 2 1 DOMAIN INDEX OF 'MY_ITEMS_NAME_IDX' SELECT id, price, name FROM my_items WHERE CATSEARCH(description, 'Car', 'price BETWEEN 101 AND 105 ORDER BY price DESC')> 0; ID PRICE NAME ---------- ---------- ------------------------------------------------ 1105 105 Car: 105 1104 104 Car: 104 1103 103 Car: 103 1102 102 Car: 102 1101 101 Car: 101 5 rows selected. Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=150 Bytes=6000) 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MY_ITEMS' (Cost=2 Card=150 Bytes=6000) 2 1 DOMAIN INDEX OF 'MY_ITEMS_NAME_IDX'
Every column used to restrict the selection or order the output in the CATSEARCH
function should have a sub-index within the index-set. The CTXCAT
index type is transactional so there is no need to synchronize the index.
CTXRULE Indexes
The CTXRULE
index type can be used to build document classification applications.
First we must define our document categories and store them, along with a suitable query for the MATCHES
function.
DROP TABLE my_doc_categories; DROP TABLE my_categories; DROP SEQUENCE my_categories_seq; DROP TABLE my_docs; DROP SEQUENCE my_docs_seq; CREATE TABLE my_categories ( id NUMBER(10) NOT NULL, category VARCHAR2(30) NOT NULL, query VARCHAR2(2000) NOT NULL ); ALTER TABLE my_categories ADD ( CONSTRAINT my_categories_pk PRIMARY KEY (id) ); CREATE SEQUENCE my_categories_seq; INSERT INTO my_categories VALUES(my_categories_seq.NEXTVAL, 'Oracle', 'ABOUT(Oracle)'); INSERT INTO my_categories VALUES(my_categories_seq.NEXTVAL, 'SQL Server', 'ABOUT(SQL Server)'); INSERT INTO my_categories VALUES(my_categories_seq.NEXTVAL, 'UNIX', 'ABOUT(UNIX)');
Next we create a table to hold our documents.
CREATE TABLE my_docs ( id NUMBER(10) NOT NULL, name VARCHAR2(200) NOT NULL, doc CLOB NOT NULL ); ALTER TABLE my_docs ADD ( CONSTRAINT my_docs_pk PRIMARY KEY (id) ); CREATE SEQUENCE my_docs_seq;
Then we create an intersection table to resolve the many-to-many relationship between documents and categories.
CREATE TABLE my_doc_categories ( my_doc_id NUMBER(10) NOT NULL, my_category_id NUMBER(10) NOT NULL ); ALTER TABLE my_doc_categories ADD ( CONSTRAINT my_doc_categories_pk PRIMARY KEY (my_doc_id, my_category_id) );
Next we create a BEFORE INSERT
trigger on the MY_DOCS
table to automatically assign the documents to the relevant categories as they are being inserted. The MATCHES
function is used to decide if the document matches any of our gategory queries. The resulting cursor is used to insert the matches into the intersect table.
CREATE OR REPLACE TRIGGER my_docs_trg BEFORE INSERT ON my_docs FOR EACH ROW BEGIN FOR c1 IN (SELECT id FROM my_categories WHERE MATCHES(query, :new.doc)>0) LOOP BEGIN INSERT INTO my_doc_categories(my_doc_id, my_category_id) VALUES (:new.id, c1.id); EXCEPTION WHEN OTHERS THEN NULL; END; END LOOP; END; /
Next we create the CTXRULE
index to support the trigger. For completeness we also create a CONTEXT
index on the document itself, although this is not involved in the category assignment process.
CREATE INDEX my_categories_query_idx ON my_categories(query) INDEXTYPE IS CTXSYS.CTXRULE; CREATE INDEX my_docs_doc_idx ON my_docs(doc) INDEXTYPE IS CTXSYS.CONTEXT; EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_CATEGORIES', cascade=>TRUE); EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_DOCS', cascade=>TRUE); EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'MY_DOC_CATEGORIES', cascade=>TRUE);
Finally we test the mechanism by inserting some rows and checking the classification.
INSERT INTO my_docs (id, name, doc) VALUES (my_docs_seq.NEXTVAL, 'Oracle Document', 'This document constains the word Oracle!'); INSERT INTO my_docs (id, name, doc) VALUES (my_docs_seq.NEXTVAL, 'SQL Server Document', 'This document constains the words SQL Server!'); INSERT INTO my_docs (id, name, doc) VALUES (my_docs_seq.NEXTVAL, 'UNIX Document', 'This document constains the word UNIX!'); INSERT INTO my_docs (id, name, doc) VALUES (my_docs_seq.NEXTVAL, 'Oracle UNIX Document', 'This document constains the word UNIX and the word Oracle!'); COLUMN name FORMAT A30; SELECT a.name, b.category FROM my_docs a, my_categories b, my_doc_categories c WHERE c.my_doc_id = a.id AND c.my_category_id = b.id; NAME CATEGORY ------------------------------ ------------------------------ Oracle Document Oracle SQL Server Document SQL Server UNIX Document UNIX Oracle UNIX Document UNIX Oracle UNIX Document Oracle 5 rows selected.
The output shows that the documents have been assigned to the correct categories. Note, the "Oracle UNIX Document" document has been assigned to both the "Oracle" and "UNIX" categories.
Index Maintenance
Not all Oracle Text indexes are automatically updated as records are added or deleted. To synchronize the index with the table you must call.
SQL> EXEC CTX_DDL.SYNC_INDEX('my_docs_doc_idx');
Regular synchronizations of the index can be automated using the DBMS_JOB
package. The following script is provided to make this task easier.
$ORACLE_HOME/ctx/sample/script/drjobdml.sql
It can be called from SQL*Plus whilst logged on as the index owner as follows.
SQL> @drjobdml.sql index-name interval-mins SQL> @drjobdml.sql my_docs_doc_idx 60
Regular synchronization of text indexes can cause fragmentation which affects query performance. To correct this situation the index can be rebuilt or optimized. Index optimization can be performed in three basic modes (FAST
, FULL
or TOKEN
). The FAST
mode compacts fragmented rows but does not remove old data.
BEGIN CTX_DDL.OPTIMIZE_INDEX('my_docs_doc_idx','FAST'); END; /
The FULL
mode optimizes either the entire index or a portion of it, with old data removed.
BEGIN CTX_DDL.OPTIMIZE_INDEX('my_docs_doc_idx','FULL'); END; /
The TOKEN
mode perfoms a full optimization for a specific token.
BEGIN CTX_DDL.OPTIMIZE_INDEX('my_docs_doc_idx','TOKEN', token=>'Oracle'); END; /
For more information see:
Hope this helps. Regards Tim...
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
한가지 여쭤보고 싶은게 있는데요.
NORMDIST 함수를 사용하고 싶은데. 라이브러리에 포함되어 있지 않은 것 같아서요.
제가 잘 몰라서 못찾는 것 같은데. 좀 알려주시면 고맙겠습니다.^^*
package JSci.maths.statistics;
import JSci.maths.*;
/**
* The NormalDistribution class provides an object for encapsulating normal distributions.
* @version 1.1
* @author Jaco van Kooten
*/
public final class NormalDistribution extends ProbabilityDistribution implements NumericalConstants {
private double mean,variance;
private double pdfDenominator,cdfDenominator;
/**
* Constructs the standard normal distribution (zero mean and unity variance).
*/
public NormalDistribution() {
this(0.0,1.0);
}
/**
* Constructs a normal distribution.
* @param mu the mean.
* @param var the variance.
*/
public NormalDistribution(double mu,double var) {
mean=mu;
if(var<=0.0)
throw new OutOfRangeException("The variance should be (strictly) positive.");
variance=var;
pdfDenominator=SQRT2PI*Math.sqrt(variance);
cdfDenominator=SQRT2*Math.sqrt(variance);
}
/**
* Constructs a normal distribution from a data set.
* @param array a sample.
* @author Mark Hale
*/
public NormalDistribution(double array[]) {
double sumX=array[0];
double sumX2=array[0]*array[0];
for(int i=1;i<array.length;i++) {
sumX+=array[i];
sumX2+=array[i]*array[i];
}
mean=sumX/array.length;
variance=(sumX2 - array.length*mean*mean)/(array.length-1);
pdfDenominator=SQRT2PI*Math.sqrt(variance);
cdfDenominator=SQRT2*Math.sqrt(variance);
}
/**
* Returns the mean.
*/
public double getMean() {
return mean;
}
/**
* Returns the variance.
*/
public double getVariance() {
return variance;
}
/**
* Probability density function of a normal (Gaussian) distribution.
* @return the probability that a stochastic variable x has the value X, i.e. P(x=X).
*/
public double probability(double X) {
return Math.exp(-(X-mean)*(X-mean)/(2*variance))/pdfDenominator;
}
/**
* Cumulative normal distribution function.
* @return the probability that a stochastic variable x is less then X, i.e. P(x<X).
*/
public double cumulative(double X) {
return SpecialMath.complementaryError(-(X-mean)/cdfDenominator)/2;
}
/**
* Inverse of the cumulative normal distribution function.
* @return the value X for which P(x<X).
*/
public double inverse(double probability) {
checkRange(probability);
if(probability==0.0)
return -Double.MAX_VALUE;
if(probability==1.0)
return Double.MAX_VALUE;
if(probability==0.5)
return mean;
// To ensure numerical stability we need to rescale the distribution
double meanSave=mean,varSave=variance;
double pdfDSave=pdfDenominator,cdfDSave=cdfDenominator;
mean=0.0;
variance=1.0;
pdfDenominator=Math.sqrt(TWO_PI);
cdfDenominator=SQRT2;
double X=findRoot(probability, 0.0, -100.0, 100.0);
// Scale back
mean=meanSave;
variance=varSave;
pdfDenominator=pdfDSave;
cdfDenominator=cdfDSave;
return X*Math.sqrt(variance)+mean;
}
}
http://jsci.sourceforge.net/api/JSci/maths/statistics/NormalDistribution.html
위 주소입니다.
http://jsci.sourceforge.net/
소스다운로드 해서 분석하면 될것 같네요..