관리 메뉴

개발공부기록

Java - 삼총사, 크기가 작은 문자열, 최소직사각형 / SQL(MySQL) - 오랜 기간 보호한 동물(2), 보호소에서 중성화한 동물, 조건별로 분류하여 주문상태 출력하기 본문

기타 개발 공부/온라인 코딩 테스트 회고

Java - 삼총사, 크기가 작은 문자열, 최소직사각형 / SQL(MySQL) - 오랜 기간 보호한 동물(2), 보호소에서 중성화한 동물, 조건별로 분류하여 주문상태 출력하기

소소한나구리 2025. 3. 13. 16:21
728x90

Java

삼총사

문제

제한조건

  • number의 길이는 3 ~ 13 사이며 각 원소는 -1,000 ~ 1,000 임
  • 각 요소의 값은 중복도 가능함

입출력 예시

나의 풀이

class Solution {
    public int solution(int[] number) {
        int count = 0;        
        for (int i = 0; i < number.length - 2; i++) {
            for (int j = i + 1; j < number.length - 1; j++) {
                for (int k = j + 1; k < number.length; k++) {
                    int sum = number[i] + number[j] + number[k];
                    if (sum == 0) {
                        count++;
                    }
                }
            }
        }
        
        return count;
    }
}
  • 다른 방법은 생각이 전혀 나지 않아서 해당 배열의 3가지의 요소를 합산 하려면 3중 반복문을 활용할 수 밖에 없다고 생각하여 문제를 접근했다.
  • 문제는 각 반복문의 시작 위치와 순회 범위를 정해야 오류가 나지않고 전체를 순회하여 값을 더할 수 있다는 것인데 이부분을 생각하는 과정이 조금 오래 걸렸다
  • 처음에는 당연히 index의 0부터 시작하고 2번째는 index 1번째 부터 시작하고 3번째는 index 2번째 부터 시작해야하는데 하드 코딩을 하면 첫번째 반복문이 증가함에 따라 중복된 값을 더할 수 있기 때문에 i = 0, j = i + 1, k = i + 1로 각각 반복문의 시작 위치를 정했다
  • 여기서 헷갈렸던 점은 k의 모든 반복문이 끝나서 j가 ++이 되어 3번째 위치의 값을 지정할 때 k가 2번째 값을 가지지 못하는 것이 문제라고 생각했는데 애초에 고려대상이 아니였던 것이 이미 그 경우의 수는 첫 번째 반복문에서 다루었기 때문에 다룰 필요가 없었던 것이였다
  • 그래서 각 반복문의 종료 시점만 정해주면 되었는데, k의 경우는 당연히 배열의 끝 요소까지 탐색해야 하므로 k < number.length 까지 반복했고, j + 1의 경우 k가 이미 배열의 끝을 순회하기 때문에 j < number.length 까지, 그리고 i의 경우 k와 j가 배열의 마지막과 마지막 -1을 순회하므로 i < number.length - 2까지 순회하도록하면 모든 배열의 요소를 더하는 반복문이 완성된다
  • 이후 sum이 0이면 count를 증가시키고 모든 순회가 끝난 후 count의 값을 반환하도록 하여 문제를 해결했다.

다른 풀이

// [두 포인터 기법]
import java.util.Arrays;

class Solution {
    public int solution(int[] number) {
        int count = 0;
        Arrays.sort(number);
        for (int i = 0; i < number.length - 2; i++) {
            int target = -number[i];
            int left = i + 1;
            int right = number.length - 1;
            while (left < right) {
                int sum = number[left] + number[right];
                if (sum < target) {
                    left++;
                } else if (sum > target) {
                    right--;
                } else {
                    if (number[left] == number[right]) {
                        int numElements = right - left + 1;
                        count += (numElements * (numElements - 1)) / 2;
                        break;
                    } else {
                        int leftCount = 1;
                        while (left + 1 < right && number[left] == number[left + 1]) {
                            leftCount++;
                            left++;
                        }
                        int rightCount = 1;
                        while (right - 1 > left && number[right] == number[right - 1]) {
                            rightCount++;
                            right--;
                        }
                        count += leftCount * rightCount;
                        left++;
                        right--;
                    }
                }
            }
        }
        return count;
    }
}

// [이중 반복문 + 이진 탐색]
import java.util.Arrays;

class Solution {
    public int solution(int[] number) {
        int count = 0;
        Arrays.sort(number);
        int n = number.length;
        for (int i = 0; i < n - 2; i++) {
            for (int j = i + 1; j < n - 1; j++) {
                int target = -(number[i] + number[j]);
                int idx = Arrays.binarySearch(number, j + 1, n, target);
                if (idx < 0) continue;
                int left = idx, right = idx;
                while (left - 1 >= j + 1 && number[left - 1] == target) {
                    left--;
                }
                while (right + 1 < n && number[right + 1] == target) {
                    right++;
                }
                count += (right - left + 1);
            }
        }
        return count;
    }
}

 

  • 3중 반복문의 시간 복잡도O(n³)를 줄이기 위해 두 포인터 기법: O(n²), 이중 반복문 + 이진 탐색: O(n² log n)의 방법이 있다고 AI 툴이 알려주었으나 코드가 너무 복잡했다..
  • 코드를 보고 어떤 코드인지는 분석은 가능했지만 이걸 떠올리기에는 현재의 나로서는 역부족이다.
  • 이부분은 알고리즘 공부를 하고나서 다시 접근을 해봐야 할 것 같다. 나중에 공부를 위해 코드만 공유한다.

크기가 작은 부분 문자열

문제

  • 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/147355
  • 숫자로 이루어진 문자열 t와 p가 주어질 때 t에서 p와 길이가 같은 부분문자열 중에서 이 부분문자열이 나타내는 수가 p가 나타내는 수보다 작거나 같은 것이 나오는 횟수를 return 하는 함수 solution을 완성
  • 예를 들어 t = "3141952"이고 p = "271"인 경우 t의 길이가 3인 부분 문자열은 314, 141, 415, 159, 592 인데 이 문자열이 나타내는 수 중 271보다 작거나 같은 수는 141, 159 2개임

제한조건

  • p의 길이: 1 ~ 18
  • t의 길이: p의 길이 ~ 10,000
  • t와 p는 숫자로만 이루어져있고 0으로 시작하지 않음

입출력 예시

나의 풀이

class Solution {
    public int solution(String t, String p) {
        int answer = 0;
        
        StringBuilder sb = new StringBuilder(t);
        int loop = t.length() - p.length();
        
        for (int i = 0; i <= loop; i++) {
            String substring = "";
            if (i == loop) {
                substring = sb.substring(i);
            } else {
                substring = sb.substring(i, i + p.length());
            }
            
            long numT = Long.parseLong(substring);
            long numP = Long.parseLong(p);
            
            if (numT <= numP) {
                answer++;
            }
            
        }
        
        return answer;
    }
}
  • 문자열 t를 가공해야 한다는 생각에 t를 StringBuilder로 했지만 막상 문제를 풀고 풀이를 적다보니 그냥 String 상태에서 했어도 substring()은 String으로 반환되고 숫자 타입으로 형변환 하여 비교하다보니 의미 없다는 생각이 들어서 이런 경우에는 그냥 String을 써야겠다.
  • 문자열 t를 반복해서 substring()으로 자른 후 형변환한 값과 p를 형변환 한 값을 비교해야 하므로 반복 횟수를 정할 때 t.length() - p.length()를 활용해서 0부터 loop의 값까지 반복하도록 했다
    • t 문자열이 "3141592" 이고 p가 "271"이면 t의 길이는 7이고 p의 길이는 3이므로 뺄셈 연산하면 결과가 4가된다.
    • 반복의 시작을 0부터 시작할 것이기 때문에 연산결과인 4를 포함하여 반복하면 0, 1, 2, 3, 4, 5까지 반복하게 되고 마지막인 5번째 위치에서 substring()을 하면 t의 마지막 substring() 값인 592를 얻을 수 있게 된다.
  • 이때 i의 값이 loop와 같다면 substring(i)으로 마지막까지 잘라서 마지막 결과값을 얻게 되었고 그게 아니라면 substring(i, i + p.length())로 문자열을 잘라내는 시작점인 i의 위치에 맞게 동일한 길이로 잘라지도록 했다
  • 이후 잘라낸 문자열과 p의 문자열을 Long.parseLong()으로 형변환하여 잘라낸 숫자 값이 p의 숫자값보다 작거나 같으면 개수를 증가시키고 증가된 값을 반환시켜 문제를 해결했다
    • 여기서 처음에는 Integer.parseInt()로 변환하여 비교하고 코드를 실행했을 때는 문제없이 통과했는데 제출을 하면 일부가 런타임에러가 발생했어서 이유를 몰랐었다
    • 고민하다가 이유를 못찾겠어서 원인에 대해서 검색해봤는데.. p의 길이가 1 ~ 18 이므로 int의 범위가 넘어갈 수 있었던 것이였다.
    • 그래서 Long 타입으로 변환 방식을 바꾸니 정상적으로 통과하게 되었다.

다른 풀이

import java.util.stream.LongStream;

class Solution {
    public int solution(String t, String p) {
        long targetNumber = Long.parseLong(p);
        int targetNumberLength = p.length();

        return (int) LongStream.range(0L, t.length() - targetNumberLength + 1L)
                .mapToObj(i -> t.substring((int) i, (int) i + targetNumberLength))
                .mapToLong(Long::parseLong)
                .filter(number -> number <= targetNumber)
                .count();
    }
}
  • 스트림을 활용해 문제를 푸는 방식이 너무 신기해서 가져왔다. 스트림이 익숙하지 않는 나로서는 이런 방식이 금방 떠오르지 않는다.
  • 우선 문자열 p를 Long타입으로 형변환 하여 비교할 타겟을 변수에 저장하고, substring을 위해 p의 길이를 int로 변환해 두었다.
  • 먼저 LongStream.range(0L, t.length() - targetNumberLength + 1L) 로 반복할 스트림의 횟수를 정한다
    • 0부터 문자열 t의 길이 - p의 길이 + 1 만큼 반복한다
    • t의 길이가 8, p의 길이가 3이면 8 - 3 + 1 = 4가 되어 0부터 4만큰 반복하여 0, 1, 2, 3, 4 즉 5번 스트림이 반복하게 된다
    • 스트림의 시작은 중간에 연산값이 클 것을 대비하여 LongStream으로 시작한듯 하다
  • 다음 mapToObj(i -> t.substring((int) i, (int) i + targetNumberLength))로 t의 문자열을 자른다
    • LongStream.range로 스트림이 반복되는 값인 i는 반복회수이자 t의 문자열을 잘라내는 시작점이다
    • 즉 내가 직접 풀이한 for문으로 i++을 활용하여 i의 값을 증가시키는 것과 같은 이치라고 보면된다
    • 나의 풀이와 동일하게 substring(i, i + targetNumberLength)로 문자열을 잘라내는 것을 확인할 수 있다
  • 다음 mapToLong(Long::parseLong)으로 구해진 문자열을 Long 타입으로 형변환 한다
    • 구현 로직이 하나이므로 메서드 참조로 표현한 코드이다
    • 아마 람다식이였으면 (sub -> Long.parseLong(sub)) 와 같이 작성되었을 것이다
  • 그다음 filter(number -> number <= targetNumber)로 구해진 값을 비교하여 요구사항에 맞는지 확인하고 여러 중간연산을 거쳐 조건에 맞게 처리된 요소들의 개수를 반환하는 count()로 개수를 반환한다.
  • 이때 메서드의 반환타입이 int이므로 int 타입으로 형변환해서 반환한다
class Solution {
    public int solution(String t, String p) {
        int answer = 0;
        long numP = Long.parseLong(p);
        int loop = t.length() - p.length();

        for (int i = 0; i <= loop; i++) {
            String substring = t.substring(i, i + p.length());
            long numT = Long.parseLong(substring);

            if (numT <= numP) {
                answer++;
            }
        }
        return answer;
    }
}
  • 이 풀이는 나의 풀이에서 다른 풀이를 보고 비효율 적인 코드를 걷어낸 코드이다
  • StringBuilder를 사용하지 않고 문자열을 그대로 사용했으며, 반복 횟수인 loop와 p의 값을 Long으로 형변환 해두고 반복문을 통해 문제를 해결한다.
  • 이때 나는 조건문을 하나 더 넣어서 i가 loop와 같아지면 substring(i)로 마지막까지 잘라지게 구분을 나누었는데 애초에 (i, i + p.length())로 해당 반복문을 반복하면 같은 결과가 나오기 때문에 의미없는 조건문이 된다는 것을 깨달아서 제거했다.
  • 다른 풀이에서는 이런 조건들을 합쳐서 한줄, 2줄로 합치거나 if문의 코드에 연산 코드가 모두 들어와있는데 나는 오히려 그게 가독성이 떨어지는 것 같아서 이렇게 마무리 지었다.

최소직사각형

문제

  • 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/86491
  • 주어진 2차원 배열에 여러 명함의 가로, 세로가 정보가 들어가있을 때 이 명함을 모두 담을 수 있는 최소 사각형(지갑)을 구하는 함수를 완성
  • 이때 명함들의 가장 긴 가로의 길이와 세로의 길이가 80, 70이면 80 * 70의 지갑을 만들면 모든 명함을 담을 수 있음
  • 그러나 세로가 긴 명함을 가로로 눕혀서 수납한다면 가장 긴 가로의 길이와 세로의 길이가 달라질 수 있으므로 더 작은 지갑을 만들 수 있음

제한조건

  • sizes의 길이는 1이상 10,000이하임
  • size의 원소는 [w, h]이며 w는 명함의 가로 길이, h는 명함의 세로 길이를 나타냄
  • w와 h는 1 이상 1,000 이하인 자연수임

입출력 예시

나의 풀이

import java.util.Arrays;

class Solution {
    public int solution(int[][] sizes) {
        int[] widthArr = new int[sizes.length];
        int[] lengthArr = new int[sizes.length];

        for (int i = 0; i < sizes.length; i++) {
            widthArr[i] = Math.max(sizes[i][0], sizes[i][1]);
            lengthArr[i] = Math.min(sizes[i][0], sizes[i][1]);
        }

        Arrays.sort(widthArr);
        Arrays.sort(lengthArr);
        int answer = widthArr[widthArr.length - 1] * lengthArr[lengthArr.length - 1];
        return answer;
    }
}
  • 먼저 이문제를 내가 약한 스트림을 활용해서 풀어보려고하다가 너무 어려운 것같고 지금 나의 스트림 문법 실력에서는 더 비효율적인 코드만 나올 것 같아서 근본적으로 접근했다.
  • 우선 2차원 배열에 주어지는 가로 값과 세로 값의 모음 중에 최대 값들을 곱하면 된다고 생각했고, 그다음 조건으로 최대 새로의 값을 가로로 보기 위해선 비교해서 세로값이 크면 그 값을 가로의 모음으로 보면 되겠다고 생각했다.
  • 그래서 각 모음을 만들기 위해 가로의 모음인 widthArr과 세로의 모음인 lengthArr을 만들어서 반복문을 통해 2차원 배열의 가로값과 세로값을 모았다.
    • 이때 2차원 배열의 각 요소의 가로 길이과 세로 위치의 정보가 고정되어있어 sizes[i][0]을 가로로, sizes[i][1]을 세로값으로 간주했다.
    • 그리고 가로의 값과 세로의 값을 비교하여 큰 값을 가로 모음인 widthArr에 보관하고 작은 값을 세로 모음인 lengthArr에 보관했다
  • 모아진 세로값과 가로값중에 가장 큰 값들을 골라야 하므로 두 값을 Arrays.sort()함수를 통해 오름 차순으로 정렬 시킨 후 해당 배열의 가장 마지막 값들을 곱해서 문제를 해결했다.
  • 여기에서 정렬을 2번 사용하는 부분을 한번의 반복문만 사용하여 조금 더 성능을 최적화 시킬 수 있는 여지가 있을 것 같은데 이부분을 어떻게 할지 생각이 나지 않아 다른 풀이를 봐야 할 것 같다.

다른 풀이

class Solution {
    public int solution(int[][] sizes) {
        int length = 0, height = 0;
        for (int[] card : sizes) {
            length = Math.max(length, Math.max(card[0], card[1]));
            height = Math.max(height, Math.min(card[0], card[1]));
        }
        int answer = length * height;
        return answer;
    }
}
  • 다른 풀이를 보니 내가 오름 차순으로 정렬하여 가장 큰 값을 구하는 방법을 Math.max를 한번더 사용하는 방법으로 반복문 한번에서 구할 수 있다는 사실을 배웠다.
  • 에초에 배열에 담지 말고, 반복을 할 때 꺼낸 값을 변수에 저장한다음, 다음 반복에서 꺼내진 값과 비교하여 큰 값들을 저장하면 가장 큰 값을 구하는 방법이 인상 깊었다.
import java.util.*;

class Solution {
    public int solution(int[][] sizes) {
        return Arrays.stream(sizes).reduce((a, b) -> new int[]{
                Math.max(Math.max(a[0], a[1]), Math.max(b[0], b[1])),
                Math.max(Math.min(a[0], a[1]), Math.min(b[0], b[1]))
                
        }).map(it -> it[0] * it[1]).get();
    }
}
  • 나처럼 스트림 숙련도가 낮다면 보통 스트림을 두 번 반복해서 결과를 구하고 반환하는 방법밖에 생각을 못했을 텐데, reduce()연산을 활용하여 한번의 스트림 반복으로 문제를 해결하는 것이 신기해서 가져왔다.
  • reduce()함수는 스트림의 여러 요소들을 하나의 결과값으로 합치는 연선으로 리스트의 모든 수를 더하거나 최대값/최소값을 찾는 경우 등에 활용할 수 있다고 한다.
  • 여기서의 reduece의 최종 결과는 2차원 배열로{Math.max(), Math.max()}가 되는데 가로의 최대 값과, 세로의 최대 값을 다음 .map 연산으로 넘긴다
    • 이 {Math.max(), Math.max()} 내부에서 요구사항을 만족하기 위해 매개 변수로 넘기는 a, b의 요소를 또 Math.max와 Math.min으로 가로 세로 값을 구하는 로직을 구성하고 있다.
  • 먼저 스트림의 요소를 2개씩 꺼내므로 2차원 배열의 각 요소를 2개씩 꺼낸다.
  • 즉, 첫 번째 reduce의 반복에서는 a[0], a[1]은 첫 번째 요소의 가로, 세로값이고, b[0],b[1]은 두 번째 요소의 가로, 세로 값이된다
    • {60, 50}, {30, 70} 이렇게 두 값이 꺼내졌다면 a[0] = 60, a[1] = 50, b[0] = 30, b[1] = 70 를 뜻하게 된다
    • 첫 번째 reduce 호출 이후, a 배열은 이전까지 계산된 {최대 가로값, 최대 세로값}의 누적 결과를 담게 되며 이후의 반복에서는 이 누적 결과(a 배열)와 새로운 카드 배열(b 배열)의 가로/세로 값이 각각 비교되어, 누적 결과가 갱신됨
  • 그 다음 () 안쪽에 있는 Math.max()와 Math.min()로 각 명함의 가로 세로를 비교하여 큰 값과 작은 값을 구하면 큰 값이 명함의 가로값이 되고 작은 값은 세로값이 되게 된다.
    • a = {60, 50}, b = {30, 70}을 Math.max(), Math.min()가로, 세로 값을 구한다.
    • Math.max(60, 50)으로 a의 가로값은 60이 되고, Math.max(30, 70)으로 b의 가로값은 70이 된다
    • Math.min(60,50)으로 a의 세로값은 50이 되고, Math.min(30, 70)으로 b의 세로 값은 30이 된다
    • 그 결과 {Math.max(60, 70), Math.max(50, 30)} 으로 내부의 max(), min() 첫 번째 연산이 완료 된다.
  • 그리고 구해진 a와 b의 가로값과 세로값을 각각 Math.max()로 비교하여 더 큰 가로값과 세로 값을 최종적으로 배열로 만들면 첫 스트림 반복이 끝난다
    • 결국 reduce의 첫 호출에서는 {70, 50}이 된다
  • reduce는 연산 결과를 누적해서 진행하는데, 처음 reduce의 연산 결과를 첫 번째 매개변수인 a에 담고 두 번째 매개변수인 b에 다음 요소를 가져와서 reduce에 작성한 로직을 반복한다
    • 첫 번째 reduce의 호출에서 나온 결과 {70, 50}와 sizes의 3번째 요소를 reduce()의 비교 로직을 수행한다.
  • 이후 .map()연산으로 reduce()의 연산결과로 생성된 배열의 0번째 인덱스(가장 큰 가로값)과 1번째 인덱스(가장 큰 세로값)을 곱해서 넓이를 구하고 .get()을 통해 int로 반환한다.

SQL(MySQL)

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

문제 및 테이블 예시

  • 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/59411
  • ANIMAL_INS 동물 보호소의 동물 정보 테이블, ANIMAL_OUTS 동물 보호소에서 입양 보낸 정보를 담은 테이블에서 입양을 간 동물 중 보호 기간이 가장 길었던 동물 두 마리의 아이디와 이름을 조회하는 SQL문을 작성
  • 결과는 보호 기간이 긴 순으로 조회

 

입출력 예시

나의 풀이

SELECT
    INS.ANIMAL_ID,
    INS.NAME
FROM
    ANIMAL_INS AS INS
JOIN
    ANIMAL_OUTS AS OUTS
ON
    INS.ANIMAL_ID = OUTS.ANIMAL_ID 
ORDER BY
    DATEDIFF(OUTS.DATETIME, INS.DATETIME) DESC
LIMIT 2
  • 우선 두 테이블을 ANIMAL_ID 로 JOIN 연산을 하면 동물 보호소에서 입양을 간 테이블로 데이터가 좁혀진다
  • 여기서 날짜 계산을 위한 함수가 생각이 안나서 검색을 했는데, DATEDIFF(첫 번째 날짜, 두 번째 날짜)를 활용하면 첫 번째 날짜 - 두 번째 날짜가 계산된다고 검색을 통해 알게 되었다
  • 그래서 이 함수를 이용해 ORDER BY 절에서 DATEDIFF(입양일 - 보호시작일)을 빼게 되면 보호 시설에 남아있게 되었던 일자를 구할 수 있게 되고 값이 가장큰 대상이 가장 보호시설에 남아있던 데이터이므로 내림차순 정렬을 하고 상위 2개만 남도록 문제를 해결했다
    • 사실 처음에 DATEDIFF(INS.DATETIME, OUTS.DATETIME) 으로 문제가 통과되었는데 풀이를 쓰다보니 정답은 맞았지만 논리적으로 날짜 계산을 잘못했다는 사실을 알았다..
    • 보호 시설에 남아있던 기간을 구하려면 더 늦은 입양일에서 보호시작일을 빼야 되는데 역으로 보호시작일에서 입양일을 빼버려서 값이 음수로 나와 보호시작일을 역으로 계산한 것이였다..
    • 그래서 함수의 위치를 다시 설정하고 내림차순을 사용하도록 변경했다

자주쓰는 MySQL 날짜 연산 함수들

  • DATEDIFF(date1, date2)
    • 두 날짜 사이의 일(day) 차이를 반환함
    • 예시: DATEDIFF('2023-05-20', '2023-05-10') -> 10
  • DATE_ADD(date, INTERVAL value unit)
    • 지정한 날짜에 측정 기간을 더한 날짜를 반환
    • 예시: DATE_ADD('2023-05-10', INTERVAL 5 DAY); →  2023-05-15
  • DATE_SUB(date, INTERVAL value unit)
    • 지정한 날짜에서 특정 기간을 뺀 날짜를 반환
    • 예시: DATE_SUB('2023-05-10', INTERVAL 3 DAY); -> 2023-05-07
  • TIMESTAMPDIFF(unit, date1, date2)
    • 두 날짜/시간 값 사이의 차이를 지정한 단위로 반환함
    • 예시: TIMESTAMPDIFF(DAY, '2023-05-10', '2023-05-20'); -> 10
  • NOW(): 현재 날짜
  • CURDATE() : 현재 시간

** 참고

  • unit 부분에는 DAY, MONTH, YEAR, HOUR, MINUTE 등 MySQL에서 지원하는 단위를 사용하면 됨
  • 내부적으로 MySQL은 날짜 데이터를 숫자(타임스탬프)로 처리하기 때문에 비교 및 산술연산이 가능함

보호소에서 중성화한 동물

문제 및 테이블 예시

  • 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/59045
  • ANIMAL_INS 동물 보호소의 동물 정보 테이블, ANIMAL_OUTS 동물 보호소에서 입양 보낸 정보를 담은 테이블
  • 보호소에 들어올 당시에는 중성화 되지 않았지만 보호소를 나갈 당시에는 중성화된 동물의 아이디와 생물 종, 이름을 조회하는 쿼리르 아이디 순으로 SQL문 작성

입출력 예시

나의 풀이

SELECT
    OUTS.ANIMAL_ID,
    OUTS.ANIMAL_TYPE,
    OUTS.NAME
FROM
    ANIMAL_INS AS INS
JOIN 
    ANIMAL_OUTS AS OUTS
ON
    INS.ANIMAL_ID = OUTS.ANIMAL_ID
WHERE
    INS.SEX_UPON_INTAKE IN ('Intact Male', 'Intact Female')
AND
    OUTS.SEX_UPON_OUTCOME IN ('Neutered Male', 'Spayed Female')
ORDER BY
    INS.ANIMAL_ID
  • 우선 두 테이블의 정보를 INNER JOIN으로 합쳐서 보호소의 데이터와 입양 나간 데이터를 합쳤다
  • 그리고 먼저 보호소의 중성화 여부 컬럼에서 중성화가 안된 데이터를 조회하도록 IN을 사용하여 조건을 찾았고, 그 데이터에서 다시한번 조건을 걸어 입양 나간 데이터의 중성화 여부 컬럼에서 마찬가지로 IN 조건을 통해 중성화가 된 데이터를 찾아서 문제를 해결했다
  • 여기서 IN이아니라 LIKE를 사용하여 중성화가 안된 동물은 'Intack%', 중성화가 된 동물은 'Neutered%' 과 'Spayed%' 를 사용해도 괜찮을 것 같다.

다른 풀이

select 
    ao.ANIMAL_ID,
    ao.ANIMAL_TYPE,
    ao.NAME
from ANIMAL_INS ai
join ANIMAL_OUTS ao on ai.ANIMAL_ID = ao.ANIMAL_ID
where SEX_UPON_INTAKE like '%Intact%'
and (SEX_UPON_OUTCOME like 'Neutered%' or SEX_UPON_OUTCOME like 'Spayed%')
order by ao.ANIMAL_ID;
  • 이 경우가 바로 내가 적은 예시의 표본인 것 같다
SELECT i.ANIMAL7_ID, i.ANIMAL_TYPE, .NAME
FROM ANIMAL_INS i
INNER JOIN ANIMAL_OUTS o ON i.ANIMAL_ID = o.ANIMAL_ID
WHERE SEX_UPON_INTAKE LIKE '%Intact%' AND SEX_UPON_OUTCOME
REGEXP('Spayed|Neutered')
  • 이전에 중복 조건을 찾을 때 검색을 통해 알게된 REGEXP()함수를 사용하여 풀이한 분도 계서서 쿼리를 가져왔다
  • 나에겐 IN, LIKE 등이 익숙하다보니 REGEXP()가 잘 떠오르지 않았는데 2건 이상포함된 데이터를 찾을 때는 정규식을 활용한 REGEXP()를 활용하는 방안을 떠올리도록 기억해야겠다.

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

문제 및 테이블 예시

  • 프로그래머스 - https://school.programmers.co.kr/learn/courses/30/lessons/59045
  • FOOD_ORDER테이블에서 2022년 5월 1일을 기준으로 주문 ID, 제품 ID, 출고일자, 출고여부를 조회하는 SQL문을 작성
  • 출고여부는 2022년 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 '출고대기'
        WHEN OUT_DATE IS NULL THEN '출고미정'
    END AS 출고여부
FROM FOOD_ORDER
ORDER BY ORDER_ID
  • CASE - WHEN - THEN 문으로 문제를 해결했다
  • CASE 문의 사용법이 익숙하지 않아서 검색을 통해 WHEN 문에는 조건을 THEN 에는 결과를 적어주면 된다는 것을 다시한번 상기시켰다

다른 풀이

SELECT ORDER_ID, PRODUCT_ID,
    IF (OUT_DATE IS NULL, '출고대기',
    	IF(OUT_DATE <= '2022-05-01', '출고완료', '출고미정'))
    AS 출고여부 
FROM FOOD_ORDER
ORDER BY ORDER_ID
  • IF 함수를 중첩으로 사용하여 문제를 해결할 수도 있다
  • IF 함수를 사용할 때 3번째 인자에는 조건이 맞지 않을 때의 값을 입력할 수 있으며 3항 연산자를 떠올리면 된다
  • outDate == null ? "출고대기" : outDate <= 2022-05-01 ? "출고완료" : "출고미정"
728x90