제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

두번의 정렬을 이용합니다. 장르 별로 정렬을 이용하고, 장르 내에 앨범 별로 정렬을 이용합니다. 

소스코드

import java.util.*;

class Solution {
    static class Album implements Comparable<Album>{
        int idx, playCnt;

        public Album(int idx, int playCnt) {
            this.idx = idx;
            this.playCnt = playCnt;
        }

        @Override
        public int compareTo(Album album) {
            if(this.playCnt == album.playCnt) {
                return this.idx - album.idx;
            } else {
                return album.playCnt - this.playCnt;
            }
        }
    }

    public int[] solution(String[] genres, int[] plays) {
        int len = genres.length;
        Map<String, Integer> genreRank = new HashMap<>();
        Map<String, List<Album>> albumRank = new HashMap<>();

        // 데이터 초기화
        for (int i = 0; i < len; i++) {
            genreRank.put(genres[i], genreRank.getOrDefault(genres[i], 0) + plays[i]);
            albumRank.putIfAbsent(genres[i], new ArrayList<>());
            albumRank.get(genres[i]).add(new Album(i, plays[i]));
        }

        // 장르별 총 재생 횟수에 따라 장르 정렬
        List<String> sortedGenres = new ArrayList<>(genreRank.keySet());
        sortedGenres.sort((a, b) -> genreRank.get(b) - genreRank.get(a));

        List<Integer> ans = new ArrayList<>();

        // 각 장르 내에서 앨범 정렬 및 상위 2곡 선택
        for (String genre : sortedGenres) {
            List<Album> genreAlbum = albumRank.get(genre);
            Collections.sort(genreAlbum);
            for (int i = 0; i < Math.min(2, genreAlbum.size()); i++) {
                ans.add(genreAlbum.get(i).idx);
            }
        }

        // 결과를 배열로 반환
        return ans.stream().mapToInt(Integer::intValue).toArray();
    }
}

코드 설명

static class Album implements Comparable<Album> {
    int idx, playCnt;

    public Album(int idx, int playCnt) {
        this.idx = idx;
        this.playCnt = playCnt;
    }

    @Override
    public int compareTo(Album album) {
        if(this.playCnt == album.playCnt) {
            return this.idx - album.idx;
        } else {
            return album.playCnt - this.playCnt;
        }
    }
}

 

  • Album 클래스:
    • 각 앨범(노래)의 인덱스(idx)와 재생 횟수(playCnt)를 나타냅니다.
    • compareTo 메서드는 재생 횟수를 기준으로 내림차순 정렬하며, 재생 횟수가 같으면 인덱스 기준으로 오름차순 정렬합니다.
  • solution 메서드:
    • genreRank 해시맵: 각 장르별 총 재생 횟수를 저장합니다.
    • albumRank 해시맵: 각 장르별로 Album 객체 리스트를 저장합니다.
    • for 루프를 사용해 genreRank와 albumRank를 초기화합니다.
      • 각 노래를 해당 장르의 총 재생 횟수에 추가하고, Album 객체를 장르 리스트에 추가합니다.
    • genreRank의 키(장르)를 재생 횟수에 따라 내림차순으로 정렬하여 sortedGenres 리스트를 만듭니다.
      sortedGenres.sort((a, b) -> genreRank.get(b) - genreRank.get(a));
// 각 장르 내에서 앨범 정렬 및 상위 2곡 선택
for (String genre : sortedGenres) {
    List<Album> genreAlbum = albumRank.get(genre);
    Collections.sort(genreAlbum);
    for (int i = 0; i < Math.min(2, genreAlbum.size()); i++) {
        ans.add(genreAlbum.get(i).idx);
    }
}
  • 장르 내 노래 정렬 및 선택:
    • 각 장르의 노래 리스트(genreAlbum)를 Collections.sort를 통해 정렬합니다.
    • 상위 2개의 노래 인덱스를 결과 리스트 ans에 추가합니다.
  • int[] 타입으로 반환해야하니 ans 리스트를 배열로 변환해 반환합니다.
    return ans.stream().mapToInt(Integer::intValue).toArray();

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

확률에서 사용했던 조합을 이용하였습니다. 각 종류별로 nC1을 하고 모든 의상을 안입는 경우를 제외하여 경우의 수를 구하였습니다.

의상 소스코드

import java.util.*;

class Solution {
    
    public int solution(String[][] clothes) {
        int len = clothes.length;
        Map<String, Integer> matches = new HashMap<>();
        
        for(int i = 0; i < len; i++) {
            String item = clothes[i][0];
            String kind = clothes[i][1];
            matches.put(kind, matches.getOrDefault(kind, 0) + 1);
        }
        int answer = 1;
        
        for(String key: matches.keySet()) {
            answer *= matches.get(key) + 1;
        }

        return answer - 1;
        
    }
}

코드 설명

 

  • matches 해시맵: 각 옷의 종류별 개수를 저장합니다.
    • clothes[i][0]은 옷의 이름, clothes[i][1]은 옷의 종류입니다.
    • 해시맵에 옷의 종류를 키로, 해당 종류의 옷의 수를 값으로 저장합니다.
  • 모든 옷의 조합 계산:
    • answer는 초기값으로 1로 설정됩니다.
    • 각 옷의 종류마다 (해당 종류의 옷 수 + 1)를 answer에 곱해줍니다.
    • +1은 해당 종류의 옷을 입지 않는 경우를 포함하기 위해 사용됩니다.
  • 결과 반환:
    • answer - 1은 아무 옷도 입지 않는 경우를 제외한 옷의 조합 수를 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

트라이 노드를 사용해서 문제를 풀었습니다. String에서 한자를 노드의 value로 생각하셔 문자가 같으면 자식 노드로 진입하면서 접두어가 같은지 확인합니다. 같지 않으면 접두어가 같지 않습니다.

 

이전에 문제를 풀었을 때 O(N^2)의 코드로 풀어서 좋은 방법이 없을까 하다가 트라이 노드로 문제를 풀었습니다. 하지만 코딩 테스트를 진행할 때에는 적합한 풀이는 아니라고 생각합니다.

폰켓몬 소스코드

import java.util.*;

class Solution {
    
    static class PhoneBook {
        // 트라이 노드 클래스 정의
        static class TrieNode {
            Map<Character, TrieNode> children = new HashMap<>();
            boolean isEndOfNumber;
        }

        // 루트 노드
        private TrieNode root = new TrieNode();

        // 번호를 트라이에 삽입
        private boolean insert(String number) {
            TrieNode current = root;
            for (char c : number.toCharArray()) {
                current.children.putIfAbsent(c, new TrieNode());
                current = current.children.get(c);

                // 번호의 중간에서 이미 끝인 경우 접두어 조건 위반
                if (current.isEndOfNumber) {
                    return false;
                }
            }

            // 번호가 완전히 끝난 경우를 표시
            current.isEndOfNumber = true;

            // 현재 노드에 자식이 있는 경우 접두어 조건 위반
            return current.children.isEmpty();
        }

        public boolean isConsistent(String[] phoneBook) {
            for (String number : phoneBook) {
                if (!insert(number)) {
                    return false;
                }
            }
            return true;
        }
    }
    
    public boolean solution(String[] phone_book) {
        PhoneBook phoneBookChecker = new PhoneBook();
        return phoneBookChecker.isConsistent(phone_book);
    }
}

코드 설명

 

  • TrieNode 클래스: 각 문자를 자식으로 연결하는 트라이 노드입니다.
  • insert 메서드: 전화번호를 트라이에 삽입하고 접두어 조건을 위반하는지 확인합니다. 중간에 이미 끝인 번호가 있거나 새로 추가된 번호의 마지막 노드에 자식이 있으면 false를 반환하여 접두어 조건 위반을 나타냅니다.
  • isConsistent 메서드: 전화번호 목록의 각 번호를 insert 메서드로 확인하여 접두어 관계가 있는 경우 false를 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

participant와 completion에서 언급된 선수가 짝수이면 완주한 선수이고 홀수이면 완주하지 못한 선수입니다.

완주하지 못한 선수 소스코드

import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        Set<String> runners = new HashSet<>();
        
        for (String runner: participant) {
            if (!runners.contains(runner)) {
                runners.add(runner);
            } else {
                runners.remove(runner);
            }
        }
        
        for (String runner: completion) {
            if (runners.contains(runner)) {
                runners.remove(runner);
            } else {
                runners.add(runner);
            }
        }
        
        for (String ans: runners) {
            return ans;
        }
        return null;
    }
}

코드 설명

 

  • Set<String> runners = new HashSet<>();
    참가자와 완주자를 저장할 집합 runners를 생성합니다.
  • for (String runner: participant)
    참가자 배열을 순회하며:
    • 이미 집합에 있는 참가자이면 집합에서 제거합니다 (완주 처리).
    • 그렇지 않으면 집합에 추가합니다.
  • for (String runner: completion)
    완주자 배열을 순회하며:
    • 집합에 있는 참가자이면 제거합니다. (완주)
    • 집합에 없는 참가자이면 집합에 추가합니다. (완주하지 못한 참가자)
  • for (String ans: runners) { return ans; }
    남은 하나의 이름이 완주하지 못한 참가자이므로 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers 알고리즘 고득점 Kit입니다.

포인트

Map을 사용하여 푸는 문제였습니다. 폰켓몬의 종류와 폰켓몬의 갯수를 확인하는 것이 포인트였다고 생각합니다.

N/2 마리를 선택할 때 폰켓몬의 종류보다 N/2가 더 크면 폰켓몬의 종류를 리턴하고 반대면 N/2를 리턴하면 됩니다.

폰켓몬 소스코드

import java.util.*;

class Solution {
    
    public int solution(int[] nums) {
        Map<Integer, Integer> ponkets = new HashMap<>();
        for (int num: nums) {
            ponkets.put(num, ponkets.getOrDefault(num, 0) + 1);
        }
        
        int halfN = nums.length / 2;
        if (halfN <= ponkets.size()) {
            return halfN;
        } else {
            return ponkets.size();
        }
    }
}

코드 설명

 

  • Map<Integer, Integer> ponkets = new HashMap<>();
    정수형 num을 키로 하고, 각 숫자가 몇 번 등장하는지를 값으로 하는 해시맵 ponkets를 생성합니다.
  • for (int num: nums) { ponkets.put(num, ponkets.getOrDefault(num, 0) + 1); }
    nums 배열을 순회하면서 각 숫자가 ponkets 맵에 이미 존재하는지 확인합니다.
    • 존재하면 해당 숫자의 값에 +1을 합니다.
    • 존재하지 않으면 기본값 0에서 +1을 하여 새로운 키-값 쌍으로 추가합니다.
  • int halfN = nums.length / 2;
    nums 배열의 길이를 반으로 나눈 halfN을 계산하여 가질 수 있는 폰켓몬 수의 최대치를 나타냅니다.
  • if (halfN <= ponkets.size()) { return halfN; } else { return ponkets.size(); }
    고유한 폰켓몬의 종류 수(ponkets.size())와 halfN을 비교합니다.
    • halfN이 고유한 폰켓몬의 종류 수보다 작거나 같으면 halfN을 반환합니다 (최대 가질 수 있는 폰켓몬 수).
    • 그렇지 않으면 고유한 폰켓몬의 종류 수를 반환합니다.

 

제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers SQL 고득점 Kit(String, Date)입니다.

루시와 엘라 찾기

SELECT
    ANIMAL_ID,
    NAME,
    SEX_UPON_INTAKE
FROM ANIMAL_INS
WHERE NAME IN ('Lucy', 'Ella', 'Pickle', 'Rogan', 'Sabrina', 'Mitty')
ORDER BY ANIMAL_ID

 

  • 이름이 'Lucy', 'Ella', 'Pickle', 'Rogan', 'Sabrina', 'Mitty'인 동물을 조회하기 위해 조건을 걸어줍니다.
    WHERE NAME IN ('Lucy', 'Ella', 'Pickle', 'Rogan', 'Sabrina', 'Mitty')
  • 동물 ID를 기준으로 정렬합니다.

이름에 el이 들어가는 동물 찾기

SELECT 
    ANIMAL_ID, 
    NAME
FROM ANIMAL_INS
WHERE UPPER(NAME) LIKE '%EL%' AND ANIMAL_TYPE = 'Dog'
ORDER BY NAME;
  • 이름에 EL이 들어가는 개를 찾기 위해 UPPER(또는 LOWER)를 사용하여 Like절을 통해 조회합니다.
    WHERE UPPER(NAME) LIKE '%EL%' AND ANIMAL_TYPE = 'Dog'
  • 이름을 통해 정렬합니다.

중성화 여부 파악하기

SELECT
    ANIMAL_ID,
    NAME,
    CASE
        WHEN SEX_UPON_INTAKE LIKE 'Neutered%' Then 'O'
        WHEN SEX_UPON_INTAKE LIKE 'Spayed%' Then 'O'
        WHEN SEX_UPON_INTAKE LIKE 'Intact%' THEN 'X'
    END AS "중성화"
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
  • 중성화 여부를 위한 CASE 절을 이용합니다.
    CASE
            WHEN SEX_UPON_INTAKE LIKE 'Neutered%' Then 'O'
            WHEN SEX_UPON_INTAKE LIKE 'Spayed%' Then 'O'
            WHEN SEX_UPON_INTAKE LIKE 'Intact%' THEN 'X'
        END AS "중성화"
  • 동물 ID를 통해 정렬합니다.

오랜기간 보호한 동물(2)

SELECT ANIMAL_ID, NAME
FROM (
    SELECT
        INS.ANIMAL_ID,
        INS.NAME,
        (OUTS.DATETIME - INS.DATETIME) AS PROTECTION_DAYS
    FROM
        ANIMAL_INS INS
        JOIN ANIMAL_OUTS OUTS ON INS.ANIMAL_ID = OUTS.ANIMAL_ID
    ORDER BY
        PROTECTION_DAYS DESC
)
WHERE ROWNUM <= 2;
  • 입양간 동물을 구하기 위해 JOIN 합니다.
    FROM ANIMAL_INS INS JOIN ANIMAL_OUTS OUTS ON INS.ANIMAL_ID = OUTS.ANIMAL_ID
  • 보호기간을 구하기 위해 PROTECTION_DAYS 컬럼을 생성하고 해당 컬럼을 통해 내림차순 정렬합니다.
    (OUTS.DATETIME - INS.DATETIME) AS PROTECTION_DAYS
  • 상위 두개의 레코드를 조회하기 위해 WHERE절을 이용합니다.
    WHERE ROWNUM <= 2;

카테고리 별 상품 개수 구하기

SELECT 
    SUBSTR(PRODUCT_CODE, 0, 2) AS CATEGORY, 
    COUNT(*)
FROM PRODUCT
GROUP BY SUBSTR(PRODUCT_CODE, 0, 2)
ORDER BY CATEGORY
  • 상품 카테고리 앞 두자리를 기준으로 그룹화를 진행합니다.
    GROUP BY SUBSTR(PRODUCT_CODE, 0, 2)
  • 카테고리를 기준으로 오름차순 정렬합니다.

DATETIME에서 DATE로 형변환

SELECT
    ANIMAL_ID,
    NAME,
    TO_CHAR(DATETIME, 'YYYY-MM-DD') AS "날짜"
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
  • DATETIME을 YYYY-MM-DD로 형변환합니다.
  • 동물 ID를 기준으로 오름차순 정렬합니다.

연도 별 평균 미세먼지 농도 조회하기

SELECT
    YEAR(YM) AS YEAR, 
    ROUND(AVG(PM_VAL1),2) AS PM10, 
    ROUND(AVG(PM_VAL2),2) AS 'PM2.5'
FROM AIR_POLLUTION
WHERE LOCATION2 = '수원'
GROUP BY YEAR
ORDER BY YEAR;
  • 지역이 수원인 동네에 연도별 미세먼지 농도를 구하기 위해 GROUP BY를 사용합니다.
    WHERE LOCATION2 = '수원'
    GROUP BY YEAR
  • 연도를 기준으로 오름차순 정렬합니다.

한 해에 잡은 물고기 수 구하기

SELECT
    COUNT(*) AS FISH_COUNT
FROM FISH_INFO
WHERE YEAR(TIME) = 2021
  • 2021년에 잡은 물고기 수를 출력하기 위해 조건을 걸어줍니다.
    WHERE YEAR(TIME) = 2021

분기별 분화된 대장균의 개체수 구하기

SELECT
    E.QUARTER,
    COUNT(*) AS ECOLI_COUNT
FROM (
    SELECT 
        ID,
        CASE 
            WHEN MONTH(DIFFERENTIATION_DATE) <= 3 THEN "1Q"
            WHEN MONTH(DIFFERENTIATION_DATE) <= 6 THEN "2Q"
            WHEN MONTH(DIFFERENTIATION_DATE) <= 9 THEN "3Q"
            WHEN MONTH(DIFFERENTIATION_DATE) <= 12 THEN "4Q"
        END AS QUARTER
    FROM ECOLI_DATA
) E
GROUP BY QUARTER
ORDER BY E.QUARTER
  • 월을 기준으로 분기를 구하기 위해 CASE 절을 이용한 서브 테이블 E를 생성합니다.
    (
        SELECT 
            ID,
            CASE 
                WHEN MONTH(DIFFERENTIATION_DATE) <= 3 THEN "1Q"
                WHEN MONTH(DIFFERENTIATION_DATE) <= 6 THEN "2Q"
                WHEN MONTH(DIFFERENTIATION_DATE) <= 9 THEN "3Q"
                WHEN MONTH(DIFFERENTIATION_DATE) <= 12 THEN "4Q"
            END AS QUARTER
        FROM ECOLI_DATA
    ) E
  • QUARTER를 기준으로 그룹화와 정렬을 진행합니다
    GROUP BY QUARTER
    ORDER BY E.QUARTER
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers SQL 고득점 Kit(String, Date)입니다.

자동차 평균 대여 기간 구하기

SELECT 
    CAR_ID, 
    TO_CHAR(ROUND(AVG(END_DATE - START_DATE + 1), 1), 'FM9990.0') AS AVERAGE_DURATION
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
HAVING AVG(END_DATE - START_DATE + 1) >= 7
ORDER BY ROUND(AVG(END_DATE - START_DATE + 1), 1) DESC, CAR_ID DESC
  • 자동차 평균 대여 기간을 구하기 위해서 자동차 ID를 기준으로 그룹화를 진행합니다.
    GROUP BY CAR_ID
  • 평균 대여 기간이 7일 이상인 레코드를 조회하기 위해 HAVING 절을 이용합니다.
    HAVING AVG(END_DATE - START_DATE + 1) >= 7
  • 소수점을 표현하기 위해 FM9990.0의 데이터 형식으로 표현해줍니다.
    TO_CHAR(ROUND(AVG(END_DATE - START_DATE + 1), 1), 'FM9990.0') AS AVERAGE_DURATION
  • 자동차 대여 기간, 자동차 ID를 기준으로 내림차순 정렬합니다.

조회수가 가장 많은 중고거래 게시판의 첨부파일 조회하기

SELECT
    CONCAT(CONCAT(
    	CONCAT(CONCAT(CONCAT('/home/grep/src/', B.BOARD_ID),'/'),file_id), 
    FILE_NAME),file_ext) AS FILE_PATH
FROM (
    SELECT
        BOARD_ID
    FROM USED_GOODS_BOARD
    ORDER BY VIEWS DESC
    FETCH NEXT 1 ROWS ONLY
) B JOIN USED_GOODS_FILE F ON B.BOARD_ID = F.BOARD_ID
ORDER BY F.FILE_ID DESC
  • 조회수가 가장 높은 레코드를 조회하기 위해 서브 테이블 B를 생성합니다.
    조회수를 기준으로 내림차순 정렬하고 첫번째 레코드를 조회합니다.
(
    SELECT
        BOARD_ID
    FROM USED_GOODS_BOARD
    ORDER BY VIEWS DESC
    FETCH NEXT 1 ROWS ONLY
) B
  • B 테이블과 USED_GOODS_FILE 테이블과 BOARD_ID를 기준으로 JOIN 합니다.
    FROM(...) B JOIN USED_GOODS_FILE F ON B.BOARD_ID = F.BOARD_ID
  • 첨부파일의 경로를 표시하기 위해 여러 컬럼을 더해주기 위해 CONCAT을 여러번 이용합니다.
    CONCAT(CONCAT(CONCAT(CONCAT(CONCAT('/home/grep/src/', B.BOARD_ID),'/'),file_id), FILE_NAME),file_ext) AS FILE_PATH
  • 파일 ID를 기준으로 내림차순 정렬합니다.

자동차 대여기록 별 대여 금액 구하기

SELECT 
    H.HISTORY_ID,
    C.DAILY_FEE * H.RENTAL_DATE * NVL(P.DISCOUNT_RATE, 1) AS FEE
FROM CAR_RENTAL_COMPANY_CAR C JOIN (
    SELECT 
        HISTORY_ID,
        CAR_ID,
        END_DATE - START_DATE + 1 AS RENTAL_DATE,
        CASE WHEN END_DATE - START_DATE + 1 >= 90 THEN '90일 이상'
            WHEN END_DATE - START_DATE + 1 >= 30 THEN '30일 이상'
            WHEN END_DATE - START_DATE + 1 >= 7 THEN '7일 이상'
            ELSE NULL
        END AS DURATION_TYPE
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
)H ON C.CAR_ID = H.CAR_ID LEFT JOIN (
    SELECT 
        DURATION_TYPE,
        (1 - (DISCOUNT_RATE / 100)) AS DISCOUNT_RATE
    FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
    WHERE CAR_TYPE = '트럭'
)P ON H.DURATION_TYPE = P.DURATION_TYPE
WHERE C.CAR_TYPE = '트럭'
ORDER BY FEE DESC, H.HISTORY_ID DESC
  • 가격 정책과 조인하기 위해 서브 테이블 H을 생성합니다.
    대여 기간 컬럼(RENTAL_DATE)와 조인을 위한 컬럼(DURATION_TYPE) 컬럼을 추가해줍니다.
(
    SELECT 
        HISTORY_ID,
        CAR_ID,
        END_DATE - START_DATE + 1 AS RENTAL_DATE,
        CASE WHEN END_DATE - START_DATE + 1 >= 90 THEN '90일 이상'
            WHEN END_DATE - START_DATE + 1 >= 30 THEN '30일 이상'
            WHEN END_DATE - START_DATE + 1 >= 7 THEN '7일 이상'
            ELSE NULL
        END AS DURATION_TYPE
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
)H
  • 자동차 ID를 기준으로 JOIN합니다.
    FROM CAR_RENTAL_COMPANY_CAR C JOIN (...) H ON C.CAR_ID = H.CAR_ID
  • 트럭에 대한 자동차 대여 기록별 대여 금액을 구하기 위해 서브테이블 P를 생성하여 대여 기간을 기준으로 LEFT JOIN 합니다.
    LEFT JOIN을 하는 이유는 7일 이하로 렌트하는 레코드를 조회하기 위함입니다.
LEFT JOIN (
    SELECT 
        DURATION_TYPE,
        (1 - (DISCOUNT_RATE / 100)) AS DISCOUNT_RATE
    FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
    WHERE CAR_TYPE = '트럭'
)P ON H.DURATION_TYPE = P.DURATION_TYPE
  • 자동차 타입이 트럭인 레코드를 조회하기 위한 조건을 걸어줍니다.
    WHERE C.CAR_TYPE = '트럭'
  • 대여 금액을 구하기 위해 해당 수식의 컬럼을 추가합니다.
    null인 것은 할인 대상이 아니기에 * 1을 해줍니다.
    C.DAILY_FEE * H.RENTAL_DATE * NVL(P.DISCOUNT_RATE, 1) AS FEE
  • 대여 금액과 대여 기록 ID를 기준으로 내림차순 조회합니다. 

조건에 부합하는 중고거래 상태 조회하기

SELECT 
    BOARD_ID, 
    WRITER_ID, 
    TITLE, 
    PRICE, 
    CASE WHEN STATUS = 'SALE' THEN '판매중'
         WHEN STATUS = 'RESERVED' THEN '예약중'
         WHEN STATUS = 'DONE' THEN '거래완료'
    END 
FROM USED_GOODS_BOARD
WHERE TO_CHAR(CREATED_DATE, 'YYYY-MM-DD') = '2022-10-05'
ORDER BY BOARD_ID DESC;
  • 2022년 10월 5일에 등록된 레코드를 조회하기 위해 조건을 걸어줍니다.
    WHERE TO_CHAR(CREATED_DATE, 'YYYY-MM-DD') = '2022-10-05'
  • STATUS를 통해 판매중/예약중/거래완료를 처리하기 위해 CASE 절을 사용합니다.
    CASE WHEN STATUS = 'SALE' THEN '판매중'
             WHEN STATUS = 'RESERVED' THEN '예약중'
             WHEN STATUS = 'DONE' THEN '거래완료'
        END 
  • 게시물 ID를 기준으로 내림차순 정렬합니다.

특정 옵션이 포함된 자동차 리스트 구하기

SELECT 
    CAR_ID,
    CAR_TYPE,
    DAILY_FEE,
    OPTIONS
FROM CAR_RENTAL_COMPANY_CAR
WHERE OPTIONS LIKE '%네비게이션%'
ORDER BY CAR_ID DESC
  • 네비게이션 옵션이 있는 레코드를 조회하기 위해 like 절을 사용합니다.
    WHERE OPTIONS LIKE '%네비게이션%'
  • 자동차 ID를 기준으로 내림차순 정렬합니다.

자동차 대여 기록에서 장기/단기 대여 구분하기

SELECT
    HISTORY_ID,
    CAR_ID,
    TO_CHAR(START_DATE, 'YYYY-MM-DD') AS START_DATE,
    TO_CHAR(END_DATE, 'YYYY-MM-DD') AS END_DATE,
    CASE WHEN END_DATE - START_DATE + 1 >= 30 THEN '장기 대여'
    ELSE '단기 대여'
    END AS RENT_TYPE
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE EXTRACT(MONTH FROM START_DATE) = 9
ORDER BY HISTORY_ID DESC
  • 대여 시작일이 9월인 레코드를 조회하기 위해 조건을 걸어줍니다.
    DATETIME 형식에서 월만 추출하기 위해 EXTRACT 절을 사용합니다.
    WHERE EXTRACT(MONTH FROM START_DATE) = 9
  • 30일 이상 대여하는 레코드에 대해서는 장기 대여 아닌 것에 대해서는 단기 대여를 위해 CASE 절을 사용합니다.
    CASE WHEN END_DATE - START_DATE + 1 >= 30 THEN '장기 대여'
        ELSE '단기 대여'
        END AS RENT_TYPE
  • 예약 ID를 기준으로 내림차순 정렬합니다.

조건별로 분류하여 주문상태 출력하기

SELECT
    ORDER_ID, 
    PRODUCT_ID, 
    TO_CHAR(OUT_DATE, 'YYYY-MM-DD') AS OUT_DATE,
    CASE WHEN TO_CHAR(OUT_DATE, 'YYYYMMDD') <= '20220501' THEN '출고완료'
         WHEN TO_CHAR(OUT_DATE, 'YYYYMMDD') > '20220501' THEN '출고대기'
         ELSE '출고미정'
    END AS 출고여부
FROM FOOD_ORDER
ORDER BY ORDER_ID
  • 2022년 5월 1일을 기준으로 출고 여부를 조회하기 위해 CASE 절을 사용합니다.
    CASE WHEN TO_CHAR(OUT_DATE, 'YYYYMMDD') <= '20220501' THEN '출고완료'
             WHEN TO_CHAR(OUT_DATE, 'YYYYMMDD') > '20220501' THEN '출고대기'
             ELSE '출고미정'
        END AS 출고여부
  • 주문 ID를 기준으로 오름차순 정렬합니다.

대여 기록이 존재하는 자동차 리스트 구하기

SELECT
    H.CAR_ID
FROM (
    SELECT
        DISTINCT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
    WHERE EXTRACT(MONTH FROM START_DATE) = 10
) H JOIN (
    SELECT
        CAR_ID
    FROM CAR_RENTAL_COMPANY_CAR
    WHERE CAR_TYPE = '세단'
) C ON H.CAR_ID = C.CAR_ID
ORDER BY H.CAR_ID DESC
  • 10월에 대여 시작한 테이블을 조회하기 위해 서브 테이블 H를 생성합니다.
    중복을 제거하기 위해 DISTINCT 키워드를 사용합니다.
(
    SELECT
        DISTINCT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
    WHERE EXTRACT(MONTH FROM START_DATE) = 10
) H
  • 자동차 종류가 세단인 자동차 데이터를 조회하기 위해 서브 테이블 C를 생성합니다.
(
    SELECT
        CAR_ID
    FROM CAR_RENTAL_COMPANY_CAR
    WHERE CAR_TYPE = '세단'
) C
  • 자동차 ID를 기준으로 H 테이블과 C 테이블을 JOIN합니다.
    FROM (...) H JOIN (...) C ON H.CAR_ID = C.CAR_ID
  • 자동차 ID를 기준으로 내림차순 정렬합니다.

조건에 맞는 사용자 정보 조회하기

SELECT
    U.USER_ID,
    U.NICKNAME,
    CONCAT(CONCAT(CONCAT(CONCAT(CITY, ' '), STREET_ADDRESS1), ' '), STREET_ADDRESS2) AS 전체주소,
    CONCAT(CONCAT(CONCAT(SUBSTR(TLNO,0, 3), '-'), CONCAT(SUBSTR(TLNO, 4, 4), '-')), SUBSTR(TLNO, 8)) AS 전화번호
FROM USED_GOODS_USER U JOIN (
    SELECT
        WRITER_ID
    FROM USED_GOODS_BOARD
    GROUP BY WRITER_ID
    HAVING COUNT(*) >= 3
) B ON U.USER_ID = B.WRITER_ID
ORDER BY U.USER_ID DESC
  • 중고 거래 게시물을 작성한 갯수를 구하기 위해 3건 이상 작성한 작성자 ID를 기준으로 그룹화한 서브 테이블 B를 생성합니다.
(
    SELECT
        WRITER_ID
    FROM USED_GOODS_BOARD
    GROUP BY WRITER_ID
    HAVING COUNT(*) >= 3
) B
  • USED_GOODS_USER 테이블과 USER_ID를 B.WRITER_ID를 기준으로 JOIN합니다.
    FROM USED_GOODS_USER U JOIN (...) B ON U.USER_ID = B.WRITER_ID
  • 전체 주소를 조회하기 위해 CITY와 도로명 주소 사이에 CONCAT 함수를 이용하여 띄어쓰기를 넣어줍니다.
    CONCAT(CONCAT(CONCAT(CONCAT(CITY, ' '), STREET_ADDRESS1), ' '), STREET_ADDRESS2) AS 전체주소,
  • 전화번호 11자 사이에 대쉬(-)를 넣어주기 위해 SUBSTR과 CONCAT 함수를 이용합니다.
    SUBSTR(A, B, C) => A를 B부터 C자리까지 자름
    SUBSTR(A, B) => A를 B이후 문자열
    CONCAT(CONCAT(CONCAT(SUBSTR(TLNO,0, 3), '-'), CONCAT(SUBSTR(TLNO, 4, 4), '-')), SUBSTR(TLNO, 8)) AS 전화번호
  • USER_ID를 기준으로 내림차순 정렬합니다.
     

취소되지 않은 진료 예약 조회하기

SELECT
    A.APNT_NO,
    P.PT_NAME,
    A.PT_NO,
    A.MCDP_CD,
    D.DR_NAME,
    A.APNT_YMD
FROM DOCTOR D JOIN (
    SELECT
        APNT_YMD,
        APNT_NO,
        PT_NO,
        MCDP_CD,
        MDDR_ID
    FROM APPOINTMENT
    WHERE APNT_CNCL_YN = 'N' AND TO_CHAR(APNT_YMD, 'YYYY-MM-DD') = '2022-04-13'
) A ON D.DR_ID = A.MDDR_ID JOIN PATIENT P ON A.PT_NO = P.PT_NO
WHERE A.MCDP_CD = 'CS'
ORDER BY A.APNT_YMD ASC
  • 2022년 4월 13일에 취소되지 않은 진료예약을 구하기 위해 서브테이블 A를 생성합니다.
(
    SELECT
        APNT_YMD,
        APNT_NO,
        PT_NO,
        MCDP_CD,
        MDDR_ID
    FROM APPOINTMENT
    WHERE APNT_CNCL_YN = 'N' AND TO_CHAR(APNT_YMD, 'YYYY-MM-DD') = '2022-04-13'
) A
  • 의사 ID와 환자 ID를 기준으로 JOIN합니다.
    FROM DOCTOR D JOIN (...) A ON D.DR_ID = A.MDDR_ID JOIN PATIENT P ON A.PT_NO = P.PT_NO
  • 흉부외과 데이터를 조회하기 위해 조건을 걸어줍니다.
    WHERE A.MCDP_CD = 'CS'
  • 진료 예약일시를 기준으로 오름차순 정렬합니다.
제가 공부한 내용을 정리하는 블로그입니다.
아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
Programmers SQL 고득점 Kit(JOIN)입니다.

특정 기간동안 대여 가능한 자동차들의 대여 비용 구하기

SELECT
    H.CAR_ID,
    C.CAR_TYPE,
    C.DAILY_FEE * 30 * (1 - (DISCOUNT_RATE / 100)) AS FEE
FROM (
    SELECT DISTINCT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY H
    GROUP BY CAR_ID
    HAVING MAX(TO_CHAR(END_DATE, 'YYYY-MM')) < '2022-11'
) H JOIN (
    SELECT CAR_ID, CAR_TYPE, DAILY_FEE
    FROM CAR_RENTAL_COMPANY_CAR C
    WHERE CAR_TYPE = '세단' OR CAR_TYPE = 'SUV'
) C ON H.CAR_ID = C.CAR_ID JOIN (
    SELECT CAR_TYPE, DURATION_TYPE, DISCOUNT_RATE
    FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
    WHERE (CAR_TYPE = '세단' OR CAR_TYPE = 'SUV') AND (DURATION_TYPE = '30일 이상')
) P ON C.CAR_TYPE = P.CAR_TYPE
WHERE C.DAILY_FEE * 30 * (1 - (DISCOUNT_RATE / 100)) BETWEEN 500000 AND 2000000
ORDER BY FEE DESC, C.CAR_TYPE ASC, H.CAR_ID DESC
  • 2022년 11월 대여가 가능한 차를 찾아야 하기에 CAR_RENTAL_COMPANY_RENTAL_HISTORY에서 반납일이 11월보다 작은 차를 조회하기 위한 서브 테이블 H를 생성합니다.
(
    SELECT DISTINCT CAR_ID
    FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY H
    GROUP BY CAR_ID
    HAVING MAX(TO_CHAR(END_DATE, 'YYYY-MM')) < '2022-11'
) H
  • 자동차의 종류가 세단이나 SUV여야 하므로 서브테이블 C를 생성합니다.
(
    SELECT CAR_ID, CAR_TYPE, DAILY_FEE
    FROM CAR_RENTAL_COMPANY_CAR C
    WHERE CAR_TYPE = '세단' OR CAR_TYPE = 'SUV'
) C
  • H테이블과 C 테이블을 CAR_ID를 기준으로 조인합니다
    FROM (...) H JOIN (...)C ON H.CAR_ID = C.CAR_ID
  • 세단과 SUV의 30일 이상 대여할 시의 할인율을 구하기 위한 서브 테이블 P를 생성합니다.
(
    SELECT CAR_TYPE, DURATION_TYPE, DISCOUNT_RATE
    FROM CAR_RENTAL_COMPANY_DISCOUNT_PLAN
    WHERE (CAR_TYPE = '세단' OR CAR_TYPE = 'SUV') AND (DURATION_TYPE = '30일 이상')
) P
  • P 테이블과 C테이블의 CAR_TYPE을 기준으로 조인합니다.
    C.CAR_TYPE = P.CAR_TYPE
  • 30일 간의 대여 금액이 50만원 이상 200만원 미만인 자동차를 구하기 위해 BETWEEN 함수를 이용하빈다.
    DISCOUNT_RATE가 퍼센트로 표시되어있기에 해당 계산을 이용합니다.
    WHERE C.DAILY_FEE * 30 * (1 - (DISCOUNT_RATE / 100)) BETWEEN 500000 AND 2000000
  • 자동차 종류를 기준으로 오름차순 정렬, 자동차 ID를 기준으로 내림차순 정렬합니다.

5월 식품들의 총 매출 조회하기

SELECT
    P.PRODUCT_ID,
    P.PRODUCT_NAME,
    P.PRICE * O.AMOUNT AS TOTAL_SALES
FROM FOOD_PRODUCT P JOIN (
    SELECT PRODUCT_ID, SUM(AMOUNT) AS AMOUNT
    FROM FOOD_ORDER O
    WHERE TO_CHAR(PRODUCE_DATE, 'YYYY-MM') = '2022-05'
    GROUP BY PRODUCT_ID
) O ON P.PRODUCT_ID = O.PRODUCT_ID
ORDER BY TOTAL_SALES DESC, P.PRODUCT_ID ASC
  • 2022년 5월에 생산된 식품을 조회하기 위한 서브 테이블 O를 생성합니다.
    총 매출(TOTAL_ORDER * PRICE)을 구해야하기 때문에 식품ID를 기준으로 그룹화를 하고 SUM 함수를 사용합니다.
(
    SELECT PRODUCT_ID, SUM(AMOUNT) AS AMOUNT
    FROM FOOD_ORDER O
    WHERE TO_CHAR(PRODUCE_DATE, 'YYYY-MM') = '2022-05'
    GROUP BY PRODUCT_ID
) O

 

  • FOOD_PRODUCT 테이블과 PRODUCT_ID를 기준으로 JOIN합니다.
    FROM FOOD_PRODUCT P JOIN (...) O ON P.PRODUCT_ID = O.PRODUCT_ID
  • 총 매출을 구하기 위해 다음과 같이 TOTAL_SALES 컬럼을 구성합니다.
    P.PRICE * O.AMOUNT AS TOTAL_SALES
  • 총 매출을 기준으로 내림차순 정렬하고, 식품 ID를 기준으로 오름차순 정렬합니다.

주문량이 많은 아이스크림들 조회하기

SELECT
    H.FLAVOR
FROM (
    SELECT FLAVOR, TOTAL_ORDER
    FROM FIRST_HALF
) H JOIN (
    SELECT FLAVOR, SUM(TOTAL_ORDER) AS TOTAL_ORDER
    FROM JULY
    GROUP BY FLAVOR
) J ON H.FLAVOR = J.FLAVOR
ORDER BY H.TOTAL_ORDER + J.TOTAL_ORDER DESC
FETCH NEXT 3 ROWS ONLY
  • 상반기 아이스크림의 총 주문량을 구하기 위한 서브 테이블 H를 생성합니다.
(
    SELECT FLAVOR, TOTAL_ORDER
    FROM FIRST_HALF
) H
  • 7월에는 주문량이 많아 다른 두 공장에서 같은 맛을 출하를 진행하였으므로 FLAVOR를 기준으로 그룹화를 진행하고 총 주문량을 조회하는 서브테이르 J를 생성합니다.
(
    SELECT FLAVOR, SUM(TOTAL_ORDER) AS TOTAL_ORDER
    FROM JULY
    GROUP BY FLAVOR
) J
  • H 테이블과 J 테이블을 FLAVOR을 기준으로 JOIN 합니다.
    FROM (...) H JOIN (...) J ON H.FLAVOR = J.FLAVOR
  • 총 주문량에 대해 내림차순 정렬 후 상위 3개의 레코드를 조회합니다.
    ORDER BY H.TOTAL_ORDER + J.TOTAL_ORDER DESC
    FETCH NEXT 3 ROWS ONLY

조건에 맞는 도서와 저자 리스트 출력하기

SELECT 
    B.BOOK_ID, 
    A.AUTHOR_NAME, 
    TO_CHAR(B.PUBLISHED_DATE, 'YYYY-MM-DD') AS PUBLISHED_DATE
FROM BOOK B JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID
WHERE B.CATEGORY = '경제'
ORDER BY B.PUBLISHED_DATE
  • 도서 테이블과 저자 테이블을 JOIN하기 위해 AUTHOR_ID를 기준으로 JOIN합니다.
    FROM BOOK B JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID
  • 경제 카테고리에 해당하는 도서를 찾기 위해 조건을 걸어줍니다.
    WHERE B.CATEGORY = '경제'
  • 출판일을 기준으로 오름차순 정렬합니다.

그룹별 조건에 맞는 식당 목록 출력하기

WITH MemberReviewCount AS (
    SELECT
        MEMBER_ID,
        COUNT(*) AS REVIEW_COUNT
    FROM
        REST_REVIEW
    GROUP BY
        MEMBER_ID
),
MaxReviewMember AS (
    SELECT
        MEMBER_ID
    FROM
        MemberReviewCount
    WHERE
        REVIEW_COUNT = (SELECT MAX(REVIEW_COUNT) FROM MemberReviewCount)
)

SELECT
    MP.MEMBER_NAME,
    RR.REVIEW_TEXT,
    TO_CHAR(RR.REVIEW_DATE, 'YYYY-MM-DD')
FROM
    REST_REVIEW RR
    JOIN MEMBER_PROFILE MP ON RR.MEMBER_ID = MP.MEMBER_ID
    JOIN MaxReviewMember MRM ON RR.MEMBER_ID = MRM.MEMBER_ID
ORDER BY
    TO_CHAR(RR.REVIEW_DATE, 'YYYY-MM-DD') ASC,
    RR.REVIEW_TEXT ASC;
  • WITH 키워드를 이용하여 테이블을 미리 생성합니다.
    첫번째 테이블 MemberReviewCount는 MEMBER_ID를 기준으로 리뷰 횟수를 조회하는 테이블을 생성합니다.
WITH MemberReviewCount AS (
    SELECT
        MEMBER_ID,
        COUNT(*) AS REVIEW_COUNT
    FROM
        REST_REVIEW
    GROUP BY
        MEMBER_ID
),
  • 두번째 테이블은 리뷰를 가장 많이 작성한 회원 ID를 조회하기 위한 테이블을 생성합니다
MaxReviewMember AS (
    SELECT
        MEMBER_ID
    FROM
        MemberReviewCount
    WHERE
        REVIEW_COUNT = (SELECT MAX(REVIEW_COUNT) FROM MemberReviewCount)
)
  • 레스토랑 리뷰 테이블과 멤버 정보 테이블, MaxReviewMember 테이블을 Member_ID를 기준으로 JOIN 합니다.
    FROM
        REST_REVIEW RR
        JOIN MEMBER_PROFILE MP ON RR.MEMBER_ID = MP.MEMBER_ID
        JOIN MaxReviewMember MRM ON RR.MEMBER_ID = MRM.MEMBER_ID
  • 리뷰 작성일을 기준으로 오름차순, 리뷰 텍스트를 기준으로 오름차순 정렬합니다.

없어진 기록 찾기

SELECT
    O.ANIMAL_ID,
    O.NAME
FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID(+) = O.ANIMAL_ID
WHERE I.ANIMAL_ID IS NULL
ORDER BY O.ANIMAL_ID, O.NAME;
  • 동물 보호소에 들어온 동물과 입양 보낸 동물들을 ANIMAL_ID를 기준으로 LEFT JOIN합니다.
    LEFT JOIN하는 이유는 입양 들어온 기록은 존재하는데 입양 보낸 기록이 없을 때를 찾기 위함입니다.
    FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID(+) = O.ANIMAL_ID
  • 없어진 동물의 기록을 찾아야하기 때문에 I.ANIMAL_ID가 NULL인 데이터를 조회합니다.
    WHERE I.ANIMAL_ID IS NULL
  • 동물 ID와 이름을 기준으로 오름차순 정렬합니다.

있었는데요 없었습니다

SELECT
    I.ANIMAL_ID,
    I.NAME
FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE I.DATETIME > O.DATETIME
ORDER BY I.DATETIME ASC
  • 동물 보호소에 들어온 동물과 입양 보낸 동물들을 ANIMAL_ID를 기준으로 JOIN합니다.
    FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
  • 보호 시작일이 입양일보다 더 늦은 레코드를 조회하기 위해 조건을 걸어줍니다.
    WHERE I.DATETIME > O.DATETIME
  • 보호 시작일이 빠른 순으로 조회합니다.(오름차순으로 정렬합니다)

오랜기간 보호한 동물(1)

SELECT
    I.NAME, 
    I.DATETIME
FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID(+)
WHERE O.ANIMAL_ID IS NULL
ORDER BY I.DATETIME ASC
FETCH NEXT 3 ROWS ONLY
  • 동물 보호소에 들어온 동물과 입양 보낸 동물들을 ANIMAL_ID를 기준으로 RIGHT JOIN합니다.
    RIGHT JOIN하는 이유는 입양을 아직 못간 동물을 찾기 위합니다.
    FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID(+)
  • 입양을 못간 동물을 조회하기 위해 조건을 걸어줍니다.
    WHERE O.ANIMAL_ID IS NULL
  • 보호 시작일을 기준으로 오름차순으로 정렬하고 상위 3개의 레코드를 조회하기 위해 FETCH NEXT 절을 사용합니다.
    ORDER BY I.DATETIME ASC
    FETCH NEXT 3 ROWS ONLY

보호소에서 중성화한 동물

SELECT
    I.ANIMAL_ID, 
    I.ANIMAL_TYPE, 
    I.NAME
FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE I.SEX_UPON_INTAKE LIKE 'Intact%' 
    AND (O.SEX_UPON_OUTCOME LIKE 'Spayed%' OR O.SEX_UPON_OUTCOME LIKE 'Neutered%')
ORDER BY I.ANIMAL_ID
  • 동물 보호소에 들어온 동물과 입양 보낸 동물들을 ANIMAL_ID를 기준으로 JOIN합니다.
    FROM ANIMAL_INS I JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
  • 보호소에서 중성화한 동물을 찾기 위해 조건을 걸어줍니다.
    들어왔을 때는 아니였지만 나갈때 중성화가 되어있으면 보호소에서 중성화 한 것입니다.
    WHERE I.SEX_UPON_INTAKE LIKE 'Intact%' 
        AND (O.SEX_UPON_OUTCOME LIKE 'Spayed%' OR O.SEX_UPON_OUTCOME LIKE 'Neutered%')
  • 동물 ID를 기준으로 오름차순 정렬합니다.

상품 별 오프라인 매출 구하기

SELECT
    P.PRODUCT_CODE,
    P.PRICE * S.TOTAL_SALES AS SALES
FROM PRODUCT P JOIN (
    SELECT PRODUCT_ID, SUM(SALES_AMOUNT) AS TOTAL_SALES
    FROM OFFLINE_SALE
    GROUP BY PRODUCT_ID
) S ON P.PRODUCT_ID = S.PRODUCT_ID
ORDER BY SALES DESC, P.PRODUCT_CODE ASC
  • 오프라인에서 상품별 판매량을 구하기 위해 상품 ID를 기준으로 그룹화한 테이블을 S로 생성합니다.
(
    SELECT PRODUCT_ID, SUM(SALES_AMOUNT) AS TOTAL_SALES
    FROM OFFLINE_SALE
    GROUP BY PRODUCT_ID
) S
  • 상품 테이블과 S를 PRODUCT_ID를 기준으로 JOIN합니다.
    FROM PRODUCT P JOIN (...) S ON P.PRODUCT_ID = S.PRODUCT_ID
  • 매출을 구하기 위해 다음과 같이 필드를 생성합니다.
    P.PRICE * S.TOTAL_SALES AS SALES
  • 총 매출량을 기준으로 내림차순, 상품 코드를 기준으로 오름차순 정렬합니다.

상품을 구매한 회원 비율 구하기

SELECT 
    TO_CHAR(O.SALES_DATE, 'YYYY') AS YEAR,
    EXTRACT(MONTH FROM O.SALES_DATE) AS MONTH,
    COUNT(DISTINCT U.USER_ID) AS PUCHASED_USERS,
    ROUND(COUNT(DISTINCT U.USER_ID)/(SELECT COUNT(*) FROM USER_INFO WHERE TO_CHAR(joined, 'YYYY') = '2021'), 1) AS PUCHASED_RATIO
FROM USER_INFO U JOIN ONLINE_SALE O ON U.USER_ID = O.USER_ID
WHERE TO_CHAR(U.JOINED, 'YYYY') = '2021'
GROUP BY TO_CHAR(O.SALES_DATE, 'YYYY'), EXTRACT(MONTH FROM O.SALES_DATE)
ORDER BY TO_CHAR(O.SALES_DATE, 'YYYY'), EXTRACT(MONTH FROM O.SALES_DATE)
  • 상품을 구매한 회원을 찾기 위해 USER_INFO 테이블과 ONLINE_SALE 테이블을 JOIN 합니다.
    FROM USER_INFO U JOIN ONLINE_SALE O ON U.USER_ID = O.USER_ID
  • 그 중 2021년에 가입한 회원을 구하기 위해 조건을 걸어줍니다.
    WHERE TO_CHAR(U.JOINED, 'YYYY') = '2021'
  • 연, 월별로 (구매 회원수 / 가입 회원) 을 구해야하기에 연, 월을 기준으로 그룹화를 진행합니다.
    GROUP BY TO_CHAR(O.SALES_DATE, 'YYYY'), EXTRACT(MONTH FROM O.SALES_DATE)
  • 상품을 구매한 회원의 비율을 소수점 두번째 자리에서 반올림해야 하기에 다음과 같이 컬럼을 구성합니다.
    2021년에 가입한 회원의 전체 수를 구하기 위해 서브 쿼리를 이용합니다.
    ROUND(COUNT(DISTINCT U.USER_ID)/(SELECT COUNT(*) FROM USER_INFO WHERE TO_CHAR(joined, 'YYYY') = '2021'), 1) AS PUCHASED_RATIO
  • 연과 월을 기준으로 오름차순 정렬합니다.

FrontEnd 개발자 찾기

SELECT
    D.ID,
    D.EMAIL,
    D.FIRST_NAME,
    D.LAST_NAME
FROM
    DEVELOPERS D
WHERE
    EXISTS (
        SELECT 1
        FROM SKILLCODES S
        WHERE S.CATEGORY = 'FRONT END'
          AND (D.SKILL_CODE & S.CODE) <> 0
    )
ORDER BY
    D.ID ASC;
  • 서브 쿼리를 사용합니다.
    SKILL이 FRONT END이고 DEVELOPERS의 SKILL_CODE와 비트 연산을 통해 0보다 큰(공통 비트가 존재하는) 컬럼을 조회합니다.
FROM
    DEVELOPERS D
WHERE
    EXISTS (
        SELECT 1
        FROM SKILLCODES S
        WHERE S.CATEGORY = 'FRONT END'
          AND (D.SKILL_CODE & S.CODE) <> 0
    )
  • 개발자 ID를 기준으로 정렬합니다.

+ Recent posts