database.sarang.net
UserID
Passwd
Database
DBMS
MySQL
PostgreSQL
Firebird
Oracle
Informix
Sybase
ㆍMS-SQL
DB2
Cache
CUBRID
LDAP
ALTIBASE
Tibero
DB 문서들
스터디
Community
공지사항
자유게시판
구인|구직
DSN 갤러리
도움주신분들
Admin
운영게시판
최근게시물
MS-SQL Q&A 6877 게시물 읽기
No. 6877
조인에 대한 문의입니다
작성자
kshap(ksw0829)
작성일
2015-01-22 13:48:25
조회수
2,581

안녕하세요

조인을 하는데 해결이 안되네요..

 

예를 들어서

학생테이블(학번(pk), 이름)

게시판테이블(게시글번호(pk), 학번, 게시글등록날짜, 게시글내용)

 

이렇게 두 테이블이 있다고 할때 한 학생이 게시판에 글을 여러번 남길 수 있고

게시글을 남길때마다 게시판 테이블에 행이 하나씩 추가됩니다.

 

이런상황에서 '모든 학생에 대해 각각의 학생들이 마지막으로 글을 남긴 날짜'만 빼오고 싶습니다

조건으로 특정 기간을 주어서 이 기간동안 게시글을 남긴 학생의 최종 게시 날짜

또 반대로 특정 기간동안 게시글을 남기지 않은 학생리스트

이렇게 결과를 출력해야합니다.

조인을 하면 게시글을 여러번 남긴학생의 경우 한 학생마다 여러행이 생기게 되고..

학번을 distinct 해봤는데 그래도 여러행으로 나오게 되네요.....

 

조언부탁드립니다ㅠㅠ

이 글에 대한 댓글이 총 13건 있습니다.

With 인사정보 As
(
  Select '1' As 학번 Union All
  Select '2' As 학번 Union All
  Select '3' As 학번 Union All
  Select '4' As 학번
),
게시판 As
(
  Select '1' As 학번,'1' As 게시글번호,'20150112' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '1' As 학번,'2' As 게시글번호,'20150113' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '1' As 학번,'3' As 게시글번호,'20150114' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '1' As 학번,'4' As 게시글번호,'20150115' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '3' As 학번,'3' As 게시글번호,'20150114' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '3' As 학번,'4' As 게시글번호,'20150112' As 게시글일자,'테스트' As 게시글내용
)
      Select
             a.학번                  As 학번
            ,IsNull(b.게시글일자,'') As 게시글일자
        From 인사정보 a
                        Left Outer Join
                                        (
                                           Select
                                                  b.학번     
                                                 ,b.게시글번호
                                                 ,b.게시글일자
                                                 ,b.게시글내용                                                                        
                                             From
                                                   (
                                                      Select
                                                             b.학번        As 학번
                                                            ,b.게시글번호  As 게시글번호
                                                            ,b.게시글일자  As 게시글일자
                                                            ,b.게시글내용  As 게시글내용
                                                            ,Row_Number() Over (Partition By 학번 Order By 게시글일자 Desc) As seq
                                                       From 게시판 b
                                                      Where b.게시글일자 Between '20150101' And '20150131'
                                                   ) b
                                            Where b.seq = 1
                                        ) b
                                            On a.학번 = b.학번

최한영(terry0515)님이 2015-01-22 16:58:15에 작성한 댓글입니다.

댓글 감사합니다. 남겨주신 댓글 계속 보고 있는데

제가 with와 윈도우 함수를 써본적이 없어서

공부하고 있습니다.

하지만 잘 이해가 가지를 않아서요 ㅠㅠ

처음에 with 후에 인사정보와 게시판을 union all 을 해주는 이유가 무엇인가요?

kshap(ksw0829)님이 2015-01-26 10:44:14에 작성한 댓글입니다.

with 는 임시테이블을 대신해서 사용하는 거라고 보시면 될겁니다.

임시테이블 사용시

Create Table #임시테이블( ~~~~)

Insert Into ( ~~~~ )

Select * From #임시테이블

 

과 같은 형태로 사용을 하게 되겠지요.

저같은 경우는 해당 데이터가 없는 상태이니

임시로 사용할 테이블과 데이터를 몇건

with 절에 먼저 기술을 해두고

하기에 있는 select 절에서는

with 절에 기술했던 data 를 활용하는 형태입니다.

 

row_number() Over ( Partition By 컬럼 Order By 일자 )

row_number는 특정조건에 맞게 sequence 를 잡아주는거라고 보시면 되요.

1) row_number() Over ( Order By 일자 Desc )

  -> 일자 기준으로 오름차순 정렬하여, 순서대로 순번을 매기는 거죠.

    제일 큰 일자가.. 1이 되겠죠?

2) Row_number() Over ( Partition By 학번

  -> 자..Order By 일자 Desc 로만 잡아주시면..

     모든 학번에 대해서, 일자로만 순번을 매겨버려요..

     그럼 문제가 되겠죠??? 각각의 학번마다..순번을 매기는거죠..

3) Row_number() Over ( Partition By 학번 Order By 일자 Desc )

  -> 상기 1) 과 2)를 조합해서 보세요..

      학번별로, 일자를 기준으로 Sequence ( 순번 ) 이 정해지겠죠..

     자, 그럼 순번이 1인 건은 무엇일까요?

     해당 학번의 최종 게시글등록한 일자가 되겠죠..

 

그래서 Where 조건절에 seq = 1 인건만 가져오게끔

쿼리에 기술해두었습니다.

Where 조건절에 일자 Between 걸어두신건 아실테고..

Left Outer Join 도 Ansi 표준이니..아실테구..

 

이정도면 되셨는지요?

 

With 나 Row_number() 등에 대해서는

웹에서 검색하시면 많은 자료가 있으니..

그걸 참고하시면 될겁니다...

 

참..With 절과..Row_number() 의 경우..

MSSQL-2005 이상 버전에서 사용이 가능합니다...

(MSSQL-2005 이전 버전 [ 대표적으로 MSSQL-2000] 에서는 지원되지 않는 함수)

 

 

최한영(terry0515)님이 2015-01-26 11:10:58에 작성한 댓글입니다.

Union All......

 

검색해보세요.......

최한영(terry0515)님이 2015-01-26 11:11:46에 작성한 댓글입니다.

Union All

 

동일한 데이터 들을 묶어주는 역할입니다.

 

Select 1 As 학번,'20141201' As 게시글일자

Union All

Select 2 As 학번,'20141213' As 게시글일자

 

With 절을 썻지요?

 

With 학번 As

(

Select 1 As 학번,'20141201' As 게시글일자

Union All

Select 2 As 학번,'20141213' As 게시글일자

)

 

저렇게 하면.. 학번이라는 임시테이블( 은 아니지만 그렇게 이해하시면 될듯하네요 )

을 이용해서 Select 할 수 있습니다.

 

 

------- 예제

With 학번 As

(

Select 1 As 학번,'20141201' As 게시글일자

Union All

Select 2 As 학번,'20141213' As 게시글일자

)

Select * From 학번

 

--결과

학번     게시글일자

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

1            20141201

2             20141231

 

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

 

제일 처음 달았던 댓글에

With 인사정보 As
(
  Select '1' As 학번 Union All
  Select '2' As 학번 Union All
  Select '3' As 학번 Union All
  Select '4' As 학번
),
게시판 As
(
  Select '1' As 학번,'1' As 게시글번호,'20150112' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '1' As 학번,'2' As 게시글번호,'20150113' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '1' As 학번,'3' As 게시글번호,'20150114' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '1' As 학번,'4' As 게시글번호,'20150115' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '3' As 학번,'3' As 게시글번호,'20150114' As 게시글일자,'테스트' As 게시글내용  Union All
  Select '3' As 학번,'4' As 게시글번호,'20150112' As 게시글일자,'테스트' As 게시글내용
)
 

 

저렇게 작성한건

인사정보 -> 라는 임시테이블

게시판    -> 이라는 임시테이블

 

및 임시테이블내의 내용을 미리 선언해둔거구요.

 

  Select
             a.학번                  As 학번
            ,IsNull(b.게시글일자,'') As 게시글일자
 

저기 Select 부터는

위의 With 절에 작성해둔 임시테이블에서

데이터를 가져 와 쓰는 겁니다......

 

 

최한영(terry0515)님이 2015-01-26 11:38:06에 작성한 댓글입니다.

자세한 답변 감사합니다.

말씀해 주신대로 하게되니 이러한 쿼리가 나오게 되었습니다.

 

select a.Nm as 거래처명

, a.Ceo as 대표자명

, a.Tel as 대표전화

, a.Hp as 핸드폰

, a.Addr as 주소

, a.Code

, b.lastcall as 마지막전화날짜

from table1 a

inner join (

select b.code

, b.lastcall

, row_number() over (partition by b.code order by b.lastcall desc) as rnum

from table2 b

where (b.lastcall between '2014-01-01' and '2015-02-26')

) b on a.code = b.code

where rnum = 1

 

'table2' 테이블에는 거래처에 전화했을 경우 당일 날짜로 데이터가 추가됩니다.

쿼리 결과는 특정기간에 거래처에 전화를 했을 경우 마지막으로 전화한 날짜와

거래처 정보를 표시합니다.

 

그런데 반대 상황일 경우 어떻게 해야 하나요?

 

예를 들어 특정기간에 전화를 하지 않은 거래처에 대해 표시하고

그 거래처에 마지막으로 전화한 날짜를 표시한다고 했을 때

between을 not between으로 고쳐보았습니다만,

한가지 문제가 있었습니다. 'table2'에 마지막으로전화한 날짜가 입력이 안된채로

데이터가 들어가는 경우도 있어서 날짜가 비어있는 경우도 있습니다.

 

그래서 만일 '가나'라는 거래처에 2015-01-01에 전화한 데이터가 있고

날짜가 빈 데이터가 있다고 할때 2014-01-01~2015-01-30 사이에 전화를 안한 데이터를 뽑는다고 하면 '가나'라는 데이터는 특정기간에 전화를 했었기 때문에 결과에 나오면 안됩니다.

 

그런데 날짜가 빈 데이터가 있는 경우가 있어 결과에 나오게 되더라구요.....

말이 너무 길고 복잡한 상황이라 전달이 잘되었는지 모르겠습니다....

 

혹시 전달되었다면 그런 경우에는 어떤 방법을 써야하나요.......?

kshap(ksw0829)님이 2015-01-27 17:49:31에 작성한 댓글입니다.
이 댓글은 2015-01-27 17:55:56에 마지막으로 수정되었습니다.


     -- 1. 조회조건에 부합되는 데이터 가져오기
     select
             a.Nm as 거래처명
           , a.Ceo as 대표자명
           , a.Tel as 대표전화
           , a.Hp as 핸드폰
           , a.Addr as 주소
           , a.Code
           , b.lastcall as 마지막전화날짜
           , 1          As gubn ( //구분값 1 = 조회조건 부합데이터 , 2 = 조회조건에 부합되지 않는 데이터
       from table1 a
                     inner join (
                                   select  b.code
                                         , b.lastcall
                                         , row_number() over (partition by b.code order by b.lastcall desc) as rnum
                                     from table2 b
                                    where (b.lastcall between '2014-01-01' and '2015-02-26')
                                ) b
                                    on a.code = b.code
                                   And rnum = 1

  Union All

     -- 2. 조회조건에 부합되지 않는 데이터 가져오기.
     select
             a.Nm as 거래처명
           , a.Ceo as 대표자명
           , a.Tel as 대표전화
           , a.Hp as 핸드폰
           , a.Addr as 주소
           , a.Code
           , b.lastcall as 마지막전화날짜
           , 2          As gubn ( //구분값 1 = 조회조건 부합데이터 , 2 = 조회조건에 부합되지 않는 데이터
       from table1 a
                     inner join (
                                   select  b.code
                                         , b.lastcall
                                         , row_number() over (partition by b.code order by b.lastcall desc) as rnum
                                     from table2 b
                                    where Not Exists (
                                                        Select 'x'
                                                          From table2 x
                                                         Where (x.lastcall between '2014-01-01' and '2015-02-26')
                                                           And x.code     = b.code
                                                           And x.lastcall = b.lastcall
                                                     )
                                      And IsNull(b.lastcall,'') <> '' -- 마지막 콜한 일자에 공란이 아닌 데이터가 있는건만              
                                ) b
                                    on a.code = b.code
                                   And rnum = 1
                                                                     
 

 

공란인 경우는 2가지가 존재합니다.

1. 말그대로 공란 ( 값이 '' 인건 )

2. Null 값 (지정되지 않은 값)

    -->  IsNull(b.lastcall,'') <> ''

        -- Null 값이면, '' (공란) 으로 치환해주고..그 치환해준값이 '' 가 아닌건.. ( 즉 공란이 아닌건 )

 

제대로 이해를 한건지 모르겠네요-_-;;;

최한영(terry0515)님이 2015-01-27 18:20:36에 작성한 댓글입니다.
이 댓글은 2015-01-27 18:20:56에 마지막으로 수정되었습니다.

마지막 콜한 일자가 있는 데이터는 마지막 콜한 일자로 가지고 오고

콜한 일자가 없는 데이터는 공란인 채로 갖고 와야 합니다.

그런데 한 거래처당 공란인 데이터가 여러개 일 수 있습니다...

그래서 굉장히 난해 하네요ㅠㅠ

kshap(ksw0829)님이 2015-01-28 08:19:15에 작성한 댓글입니다.

자..일단 조회조건을 한번 정리를 해보시지요..

1. 조회기간중 콜한 데이터가 있는건

    --> 기존 쿼리대로 데이터를 가지고 온다. ( 문제될 소지 없음 )

2. 조회기간에 데이터는 있지만..콜한 데이터가 없는건

    --> 조회된 값들중 최종일자를 보여준다.

 

상기 1과 2가 맞는건지요?

그리고 또 하나..

해당 데이터는 거래처별로 나와야 할 듯 한데..

1. 에서 나온 거래처의 데이터는

2. 에서도 안나와야 되는게 맞는지??

 

* 추가 : 애매한 부분이 2번인듯한데요..

table1 에는 일자 관련 데이터가 없는듯하고..

table2 에 일자 데이터가 있는 듯 합니다...

 

table2 의 일자 컬럼 ( lastcall ) 이 공란인경우

무엇을 기준으로?????? 데이터를 빼야되는지요?

최한영(terry0515)님이 2015-01-28 08:42:57에 작성한 댓글입니다.
이 댓글은 2015-01-28 09:00:51에 마지막으로 수정되었습니다.

1과 2는 쿼리를 따로 씁니다.

ex) 공백이 하나밖에 안먹어서 \로 컬럼을 구분했습니다

'A테이블'

code \ name

1 \ 가

1 \ 가

1 \ 가

1 \ 가

2 \ 나

2 \ 나

2 \ 나

2 \ 나

3 \ 다

 

 

'B테이블'

code \ calldate

1 \ 2014-11-11

1 \ 2014-12-11

1 \ null

1 \ (공백)

2 \ 2015-01-12

2 \ 2015-01-12

2 \ 2015-01-10

2 \ null

2 \ (공백)

3 \ null

 

 

이러한 데이터가 있다고 할때

1의 경우 '2014-11-01' ~ '2015-01-20' 기간에 전화를 건 데이터를 뽑으면

'가' 거래처의 2014-12-11과 '나'거래처의 2015-01-12 두개의 행만 나와야 합니다.

'다' 거래처는 나오지 않구요

 

2의 경우 '2015-01-01' ~ '2015-01-20' 기간에 전화를 하지 않은 데이터를 뽑으면

'가' 거래처의 2014-12-11과 '다'거래처가 날짜가 공백 또는 null인채로 나와야 합니다.

 

2에서의 문제는 '나'거래처가 조회기간에 전화를 했음에도 날짜가 null인 데이터와 공백인 데이터가 있어서 결과에 포함이 된다는 것입니다...

kshap(ksw0829)님이 2015-01-28 09:20:43에 작성한 댓글입니다.
이 댓글은 2015-01-28 09:25:39에 마지막으로 수정되었습니다.

2의 경우입니다.

 

With table1 As
(
  Select 1 As code,'가' As name Union All
  Select 1 As code,'가' As name Union All
  Select 1 As code,'가' As name Union All
  Select 1 As code,'가' As name Union All
  Select 2 As code,'나' As name Union All
  Select 2 As code,'나' As name Union All
  Select 2 As code,'나' As name Union All
  Select 2 As code,'나' As name Union All
  Select 3 As code,'다' As name
)
,table2 As
(
  Select 1 As code,'2014-11-11' As lastcall Union All
  Select 1 As code,'2014-12-11' As lastcall Union All
  Select 1 As code,null         As lastcall Union All
  Select 1 As code,''           As lastcall Union All
  Select 2 As code,'2015-01-12' As lastcall Union All
  Select 2 As code,'2015-01-12' As lastcall Union All
  Select 2 As code,'2015-01-10' As lastcall Union All
  Select 2 As code,null         As lastcall Union All
  Select 2 As code,''           As lastcall Union All
  Select 3 As code,null         As lastcall
)
,table3 As
(
     Select
              a.code         As code
           , Max(b.lastcall) As lastcall
       From table1 a
                     inner join (
                                   select  b.code
                                         , b.lastcall
                                         , row_number() over (partition by b.code order by b.lastcall desc) as rnum
                                     from table2 b
                                    where (b.lastcall between '2015-01-01' and '2015-01-20')
                                ) b
                                    on a.code = b.code
                                   And rnum = 1
     Group By a.code
)
    Select 
           a.code
          ,Max(b.lastcall)
      From table1 a
                     inner join (
                                   select  b.code
                                         , b.lastcall
                                         , row_number() over (partition by b.code order by b.lastcall desc) as rnum
                                     from table2 b                                                   
                                    where (
                                                b.lastcall Not between '2015-01-01' and '2015-01-20'
                                            Or ISNULL(b.lastcall,'') = ''
                                          )
                                      And Not Exists (
                                                      Select 'x'
                                                        From table3 x
                                                       Where b.code = x.code
                                                     )                             
                                ) b
                                    on a.code = b.code
                                   And rnum = 1
  Group By a.code                                 
 

최한영(terry0515)님이 2015-01-28 10:27:06에 작성한 댓글입니다.

답변 감사드립니다!!^^

kshap(ksw0829)님이 2015-01-29 13:05:58에 작성한 댓글입니다.

사견 입니다만...

저렇게 복잡한 쿼리는 사용이 좀 그렇죠.

몇건 안되는 것을 경우 속도나 효율성은 문제가 없으나

수많은 데이타가 존재할 경우 문제가 될수 있죠.

그래서,

학생테이블에 최종게시물 번호와 최종게시 일자 필드를 추가하여,

학생이 게시물을 등록할때 학생테이블에 최종게시물번호 및 최종게시일자를 업데이트할도록 하는게 나중을 위해 좋다고 생각합니다.

그러면 학생테이블만 보면 최종게시일자가 있고 또 해당게시물과 조인하면 최종게시물도 확인 할수 있죠.

그리고 학생테이블과 게시물 테이블을 OUT JOIN 하여, 해당기간의 게시물과 게시물이 없는 학생들이 동시에 조회 됩니다.

쿼리문도 간단해지죠.

음~ 한학생이 해당기간에 2개이상의 개시물이 있을경우에는 여러개가 나올수는 있겠군요. 뭐 있나? 없냐?가 중요할 경우에 학생테이블로 Group By를 써서 게시물 테이블을 Count(*)하면 될수도 있긴한데...

 

select a.학번, a.이름, a.최종게시번호, a.최종일자, count(b.학번) 게시 개수

from 학생 a

left join 게시물 b on a.학번=b.학번 and b.게시물등록날짜 between '2015-01-01 and '2015-01-31'

group by a.학번, a.이름, a.최종게시번호, a.최종일자

 

이승철(yamyo)님이 2015-01-30 14:40:50에 작성한 댓글입니다.
이 댓글은 2015-01-30 14:53:39에 마지막으로 수정되었습니다.
[Top]
No.
제목
작성자
작성일
조회
6880답변형 댓글 구현 도움 부탁드립니다. [2]
최덕현
2015-02-02
2136
6879mssql복구중 관련 문의
왕초보
2015-01-29
2077
6878두 테이블을 조인 후 left table에 하나의 ROW로 합칠 수 있을까요? [1]
꽃씨하나
2015-01-27
2264
6877조인에 대한 문의입니다 [13]
kshap
2015-01-22
2581
6876DBO 에 대하여 질문 드립니다. [2]
이성원
2015-01-22
1981
6875MSSQL JDBC Driver Version 1 [2]
유상진
2015-01-21
2421
6874WHERE 절에서 멀티 값을 주고 싶습니다. [1]
이성원
2015-01-20
2071
Valid XHTML 1.0!
All about the DATABASE... Copyleft 1999-2019 DSN, All rights reserved.
작업시간: 0.085초, 이곳 서비스는
	PostgreSQL v11.1로 자료를 관리합니다