반응형

에러 메시지 : 에러 메시지는 플렉스 빌더 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

Posted by 1010
53.jasper&ireport2012. 9. 25. 03:22
반응형
이 문서는 나는나 님의 블로그에 올려져 있는
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로 템플레이트 파일 만들기
- 우선 설치한 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 버튼을 선택합니다.
아래 그림처럼 입력합니다.(각자의 환경에 맞추면 됩니다.)





이 문서에서는
Username 에는 postgres
Password에는 postgres를 넣었습니다.
그 옆에 Save password는 체크해 주는 것이 나중을 위해 편합니다.
( 안 그러면 새로 실행할 때마다 패스워드 넣으라고 시끄럽게 떠듭니다. )
Test 버튼을 눌러 제대로 연결이 되는지 확인합니다.
연결이 안되면 물론 안됩니다.
당연한 얘기겠지만 데이타베이스명, 드라이버명,유저명,패스워드등을 잘 체크합니다.
연결이 잘 되면 Save버튼으로 저장하고 나옵니다.
- 여기서 데이타베이스의 데이타를 정리하고 갑시다.
현재 이 문서에서 사용하고 있는 데이타베이스는 PostgreSql이므로 그것을 위주로
설명을 하겠지만 다른 데이타베이스도 별 문제없이 사용될 것입니다.
데이타베이스에 대한 설명은 위에서도 밝혔듯이 하지 않습니다.
어느 정도 데이타베이스를 다룰줄 알아야 이 밑의 데이타 준비를 할 수 있을 것입니다.
데이타베이스 생성
CREATE DATABASE "MyDB01"
WITH OWNER = postgres
ENCODING = 'EUC_KR'
TABLESPACE = pg_default;
테이블생성
: rel 이라는 테이블을 생성합니다.
이것은 인맥구분에 사용할 테이블입니다.
다음은 생성 SQL입니다.
CREATE TABLE rel
(
rel_id int2 NOT NULL,
rel_name text,
CONSTRAINT "rel_PK" PRIMARY KEY (rel_id)
)
: tbl01 이라는 테이블을 생성합니다.
이것은 인맥정보로서 사용할 테이블입니다.
다음은 생성 SQL입니다.
CREATE TABLE tbl01
(
uid int4 NOT NULL,
fname text,
telno1 text,
rel_id int2,
CONSTRAINT "tbl01_PK" PRIMARY KEY (uid)
)
다음은 위의 테이블에 넣을 데이타를 SQL문으로 만든 것입니다.
insert into rel(rel_id, rel_name) values(1,'가족');
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(1,'아버지','011-111-1111',1);
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(5,'삼촌','055-555-5555',2);
insert into tbl01(uid,fname,telno1,rel_id) values(6,'매형','066-666-6666',2);
insert into tbl01(uid,fname,telno1,rel_id) values(7,'덕팔이','077-777-7777',3);
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(10,'최선배','100-100-1000',4);
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(13,'송혜교','123-123-1234',5);
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);
이 SQL을 실행시켜 기본 데이타를 준비하십시오.
(각자, 준비해도 됩니다.)
- 데이타까지 준비가 됐으니 보고서를 만들 SQL을 만듭니다.
이 문서에서는 이렇게 만들었습니다.
SELECT b.rel_name, a.fname, a.telno1
FROM tbl01 a LEFT JOIN rel b ON a.rel_id = b.rel_id
ORDER BY b.rel_name
- 이제 보고서를 만들 준비가 끝났습니다.
보고서를 만듭시다.(자신의 화면과 이 문서를 맞추어서 보시길...)
우선 iReport의 기능중에 Report Wizard 라는 편리한 기능이 있는데
이것을 이용하면 간단한 보고서 양식은 금방 만들 수 있습니다.
- 메뉴에서 File->Report Wizard 선택
- Step 1 : insert the query to retrieve report fields
Connection / Datasource 에 위에서 만든 데이타소스명을 선택합니다.
여기서는 MyDB01
밑에 SQL Query에 위에서 준비한 SQL 문장을 넣습니다.
SELECT b.rel_name, a.fname, a.telno1
FROM tbl01 a LEFT JOIN rel b ON a.rel_id = b.rel_id
ORDER BY b.rel_name


- Next 버튼을 누르면 "SmartCard PIN"이라는 윈도우가 나와서 Password를
넣으라고 나오는 사람은 데이타베이스 접속 패스워드를 넣어주면 됩니다.
위에서 데이타소스 정의시 Save password에 체크를 해 둔 사람은 이 윈도우는
나타나지 않습니다.
- Step 2 : select fields 화면에서 모든 컬럼명을 오른쪽으로 선택해서 넣습니다.
모든 컬럼을 선택하는 것은 단순히 ">>" 버튼을 누르면 됩니다.
Next 버튼 클릭
- Step 3 : Group by 윈도우는 그냥 두고 Next 버튼 클릭
- Step 4 : choose layout
Tabular layout을 선택
classicT.xml 선택
Next 버튼 선택
- Step 5 : finish
Finish 버튼 클릭
여기까지 왔으면 보고서 하나가 거의 완성이 되었습니다.
우선 저장을 합니다.
(알기쉬운 곳에 저장하는게 좋겠죠?
괜히 아주 잘 보관하겠다고 했다가
나중에 어디다 저장했는지 까먹는 황당한 수가 생깁니다.)
이 문서의 경우에는 D:/TEST/report01.jrxml라는 이름으로 저장했습니다.
다음은, 컴파일
메뉴->Build->Compile 를 선택하면 컴파일 됩니다.
메뉴바에 이쁘장한 아이콘들도 준비되어 있는데 컴파일 아이콘을 찾아서 해도 됩니다.
오른쪽 끄트머리쯤에 있습니다.
( 이 컴파일에서 나오는 에러메세지 같은 것들은 각자 알아서 해결하시길...
이 부분은 저도 설명할만한 수준이 아니라서리..)
다음은 출력될 보고서를 미리 봅니다.
메뉴->Build->JRViewer preview 선택
메뉴->Build->Execut(with active connection)을 선택
잘 보이나요?
잘 보여야 합니다.
안 보이면? 당연히 앞으로 돌아가서 다시 체크해 봐야겠죠?
다음은 PDF 형식으로 보죠.
메뉴->Build->PDF preview 선택
메뉴->Build->Execut(with active connection)을 선택
잘 보이나요?
잘 안보일겁니다.
미리 설명하려다가 위에서 잊어먹었습니다.
PDF로 보려면 iReport에 PDF는 뭘로 보겠다고 정해줘야 합니다.
이 문서에서는 Adobe Reader로 PDF를 볼 것입니다.
그래서 문두의 테스트 환경에 Adobe Reader 설치라고 친절히(?) 적어 놓았던 거죠.
메뉴->Options->Settings 선택
External programs 탭 선택
PDF Viewer에 Adobe Viewer 프로그램 선택
(이 문서에서는 C:/Program Files/AdobeAcrobat 7.0/Reader/AcroRd32.exe )
Save 버튼을 선택해서 저장하고 나옵니다.
다시 위의 PDF 로 보는 걸 해보죠.
잘 보이나요?
이번엔 보입니다.
보이긴 보이는데 뭔가 이상하죠?
한글 항목이 나타나질 않습니다.
그럼 제대로 보이는겁니다.
장난하냐?
아직까진 제대로 된 거란 얘기입니다.
이제부터 한글이 제대로 보이도록 하는 작업을 합니다.
파일을 다운받는데 iTextAsian.jar 라는 파일을 받습니다.
자세히는 모르겠는데 iText.jar와 연계해서 2바이트 코드를 PDF생성시
가능하게 해 주는 것 같습니다.
다운받은 iTextAsian.jar 를 iReport의 lib 폴더에 넣어줍니다.
이 문서에서는 C:/Program Files/JasperSoft/iReport-1.2.5/lib 에 복사했습니다.
iReport를 다시 실행시킵니다.
(노파심; 현재 iReport가 화면에 떠 있으면 닫고 다시 iReport를 실행해야 합니다.)
좀 전에 만든 D:/TEST/report01.jrxml을 불러들입니다.
그리고 한글이 보일 항목을 지정하고 옆에 Element Properties 창에서
PDF Font에 HYGoThic-Medium 를
PDF Encoding에 UniKS-UCS2-H (Korean)를 지정하고
다시 PDF Preview를 해봅니다.
한글이 잘 보이는가요?
잘 보여야 합니다.
더 이상은 나도 잘 모르기 때문에....
잘 보이면 보고서를 좀 더 다듬죠.
영어로된 항목명을 적당히 한글로 고쳐주고 이 때도 폰트를 맞춰줘야 합니다.
(위에 설명한 폰트말고 몇 개가 더 되는게 있는데 그건 각자 찾아 보십시오.)
이 문서에서는 이렇게 다듬었습니다.




참고로 위의 폰트 설정은 나중에 웹보고서 출력시 PDF로 출력할 때
한글을 보여주려고 하기 때문입니다.
위와 같이 설정하고 실행하는 웹어플리케이션에서 PDF로 볼 때 iTextAsian.jar를
찾지 못하면 에러가 납니다.
하지만 애플릿뷰어로 보여줄 때는 맞춰 줄 필요 없습니다.
이제 iReport로 작업하는 것은 이것으로 끝냅니다.
iReport의 디자인 방법이나 다른 기능들은 이 문서에서 설명하지 않습니다.
이 문서의 목적은 웹보고서를 어떻게 구현하는가에 초점을 맞추고 있기 때문입니다.

지금까지 한 것은 자동완성기능인 Report Wizard 를 이용한 것입니다.
수동으로 만드는 것도 각자 연습해 보시기 바랍니다.
아마 다른 비쥬얼툴을 사용해 보신 분이라면 어려움 없이 하실 수 있을 겁니다.
첨부파일을 이용하기
다음은 이 보고서를 웹을 통해 보여주어야 하기 때문에
웹어플리케이션을 하나 만듭니다.
아래의 첨부파일을 풀어서 톰캣이 웹어플리케이션으로서
인식하도록 합니다.


첨부파일 : MyWeb02.zip
웹어플리케이션을 만드는 부분은 설명하지 않습니다.
제 블로그를 보시거나 다른 사이트를 참조하시기 바랍니다.
이제부터 첨부파일의 구조대로 설명합니다.
이 문서에서는 톰캣을 이용하여 MyWeb02라는 웹어플리케이션을 생성했고
그 구조는 다음과 같습니다.




이 예제 웹어플리케이션을 실행하기 위해서는 준비과정이 필요합니다.

예제 소스에는 모든 라이브러리 파일은 담지 않았습니다.

파일크기도 커지고 각자 해 보는 것이 좋기 때문입니다.

예제에 필요한 파일 복사해 넣기

/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 안을 확인해 보면 클래스 파일들이
생성되어 있을 겁니다.

자~~~~ㅅ! 다~~ 되었습니다.

이제 웹어플리케이션 MyWeb02를 재로드하거나
톰캣을 재기동한 후 브라우저로 열어봅니다.

http://localhost:8080/MyWeb02

그러면

MENU



Applet View

PDF View
이런 화면이 나타납니다.
Applet View 를 선택하면
Report name : report01 report02
cond1 : ___________
cond2 : ___________


이런 화면이 나타납니다.
블로그 화면에선 좀 틀리네요.
화면 이미지를 넣을까하다가 중요한 것도 아니라서 관뒀습니다.
test 버튼을 누르면
iReport에서 JRViewer preview 로 본 화면이 나타날 것입니다.
여기서
혹시, 서버와 클라이언트 이렇게 두 대로 이 문서를 따라 하시는 분이 계신다면
클라이언트에서 서버에 접속해서 위의 페이지를 열었을 때
해당 클라이언트에 JRE가 설치되어 있지 않거나
JRE5.0 Update8 이하의 버전이 설치되어 있다면
아래의 화면이 나타날 것입니다.





예 버튼을 클릭하면 JRE를 설치하는 화면이 나옵니다.





동의함 버튼을 선택합니다.
설치가 진행됩니다.
설치가 끝나면 자동으로 혹은 지멋대로
C:/Program Files/Java/jre1.5.0_08 에 JRE가 설치되어 있습니다.
이 경로는 어디서 컨트롤 하는건지 알 수 가 없었습니다.
자동으로 여기에 설치되는 것 같은데 테스트한 운영체제도 역시
Windows2000이였는데 다른 운영체제에서도 같은 장소에 깔리는지는
체크해 보지 못했습니다.
하여간 설치가 된 후 애플릿 화면이 나오길 기대하시겠지만
현재는 에러가 납니다.
그것은 소스 안을 살펴보시면 알게되는데
http://localhost:8080/MyWeb02~~~ 라고 정의 되어 있기
때문입니다.
이 부분도 여러분의 환경에 맞게 서버명을 고쳐서 해 보시기 바랍니다.
그리고 밑에서 설명하는 policy 설정 툴은
C:/Program Files/Java/jre1.5.0_08/bin 안에 들어있습니다.
자~~ ... 여하튼 현재 서버도 클라이언트도 같은 머신이라는 가정하에
계속 갑시다.
서버와 클라이언트가 같은 머신이라면 이상없이 애플릿 화면이 나타날텐데
그 화면에서 프린터 아이콘을 눌러 인쇄를 하려 하면 현재로는 에러 화면이
나타날 것입니다.
이 에러를 잡느라 이틀동안 인터넷을 얼마나 많이 헤멧는지 모릅니다.
원인은 애플릿의 클라이언트 권한 문제였는데
다음과 같이 해서 우선 문제를 해결하시고
클라이언트에게 권한파일을 배포하시는 건 알아서 하시길 바랍니다.
(너무 무책임한 것 같지만 저도 잘 모르겠다는...)
하여간 다음을 보시고 판단하시길 바랍니다.
클라이언트의 명령 프롬프트로 나가서 아래 명령을 실행합니다.
>policytool[엔터]
다음과 같은 메세지가 나타납니다.



확인을 누른 다음
나타난 화면에서 정책항목추가 버튼을 선택합니다.
다음 화면에서 사용권한추가 버튼을 선택합니다.
사용권한 드롭다운리스트박스에서 AllPermission 을 선택합니다.
화면은 이렇게 됩니다.




다음은 확인을 선택합니다.
완료를 선택합니다.
파일->저장을 눌러
이 문서에서는
C:/Documents and Settings/Administrator 에 .java.policy 라는 파일명으로
저장합니다.
파일명 앞에 쩜(.)이 있는 것에 주의하시기 바랍니다.
노파심 : Windows2000이나 XP 사용하시는 분은 로그인한 유저의
루트 디렉토리에 만들어 줍니다.
서버가 아닙니다.
이 웹어플리케이션에 접근하는 클라이언트 입니다.
이렇게 설정이 끝났으면 좀 전의 웹어플리케이션을 처음부터 다시
실행해서 애플릿의 프린터 아이콘으로 인쇄를 해 보시기 바랍니다.
프린터 선택화면이 나오면 성공적으로 퍼미션 문제가 해결된 것입니다.
사실, 저는 이 자바의 퍼미션에 관해 자세히 모릅니다.
위에서 정의한 AllPermission은 좋은 설정이 아닐지 모르니 실제 적용에는
주의하시기 바랍니다.(혹시, 잘 아시는 분이 계시면 저에게도 알려주십시오.)
다음은 PDF 도 실행해 보십시오.
아마, 잘 될 것입니다.
이 문서의 환경과 같고 제가 올린 소스를 그대로 적용하신 분이라면
별문제없이 잘 실행될 것입니다.
이 문서를 작성하느라 제 운영체제를 세번이나 새로 설치해서
이 문서를 보며 재검증까지 마쳤으니까요.
( 그런대도 설명이 이모양이냐? 하시면 저도 어쩔수가 없다는.... )
이제 iReport를 이용한 웹보고서 출력 기능 설명은 끝났습니다.
첨부파일에 대한 자세한 설명은 하지 않았습니다.
설명을 드리지 않아도 제일 처음에 적은 것 처럼
이 정도는 해석하실 수 있는 분에게 맞춰서 작성한 문서입니다.
대신에 이 문서의 처음에 소개해 드린 나는나님의 블로그에 방문하셔서
매뉴얼을 다운받아서 살펴 보시면 많은 도움이 되실 것입니다.
(사실, 소스설명하기에는 실력이 딸려서리...)
또 한가지, 예제의 폼에 있는
cond1과 cond2 는 다른 보고서를 만들 때 SQL에 파라메터를 넘길 경우를
테스트하기 위해 만들어 둔 것입니다.
이 부분은 위의 나는나님의 매뉴얼을 보면서 공부를 해 보시기 바랍니다.
센스가 있으신 분들은 금방 하실 수 있으리라 믿습니다.
그리고 소스의 안에도 위의 두 개의 파라메터를 처리하는 부분이 같이 들어있습니다.
여러분의 몫으로 남겨 둡니다.
(
하다만듯한 느낌이지만 직접해 보시는 것이 나을 것 같습니다.
비록, 문장도 문서의 구성도 서툴지만 찢어진 신문을 읽는 것처럼
이런 문서를 읽는 분들이 있기 때문에 되도록이면 직접해 보시라고...
제 게으름의 변명입니다.
)
그리고, 아직 왜 그런지 정확하게 파악이 안됐지만
위의 JDK 5.0 환경에서 애플릿으로 보고서를 출력시
클라이언트에서 서버로부터 애플릿을 다운받아 볼 때 시간이 꽤 걸렷습니다.
그런데 환경을 J2SE1.4.2_12 로 바꾸고 재컴파일하여 실행하니 굉장히 빨라졌습니다.
하지만 어떤 클라이언트에서는 JRE5.0대가 깔려있는데도 상관없이
빠른 반응속도를 보이기도 했는데 정확하게 원인규명이 되지 않았습니다.
왜 마지막에 이런 내용을 적냐면요....
음...
제 테스트 과정이 이랬거덩요....
하핫.......
열공하세요.

[출처] http://soulcinder.egloos.com/124044

Posted by 1010
01.JAVA/Java2012. 9. 21. 06:58
반응형

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();
  }
 }
}

 

출처 : http://pskppoppo.egloos.com/2994706

Posted by 1010
01.JAVA/Java2012. 9. 21. 06:55
반응형

사용자의 로그정보가 필요해 아래와같이 간단하게 만들어봤다.

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나 새로운 객체를 생성하여 매개인자로 넣으면 에러가난다.

Posted by 1010
01.JAVA/Java2012. 9. 21. 06:52
반응형

간단하긴 한걸까...

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로 만들어 봐야하는데 지금 귀찮음.

Posted by 1010
01.JAVA/Java2012. 9. 21. 06:46
반응형
1. split
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

차이점 알겠나?

 

Posted by 1010
01.JAVA/Java2012. 9. 21. 06:45
반응형

사용자의 로그정보가 필요해 아래와같이 간단하게 만들어봤다.

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나 새로운 객체를 생성하여 매개인자로 넣으면 에러가난다.

Posted by 1010
02.Oracle/DataBase2012. 9. 21. 06:30
반응형


 

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 개의 행이 선택되었습니다.

[출처] 분석함수|작성자 은영

Posted by 1010
02.Oracle/DataBase2012. 9. 21. 05:34
반응형

Oracle PIVOT

A common requirement for queries is to turn rows into columns, or the other way around.

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
Note line 12 where we can provide column aliases to the fresh output.

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)
)
)
/
I wonder if that might be an interesting catalyst in some future queries

 

출처 : http://www.grassroots-oracle.com/2012/05/oracle-pivot.html 

Posted by 1010
02.Oracle/DataBase2012. 9. 21. 05:30
반응형

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

출처 : http://www.oracle-developer.net/display.php?id=506

Posted by 1010