메모리: 119 MB, 시간: 49.53 ms - Answer Code1
메모리: 98.7 MB, 시간: 4.72 ms - Answer Code2
import java.util.*;
class Solution {
public int solution(int[] queue1, int[] queue2) {
Queue<Integer> que1 = new LinkedList<>();
Queue<Integer> que2 = new LinkedList<>();
long sum1 = 0, sum2 = 0;
for(int i = 0; i < queue1.length; i++) {
sum1 += queue1[i];
que1.offer(queue1[i]);
}
for(int i = 0; i < queue2.length; i++) {
sum2 += queue2[i];
que2.offer(queue2[i]);
}
int count = 0;
while(sum1 != sum2) {
count++;
if(sum1 > sum2) {
int value = que1.poll();
sum1 -= value;
sum2 += value;
que2.offer(value);
} else {
int value = que2.poll();
sum1 += value;
sum2 -= value;
que1.offer(value);
}
if(count > (queue1.length + queue2.length) * 2) return -1;
}
return count;
}
}
각 큐의 합이 같을 때까지 반복문을 돌려줬다.
또한 원소의 합이 같지 않는 경우에는 return을 -1로 두는 특수 예외 조건이 존재한다.
분기 조건이 (queue1.length + queue2.length) * 2
인 이유는 양쪽 큐 길이를 전부 돌았을 횟수로 잡았기 때문이다.
즉, 각 큐에서 원소를 추출하고 집어넣는 작업이 최대로 반복될 수 있는 횟수를 고려한 것이다.
최악의 경우, 한 큐의 모든 원소를 다른 큐에 집어넣어야 할 수 있으므로, 이때 루프의 최대 횟수는 큐의 길이의 2배가 된다.
합계(sum)가 long 형으로 한 이유는 제한사항
에서 queue1, queue2 원소의 최대 범위가 10^9이므로, 합 계산 과정 중 산술 오버플로우 발생 가능성이 있어서이다.
import java.util.*;
class Solution {
public int solution(int[] queue1, int[] queue2) {
int[] totalQueue = new int[queue1.length + queue2.length];
long queue1Sum = 0;
long queue2Sum = 0;
for(int i = 0; i < queue1.length; i++) {
int val = queue1[i];
totalQueue[i] = val;
queue1Sum += val;
}
for(int i = queue1.length; i < queue1.length + queue2.length; i++) {
int val = queue2[i - queue1.length];
totalQueue[i] = val;
queue2Sum += val;
}
if((queue1Sum + queue2Sum) % 2 == 1) return -1;
int count = 0;
int left = 0;
int right = queue1.length;
long half = (queue1Sum + queue2Sum) / 2;
while(left < right && right < totalQueue.length) {
if(queue1Sum == half) {
return count;
} else if(queue1Sum > half) {
queue1Sum -= totalQueue[left++];
} else {
queue1Sum += totalQueue[right++];
}
count++;
}
return -1;
}
}
이 글은 제미니의 개발실무의 Git 형상 관리 + 작업 단위와 PR 코드 리뷰 + 협업을 잘하는 개발자 영상을 보면서 제 생각과 같이 정리한 글입니다.
(시청자) 질문. 큰 규모의 작업을 할 때 commit 해야 할 작업 task를 나누는 기준이 궁금합니다.
- commit 할 작업 단위를 먼저 생각해보고 -> 그 단위로 작업을 하면서 중간중간 commit한다.
- 그냥 한번에 기능 단위 개발을 하고 -> 이후에 작업을 쪼개면서 commit한다.
리뷰
시스템 전체를 개발한다고 가정을 해보자. 그러면 하나의 task로 담기에는 굉장히 클 수 있다.
그렇기 때문에 먼저 작업 단위 자체
를 잘 나누는게 가장 중요하다.
리뷰
라는 기능이 있다면,
리뷰 등록, 리뷰 삭제와 같은 각 세부 기능에 대해서 하나의 작업
이라 생각하고, 그러한 작업을 Issue & Branch & PR
로 가져가는게 좋다.
큰 규모의 작업 자체가 있으면, 이는 여러가지 어려움을 만드는 것 같다.
추가/수정된 파일과 커밋 갯수가 많을 수록 다른 사람이 코드 리뷰하는게 까다로울 수 있다.
제민님의 의견: 같이 일하는 동료가 내 코드를 쉽게 이해할 수 있도록 task를 작게 가져갈 것 같다.
작업 단위를 작게 나누고, 위에 질문주신 2가지를 고민할 것 같다.
작업 단위를 작게 엄청 잘 나누게 되면, 애초에 커밋에 대한 생각을 많이 안해도 되는 경우가 있는 것 같다.
흔히 많이 하는 실수가 신규 기능 개발과 리팩터링하는 것을 하나의 task
라고 생각하고 작업하는 경우가 있다.
이러한 실수를 예방하기 위해 한 가지 작업을 딱 정해서, 최대한 작게 가져가면 커밋에 대한 고민은 크게 하지 않아도 된다.
즉, 신규 기능 개발과 리팩터링을 각각의 작업 단위로 생각해야 한다. -> task1 : 신규 기능 개발 / task2 : 리팩터링
결론적으로 질문 주신 것에 대해 답변을 하면, 1번과 2번을 다 쓰긴 한다.
어떻게 하면 동료가 코드 리뷰하기 더 좋을까에 대해서 커밋과 PR를 나눈다고 보시면 될 것 같다.
개인적으로 2번을 많이 쓴다. 2번: “그냥 한번에 기능 단위 개발을 하고 -> 이후에 작업을 쪼개면서 commit한다.”
커밋하는 부분도 협업 스킬 중 하나라고 생각한다.
아래와 같이 커밋을 작성하면 리뷰하는 사람 입장에서 힘들 수 있다.
User 클래스에 대한 작업 <- 하나의 task
--- 아래부터는 커밋 내용
feat: User 클래스가 수정!
feat: User 클래스가 수정!
feat: User 클래스가 수정! (리팩토링 포함)
feat: User 클래스가 수정!
...
feat: User 클래스가 수정!
[PR]
수정 파일이 50개
커밋이 10개
만약 작업 단위가 작았다면, 위에서처럼 발생할 수 없다.
수정 파일이 많고, 같은 클래스에서 여러 번의 커밋을 작성하면, 리뷰할 사람 입장에서는 이해하기가 어려울 수 있다.
[PR 올릴 때 커밋 정리 기준 중 하나] - 한 클래스는 한 파일에서만 수정된다.
보통 커밋은 아래와 같이 진행한다.
하나의 작업에 대한 커밋을 여러번 작성한다. -> 본인을 위해
어느 정도 커밋을 작성하고 PR을 올리기 전에, 지금까지 작성한 커밋들을 정리한다.
PR을 올리면서 동료로부터 코드 리뷰를 받고, 추가적인 커밋을 한다. -> 동료를 위해
리뷰가 끝나면, 지금까지 작성한 커밋을 한번 더 정리를 한다. -> 회사 자산 관리 + 미래의 동료를 위해
리뷰
에 대한 기능을 개발한다고 가정했을 때, 작업 단위를 나눈다. -> 리뷰 등록, 리뷰 조회, 리뷰 수정, 리뷰 삭제
그런 다음, 작업에 대한 Branch를 나누고 PR도 여러개 올린다.
리뷰 등록에 대한 Branch명: review-add
리뷰 삭제에 대한 Branch명: review-remove
그리고 세부 기능에 대해서 Branch를 연계적으로 생성한다.
이전 굿프렌즈 팀 프로젝트에서는 우리만의 Git flow 전략을 만들면서, 형상 관리를 나름 신경썼다고 생각했다.
브랜치 면에서는 이 영상에서 다루는 거와 거의 비슷하게 작업을 진행했지만, 커밋 부분에 있어서는 ‘이 정도로 신경을 써야 하는구나’라는 생각이 많이 들었다.
최근에 주문
기능에 대해서 리팩터링 과정을 진행했는데, 작은 작업 단위 보다는 큰 작업으로 진행해서 아래와 같이 15개의 커밋 내용과 17개의 파일이 수정되었다.
(물론, 10월 이후부터는 팀원들을 제외한 나 혼자만의 리팩터링을 진행해서 더 신경을 안쓴것도 맞다..ㅎㅎ 😅)
다음에 팀 프로젝트를 진행한다면, 커밋
에 대해서도 신경쓰면서 작업을 진행하면 좋겠다는 생각이 들었다.
(해당 영상을 참고하시면 더 자세하게 설명해주시니 참고하면 좋을 것 같습니다!)
문제: PRODUCT
테이블과 OFFLINE_SALE
테이블에서
상품코드 별 매출액(판매가 * 판매량) 합계를 출력하는 SQL문을 작성해주세요.
결과는 매출액을 기준으로 내림차순 정렬해주시고
매출액이 같다면 상품코드를 기준으로 오름차순 정렬해주세요.
SELECT A.PRODUCT_CODE,
(A.PRICE * B.TOT_SALES_AMOUNT) AS SALES
FROM PRODUCT A
LEFT JOIN (
SELECT PRODUCT_ID
, SUM(SALES_AMOUNT) TOT_SALES_AMOUNT
FROM OFFLINE_SALE B
GROUP BY
PRODUCT_ID
) B
ON A.PRODUCT_ID = B.PRODUCT_ID
ORDER BY SALES DESC, PRODUCT_CODE ASC
문제를 풀기 위해서 해야할 작업들
첫 번째 단계는 OFFLINE_SALE 테이블에서 PRODUCT_ID 별 판매 수량을 집계한다.
이때 GROUP BY를 집계해서 각 상품별 수량을 산출한다.
SELECT PRODUCT_ID
, SUM(SALES_AMOUNT) TOT_SALES_AMOUNT
FROM OFFLINE_SALE B
GROUP BY
PRODUCT_ID
두 번째 단계는 앞에서 진행한 결과를 서브쿼리로 넣고, PRODUCT 테이블을 기준으로 LEFT JOIN을 해준다.
이 결과를 통해 각 상품별 총 판매개수와 상품별 금액을 알 수 있다.
SELECT *
FROM PRODUCT A
LEFT JOIN (
SELECT PRODUCT_ID
, SUM(SALES_AMOUNT) TOT_SALES_AMOUNT
FROM OFFLINE_SALE B
GROUP BY
PRODUCT_ID
) B
ON A.PRODUCT_ID = B.PRODUCT_ID
SELECT A.PRODUCT_CODE,
(A.PRICE * B.TOT_SALES_AMOUNT) AS SALES
FROM PRODUCT A
LEFT JOIN (
SELECT PRODUCT_ID
, SUM(SALES_AMOUNT) TOT_SALES_AMOUNT
FROM OFFLINE_SALE B
GROUP BY
PRODUCT_ID
) B
ON A.PRODUCT_ID = B.PRODUCT_ID
SELECT A.PRODUCT_CODE,
(A.PRICE * B.TOT_SALES_AMOUNT) AS SALES
FROM PRODUCT A
LEFT JOIN (
SELECT PRODUCT_ID
, SUM(SALES_AMOUNT) TOT_SALES_AMOUNT
FROM OFFLINE_SALE B
GROUP BY
PRODUCT_ID
) B
ON A.PRODUCT_ID = B.PRODUCT_ID
ORDER BY SALES DESC, PRODUCT_CODE ASC
8월 1일부터 9월 27일까지 팀 프로젝트를 진행했지만, 그 당시에는 ‘구현’에 초점이 맞춰졌기 때문에, 개개인의 코드의 품질에 제대로 신경쓰지 못하고 개발했다.
(초반에는 코드 리뷰를 열심히 해왔지만, 한정된 시간안에 구현해야 하는 압박감과 부담감에 후반으로 갈 수록 코드 리뷰를 제대로 못했다..)
그리고 좋은 코드가 무엇인지, 좋은 코드를 짜기 위해서 어떻게 리팩터링하는지에 대해서 제대로 알지 못했다.
그래서 팀 프로젝트가 끝난 이후에, ‘나중에 반드시 리팩터링 해야겠다!’ 고 다짐했다.
그렇게 한달이 지나고, 예상치 못한 좋은 기회로 11월부터 12월까지 우아한 스터디의 “내 코드가 그렇게 이상한가요?
” 주제에 참가하게 되어서, 좋은 코드가 무엇인지 배우게 되었다.
“내 코드가 그렇게 이상한가요?” 책을 읽으면서 스터디에 참가하는 분들과 토론하면서 좋은 코드로 개선하는 방법을 알게되었다.
(해당 책을 공부하면서 내 블로그: GoodGode에 따로 정리해두었다)
해당 스터디에서 배운 내용을 기반으로 기존에 구현했던 기능들, 특히 사용자 기반 기능(프로필, 상품, 주문, 신고)을 우선적으로 리팩터링을 진행했다.
주문
도메인에 대한 리팩터링 과정주요 기능 중 하나인
주문
에 대한 리팩터링 과정을 자세히 확인하고 싶다면, PR을 참고하자.
주문
도메인에서 본인이 판매 등록한 물품에 들어온 주문서 전체를 조회하는 API와 관련된 비즈니스 로직에 대한 리팩터링 과정을 정리하고자 한다.리팩터링 전)
주문
도메인에서 주문서 전체를 조회하는 API에 대한 비즈니스 로직(OrderService.class) - findAllOrder
# 목표
- [ ] 구조를 파악할 수 있도록 기존의 클래스/메서드/변수명 수정할 것 - 굿프렌즈 WIKI에 있는 [객체 및 메서드 생성 규칙](https://github.com/woorifisa-projects/GoodFriends/wiki/객체-및-메서드-생성-규칙)
- [ ] 불변 활용할 것
- [ ] 단일 책임 원칙을 지키기 위해 하나의 메서드에 한가지 일만 담당하도록 구현한다.
- [ ] 메서드 최대 길이 15줄 이내로 할 것
- [ ] 리팩터링 이후 불필요한 주석은 제거할 것
findAllOrder()
메서드명을 findAllMyProductOrders()
로 수정했다.[2] 그리고 findAllMyProductOrders()
메서드에서 유효성을 검사하는 로직을 validateOffenderAndMyProduct()
메서드를 생성해서 처리해주도록 수정했다.
추가적으로 예외 클래스명에 대해서도 직관적이고 이해할 수 있는 클래스명으로 수정했다. (예외 처리 클래스를 각 도메인의 비즈니스 규칙에 맞도록 커스터마이징했다)
기존) NotAccessProductException
-> 변경) InactiveUserAccessException
- 예외 메시지: ‘비활성화 상태인 유저로 해당 페이지에 접근이 불가능합니다’
기존) NotOwnProductException
-> 변경) InvalidProductOrderAccessException
- 예외 메시지: ‘본인이 등록하지 않은 상품의 주문서는 조회할 수 없습니다.’
[3] findAllMyProductOrders()
에는 여러 로직을 처리하고 있어서 단일 책임 원칙
에 위배되고 있다.
이를 해결하기 위해 첫 번째로 하나의 메서드에 한 가지 일만 담당하도록 로직을 수정했고, 동시에 중첩 if문을 제거했다.
handleNonSellProduct()
[4] 그런 다음, 두 번째로 map 부분에서 OrderProductResponse
클래스(이전 클래스명: OrderViewOneResponse
) 정적 팩토리 메서드
(of)를 추가하여 중복되는 부분을 최소화했다.
팩토리 메서드 패턴
(Factory Method Pattern)은 객체 생성을 처리하기 위한 디자인 패턴 중 하나다.
이 패턴을 사용함으로써 객체 생성과정이 클래스 외부로 추상화되어, 코드의 가독성이 높아지고 유지보수가 용이해진다.
또한 팩토리 메서드 패턴
은 객체 생성에 필요한 복잡한 로직을 한 곳에 모아둠으로써 중복을 줄일 수 있다.
of
메서드는 주로 정적(static) 팩터리 메서드로 사용되며, 해당 객체를 생성하고 초기화하는데 사용된다. 불변성(Immutability)을 강조하거나 특정한 시나리오에서 명확하게 사용할 수 있도록 하는데 주로 쓰인다.
findAllMyProductOrders()
, handleNonSellProduct()
두 메서드의 return 하는 부분에서 true/false 값은 판매 상태 여부를 나타내는 걸 의미하는데, 이를 private static final
로 상수화로 선언해서 아래와 같이 수정했다.findAllMyProductOrders()
메서드에서 주문 응답을 가져오는 로직을 getOrderProductResponses()
메서드로 추출했다.마지막으로 findAllMyProductOrders()
메서드에서 로직을 처리하는 순서에 맞게 조정했다.
그리고 handleNonSellProduct()
메서드 명에 대해서도 직관적으로 이해할 수 있게 beginDealForOrder()
으로 다시 수정했다.
이렇게 해서 주문
도메인에서 본인이 판매 등록한 물품에 들어온 주문서 전체를 조회하는 API와 관련된 비즈니스 로직에 대한 리팩터링을 진행했다.
# 목표 달성
- [x] 구조를 파악할 수 있도록 기존의 클래스/메서드/변수명 수정할 것 - 굿프렌즈 WIKI에 있는 [객체 및 메서드 생성 규칙](https://github.com/woorifisa-projects/GoodFriends/wiki/객체-및-메서드-생성-규칙)
- [x] 불변 활용할 것
- [x] 단일 책임 원칙을 지키기 위해 하나의 메서드에 한가지 일만 담당하도록 구현한다.
- [x] 메서드 최대 길이 15줄 이내로 할 것
- [x] 리팩터링 이후 불필요한 주석은 제거할 것
리팩터링 후)
주문
도메인에서 주문서 전체를 조회하는 API에 대한 비즈니스 로직(OrderService.class) -findAllMyProductOrders()
사실 주문
도메인에 대한 리팩터링 하기 이전에, 이미 프로필
, 신고
, 상품
에 대해 리팩터링을 진행했다.
스터디에서 배운 내용을 기반으로 기존 프로젝트(굿프렌즈)에서 사용자 기반의 기능들을 모두 리팩터링하면서 적용해봤다.
책에 있는 기술, 개념들을 적용하는 과정에서 시간이 많이 걸렸지만, 그 만큼 많은 경험을 얻게 되었다.
다음에는 이전에 테스트 코드 강의를 듣고 정리해둔 Practical Testing: 테스트 코드 작성 방법을 기반으로 굿프렌즈 프로젝트에 단위 및 통합 테스트 코드를 작성해보자!
문제: CAR_RENTAL_COMPANY_RENTAL_HISTORY
테이블에서 대여 시작일이
2022년 9월에 속하는 대여 기록에 대해서 대여 기간이 30일 이상이면 ‘장기 대여’
그렇지 않으면 ‘단기 대여’ 로 표시하는 컬럼(컬럼명: RENT_TYPE)을 추가하여
대여기록을 출력하는 SQL문을 작성해주세요.
결과는 대여 기록 ID를 기준으로 내림차순 정렬해주세요.
SELECT HISTORY_ID, CAR_ID,
DATE_FORMAT(START_DATE, '%Y-%m-%d') AS START_DATE,
DATE_FORMAT(END_DATE, '%Y-%m-%d') AS END_DATE,
CASE WHEN DATEDIFF(END_DATE, START_DATE) + 1 >= 30 THEN "장기 대여"
ELSE "단기 대여" END AS RENT_TYPE
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE BETWEEN '2022-09-01' AND '2022-09-30'
ORDER BY HISTORY_ID DESC;
문제를 풀기 위해서 해야할 작업들
RENT_TYPE
컬럼 추가)대여 기옥 ID
를 기준으로 내림차순하기
DATE_FORMAT
: MySQL에서 사용되는 날짜 데이터의 출력 형식을 지정하는 함수
DATE_FORMAT(START_DATE, '%Y-%m-%d')
은 start_date 에서 '%Y-%m-%d'
형식으로 반환해준다.
(대부분의 날짜 데이터는 %Y-%m-%d
형식을 많이 사용한다.)
%Y
: 4자리 연도(예: 2023) / %y
: 2자리 연도 (예: 23)
%m
: 월 (01부터 12까지)
%d
: 일 (01부터 31까지)
DATEDIFF
: MySQL에서 사용되는 두 날짜 간의 차이를 계산하는 함수
이 함수는 첫 번째 날짜에서 두 번째 날짜를 뺀 결과를 반환해준다.
기본 사용 형식: DATEDIFF(END_DATE, START_DATE)
중요) 오늘 대여하고 오늘 반납해도 대여 기간은 하루 기간이기 때문에, (END_DATE - START_DATE) +1 >= 30 으로 계산해야 한다.
# 대여기간 예시
2023년 12월 22일 대여 >> 2023년 12월 22일 반납: 1일
= (22 - 22)일 + 1일
= 1일
2023년 12월 22일 대여 >> 2023년 12월 23일 반납: 2일
= (23 - 22)일 + 1일
= 2일
CASE 문
: 조건에 따라 새로운 값을 부여하여 새로운 컬럼을 부여하는 경우에 사용한다.
CASE 문
의 기본 사용 형식이다.CASE
WHEN 조건 1
THEN 조건 1 만족 시 반환하는 값
WHEN 조건 2
THEN 조건 2 만족 시 반환하는 값
ELSE 조건들에 만족 안 하는 경우 반환 값
END
문제: CAR_RENTAL_COMPANY_CAR
테이블에서 ‘네비게이션’ 옵션이 포함된 자동차 리스트를 출력하는 SQL문을 작성해주세요.
결과는 자동차 ID를 기준으로 내림차순 정렬해주세요.
select car_id, car_type, daily_fee, options
from car_rental_company_car
where options like '%네비게이션%'
order by car_id desc
문제를 풀기 위해서 해야할 작업들
네비게이션
옵션이 포함된 자동차 리스트를 출력한다.CAR_RENTAL_COMPANY_CAR
테이블에서 ‘네비게이션’ 옵션이 포함 => where + like 절을 사용한다. => like '%네비게이션%'
order by car_id desc
=> DESC
는 내림차순(ASC
는 오름차순 이다)
문제: USED_GOODS_BOARD
테이블에서 2022년 10월 5일에 등록된
중고거래 게시물의 게시글 ID, 작성자 ID, 게시글 제목, 가격, 거래상태를 조회하는 SQL문을 작성해주세요.
거래상태가 SALE 이면 판매중, RESERVED이면 예약중, DONE이면 거래완료 분류하여 출력해주시고,
결과는 게시글 ID를 기준으로 내림차순 정렬해주세요.
SELECT BOARD_ID, WRITER_ID, TITLE, PRICE,
CASE
WHEN STATUS = 'SALE' THEN '판매중'
WHEN STATUS = 'RESERVED' THEN '예약중'
WHEN STATUS = 'DONE' THEN '거래완료'
END STATUS
FROM USED_GOODS_BOARD
WHERE BOARD_ID IN(
SELECT BOARD_ID
FROM USED_GOODS_BOARD
WHERE CREATED_DATE = '2022-10-05'
)
ORDER
BY BOARD_ID DESC;
문제를 풀기 위해서 해야할 작업들
문제: USED_GOODS_BOARD
와 USED_GOODS_USER
테이블에서
중고 거래 게시물을 3건 이상 등록한 사용자의 사용자 ID, 닉네임, 전체주소, 전화번호를 조회하는 SQL문을 작성해주세요.
이때, 전체 주소는 시, 도로명 주소, 상세 주소가 함께 출력되도록 해주시고,
전화번호의 경우 xxx-xxxx-xxxx
같은 형태로 하이픈 문자열(-)을 삽입하여 출력해주세요.
결과는 회원 ID를 기준으로 내림차순 정렬해주세요.
select user.user_id, user.nickname,
concat(user.city,' ', user.street_address1,' ', user.street_address2) AS 전체주소,
concat(LEFT(tlno, 3), '-', MID(tlno, 4, 4),'-', RIGHT(tlno, 4)) AS 전화번호
from used_goods_user as user
join used_goods_board as board
on user.user_id = board.writer_id
group by board.writer_id
having count(board.writer_id) >= 3
order by user.user_id desc;
문제를 풀기 위해서 해야할 작업들
USED_GOODS_BOARD
테이블에서 writer_id 컬럼의 값을 count하여 3개 이상인 writer_id 찾기USED_GOODS_USER
의 CITY, STREET_ADDRESS1, STREET_ADDRESS2를 활용하여 ‘전체주소’ 생성하기USED_GOODS_USER
의 TLNO를 활용하여 ‘전화번호’ 생성하기
GROUP BY
구문: 주로 집계 함수(COUNT)와 함께 사용되어 특정 열의 값에 기반하여 그룹을 형성하는 역할
group by board.writer_id
having count(board.writer_id) >= 3
USER_GOODS_BOARD
테이블에서 WRITER_ID
열을 기준으로 그룹을 형성한다.
그런 다음, HAVING
구문을 사용하여 USER_GOOD_BOARD
테이블에서 각 작성자(WRITER_ID
)가 최소 3개 이상의 게시물을 작성한 경우에만 해당 그룹이 선택된다.
CONCAT()
함수: SQL에서 사용되는 문자열을 연결하는 함수
concat(user.city,' ', user.street_address1,' ', user.street_address2) AS 전체주소
SELECT 절에서 “CONCAT() 함수”를 이용해서 CITY
, STREET_ADDRESS1
, STREET_ADDRESS2
컬럼을 하나의 문자열로 연결하는 작업이다.
예시에서 나온 것과 같게 만들기 위해서는 공백(‘ ‘)으로 각 컬럼의 값을 구분하고, AS 절을 이용하여 요구한 컬럼의 이름과 동일하게 전체주소
라는 컬럼을 부여했다.
concat(LEFT(tlno, 3), '-', MID(tlno, 4, 4),'-', RIGHT(tlno, 4)) AS 전화번호
“CONCAT() 함수”를 이용해서 연결 사이에 ‘-‘ 으로 구분하여 하나의 문자열을 만들었다.
LEFT(tlno, 3): 전화번호 문자열의 왼쪽에서 3자리를 추출
MID(tlno, 4, 4): 전화번호 문자열에서 4번째 위치에서부터 4자리를 추출
RIGHT(tlno, 4): 전화번호 문자열의 오른쪽에서 4자리를 추출합니다.
다른 방법: SUBSTR((또는 SUBSTRING)) 함수 사용 : 문자열에서 일부분을 추출하는 데 사용되는 SQL 함수이다.
CONCAT(SUBSTR(TLNO, 1, 3), '-', SUBSTR(TLNO, 4, 4), '-', SUBSTR(TLNO, 8)) AS 전화번호
일반적인 사용 형식: SUBSTR(string, start_position, length)
string: 추출하려는 문자열
start_position: 추출을 시작할 위치입니다. 1부터 시작한다.
length (선택사항): 추출하려는 부분의 길이를 지정한다. 만약 이 부분을 생략하면 시작 위치부터 문자열의 끝까지 추출하게 된다.
SUBSTR()
(또는 SUBSTRING) 함수: 문자열에서 일부분을 추출하는 데 사용되는 SQL 함수이다.
SUBSTR(TLNO, 1, 3): 전화번호 문자열의 1번째 위치에서부터 3자리를 추출
SUBSTR(TLNO, 4, 4): 전화번호 문자열에서 4번째 위치에서부터 4자리를 추출
SUBSTR(TLNO, 8): 전화번호 문자열의 8번째 위치에서부터 끝까지 추출
SELECT USER_ID
, NICKNAME
, CONCAT(CITY, ' ', STREET_ADDRESS1, ' ', STREET_ADDRESS2) AS '전체주소'
, CONCAT(SUBSTR(TLNO, 1, 3), '-', SUBSTR(TLNO, 4, 4), '-', SUBSTR(TLNO, 8)) AS '전화번호'
FROM USED_GOODS_USER
WHERE USER_ID IN (
SELECT WRITER_ID
FROM USED_GOODS_BOARD
GROUP BY WRITER_ID
HAVING COUNT(*) >= 3
)
ORDER
BY USER_ID DESC;
위 정답은 다른 사람이 푼 것을 가져와봤다.
Answer Code1
는 join을 사용했는데, Answer Code2
는 join 없이 서브 쿼리를 사용한 SQL 구문이다.
문제: USED_GOODS_BOARD
와 USED_GOODS_FILE
테이블에서
조회수가 가장 높은 중고거래 게시물에 대한 첨부파일 경로를 조회하는 SQL문을 작성해주세요.
첨부파일 경로는 FILE ID를 기준으로 내림차순 정렬해주세요.
기본적인 파일경로는 /home/grep/src/ 이며, 게시글 ID를 기준으로 디렉토리가 구분되고,
파일이름은 파일 ID, 파일 이름, 파일 확장자로 구성되도록 출력해주세요.
조회수가 가장 높은 게시물은 하나만 존재합니다.
SELECT CONCAT('/home/grep/src/', BOARD_ID, '/', FILE_ID, FILE_NAME, FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE
WHERE BOARD_ID = (
SELECT BOARD_ID
FROM USED_GOODS_BOARD
ORDER BY VIEWS DESC
LIMIT 1
)
ORDER
BY FILE_ID DESC;
문제를 풀기 위해서 해야할 작업들
USED_GOODS_FILE
테이블에서 BOARD_ID 를 기준으로 디렉터리를 ‘/’ 으로 구분하고, 파일이름은 FILE_ID, FILE_NAME, FILE_EXT로 구성되도록 한다.USED_GOODS_BOARD
테이블에서 가장 높은 조회수(VIEW)를 SELECT 절과 MAX를 이용해서 하나만 조회하도록 한다.[1]. CONCAT() 함수를 이용하여 USED_GOODS_FILE
테이블에서 BOARD_ID 를 기준으로 디렉터리를 ‘/’ 으로 구분하고, 파일이름은 FILE_ID, FILE_NAME, FILE_EXT로 구성되도록 한다.
SELECT CONCAT('/home/grep/src/', BOARD_ID, '/', FILE_ID, FILE_NAME, FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE
[2]. 조건 where 절에서 USED_GOODS_BOARD
테이블에서 가장 높은 조회수(VIEW)를 SELECT 절과 ORDER BY, LIMIT절을 이용해서 하나만 조회한다.
WHERE BOARD_ID = (
SELECT BOARD_ID
FROM USED_GOODS_BOARD
ORDER BY VIEWS DESC
LIMIT 1
)
[3]. FILE_ID를 기준으로 내림차순 정렬하여 결과를 출력한다.
ORDER
BY FILE_ID DESC;
SELECT
CONCAT('/home/grep/src/', FILE.BOARD_ID, '/', FILE_ID, FILE_NAME, FILE_EXT) AS FILE_PATH
FROM USED_GOODS_FILE AS FILE
LEFT JOIN USED_GOODS_BOARD AS BOARD
ON FILE.BOARD_ID = BOARD.BOARD_ID
WHERE VIEWS = (SELECT MAX(VIEWS) FROM USED_GOODS_BOARD)
ORDER BY FILE_ID DESC
문제: FOOD_ORDER
테이블에서 5월 1일을 기준으로 주문 ID, 제품 ID, 출고일자, 출고여부를 조회하는 SQL문을 작성해주세요.
출고여부는 5월 1일까지 출고완료로 이 후 날짜는 출고 대기로 미정이면 출고미정으로 출력해주시고,
결과는 주문 ID를 기준으로 오름차순 정렬해주세요.
SELECT ORDER_ID, PRODUCT_ID, DATE_FORMAT(OUT_DATE, '%Y-%m-%d') as OUT_DATE,
case
when OUT_DATE <= '2022-05-01' then '출고완료'
when OUT_DATE > '2022-05-01' then '출고대기'
else '출고미정'
end '출고여부'
from FOOD_ORDER
order by ORDER_ID asc;