검색조건 중 어디가 문제인지 모르겠습니다. 0 10 1,921

by 토드초보입니다 [Oracle 기초] 쿼리 조건 sql [2017.02.16 20:18:37]


오라클.jpg (200,876Bytes)

안녕하세요! 테이블 조인해서 데이터 값을 구하려다가 잘 안되서 질문드립니다.
처음에는 아래와 같이 구문을 만들어서 검색하니 원하는 값이 나왔습니다만,
SELECT   A.A_id,
         A.name,
         A.number,
         COUNT(B.B_id),
         MIN(B.sub_reg)
    FROM A, B
   WHERE A.A_id = B.A_id and B.sub_stat = '1'
GROUP BY A.A_id,
         A.name,
         A.number
  HAVING MIN (B.sub_reg) > '20150101'

이 후 추가 데이터 검색이 필요해서 테이블 하나를 더 조인해서 아래와 같이 검색했더니 조건에 맞지 않는 회원 정보가 추가로 생성되고, count(B_id) 값과 min(B.sub_reg) 값이 이상하게 나옵니다.( count(B_id)값이 grade 개수와 동일하게 나오고, min(B.sub_reg) 역시 동일한 값이 아닌 값이 나옵니다.)

SELECT   A.A_id,
         A.name,
         A.number,
         COUNT(B.B_id),
         MIN(B.sub_reg),
         B.B_id,
         B.sub_name,
         wm_concat(C.grade)
    FROM A, B, C
   WHERE A.A_id = B.A_id and B.sub_stat = '1' and B.B_id = C.B_id
GROUP BY A.A_id,
         A.name,
         A.number,
         B.B_id,
         B.sub_name
  HAVING MIN (B.sub_reg) > '20150101'

어느 부분에 문제가 생긴 건지 궁금합니다.

by jkson [2017.02.17 08:55:26]

'과목 수강일이 2015년 1월 1일 이후인 자격들을 가지고 있는 모든 회원' 이라는 말이

2015년 1월 1일 이후 한 과목이라도 수강한 회원이란 말인가요?

지금 만들어 놓으신 두 번째 쿼리를 보면 그룹핑이 회원id, 과목id로 되어있어서

실제로 HAVING MIN (B.sub_reg) > '20150101' 조건은 회원-과목별 최초 등록일이 2015년 1월 1일 이후인

데이터를 보여달라는 말이 되어서 길동-국어 데이터 한 줄만 나올 것 같은데요.

성적 정보 테이블도 과목하고 점수만 매칭되어있네요. 성적정보 테이블에 회원id 정보가 빠진 것 아닌가요?

그리고 오공-미술의 성적정보가 없어서 결과로 안 나올 것 같은데.. 아우터 조인하셔야할 것 같구요.


by 토드초보입니다 [2017.02.17 12:33:59]

jkson님 빠른 답변 감사합니다.

머리 속에 있는 생각을 쉽게 설명하기 위해 풀어쓰려고 했는데 많이 부족했습니다.

일단 "2015년 1월 1일 이후 최초로 1개 이상 과목을 등록한 모든 회원 이름, 회원번호, 등록하고 있는 과목 개수, 최초과목등록일, 과목ID, 과목이름, 등급을 구하는" 것입니다.

그래서 having 절에 min(B.sub_reg) > '20150101' 조건을 걸었고, where절에 B.B_id = C.B_id 를 넣어 C의 grade가 나오게끔했습니다만,

제가 건 조건(where절이나 having절)으로는 위의 조건 이외에도 "2015년 1월 1일 이전에 등록한 과목이 있으며, 2015년 1월 1일 이후에도 등록한 과목이 있는 회원 및 2015년 1월 1일 이후 등록된 과목"들이 포함되어버리더군요.

그렇다면 having 절에 2015년1월1일 이전에 등록된 과목이 없다는 조건을 따로 넣어야 하는건가요?


by jkson [2017.02.17 14:16:10]
select a.a_id,a.name,a.num,x.sub_cnt,x.sub_reg,b.b_id,b.sub_name,to_char(wm_concat(c.grade))grade
  from
    (
   select a_id, count(distinct b_id) sub_cnt, min(sub_reg) sub_reg
     from b
    where sub_stat = '1'
    group by a_id
   having min(case when sub_reg > '20150101' then 'Y' end) = 'Y'
    ) x, a, b, c
 where x.a_id = a.a_id
   and a.a_id = b.a_id
   and b.b_id = c.b_id (+)
   and b.sub_stat = '1'--추가했습니다.
 group by a.a_id, a.name, a. num, x .sub_cnt , x.sub_reg, b. b_id, b .sub_name

 


by 토드초보입니다 [2017.02.17 19:09:05]

jkson 님 답변 감사합니다.

이 쿼리로 돌리면 sub_stat 값이 1이 아닌 값들도 넘어오는 것 같습니다.  where 절에서 x, a, b 조인은 된거 같은데 정확히는 모르겠습니다. 


by 마농 [2017.02.17 14:26:33]

표현이 좀 고민을 많이 하게 만드는 표현이네요?
  - "2015년 1월 1일 이후 최초로 1개 이상 과목을 등록한 모든 회원"
  - 위 표현 보다는 아래 표현이 명확한 듯 하네요.
  - "최초 등록일이 2015년 1월 1일 이후인 회원"
최초 등록일자를 가져오는 부분은
  - 원본 쿼리는 회원별 최초 등록일을 가져오지만...
  - 수정 쿼리는 그룹바이 조건에 회원 외에 과목이 추가되었습니다.
  - 따라서 단순 집계함수로는 회원별 최초일자를 가져올 수가 없죠.

WITH a AS
(
SELECT 'a1' a_id, '길동' name, 1 num FROM dual
UNION ALL SELECT 'a2', '둘리', 2 FROM dual
UNION ALL SELECT 'a3', '오공', 3 FROM dual
UNION ALL SELECT 'a4', '비비', 4 FROM dual
)
, b AS
(
SELECT 'b1' b_id, 'a1' a_id, 1 sub_stat, '과학' sub_name, '20150101' sub_reg FROM dual
UNION ALL SELECT 'b2', 'a2', 1, '수학', '20140101' FROM dual
UNION ALL SELECT 'b3', 'a1', 1, '국어', '20150201' FROM dual
UNION ALL SELECT 'b4', 'a3', 1, '미술', '20160102' FROM dual
)
, c AS
(
SELECT 'b1' b_id, '1급' grade FROM dual
UNION ALL SELECT 'b1', '2급' FROM dual
UNION ALL SELECT 'b2', '1급' FROM dual
UNION ALL SELECT 'b3', '1급' FROM dual
)
SELECT a_id
     , name
     , num
     , cnt_b
     , min_sub_reg
     , b_id
     , sub_name
     , grade
  FROM (SELECT a.a_id
             , a.name
             , a.num
             , COUNT(*) OVER(PARTITION BY a.a_id) cnt_b
             , MIN(MIN(b.sub_reg)) OVER(PARTITION BY a.a_id) min_sub_reg
             , b.b_id
             , b.sub_name
             , c.grade
          FROM a
             , b
             , (SELECT b_id
--                   , wm_concat(grade) grade
                     , LISTAGG(grade, ',') WITHIN GROUP(ORDER BY grade) grade
                  FROM c
                 GROUP BY b_id
                ) c
         WHERE a.a_id = b.a_id
           AND b.sub_stat = '1'
           AND b.b_id = c.b_id(+)
         GROUP BY a.a_id, a.name, a.num
             , b.b_id, b.sub_name
             , c.grade
        )
 WHERE min_sub_reg >= '20150101'
 ORDER BY a_id, b_id
;

 


by 토드초보입니다 [2017.02.17 18:03:15]

마농님 답변 감사합니다.

오라클 10G라서 LISTAGG() 함수는 쓰지못하는 관계로 아쉬우나, 짜 주신 내용 보고 이해해보도록 하겠습니다.

감사합니다.

 


by jkson [2017.02.17 14:47:24]

저도 아직도 좀 헷갈리는데

최초 작성한 쿼리를 보면 MIN (B.sub_reg) > '20150101' 인데 길동이 결과 건으로 나오는 것으로 보아

한 과목이라도 2015년 1월 1일 이후 등록한 건이 있으면 데이터를 출력해주는 것이라고 이해했습니다.


by 마농 [2017.02.17 14:50:01]

네 모든 표현이 모호한 표현 입니다.
여러가지로 해석이 가능한 표현들 이네요.
쿼리를 작성하는것은 어렵지 않은데, 문장을 해석하는게 어렵네요.


by jkson [2017.02.17 14:57:10]

마농님 답변 참고해서 집계함수에 다시 윈도함수를 씌워서

같은 테이블에 두번 접근하지 않도록 다시 만들어봤습니다.

select a_id, name, num sub_cnt, sub_reg, b_id, sub_name, grade 
  from
    (  
    select a.a_id, a.name, a.num
         , count(distinct b.b_id) over (partition by a.a_id) sub_cnt
         , min(min(sub_reg)) over(partition by a.a_id) sub_reg
         , min(min(case when sub_reg > '20150101' then 'Y' end)) over(partition by a.a_id) reg_yn 
         , b.b_id, b.sub_name, to_char(wm_concat(distinct c.grade)) grade
      from a, b, c
     where a.a_id = b.a_id
       and b.b_id = c .b_id (+)
       and b.sub_stat = '1'
     group by a.a_id, a.name, a.num, b.b_id, b.sub_name
    )
 where reg_yn = 'Y' 

by jkson [2017.02.17 19:31:41]

다시 한번 토드님 글과 댓글을 쭉 읽어보니 아무래도 질문자님이 원하시는 데이터는 마농님 답변 같네요.

토드님이 주신 조건은 HAVING MIN (B.sub_reg) > '20150101' 인데(20150101 미포함) 최종 결과표에는 20150101인 데이터도 있었기 때문에 저는

2015년 이후 한 과목이라도 등록한 회원의 모든 과목 정보를 다 표시해주었습니다. 그런데 댓글에 2015년 1월 1일 이전 데이터는 있으면 안 된다고 하셨으니

'모든 과목을 통틀어 2015년 이전에는 등록한 과목이 없는 회원의 과목 정보' 를 표시해주는 게 맞겠네요.

제가 답변한 쿼리는 분석하지 마시구요. 마농님 쿼리 분석하시면 됩니다.

LISTAGG가 안 되시면 to_char(wm_concat(distinct grade)) 로 하시면 되고요.

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