3개 테이블 조인 질문 0 13 3,835

by 갈매기 [Oracle 기초] 테이블 조인 조건 2개 [2016.04.21 00:48:11]


1. MS SQL 입니다. 

2. 3개의 테이블 을 조인하려 합니다. 일정기간을 정하여 원하는 값을 집계하고 싶습니다. 데이터 량이 많아서 한달로 끊어서 집계하려고 합니다. 본 사항을 매월 1일에 자동으로 집계하고자 합니다. 예를 들면, 4/1 에 3월 집계를 자동으로 해 볼 수 있는 방법을 구합니다.

3. 특이사항:

     alarm 테이블에서, 21번과 210번을 카운트해야 하는데, 210번의 경우 NDC 값이 없습니다. 

     alarm 내역에서 21번일 경우, 메세지 내용에 Qty:가 나오는데, 10개 이하는 카운트에서 뺍니다. 

테이블 1. fac

Fac_id Fac_name
00014 HOME 1
00040 HOME 2

테이블 2.dsps

fac_id NDC dsps_dtm return_qty dsps_qty
00014 00000000001 20151001115631 0 2
00014 00000000001 20151002115631 1 1
00014 00000000002 20151003115631 0 1
00014 00000000002 20151004115631 1 2
00014 00000000003 20151024115631 1 5
00014 00000000003 20151015115631 0 3
00014 00000000003 20151019115631 0 4
00014 00000000001 20151001110000 1 3
00040 00000000001 20151030010000 1 2

테이블 3 alarm

fac_id NDC alarm_dtm alarm_num alarm_message
00014 00000000001 20151003100222 21 NDC:00000000001, Qty:150, CBN:242 
00014 00000000001 20151001120631 21 NDC:00000000001, Qty:9, CBN:24
00014 00000000002 20151010120632 21 NDC:00000000002, Qty:120, CBN:2
00014 00000000003 20151012121111 21 NDC:00000000003, Qty:115, CBN:123
00014 00000000003 20151023121111 21 NDC:00000000003, Qty:113, CBN:123
00014 00000000003 20151025121212 21 NDC:00000000003, Qty:8, CBN:123
00040 00000000003 20151008121212 21 NDC:00000000003, Qty:90, CBN:123
00014   20151008121212 210  
00014   20151029121212 210  

원하는 결과 값 (기간을 2015년 10월부터 2015년 11월로 제한)

Fac_id Fac_name sum_dsps_qty sum_return_qty Count_alarm_21 count_alarm_210
00014 HOME 1 21 3 4 2
00040 HOME 2 2 1 1 0

 

고견 부탁드립니다. 

by 마농 [2016.04.21 11:09:33]
WITH fac(Fac_id, Fac_name) AS
(
          SELECT '00014', 'HOME 1'
UNION ALL SELECT '00040', 'HOME 2'
)
, dsps(fac_id, NDC, dsps_dtm, return_qty, dsps_qty) AS
(
          SELECT '00014', '00000000001', '20151001115631', 0, 2
UNION ALL SELECT '00014', '00000000001', '20151002115631', 1, 1
UNION ALL SELECT '00014', '00000000002', '20151003115631', 0, 1
UNION ALL SELECT '00014', '00000000002', '20151004115631', 1, 2
UNION ALL SELECT '00014', '00000000003', '20151024115631', 1, 5
UNION ALL SELECT '00014', '00000000003', '20151015115631', 0, 3
UNION ALL SELECT '00014', '00000000003', '20151019115631', 0, 4
UNION ALL SELECT '00014', '00000000001', '20151001110000', 1, 3
UNION ALL SELECT '00040', '00000000001', '20151030010000', 1, 2
)
, alarm(fac_id, NDC, alarm_dtm, alarm_num, alarm_message) AS
(
          SELECT '00014', '00000000001', '20151003100222',  21, 'NDC:00000000001, Qty:150, CBN:242'
UNION ALL SELECT '00014', '00000000001', '20151001120631',  21, 'NDC:00000000001, Qty:9, CBN:24'
UNION ALL SELECT '00014', '00000000002', '20151010120632',  21, 'NDC:00000000002, Qty:120, CBN:2'
UNION ALL SELECT '00014', '00000000003', '20151012121111',  21, 'NDC:00000000003, Qty:115, CBN:123'
UNION ALL SELECT '00014', '00000000003', '20151023121111',  21, 'NDC:00000000003, Qty:113, CBN:123'
UNION ALL SELECT '00014', '00000000003', '20151025121212',  21, 'NDC:00000000003, Qty:8, CBN:123'
UNION ALL SELECT '00040', '00000000003', '20151008121212',  21, 'NDC:00000000003, Qty:90, CBN:123'
UNION ALL SELECT '00014',          Null, '20151008121212', 210, Null
UNION ALL SELECT '00014',          Null, '20151029121212', 210, Null
)
SELECT a.Fac_id
     , a.Fac_name
     , b.sum_dsps_qty
     , b.sum_return_qty
     , c.Count_alarm_21
     , c.Count_alarm_210
  FROM fac a
  LEFT OUTER JOIN
       (SELECT Fac_id
             , SUM(dsps_qty  ) sum_dsps_qty
             , SUM(return_qty) sum_return_qty
          FROM dsps
         GROUP BY Fac_id
        ) b
    ON a.Fac_id = b.Fac_id
  LEFT OUTER JOIN
       (SELECT Fac_id
             , COUNT(CASE alarm_num WHEN 21  THEN 1 END) Count_alarm_21
             , COUNT(CASE alarm_num WHEN 210 THEN 1 END) Count_alarm_210
          FROM alarm
         WHERE alarm_dtm LIKE '201510%'
           AND ISNULL(SUBSTRING( alarm_message
                               , CHARINDEX('Qty:', alarm_message) + 4
                               , CHARINDEX('CBN:', alarm_message)
                               - CHARINDEX('Qty:', alarm_message) - 6
                               ), 11) > 10
         GROUP BY Fac_id
        ) c
    ON a.Fac_id = c.Fac_id
;

 


by 갈매기 [2016.04.22 01:08:27]

 

마농님, 

감사합니다. 제가 돌려 보니(실제 DB에서), 하기와 같은 에러 메세지가 나옵니다.


Invalid length parameter passed to the LEFT or SUBSTRING function.

 

제가 짰던 쿼리는,

저는 10개 미만을 찾을때 기간을 10월로 찾으때 where 절에 하기와 같이 썼습니다.

alarm_message not like N'%Qty:[0-10],%'
alarm_dtm between '20151001000000' and '20151100000000' 

결과값은 같은데 효율성에서 어떤게 좋은지 궁금합니다.

 

마지막으로 left outer join 과 ISNULL()>10 에 대해 간단한 설명 부탁 드려도 될까요? 


by 마농 [2016.04.22 09:00:45]

1. 에러 원인은...
  - Substring 에서 마이너스 값이 대입되어서 그렇습니다.
  - 예시에 없는 'Qty:', 'CBN:' 이 없는 자료가 포함되어서 있어서 그렇습니다.
  - 이런 자료들에 대한 처리를 어떻게 할지 생각해야 합니다.
2. not like ???
  - NOT 이 빠져야 하는것 아닌가요?
3. alarm_dtm between '20151001000000' and '20151100000000' ?
  - LIKE 나 BETWEEN 이나 성능은 동일할 듯 하구요.
  - 문자열 검색이기 때문에 굳이 자리수 맞추고 11월 대입할 필요는 없습니다.
  - between '201510' AND '201510' + '9' 요런식으로 해도 됩니다.
4. left outer join 은?
  - Fac_id 별로 dsps 와 alarm 의 데이터가 없어도 나오게 하기 위함입니다.
  - 아우터 조인이 꼭 필요한지는 고민해 보셔야 하구요.
5. ISNULL()>10 은
  - 210 자료의 alarm_message 가 Null 이지만 결과에 포함되어야 하기 때문에 사용한 조건입니다.


by 갈매기 [2016.04.22 23:08:58]

여러분들께서 마농님이 답을 달아 주시면 왜 아....하는 줄 알겠습니다. 성실한 답변에 다시 한번 놀랐습니다. 

1. 없는 자료는 포함하지 않을 려면 고민을 해봐야 겠습니다. 

2. 0-10은 포함하지 않는 것이라, not like가 맞습니다. not like로 하니 에러 없이 돌아가네요.

3. between '201510' and '201511' +'9' 몰랐는데, 좋네요.

4. 감사합니다. 실 디비에서는 join만 썼습니다. 

5. 잘 이해가 되었습니다. 

 

감사합니다. 

 


by 마농 [2016.04.25 08:25:13]

0 부터 10 사이 값을 나타내기 위해 0-10 하신 듯 한데요??? 정규식은 틀렸네요.
숫자 사이값을 표시하는게 아니라 문자 사이값을 표시하는 구문이며
문자는 하나의 문자죠 '10' 처럼 두개 문자를 의미하지는 않습니다.
사용한 구문을 해석하면 '0-10' 은 '0-1' 과 '0' 으로 나뉘어, 0에서1사이 문자와 0 을 의미합니다.
즉, 0 과 1 을 의미하죠.
숫자값으로 10이하를 제외시키려면 제가 제시한 방법대로 해야 합니다.


by 갈매기 [2016.04.25 23:56:43]

마농님, 

잘 알겠습니다. 9까지는 되어도 10은 안된다는 말쓰이시죠? 언급하신데로, QTY, CBN이 없는 자료를 솎아낼 방법이 필요합니다. 아이디어 있으시면 알려주세요. 


by 마농 [2016.04.26 08:06:37]
SELECT Fac_id
     , COUNT(CASE alarm_num WHEN 21  THEN 1 END) Count_alarm_21
     , COUNT(CASE alarm_num WHEN 210 THEN 1 END) Count_alarm_210
  FROM alarm
 WHERE alarm_dtm LIKE '201510%'
   AND alarm_num IN (21, 210)
   AND ISNULL(SUBSTRING( alarm_message + 'Qty:0, CBN:'
                       , CHARINDEX('Qty:', alarm_message + 'Qty:0, CBN:') + 4
                       , CHARINDEX('CBN:', alarm_message + 'Qty:0, CBN:')
                       - CHARINDEX('Qty:', alarm_message + 'Qty:0, CBN:') - 6
                       ), 11) > 10
 GROUP BY Fac_id
;

 


by 갈매기 [2016.04.27 02:27:08]

마농님, 

 

아.... 하는 순간 입니다. 감사합니다. 

 

결과 값 중에, 

b.sum_dsps_qty 의 평균을 컬럼에 추가하려면, 할 수 있는 방법이 있나요?

AVG(SUM(b.sum_dsps_qty)) 는  
​Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
에러가 나네요.
sum(b.sum_dsps_qty)/count(*) 도 b.sum_dsps_qty와 같은 값이 나옵니다.

 

 

 


by 마농 [2016.04.27 08:19:29]

글쎄요? 평균의 의미를 명확하게 알려주세요. 어떤 평균인지?
a 서브쿼리 안쪽에서의 dsps_qty 의 평균인 AVG(dsps_qty) 를 의미하는지?
아니면 sum_dsps_qty 의 전체 평균인 AVG(b.sum_dsps_qty) OVER() 를 의미하는지?


by 갈매기 [2016.04.28 01:29:21]

제가 명확히 표현하지 못하였네요. 

 

후자 입니다. 후자로 

sum(b.sum_dsps_qty) over (partition by a.fac_id)
,avg(b.sum_dsps_qty) over (partition by a.fac_id)

했지만, 둘다 b.sum_dsps_qty와 같은 값이 나오네요.


by 마농 [2016.04.28 09:21:49]

partition by 없는 그냥 빈 over() 해보세요.


by 갈매기 [2016.04.29 22:32:32]

잘됩니다. 감사합니다. 빈 over()가 되는 이유가 궁금합니다. 


by 마농 [2016.05.02 10:57:33]

partition By 는 그룹을 나누어서 그룹별로 집계하겠다는 거죠.

이게 없으면 그룹을 안나누고 전체에 대해 집계하겠다는 거죠.

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입