1. 테스트용 테이블 생성


1) 테이블 생성

create table t_tree_test(
 no number(10),
 name varchar2(30),
 parent_no number(10)
);


2) 기본키 지정

alter table t_tree_test
add constraint t_tree_test_pk primary key(no);


3) 자기참조관계 외래키 지정

 - 존재하는 레코드만 부모로 사용될 수 있음.

 - 부모레코드 삭제시 해당 외래키값 null로 셋팅하여 부모없는 자식이 됨.

alter table t_tree_test
add constraint t_tree_test_fk01 foreign key(parent_no)
references t_tree_test(no) on delete set null;


4) 부모를 의미하는 칼럼에 index생성

create index t_tree_test_idx_01
on t_tree_test(parent_no);

 


 

2. 테스트용 데이터 생성

begin


  --root 데이터 입력
  insert into t_tree_test
         (no, name)
  values (0, '개발회사');
 
  --2레벨 데이터 입력
  for i in 1..3
  loop
      insert into t_tree_test
      values(i, '개발'||i||'팀', 0);   
  end loop;
 
  --3레벨 데이터 입력
  for i in 4..13
  loop
    if i <= 6 then
      insert into t_tree_test
      values(i, '개발자'||i, 1);
    elsif i > 6 and i <= 9 then
      insert into t_tree_test
      values(i, '개발자'||i, 2);     
    else
      insert into t_tree_test
      values(i, '개발자'||i, 3);   
    end if;
  end loop; 


  commit;


end;
/

 


 


3. 계층쿼리

1) ​우선 가장 보편적인 형태

select lpad(' ', 5*(level-1))||name name, no, parent_no
from t_tree_test
where no <> 13
start with no = 0
connect by parent_no = prior no;


 


 - start with no = 0 : 최상위 root를 지정함

 - connect by parent_no = prior no : 부모, 자식관계 설정

   (자식을 의미하는 항의 칼럼 앞에 prior 지시어를 사용하도록 함.)

 - 만약 위와 같이 부모->자식이 아닌 자식->부모 형태로 출력을 원한다면

   자식을 root로 사용하게 되는 셈이므로 start with 절에 해당 자식을 지정하도록 하고,

   부모를 의미하는 항의 칼럼 앞에 prior 지시어를 사용하도록 함.


※ 계층쿼리의 경우 수행순서는 start with > connect by > where

그렇기 때문에 부득이하게 where조건으로 필터한 뒤에 계층쿼리를 사용하고 싶다면

아래와 같이 inline view 형태로 만들어야 한다.

select lpad(' ', 5*(level-1))||name name, no, parent_no
from
(select name, no, parent_no
from t_tree_test
where no <> 13)
start with no = 0
connect by parent_no = prior no;


2) 자식들 간에 정렬이 필요한 경우

select lpad(' ', 5*(level-1))||name name, no, parent_no
from t_tree_test
where no <> 13
start with no = 0
connect by parent_no = prior no
order siblings by no desc;



3) 무한루프를 방지

update t_tree_test
set parent_no = 0
where no = 0;

commit;

위 구문을 통해 최상위 root의 parent_no를 자기 자신으로 하도록 한다.

2)번 항목의 계층쿼리를 수행하면 아래와 같은 에러 메시지가 출력된다.


----------------------------------------------------

ORA-01436: CONNECT BY의 루프가 발생되었습니다
01436. 00000 -  "CONNECT BY loop in user data"
*Cause:   
*Action:

----------------------------------------------------


일반적으로 최상위 root 레코드에서 부모를 의미하는 칼럼 데이터가 자기자신일때

무한루프가 발생하게 되어 위와 같은 에러메시지를 출력하게 된다.

이때는 아래와 같이 connect by 바로 뒤에 nocycle 키워드를 지정해서 무한루프를 방지해야 한다.
nocycle를 적용하게 되면 중복된 데이터 전개될 경우 이를 leaf로 판단하여 더이상 전개하지 않게 된다.


select lpad(' ', 5*(level-1))||name name, no, parent_no
from t_tree_test
where no <> 13
start with no = 0
connect by nocycle parent_no = prior no
order siblings by no desc;

계층쿼리에서 이러한 무한루프가 발생될 수 있는 현상때문에

대부분 최상위 root 레코드에서 부모를 의미하는 칼럼 데이터를 자기자신으로 할당하진 않지만

만에 하나 그렇게 된다면 위와 같이 nocycle 지시어를 통해 무한루프를 회피할 수 있다.

계층쿼리 사용시 기본적으로 항상 위와 같이 nocycle옵션을 포함해서 작성할 것인지

아니면 경우에 따라 사용할 것인지는 본인의 선택이다.


4) 계층 쿼리에서 지원되는 가상 칼럼

select lpad(' ', 5*(level-1))||name name, no, parent_no
         ,level
         ,connect_by_isleaf isleaf
         ,connect_by_iscycle iscycle
from t_tree_test
where no <> 13
start with no = 0
connect by nocycle parent_no = prior no
order siblings by no desc;


  - level : 계층쿼리의 레벨을 나타낸다. root(1)부터 1씩 증가되어 할당됨.
  - connect_by_isleaf : 해당 row가 leaf 노드인지 아닌지를 나타낸다.(리프:1, 리프X:0)
  - connect_by_iscycle : 위에서 설명한 무한루프가 발생하는 발생하는 행인지 아닌지를 나타낸다(cycle:1, nocycle:0)
     * 해당 칼럼은 반드시 nocycle 키워드가 지정된 상태에서만 사용가능하다.
       cycle가 발생했다면 출력이 불가하다는 말인데 nocycle 키워드를 줘야 결과를 볼 수 있기 때문이다.





5) 계층쿼리에서 지원되는 함수

select lpad(' ', 5*(level-1))||name name, no, parent_no
       ,level
       ,connect_by_isleaf isleaf
       ,connect_by_iscycle iscycle
       ,connect_by_root(name) root
       ,sys_connect_by_path(name,'/') path
from t_tree_test
where no <> 13
start with no = 0
connect by nocycle parent_no = prior no
order siblings by no desc;


 - connect_by_root : 계층형 쿼리의 root 항목을 출력한다. 즉, start with 절에서 지정한 노드가 기준이 된다.
    connect_by_root(칼럼명)과 같이 루트 노드의 특정칼럼만 출력할 수 있다.
 - sys_connect_by_path : 해당 노드에 대해 root노트부터의 풀 경로를 표시할 수 있다.
    sys_connect_by_path (칼럼명, '/')와 같이 특정 칼럼을 기준으로 경로를 표시할 수 있다.
    두번째 파라미터는 구분값이다.


 

Posted by mypiece
,

1. 구현 조건


1) 원글은 내림차순, 답변글은 오름차순 정렬을 하도록 한다.
2) 원글이 삭제된 경우에도 답변글은 유지하도록 한다.  
 - 기본적으로 계층처리시 level을 유지시키기 위해 데이터를 삭제하더라도
   실제로 삭제하지는 않고 삭제를 의미하는 칼럼을 추가하여 삭제여부를 저장한다.
   실제로 삭제를 하게 되면 delete가 아니라 update를 통해 해당 칼럼을
   수정하고, 해당 레코드의 바로 아래 자식의 title에 [원글삭제] 라는 접두어를 추가하는
   procedure를 만들어서 삭제 수행시 해당 procedure를 사용하도록 하자.
   이렇게 되면 원글이 삭제되더라도 filtering하여 원글은 보이지 않지만
   답변글은 level이 유지된 채로 확인이 가능하다.


 


 


2. 테스트용 테이블 생성


create table t_replayboard_test(
 board_no number(10),    --기본키
 title varchar2(100),    --제목
 c_date date default sysdate,  --등록일
 parent_no number(10),    --상위조상
 prototype_no number(10) not null, --최상위조상(그룹번호)
 del_yn char(1) default 'N'
);


--기본키 생성
alter table t_replayboard_test add constraint t_replayboard_test_pk
primary key (board_no);


--자기참조 모델링

--바로 위의 부모를 나타내는 parent_no의 경우 board_no에 존재하는 번호가 등록가능하다.​
--기본적으로 답변글이 있는 데이터는 삭제할 수 없도록 한다.
alter table t_replayboard_test add constraint t_replayboard_test_fk01
foreign key (parent_no) references t_replayboard_test(board_no);


--최상위조상 을 나타내는 번호의 경우 board_no에 존재하는 번호만 등록가능하다.
alter table t_replayboard_test add constraint t_replayboard_test_fk02
foreign key (prototype_no) references t_replayboard_test(board_no);


--del_yn 입력값 제한
alter table t_replayboard_test add constraint t_replayboard_test_ck01
check (del_yn in('Y','N'));


--계층처리 및 삭제로직을 위한 인덱스 생성
create index t_replayboard_test_idx02
on t_replayboard_test(parent_no);


 


 


2. 테스트용 데이터 입력


--원글 등록
begin
  for i in 1..10
  loop
    insert into t_replayboard_test(board_no, title, c_date, prototype_no)
    values(i, '원글'||i, sysdate+(i/24/60/60), i); --등록시간은 1초의 차이가 나도록    
  end loop;
  commit;
end;

--답변등록

--답변1
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(11, '[RE]원글1의 답변', 1, 1, sysdate+(1/24/60/60));
--values(시퀀스, [RE]가 앞에 붙은 제목, 부모키, 최고조상키, 등록일);

--답변1의 답변1
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(12, '[RE][RE]원글1의 답변의 답변1', 11, 1, sysdate+(2/24/60/60));

--답변1의 답변1의 답변1
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(13, '[RE][RE][RE]원글1의 답변의 답변의 답변', 12, 1, sysdate+(3/24/60/60));

--답변1의 답변2
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(14, '[RE][RE]원글1의 답변의 답변2', 11, 1, sysdate+(4/24/60/60));


--답변2
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(15, '[RE]원글9의 답변', 9, 9, sysdate+(5/24/60/60));

--답변2의 답변1
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(16, '[RE][RE]원글9의 답변의 답변1', 15, 9, sysdate+(6/24/60/60));

--답변2의 답변2
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(17, '[RE][RE]원글9의 답변의 답변2', 15, 9, sysdate+(7/24/60/60));

--답변2의 답변1의 답변1
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(18, '[RE][RE][RE]원글9의 답변의 답변1의 답변1', 16, 9, sysdate+(8/24/60/60));

--답변2의 답변1의 답변2
insert into t_replayboard_test(board_no, title, parent_no, prototype_no, c_date)
values(19, '[RE][RE][RE]원글9의 답변의 답변2의 답변2', 16, 9, sysdate+(9/24/60/60));

commit;

 


 


4. 계층데이터 select 쿼리

 

select lpad(' ', (level-1)*5)||title title,
       board_no,
       parent_no,
       prototype_no,
       c_date      
from t_replayboard_test
where del_yn = 'N'
start with parent_no is null
connect by parent_no = prior board_no
order siblings by prototype_no desc, board_no asc;


 



4. 글 삭제 로직

 - 아래는 익명블록으로 생성하였지만 실제 시스템에서는 procedure로 만들어서 사용하면 되겠다.


--삭제로직
--board_no=16 삭제
declare
  p_board_no number(10);
begin
  p_board_no := 16;
 
  update t_replayboard_test
  set del_yn = 'Y'
  where board_no = p_board_no;
 
  update t_replayboard_test
  set title = '[원글삭제] '||title
  where parent_no = p_board_no;
 
  commit;
end; 

 

Posted by mypiece
,
--날짜차이 년/월/일/시/분/초
select trunc(months_between(afterTime, beforeTime)/12) --년
       ||'년 '||
       trunc(mod(months_between(afterTime, beforeTime),12)) --월
       ||'개월 '||
       trunc(afterTime - add_months(beforeTime, months_between(afterTime,beforeTime))) --일
       ||'일 '||
       to_char(to_date(trunc(mod(afterTime - beforeTime, 1)*24*60*60),'SSSSS'),'HH24"시간 "MI"분 "SS"초"') hms --시:분:초        
from
(
select to_date('1992-12-31 20:20:20','YYYY-MM-DD HH24:MI:SS') as beforeTime,
       to_date('2014-10-01 22:10:10','YYYY-MM-DD HH24:MI:SS') as afterTime      
 from dual
)


'밥벌이 > Database' 카테고리의 다른 글

[Oracle] 제약조건 활성화/비활성화  (0) 2015.09.18
[Oracle] 외래키 옵션  (0) 2015.09.18
[Oracle] MERGE 구문  (0) 2015.09.18
[Oracle] 계층쿼리  (0) 2015.09.18
[Oracle] 답변형 게시판 구현 로직  (0) 2015.09.18
Posted by mypiece
,