어제 문제
-- 1. 아래 테이블 생성 후(seoul_new.txt)
create table title
(
text varchar2(500)
);
select * from title;
-- 1) 게시글번호 추출
select text,
regexp_substr(text,'\d+') as 게시글번호
from title;
-- 2) 작성날짜 추출
select text,
regexp_substr(text,'\d+-\d+-\d+') as 작성날짜
from title;
-- 3) 조회수 추출
select text,
regexp_substr(text,'\d+-\d+-\d+ (\d+)',1,1,null,1) as 조회수
from title;
-- 4) 게시글 제목 추출
select text,
regexp_substr(text,'\d+ (.*) \d+-\d+-\d+',1,1,null,1) as 게시글제목
from title;
-- 서브그룹으로 원하는 결과 출력하기
select text,
regexp_substr(text,'(\d+) (.+) (\d+-\d+-\d+) (\d+)',1,1,null,1) as 게시글번호,
regexp_substr(text,'(\d+) (.+) (\d+-\d+-\d+) (\d+)',1,1,null,3) as 작성날짜,
regexp_substr(text,'(\d+) (.+) (\d+-\d+-\d+) (\d+)',1,1,null,4) as 조회수,
regexp_substr(text,'(\d+) (.+) (\d+-\d+-\d+) (\d+)',1,1,null,2) as 게시글제목
from title;
수업 내용
— [정규식 표현(Regular Expression)]
— 1. regexp_substr(대상,패턴[,시작위치,발견횟수(1),옵션,서브그룹번호])
— 2. regexp_replace(대상,패턴[,바꿀문자열,시작위치,발견횟수(0=모든),옵션])
— 3. regexp_instr(대상,패턴[,시작위치,발견횟수(1)])
select id,
regexp_instr(id,'\d'), -- 숫자
regexp_instr(id,'\W'), -- _ 제외한 특수기호 + 공백
regexp_instr(id,'[[:punct:]]') -- 특수기호
from professor;
— 4. regexp_count(대상,패턴[,시작위치,옵션])
select id,
regexp_count(id,'\d'), -- 숫자 1개가 몇개인지
regexp_count(id,'\d+'), -- 숫자 덩어리가 몇개인지
regexp_count(id,'\W'), -- _ 제외한 특수기호 + 공백
regexp_count(id,'[[:punct:]]') -- 특수기호
from professor;
— 5. regexp_like(대상,패턴[,옵션])
— 리턴은 참, 거짓이고 where절에서만 사용가능
-- 숫자 가진 행 출력
select id
from professor
where regexp_like(id,'\d');
-- 특수기호 2개이상 가진 행 출력
select id
from professor
where regexp_like(id,'[[:punct:]]{2,}');
select count(id)
from professor
where regexp_like(id,'[a-z][0-9]+');
— [윈도우 함수]
— 테이블 추가 접근(조인,서브쿼리) 없이 다른행과의 비교, 연산을 가능하게 하는 문법
— select 절에서만 사용가능. where절 사용불가.
— 1. 집계함수 형태
— min,max,sum,count,avg…over(…)
— 1) sum over() : 누적합 연산
sum(대상) over([partition by …]
[order by …]
[range|rows between A and B]);
— 그룹별 연산 수행 시 partition by 사용(group by와 같은 느낌)
— 누적합 연산 시 order by 필수
— 연산 범위 생략 시 default = range between unbounded preceding and current row
— range : 값이 같은 경우 동시 연산
— row : 각 행씩 연산
— A) unbounded preceding : 처음부터 (default)
— n preceding : n 이전부터
— current row : 현재행부터
— B) unbounded following : 끝까지
— n following : n 이후까지
— current row : 현재행까지 (default)
-- 예제) 각 직원의 사번, 이름, 급여, 부서번호와 함께 각 부서의 급여 총합 함께 출력
-- sol1) 스칼라 서브쿼리
select empno, ename, sal, deptno,
(select sum(sal) from emp where deptno=e1.deptno) as 부서별급여합
from emp e1
order by deptno;
-- sol2) 인라인 서브쿼리 (조인)
select e.empno, e.ename, e.sal, e.deptno, i.sum_sal
from emp e,
(select deptno, sum(sal) as sum_sal
from emp
group by deptno) i
where e.deptno = i.deptno
order by deptno;
-- sol3) 윈도우 함수
select empno, ename, sal, deptno,
sum(sal) over(partition by deptno) as 부서별급여합
from emp;
-- 예제) 급여 순서대로 급여의 누적합
select empno, ename, sal,
sum(sal) over(order by sal) as 누적합
from emp;
-- 주의 : sal 순서대로 값이 같은 경우 sal 갑이 같은 값을 하나로 묶어서 동시에 연산처리
-- 아래에 출력결과 확인하기
-- 해결법1) 추가 정렬값 전달
select empno, ename, sal,
sum(sal) over(order by sal, empno) as 누적합
from emp;
-- 해결법2) 옵션 수정 : range->rows
select empno, ename, sal,
sum(sal) over(order by sal
rows between unbounded preceding and current row) as 누적합
from emp;
-- 예제) 각 직원의 이름, 부서번호, 급여와 함께 부서별 급여의 누적합(급여작은순)
select ename, deptno, sal,
sum(sal) over(partition by deptno
order by sal
rows between unbounded preceding and current row) as 부서별누적합
from emp;
-- [연습문제]
-- 1. emp에서 각 부서별 최대 급여자 찾기(이름, 부서번호, 급여)
-- sol1) 인라인뷰
select e.ename, e.deptno, e.sal
from emp e,
(select deptno, max(sal) as max_sal
from emp
group by deptno) i
where e.deptno = i.deptno
and e.sal = i.max_sal;
-- sol2) 서브쿼리
select ename, deptno, sal
from emp
where (deptno,sal) in (select deptno,max(sal)
from EMP
group by deptno);
-- sol3) 윈도우함수,
select *
from (select ename, deptno, sal,
max(sal) over(partition by deptno) as max_sal
from emp)
where sal=max_sal;
-- 2. student에서 각 학년별 평균몸무게보다 낮은 학생 출력(이름, 학년, 체중)
-- sol1) 인라인뷰
select grade, avg(weight)
from student
group by grade;
select s.name, s.grade, s.weight
from student s,
(select grade, avg(weight) as avg_weight
from student
group by grade) i
where s.weight<i.avg_weight
and s.grade=i.grade;
-- sol2) 윈도우함수
select *
from (select name, grade, weight,
avg(weight) over(partition by grade) as avg_weight
from student)
where weight<avg_weight;
-- sol3) 상호연관
select s.name, s.grade, s.weight
from student S
where s.weight<(select avg(weight)
from student
where grade = s.grade);
— 2. 순위
— 1) rank
rank over([partition by …]
order by
[range|rows …])
— 2) dense_rank
dense_rank over([partition by …]
order by
[range|rows …])
— 3) row_number
row_number over([partition by …]
order by
[range|rows …])
— rank : 동순위 인정 & 다음 순위 생략 – 가장 베이직
— dense_rank : dense는 ‘밀집’. 동순위 인정 & 다음 순위 생략X
— row_number : 동순위 인정X
-- 예제) 각 직원의 급여 큰 순서대로 순위(전체기준)
select ename, sal, deptno,
rank() over(order by sal desc) as 순위1,
dense_rank() over(order by sal desc) as 순위2,
row_number() over(order by sal desc) as 순위3
from emp;
-- [연습문제]
-- 1. student_exam01에서 각 학년별로 성적이 높은 3명 출력
select *
from (select name, grade, total,
row_number() over(partition by grade
order by total desc) as 순위
from student_exam01)
where 순위<=3;
-- 2. student_exam01에서 성적이 높은 4~6등 출력 (동순위 인정X)
-- sol1)
select *
from (select name, grade, total,
row_number() over(order by total desc) as 순위
from student_exam01)
where 순위 between 4 and 6;
-- sol2)
select *
from (select rownum as rn, i.*
from (select *
from student_exam01 S
order by total desc, name) i)
where rn between 4 and 6;
-- 3. 전체 성적이 높은 순 3명 출력
-- sol1)
select *
from (select s.*,
row_number() over(order by total desc) as 순위
from student_exam01 s)
where 순위 <= 3;
-- sol2) rownum 사용
select rownum,
s.*
from student_exam01 s
where rownum <= 3
order by total desc; -- 이러면 where절부터 수행돼서 틀린 출력 나옴.
select rownum, i.*
from (select *
from student_exam01
order by total desc) i
where rownum<=3; -- 이렇게 빼서 where을 늦게 수행하게 해야됨
— 3. 이전/이후 행
— 1) lag() over() : 이전 값 가져오기
lag(대상,
[n], — 가져올 값의 위치(default : 1)
[대체값]) — 가져올 값이 없을 경우 리턴값(default : null)
over([partition by …]
order by …)
-- 예제) 바로 직전에 입사한 직원의 급여보다 낮은 급여를 받는 직원 출력
select *
from (select ename, hiredate, sal,
lag(sal) over(order by hiredate) as 이전입사자급여
from emp) I
where sal<이전입사자급여;
— 2) lead() over() : 이후 값 가져오기
lead(대상,
[n], — 가져올 값의 위치(default : 1)
[대체값]) — 가져올 값이 없을 경우 리턴값(default : null)
over([partition by …]
order by …)
-- [연습문제]
-- 1) 일자별 이용비율 총 합 출력 후 테이블 생성(movie_t1)
select * from movie;
create table movie_t1
as
select to_date(년||'/'||월||'/'||일,'YYYY/MM/DD') as 일자,
sum(이용비율) as 이용비율합
from movie
group by to_date(년||'/'||월||'/'||일,'YYYY/MM/DD');
-- 2) 이전일자대비 이용비율 증가율
select * from movie_t1;
select 일자, 이용비율합,
lag(이용비율합) over(order by 일자) as 이전일자이용비율,
(이용비율합-lag(이용비율합) over(order by 일자)) / lag(이용비율합) over(order by 일자) * 100 as 증가율
from movie_t1;
— 4. 최대/최소
— 1) first_value
first_value(대상) over([partition by …]
[order by …]
[range|rows …]) : 처음 값
— 2) last_value
last_value(대상) over() : 마지막 값
-- 예제) 시작값과 끝값 출력
select e.*,
first_value(sal) over(order by sal) as "first_value",
last_value(sal) over(order by sal) as "last_value"
-- default가 처음부터 현재행까지라서 출력이 800, 950, 1100, 1300 이 됨.
from emp E
where job = 'CLERK'
order by sal;
-- 예제) 최소, 최대 출력
-- first_value() 사용
select e.*,
first_value(sal) over(order by sal) as 최소,
first_value(sal) over(order by sal desc) as 최대
from emp E
where job = 'CLERK'
order by sal;
-- last_value() 사용
select e.*,
last_value(sal) over(order by sal) as 자기자신,
last_value(sal) over(order by sal
range between unbounded preceding and unbounded following) as 최대,
last_value(sal) over(order by sal desc
range between unbounded preceding and unbounded following) as 최소
from emp E
where job = 'CLERK'
order by sal;
— [top n query]
— 상위 n개 추출
— 정렬 필수
— 페이징 처리
— 1) fetch
— oracle은 12c부터 제공
— 대부분의 dbms 제공
— 단일 쿼리로도 상위 n개 추출
— 2) rownum
— 출력되는 순서에 맞게 1,2,3의 항상 1씩 증가하는 번호 부여
— 일반적으로 입력순서에 따라 rownum부여, 사용자 정의 순서대로 rownum 부여 시 서브쿼리 사용 필요
-- 예제) emp에서 rownum을 사용하여 급여 순위 4~6등 출력
select rownum, i.*
from (select *
from emp
order by sal desc) i;
— 3) rank over / dense_rank over / row_number over
— 4) top(n)
— SQL-Server 문법
— 단일 쿼리로도 상위 n개 추출