D[i] = D[i - 1] + 1
if (2의 배수) D[i / 2] + 1이 D[i]보다 작으면 변경
if (3의 배수) D[i / 3] + 1이 D[i]보다 작으면 변경
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Ex1463 {
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// N : 구하고자 하는 수
int N = Integer.parseInt(br.readLine());
int[] dp = new int[N + 1];
dp[1] = 0;
// * 규칙이 반복되는 구간은 2부터
// for (i : 2 ~ N) {
// D[i] = D[i - 1] + 1 // 1을 뺀 점화시
// if (2의 배수) D[i / 2] + 1이 D[i]보다 작으면 변경
// if (3의 배수) D[i / 3] + 1이 D[i]보다 작으면 변경
// }
for (int i = 2; i <= N; i++) {
dp[i] = dp[i - 1] + 1;
if (i % 2 == 0) {
dp[i] = Math.min(dp[i], dp[i / 2] + 1);
}
if (i % 3 == 0) {
dp[i] = Math.min(dp[i], dp[i / 3] + 1);
}
}
// 출력
System.out.println(dp[N]);
}
}
2. 퇴사
- dp에서 자주 나온다는 배낭 문제와 약간 비슷하나 다른 문제였다.
- 일련의 스케줄이 주어지고 기간 제약을 고려하여 최대 수익을 내는 문제
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Ex14501 {
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// N 개의 스케줄을 각각의 일차원 배열에 받는다.
int N = Integer.parseInt(br.readLine());
// D 점화식 배열
int[] D = new int[N + 2];
// T 기간 배열
int[] T = new int[N + 1];
// P 수입 배열
int[] P = new int[N + 1];
for (int i = 1; i <= N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
T[i] = Integer.parseInt(st.nextToken());
P[i] = Integer.parseInt(st.nextToken());
}
// 터뷸레이션을 사용해서 D를 구한다.
// D[i] = i날짜부터 n+1(퇴사일)까지 벌수 있는 최대 수익
// for (i : N ~ 1){
// if (i+T[i]가 퇴사일보다 크면) D[i] = D[i + 1]
// if (i+T[i]가 퇴사일보다 크지 않으면) D[i] = Math.max(D[i + 1], P[i] + D[i + T[i]])
// }
for (int i = N; i >= 1; i--) {
if (i + T[i] > N + 1) {
D[i] = D[i + 1];
} else {
D[i] = Math.max(D[i + 1], D[i + T[i]] + P[i]);
}
}
// 출력
// D[1]
System.out.println(D[1]);
}
}
3. 이친수 구하기
- 욕 같지만 이진수를 만드는 특정 규칙를 지킨 수의 갯수를 구하는 문제였다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Ex2193 {
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
// D 배열을 만든다 (2차원 배열)
// dp[1][0] = 0, dp[1][1] = 1 초기화
long[][] dp = new long[N + 1][2];
dp[1][0] = 0;
dp[1][1] = 1;
// for (i : 2 ~ N) {
// dp[i][0] = dp[i - 1][0] + dp[i - 1][1] // 0은 0이나 1 뒤에 붙일 수 있다.
// dp[i][1] = dp[i - 1][0] // 1은 0 뒤에만 붙일 수 있다.
// }
for (int i = 2; i <= N; i++) {
dp[i][0] = dp[i - 1][0] + dp[i - 1][1];
dp[i][1] = dp[i - 1][0];
}
System.out.println(dp[N][0] + dp[N][1]);
}
}
- 간략하게 말하면, 큰 문제를 작은 단위로 쪼갠 후 다시 조합하여 문제를 해결하는 방법을 말한다.
분할: 문제를 더이상 분할할 수 없을 때까지 동일한 유형의 여러 하위 문제로 나눈다.
정복: 가장 작은 단위의 하위 문제를 해결하여 정복한다.
조합: 하위 문제에 대한 결과를 원래 문제에 대한 결과로 조합한다
*출처 : 나무위키
| 분할정복 과정
1. 문제를 하나 이상의 작은 단위로 분할
2. 작은 단위에서 부분적인 해결
3. 부분들의 해결을 조합해 전체 문제의 해답을 찾는다.
function F(x):
if F(x)가 간단 then:
return F(x)를 계산한 값
else:
x 를 x1, x2로 분할
F(x1)과 F(x2)를 호출
return F(x1), F(x2)로 F(x)를 구한 값
* 출처 : 나무위키
| 장단점
장점
어려운 문제를 해결할 수 있다 병렬적으로 문제를 해결하는데에 이점이 있다.
단점
메모리 사용량이 높다(재귀 호출을 통한 오버헤드 발생)
| 예시
분할정복의 예시로 자주 거론되는 것으로 아래 네 가지가 있다.
이 중에서 내 단계에서 언급하기 쉬워 보이는 1~3의 알고리즘만 적어보려고 한다.
1. 합병 정렬
2. 퀵소트
3. 카라추바 알고리즘
4. 고속 푸리에 변환
1. 합병정렬
- 분할정복과 투포인터의 특성을 사용해서 만들어진 정렬이라 볼 수 있을 것 같다.
- 1) 먼저 배열의 범위를 투 포인터를 통해 분할하여 지정한 후에,
- 2) 작은 범위에서 데이터를 비교하여 정렬을 하고
- 3) 작은 범위를 조합하여 전체 정렬을 이루는 과정
void mergeSort(int[] arr, int[] tmp, int left, int right){
if (left < right) {
int mid = (left + right) / 2;
// mid를 기준으로 배열을 분할한다.
mergeSort(arr, tmp, left, mid);
mergeSort(arr, tmp, mid + 1, right);
// mid를 기준으로 배열을 조합한다.
merge(arr, tmp, left, right, mid);
}
}
void merge(int[] arr, int[] tmp, int left, int right, int mid){
// mid를 기준으로 좌/우 포인터,
// index포인터를 준비하고
int l = left;
int r = mid + 1;
int idx = left;
// 좌/우 포인터를 비교하여 tmp에 오름차순으로 저장
// 적어도 두 포인터 중 하나가 범위 내에 있을 때에
while (l <= mid || r <= right){
// 조건1 : 모든 포인터가 범위내에 있다면
if (l <= mid && r <= right){
// 두 포인터의 데이터를 비교해서
// 더 작은 데이터를 tmp에 넣고, 포인터를 하나 증가한다.
if (arr[l] <= arr[r]){
tmp[idx++] = arr[l++];
} else {
tmp[idx++] = arr[r++];
}
}
// 조건2 : left포인터만 범위내에 있다면
else if (l <= mid && r > right){
tmp[idx++] = arr[l++];
}
// 조건3 : right포인터만 범위내에 있다면
else {
tmp[idx++] = arr[r++];
}
}
// tmp의 데이터를 arr에 옮긴다.
for (int i = left; i <= right; i++){
arr[i] = tmp[i];
}
}
2. 퀵소트
- 피봇이라는 기준값을 토대로 투포인터와 분할정복을 활용한 정렬 알고리즘이다.
- 1) 먼저 피봇값이라는 기준값을 지정하고 반정렬
- 2) 피봇값을 기준으로 배열을 분할
- 3) 분할된 배열을 부분적으로 정렬
void quickSort(int[] arr, int left, int right){
// 탈출문
if (left >= right) {
return;
}
// 피봇값을 지정
int pivot = partition(arr, left, right);
// 피봇값을 기준으로 배열을 분할
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 1, right);
}
void int partition(int[] arr, int left, int right){
// 투 포인터와 피봇값 지정
int l = left;
int r = right;
int pivot = arr[left];
// 피봇값을 기준으로 양 끝 포인터를 올리거나 내려준다
while(l < r) {
while (arr[r] > pivot && l < r) {
r--;
}
while (arr[l] <= pivot && l < r) {
l++;
}
swap(arr, l, r);
}
// 기존 피봇값 위치 데이터와 피봇값을 교환
swap(arr, left, l);
return l;
}
3. 카라추바 알고리즘
- 카라추바라는 사람이 1960년에 만든 분할정복 알고리즘으로,
- 초등학교 곱셈 공식에서 배웠던 각 자릿수를 곱하는 방식을 응용해 만든 알고리즘이다.
- 알고리즘 구현의 단계는 아래와 같다.
1. z0 = a * c
2. z1 = b * d
3. z2 = (a + b)(c + d)
4. z3 = z0 - ac - bd
5. (z0 * 10^n) + (z3 * 10^(n/2)) + z1
- 아래 코드 외에 아예 ArrayList를 사용해서 각 자릿수를 저장하는 방식 또한 있는데,
굉장히 복잡하기 때문에 관련된 코드는 검색을 통해 복사하여 사용하는 편이 좋을 것 같다.
** 자바에는 BigInteger라는 큰 수를 다루는 객체가 따로 있다.
(큰 수의 곱셈은 이 객체의 multiply() 메소드를 쓰는 것이 코드를 가독성 있게 짜기에는 더 좋아 보인다.)
public static long karatsuba(long x, long y) {
// 범위가 작은 경우
if (x <= 50 && y <= 50) {
return x * y;
}
// a와 b의 자릿수
int numXLen = (int)Math.log10(x);
int numYLen = (int)Math.log10(y);
// a와 b의 자릿수 중에 더 큰 자릿수를 선택
int maxLen = Math.max(numXLen, numYLen); // ex 1234 -> 4
// 분할한 자릿수
Integer halfMaxLen = (maxLen / 2) + (maxLen % 2); // ex 1234 -> 2
// Multiplier : 곱할 10^n
long halfMaxLenTen = (long)Math.pow(10, halfMaxLen); // ex 1234 -> 10^2
// 분할한 수들을 구한다.
long a = x / halfMaxLenTen;
long b = x % halfMaxLenTen;
long c = y / halfMaxLenTen;
long d = y % halfMaxLenTen;
// 4단계에 따라 각 수들을 곱하고 더한다.
// 1. a * c
// 2. b * d
// 3. (a+b)(c+d)
// 4. (3)에서 (1), (2)를 뺀다
// 5. (1) x halfMaxLen + (2) + (4) x (halfMaxLen / 2)
long z0 = karatsuba(a, c);
long z1 = karatsuba(b, d);
long z2 = karatsuba(a + b, c + d);
long z3 = z2 - z1 - z0;
long res = (z0 * (long)Math.pow(10, halfMaxLen * 2)) + (z3 * (long)Math.pow(10, halfMaxLen)) + z1;
return res;
}
public class Ex11724 {
static int N;
static int M;
static List<List<Integer>> graph;
static boolean[] visited;
static int cnt;
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
// 인접 리스트 그래프 초기화
graph = new ArrayList<>();
for (int i = 0; i <= N; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
graph.get(start).add(end);
graph.get(end).add(start);
}
// 방문 배열 초기화
visited = new boolean[N + 1];
cnt = 0;
}
}
[2] 문제에서 요구하는 연결망의 갯수 구하는 코드를 넣는다.
- 이 문제는 프로그래머스의 '네트워크' 문제와도 유사했는데, 분리된 연결망을 구하는 문제였다.
> 분기를 확인하기 위한 코드
// 2. dfs
for (int i = 1; i <= N; i++) {
if (!visited[i]) {
dfs_stack(i);
cnt++;
}
}
System.out.println(cnt);
> 예외 상황 : 노드가 하나밖에 없고 간선이 없는 경우 - 네트웍은 하나다.
// 노드가 하나밖에 없고 간선이 없어도 네트웍은 하나로 치부
if (N == 1 && M == 0) {
System.out.println(1);
return;
}
[3] 본격적인 dfs를 짜기
[3-1] 재귀 방식의 dfs
public static void dfs(int root) {
// 루트 방문 체크
visited[root] = true;
// 인접한 노드들 중 방문하지 않은 곳을 방문
for (int i = 0; i < graph.get(root).size(); i++) {
int adjNode = graph.get(root).get(i);
if (!visited[adjNode]) {
dfs(adjNode);
}
}
}
[3-2] 스택 방식의 dfs
public static void dfs_stack(int start) {
Stack<Integer> stack = new Stack<>();
visited[start] = true;
stack.push(start);
while (!stack.isEmpty()) {
int curNode = stack.pop();
for (int i = 0; i < graph.get(curNode).size(); i++) {
int adjNode = graph.get(curNode).get(i);
if (!visited[adjNode]) {
stack.push(adjNode);
visited[adjNode] = true;
}
}
}
}
package graph;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;
public class Ex11724 {
static int N;
static int M;
static List<List<Integer>> graph;
static boolean[] visited;
static int cnt;
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
if (N == 1 && M == 0) {
System.out.println(1);
return;
}
// 1. 시작 노드를 정한 후 사용할 자료구조 초기화
// 인접 리스트 그래프 초기화
graph = new ArrayList<>();
for (int i = 0; i <= N; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < M; i++) {
st = new StringTokenizer(br.readLine());
int start = Integer.parseInt(st.nextToken());
int end = Integer.parseInt(st.nextToken());
graph.get(start).add(end);
graph.get(end).add(start);
}
// 방문 배열 초기화
visited = new boolean[N + 1];
cnt = 0;
// 2. dfs
for (int i = 1; i <= N; i++) {
if (!visited[i]) {
dfs_stack(i);
cnt++;
}
}
System.out.println(cnt);
}
public static void dfs(int root) {
// 루트 방문 체크
visited[root] = true;
// 인접한 노드들 중 방문하지 않은 곳을 방문
for (int i = 0; i < graph.get(root).size(); i++) {
int adjNode = graph.get(root).get(i);
if (!visited[adjNode]) {
dfs(adjNode);
}
}
}
public static void dfs_stack(int start) {
Stack<Integer> stack = new Stack<>();
visited[start] = true;
stack.push(start);
while (!stack.isEmpty()) {
int curNode = stack.pop();
for (int i = 0; i < graph.get(curNode).size(); i++) {
int adjNode = graph.get(curNode).get(i);
if (!visited[adjNode]) {
stack.push(adjNode);
visited[adjNode] = true;
}
}
}
}
}
(2) 시간 복잡도
- DFS는 그래프의 정점의 수(V)와 간선의 수(E)를 통해 시간 복잡도를 표현하는데,
- 인접 행렬을 사용하는 경우, 행으로는 전체 정점(V)을, 열로는 인접한 정점과의 연결 여부(E)를 확인하므로,
-- O(V^2) 의 시간 복잡도를 가진다.
- 인접 리스트를 사용하는 경우, 각 정점(V)을 방문한 후, 인접 노드를 따라 하나씩 탐색하기 때문에,
-- O(V + E)의 시간 복잡도를 가진다.
- 그래프를 완전 탐색할 때에는 따라서, 간선이 적을 때에 인접 리스트를 사용하는 것이 좋다.
2. BFS (너비 우선 탐색)
bfs란, 루트에서 시작해서 인접한 노드를 먼저 탐색하는 방법
[출처] https://gmlwjd9405.github.io/2018/08/15/algorithm-bfs.html
- DFS의 경우, 깊이 있게 탐색했다면, BFS는 가까운 지점을 먼저 탐색하는 "넓은" 탐색이다.
- 참고한 블로그에서는, 두 노드 사이의 최단 경로 혹은 임의의 경로를 찾고 싶을 때에 이 방법을 쓴다고 했다.
- 보안성 : 계정 권한 수준에 따라 노출되지 않아야 하는 데이터를 숨김 처리 할 수 있음
-- 기본 구문
CREATE VIEW 뷰명 AS
SELECT *
FROM 테이블명
-- 예시
create view v_member as
select
m.member_type, m.user_id,m.name,
md.mobile_no, md.marketing_yn, md.register_date
from member as m
join member_detail md
on m.member_type = md.member_type and m.user_id = md.user_id
;
| (사용자 정의)함수와 프로시져
(사용자 정의)함수
프로시져
언제쓰나?
자주쓰는 기능 저장할때
특정 비즈니스 로직을 저장해서 꺼내 쓸 때
공통점
선언부와 구현부가 있는 함수 형태
차이점
반환값 O
반환값 X
A. 함수
-- 기본 구문
CREATE FUNCTION 함수명(매개변수명 타입)
RETURNS 반환타입
BEGIN
RETURN
구현부
END;
-- 예시
-- A. 생성
create function sf_password(password varchar(255))
returns varchar(255)
begin
return
case
when length(password) > 2 then
concat(substring(password, 1, 2), '**')
else '****'
end;
end;
-- B. 호출
select
sf_password(password) as password_mask
from member;
-- C. 삭제
drop function sf_password;
B. 프로시저
-- 기본 구문
CREATE PROCEDURE 함수명()
BEGIN
구현부
END
;
-- 예시
-- A. 생성
create procedure sp_select_memeber()
begin
select *
from member;
select *
from member_detail;
end;
-- B. 호출
call sp_select_memeber();
-- C. 삭제
drop procedure sp_select_memeber;
C. delimiter를 사용해야 할까?
- 원칙적으로, 함수나 프로시저를 생성할 때, delimiter를 사용하는 것이 필요하다.
** delimiter 곧, SQL 명령어 간의 구분은 기본적으로 세미콜론(';')을 통해 이루어지기에,
SQL 명령어를 구현부에 중첩시키면 하위 명령어는 실행되지 않는 현상이 발생)
- 다만, 툴을 사용하면 delimiter를 사용하지 않아도 오류 없이 잘 실행된다.
delimiter $$ -- delimiter 변경
create procedure sp_select_memeber()
begin
select *
from member;
select *
from member_detail;
end;
delimiter ; -- delimiter default인 ;로 원상복구
| 트리거
특정 조건이 만족되면 자동으로 실행 시작
rf. 일반적으로 히스토리를 남기는 데에 사용된다.
rf. 상황에 따라 양날의 검이 될 수 있는 명령문이다. (사용에 주의가 필요)
event
old
new
insert
X
O
update
O
O
delete
O
X
-- RQ. MEMBER 테이블의 핸드폰 번호를 갱신(update)할 때마다 히스토리를 남기고 싶다.
-- A. 변경 사항 담을 히스토리 테이블
create table member_detail_history
(
id int auto_increment primary key ,
member_type varchar(10),
user_id varchar(50),
mobile_no varchar(50),
new_mobile_no varchar(50),
update_date datetime
);
-- B. 트리거 예시
delimiter &&
create trigger tg_member_mobile_no_history
before update on member_detail -- update 되기 이전에 트리거가 일어난다.
for each row
begin
insert into member_detail_history
(
member_type,
user_id,
mobile_no,
new_mobile_no,
update_date
)
values
(
old.member_type,
old.user_id,
old.mobile_no,
new.mobile_no,
now()
);
end;
delimiter ;
select
m.id as 회원아이디,
m.password as 비밀번호,
m.name as 회원명
from member as m;
| JOIN문
- JOIN만 적히는 경우 INNER JOIN이 사용된다.
INNER JOIN
키 값 기준 데이터 결합
LEFT JOIN
키 값 기준 데이터 결합 + 좌측의 나머지
RIGHT JOIN
키 값 기준 데이터 결합 + 우측의 나머지
FULL JOIN
LEFT데이터 X RIGHT데이터
- 수업에서 제공되었던 테이블은, MEMBER와 MEMBER_DETAIL 이었는데,
이와 같이 회원 정보를 간편 정보 / 상세 정보로 나누어서 필요에 따라 테이블을 조인하는 식으로 사용한다고 한다.
-- 회원정보 > 로그인 정보
create table member
(
member_type varchar(10) not null comment '회원구분',
user_id varchar(50) not null comment '회원 아이디',
password varchar(50) null comment '비밀번호',
name varchar(20) null comment '이름',
primary key (member_type, user_id)
) comment '회원정보';
-- 회원상세정보 > 로그인에 필요하지 않은 이외 정보
create table member_detail
(
member_type varchar(10) not null comment '회원구분',
user_id varchar(50) not null comment '회원 아이디',
mobile_no varchar(12) null comment '휴대폰 번호',
marketing_yn bit null comment '마케팅 수신 여부',
register_date datetime default current_timestamp() null comment '가입일',
primary key (member_type, user_id),
constraint fk_member_detail foreign key (member_type, user_id) references member (member_type, user_id)
) comment '회원상세정보';
- JOIN 코드
-- 조인 (INNER JOIN)
-- ** 단순히 select * from.. 을 쓰면 어떤 DBMS에서는 오류가 날 수 있다.
select
m.*,
md.*
from member as m
join member_detail as md
on m.member_type = md.member_type and m.user_id = md.user_id
;
-- 중복된 컬럼 제외
select
m.member_type, m.user_id, m.password, m.name,
md.mobile_no, md.marketing_yn, md.register_date
from member as m
join member_detail as md
on m.member_type = md.member_type and m.user_id = md.user_id
;
-- LEFT JOIN : member(left)를 기준으로 member_detail(right)와 키값이 동일한 데이터를 먼저 솎아내고, 그 후에 member의 나머지를 추가
select
m.member_type, m.user_id, m.password, m.name,
md.mobile_no, md.marketing_yn, md.register_date
from member as m
left join member_detail as md
on m.member_type = md.member_type and m.user_id = md.user_id
;
-- RIGHT JOIN : RIGHT를 기준으로
select
m.member_type, m.user_id, m.password, m.name,
md.mobile_no, md.marketing_yn, md.register_date
from member as m
right join member_detail as md
on m.member_type = md.member_type and m.user_id = md.user_id
;
-- FULL JOIN : LEFT x RIGHT
select
m.member_type, m.user_id, m.password, m.name,
md.mobile_no, md.marketing_yn, md.register_date
from member as m
join member_detail as md
;
| 내장함수
- 각 DBMS별로 내장되어 있는 함수들이 있다.
- 구분
입력값의 갯수에 따라
- 단일행 함수 : 함수의 입력값이 단일행 값일 때 - 다중행 함수 : 함수의 입력값이 다중행 값일 때 (ex. 집계 함수, 그룹 함수)
데이터 타입에 따라
- 문자형 함수 - 숫자형 함수 - 날짜형 함수 - 변환형 함수 : 데이터 타입 변환 - NULL 관련 함수
- SQL 조건문 : CASE ~ END
-- SQL 조건문 : 자바의 switch case와 동일
CASE
WHEN 조건 THEN 실행
ELSE 실행
END
- 수업에서 다루었던 코드
-- A. 문자형
-- (1) 비밀번호 * 처리 (substring, length, concat)
-- 오라클의 경우, 문자열 결합 시 : concat(문자열, 결합할 문자) 또는 문자열 || 결합할문자
SELECT
member_type,
user_id,
password,
name,
-- concat(substring(password, 1, 2), '**') as password_mask, -- idx가 1부터 시작
-- length(password) as password_length,
case
when length(password) > 2 then concat(substring(password, 1, 2), '**')
else ''
end as password_mask
FROM member;
-- B. 데이터 포맷 변환
SELECT register_date,
date_format(register_date, '%Y.%m.%d') as dt_format
FROM member_detail;
SELECT
'20220321',
str_to_date('20220321', '%Y%m%d') as dt_date,
date_add(str_to_date('20220321', '%Y%m%d'), interval 1 month) as dt_date2
FROM dual;
-- 현재 날짜를 통해 월초와 월말을 구하기
SELECT
date_format(now(), '%Y-%m-%01') as start_date, -- 이번달 첫일
date_add(date_add(str_to_date(date_format(now(), '%Y-%m-01'), '%Y-%m-%d'), interval 1 month), interval -1 day) as end_date
FROM dual;
--!! 주의사항 !! 특히하게 LIMIT은 idx를 0부터 시작한다.
-- 전체 data에 대해 첫번째부터 10개만 출력
SELECT *
FROM member
LIMIT 0, 10;
-- 나열된 data에 대해 10번째부터 10개만 출력
select
c.code, c.company_name, c.eng_company_name, c.category,
row_number() over (order by c.code desc) as row_index
from company c
order by c.code desc
limit 10, 10
;
-- 범위를 지정하여 출력
select *
from
(
select *
from
(
select
c.code, c.company_name, c.eng_company_name, c.category,
row_number() over (order by c.code desc) as row_index
from company c
order by c.code desc
) t1
where row_index <= 30
) t2
where row_index > 20;
📡 ORACLE
-- 기본 구문
--- '*'을 사용하면 데이터가 깨진다.
--- 테이블.* 또는 칼럼명을 하나씩 작성 필요.
SELECT
칼럼1,
칼럼2,
ROWNUM as rnum
FROM member
ORDER BY 순서기준
-- 실제 사용
SELECT *
FROM (
SELECT
a.*, ROWNUM as rnum
FROM (
SELECT
* FROM member
ORDER BY member_no
) a
)
WHERE rnum >= 1 AND rnum <= 10;